From 87a7cf733238bf16a1321104958a65abd486c285 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 6 May 2024 12:33:09 +0200 Subject: [PATCH] Issue #2472 - Part 6: Fire cancel event and close modal on [ESC] This implementation is different than Mozilla, not indirectly queueing cancel tasks to be arbitrarily ordered, but rather synchronously close in reverse top layer order. --- dom/base/nsDocument.cpp | 13 +++++++++++++ dom/base/nsDocument.h | 3 +++ dom/base/nsIDocument.h | 5 +++++ dom/html/HTMLDialogElement.cpp | 21 +++++++++++++++++++++ dom/html/HTMLDialogElement.h | 1 + layout/base/nsPresShell.cpp | 6 ++++++ 6 files changed, 49 insertions(+) diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 68dfd7eebd..c13e775a94 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -159,6 +159,7 @@ #include "nsEscape.h" #include "nsObjectLoadingContent.h" #include "nsHtml5TreeOpExecutor.h" +#include "mozilla/dom/HTMLDialogElement.h" #include "mozilla/dom/HTMLLinkElement.h" #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/dom/HTMLIFrameElement.h" @@ -10138,6 +10139,18 @@ nsDocument::MozCancelFullScreen() return NS_OK; } +void nsDocument::TryCancelDialog() { + // Check if the document is blocked by modal dialog + for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) { + nsCOMPtr element(do_QueryReferent(weakPtr)); + if (HTMLDialogElement* dialog = + HTMLDialogElement::FromContentOrNull(element)) { + dialog->CancelDialog(); + break; + } + } +} + void nsIDocument::ExitFullscreen() { diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 16b93d245d..70bda75fed 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -918,6 +918,9 @@ public: // flag. bool SetFullscreenElement(Element* aElement); + // Cancel the dialog element if the document is blocked by the dialog + void TryCancelDialog(); + // Returns the top element from the full-screen stack. Element* GetTopLayerTop() override; // Return the fullscreen element in the top layer diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index 2a18c2259e..bf4b053f5e 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -1349,6 +1349,11 @@ public: virtual bool TopLayerPush(Element* aElement) = 0; virtual Element* TopLayerPop(FunctionRef aPredicateFunc) = 0; + /** + * Cancel the dialog element if the document is blocked by the dialog. + */ + virtual void TryCancelDialog() = 0; + /** * Synchronously cleans up the fullscreen state on the given document. * diff --git a/dom/html/HTMLDialogElement.cpp b/dom/html/HTMLDialogElement.cpp index 795b44dc1a..ef6d40b832 100644 --- a/dom/html/HTMLDialogElement.cpp +++ b/dom/html/HTMLDialogElement.cpp @@ -111,6 +111,27 @@ HTMLDialogElement::ShowModal(ErrorResult& aError) aError.SuppressException(); } +void HTMLDialogElement::CancelDialog() { + // 1) Let close be the result of firing an event named cancel at dialog, with + // the cancelable attribute initialized to true. + bool defaultAction = true; + nsContentUtils::DispatchTrustedEvent( + OwnerDoc(), + static_cast(this), + NS_LITERAL_STRING("cancel"), + false, /* can bubble */ + true, /* can cancel */ + &defaultAction); + + // 2) If close is true and dialog has an open attribute, then close the dialog + // with no return value. + if (defaultAction) { + Optional retValue; + Close(retValue); + } +} + + JSObject* HTMLDialogElement::WrapNode(JSContext* aCx, JS::Handle aGivenProto) { diff --git a/dom/html/HTMLDialogElement.h b/dom/html/HTMLDialogElement.h index 6386452d91..4bc9ab879f 100644 --- a/dom/html/HTMLDialogElement.h +++ b/dom/html/HTMLDialogElement.h @@ -49,6 +49,7 @@ public: void ShowModal(ErrorResult& aError); bool IsInTopLayer() const; + void CancelDialog(); nsString mReturnValue; diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 713b06cb96..36de447a32 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -8059,6 +8059,12 @@ PresShell::HandleEventInternal(WidgetEvent* aEvent, aEvent->mFlags.mDefaultPreventedByChrome) { mIsLastChromeOnlyEscapeKeyConsumed = true; } + if (aEvent->mMessage == eKeyDown && + !aEvent->mFlags.mDefaultPrevented) { + if (nsIDocument* doc = GetDocument()) { + doc->TryCancelDialog(); + } + } } } break;