From 3058bef84083adf3736635f80bbff198e494b2d6 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Fri, 1 Mar 2024 13:08:06 +0100 Subject: [PATCH 1/9] Issue #2472 - Part 1: Add helper method to directly dispatch events to widgets. --- dom/base/nsContentUtils.cpp | 105 +++++++++++++++++++++++++++++------- dom/base/nsContentUtils.h | 68 +++++++++++++++++++++++ 2 files changed, 153 insertions(+), 20 deletions(-) diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index da3207e9c9..624f9c502f 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -738,20 +738,40 @@ nsContentUtils::InitializeModifierStrings() sModifierSeparator = new nsString(modifierSeparator); } +mozilla::EventClassID +nsContentUtils::GetEventClassIDFromMessage(EventMessage aEventMessage) +{ + switch (aEventMessage) { +#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \ + case message_: return struct_; +#include "mozilla/EventNameList.h" +#undef MESSAGE_TO_EVENT + default: + MOZ_ASSERT_UNREACHABLE("Invalid event message?"); + return eBasicEventClass; + } +} + +static nsIAtom* +GetEventTypeFromMessage(EventMessage aEventMessage) +{ + switch (aEventMessage) { +#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \ + case message_: return nsGkAtoms::on##name_; +#include "mozilla/EventNameList.h" +#undef MESSAGE_TO_EVENT + default: + return nullptr; + } +} + // Because of SVG/SMIL we have several atoms mapped to the same // id, but we can rely on MESSAGE_TO_EVENT to map id to only one atom. static bool ShouldAddEventToStringEventTable(const EventNameMapping& aMapping) { - switch(aMapping.mMessage) { -#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \ - case message_: return nsGkAtoms::on##name_ == aMapping.mAtom; -#include "mozilla/EventNameList.h" -#undef MESSAGE_TO_EVENT - default: - break; - } - return false; + MOZ_ASSERT(aMapping.mAtom); + return GetEventTypeFromMessage(aMapping.mMessage) == aMapping.mAtom; } bool @@ -4149,10 +4169,12 @@ nsContentUtils::DispatchEventForPreloadURI(nsIDOMNode* aNode, // static nsresult -nsContentUtils::DispatchTrustedEvent(nsIDocument* aDoc, nsISupports* aTarget, +nsContentUtils::DispatchTrustedEvent(nsIDocument* aDoc, + nsISupports* aTarget, const nsAString& aEventName, - bool aCanBubble, bool aCancelable, - bool *aDefaultAction) + bool aCanBubble, + bool aCancelable, + bool* aDefaultAction) { return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable, true, aDefaultAction); @@ -4160,10 +4182,12 @@ nsContentUtils::DispatchTrustedEvent(nsIDocument* aDoc, nsISupports* aTarget, // static nsresult -nsContentUtils::DispatchUntrustedEvent(nsIDocument* aDoc, nsISupports* aTarget, +nsContentUtils::DispatchUntrustedEvent(nsIDocument* aDoc, + nsISupports* aTarget, const nsAString& aEventName, - bool aCanBubble, bool aCancelable, - bool *aDefaultAction) + bool aCanBubble, + bool aCancelable, + bool* aDefaultAction) { return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable, false, aDefaultAction); @@ -4171,10 +4195,13 @@ nsContentUtils::DispatchUntrustedEvent(nsIDocument* aDoc, nsISupports* aTarget, // static nsresult -nsContentUtils::DispatchEvent(nsIDocument* aDoc, nsISupports* aTarget, +nsContentUtils::DispatchEvent(nsIDocument* aDoc, + nsISupports* aTarget, const nsAString& aEventName, - bool aCanBubble, bool aCancelable, - bool aTrusted, bool *aDefaultAction, + bool aCanBubble, + bool aCancelable, + bool aTrusted, + bool* aDefaultAction, bool aOnlyChromeDispatch) { nsCOMPtr event; @@ -4189,12 +4216,50 @@ nsContentUtils::DispatchEvent(nsIDocument* aDoc, nsISupports* aTarget, return target->DispatchEvent(event, aDefaultAction ? aDefaultAction : &dummy); } +// static +nsresult +nsContentUtils::DispatchEvent(nsIDocument* aDoc, + nsISupports* aTarget, + WidgetEvent& aEvent, + EventMessage aEventMessage, + bool aCanBubble, + bool aCancelable, + bool aTrusted, + bool* aDefaultAction, + bool aOnlyChromeDispatch) +{ + MOZ_ASSERT_IF(aOnlyChromeDispatch, aTrusted); + + nsCOMPtr target(do_QueryInterface(aTarget)); + + aEvent.mTime = PR_Now(); + + aEvent.mSpecifiedEventType = GetEventTypeFromMessage(aEventMessage); + aEvent.SetDefaultComposed(); + aEvent.SetDefaultComposedInNativeAnonymousContent(); + + aEvent.mFlags.mBubbles = aCanBubble; + aEvent.mFlags.mCancelable = aCancelable; + aEvent.mFlags.mOnlyChromeDispatch = aOnlyChromeDispatch; + + aEvent.mTarget = target; + + nsEventStatus status = nsEventStatus_eIgnore; + nsresult rv = EventDispatcher::DispatchDOMEvent(target, &aEvent, nullptr, + nullptr, &status); + if (aDefaultAction) { + *aDefaultAction = (status != nsEventStatus_eConsumeNoDefault); + } + return rv; +} + nsresult nsContentUtils::DispatchChromeEvent(nsIDocument *aDoc, nsISupports *aTarget, const nsAString& aEventName, - bool aCanBubble, bool aCancelable, - bool *aDefaultAction) + bool aCanBubble, + bool aCancelable, + bool* aDefaultAction) { nsCOMPtr event; diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 3d5476d7aa..37866aaf64 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -19,6 +19,7 @@ #include "js/TypeDecls.h" #include "js/Value.h" #include "js/RootingAPI.h" +#include "mozilla/BasicEvents.h" #include "mozilla/EventForwards.h" #include "mozilla/GuardObjects.h" #include "mozilla/TimeStamp.h" @@ -1208,6 +1209,33 @@ public: bool aCancelable, bool *aDefaultAction = nullptr); + /** + * This method creates and dispatches a trusted event using an event message. + * @param aDoc The document which will be used to create the event. + * @param aTarget The target of the event, should be QIable to + * EventTarget. + * @param aEventMessage The event message. + * @param aCanBubble Whether the event can bubble. + * @param aCancelable Is the event cancelable. + * @param aDefaultAction Set to true if default action should be taken, + * see nsIDOMEventTarget::DispatchEvent. + */ + template + static nsresult DispatchTrustedEvent(nsIDocument* aDoc, + nsISupports* aTarget, + mozilla::EventMessage aEventMessage, + bool aCanBubble, + bool aCancelable, + bool *aDefaultAction = nullptr, + bool aOnlyChromeDispatch = false) + { + WidgetEventType event(true, aEventMessage); + MOZ_ASSERT(GetEventClassIDFromMessage(aEventMessage) == event.mClass); + return DispatchEvent(aDoc, aTarget, event, aEventMessage, + aCanBubble, aCancelable, true, + aDefaultAction, aOnlyChromeDispatch); + } + /** * This method creates and dispatches a untrusted event. * Works only with events which can be created by calling @@ -1228,6 +1256,33 @@ public: bool aCancelable, bool *aDefaultAction = nullptr); + /** + * This method creates and dispatches a untrusted event using an event message. + * @param aDoc The document which will be used to create the event. + * @param aTarget The target of the event, should be QIable to + * EventTarget. + * @param aEventMessage The event message. + * @param aCanBubble Whether the event can bubble. + * @param aCancelable Is the event cancelable. + * @param aDefaultAction Set to true if default action should be taken, + * see nsIDOMEventTarget::DispatchEvent. + */ + template + static nsresult DispatchUntrustedEvent(nsIDocument* aDoc, + nsISupports* aTarget, + mozilla::EventMessage aEventMessage, + bool aCanBubble, + bool aCancelable, + bool *aDefaultAction = nullptr, + bool aOnlyChromeDispatch = false) + { + WidgetEventType event(false, aEventMessage); + MOZ_ASSERT(GetEventClassIDFromMessage(aEventMessage) == event.mClass); + return DispatchEvent(aDoc, aTarget, event, aEventMessage, + aCanBubble, aCancelable, false, + aDefaultAction, aOnlyChromeDispatch); + } + /** * This method creates and dispatches a trusted event to the chrome * event handler (the parent object of the DOM Window in the event target @@ -2883,6 +2938,16 @@ private: bool *aDefaultAction = nullptr, bool aOnlyChromeDispatch = false); + static nsresult DispatchEvent(nsIDocument* aDoc, + nsISupports* aTarget, + mozilla::WidgetEvent& aWidgetEvent, + mozilla::EventMessage aEventMessage, + bool aCanBubble, + bool aCancelable, + bool aTrusted, + bool *aDefaultAction = nullptr, + bool aOnlyChromeDispatch = false); + static void InitializeModifierStrings(); static void DropFragmentParsers(); @@ -2894,6 +2959,9 @@ private: static void* AllocClassMatchingInfo(nsINode* aRootNode, const nsString* aClasses); + static mozilla::EventClassID + GetEventClassIDFromMessage(mozilla::EventMessage aEventMessage); + // Fills in aInfo with the tokens from the supplied autocomplete attribute. static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal, mozilla::dom::AutocompleteInfo& aInfo); From 1f4152a5ac43e4c094e74717c785aa5720d05f17 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Thu, 7 Mar 2024 11:14:20 +0100 Subject: [PATCH 2/9] Issue #2472 - Part 2: Add FunctionRef callable type Prerequisite for top layer refactor. FunctionRef is a generic callable type that can be initialized from any compatible callable, suitable for use as a function argument for the duration of the function call (and no longer). --- mfbt/FunctionRef.h | 220 +++++++++++++++++++++++++++++++++++++++++++++ mfbt/moz.build | 1 + 2 files changed, 221 insertions(+) create mode 100644 mfbt/FunctionRef.h diff --git a/mfbt/FunctionRef.h b/mfbt/FunctionRef.h new file mode 100644 index 0000000000..874e2d413f --- /dev/null +++ b/mfbt/FunctionRef.h @@ -0,0 +1,220 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * A generic callable type that can be initialized from any compatible callable, + * suitable for use as a function argument for the duration of the function + * call (and no longer). + */ + +#ifndef mozilla_FunctionRef_h +#define mozilla_FunctionRef_h + +#include "mozilla/OperatorNewExtensions.h" // mozilla::NotNull, ::operator new + +#include // std::nullptr_t +#include // std::{declval,integral_constant}, std::is_{convertible,same,void}_v, std::{enable_if,remove_reference,remove_cv}_t + +// This concept and its implementation are substantially inspired by foonathan's prior art: +// +// https://foonathan.net/2017/01/function-ref-implementation/ +// https://github.com/foonathan/type_safe/blob/2017851053f8dd268372f1612865792c5c621570/include/type_safe/reference.hpp +// +// Note: this is C++ code voodoo. Be careful if you have to make any changes. + +namespace mozilla { + +namespace detail { + +// Template helper to determine if |Returned| is a return type compatible with +// |Required|: if the former converts to the latter, or if |Required| is |void| +// and nothing is returned. +template +using CompatibleReturnType = + std::integral_constant || + std::is_convertible_v>; + +// Template helper to check if |Func| called with |Params| arguments returns +// a type compatible with |Ret|. +template +using EnableMatchingFunction = std::enable_if_t< + CompatibleReturnType< + decltype(std::declval()(std::declval()...)), Ret>::value, + int>; + +struct MatchingFunctionPointerTag {}; +struct MatchingFunctorTag {}; +struct InvalidFunctorTag {}; + +// Template helper to determine the proper way to store |Callable|: as function +// pointer, as pointer to object, or unstorable. +template +struct GetCallableTag { + // Match the case where |Callable| is a compatible function pointer or + // converts to one. (|+obj| invokes such a conversion.) + template + static MatchingFunctionPointerTag test( + int, T& obj, EnableMatchingFunction = 0); + + // Match the case where |Callable| is callable but can't be converted to a + // function pointer. (|short| is a worse match for 0 than |int|, causing the + // function pointer match to be preferred if both apply.) + template + static MatchingFunctorTag test(short, T& obj, + EnableMatchingFunction = 0); + + // Match all remaining cases. (Any other match is preferred to an ellipsis + // match.) + static InvalidFunctorTag test(...); + + using Type = decltype(test(0, std::declval())); +}; + +// If the callable is |nullptr|, |std::declval()| will be an +// error. Provide a specialization for |nullptr| that will fail substitution. +template +struct GetCallableTag {}; + +template +using EnableFunctionTag = std::enable_if_t< + std::is_same_v::Type, + Result>, + int>; + +} // namespace detail + +/** + * An efficient, type-erasing, non-owning reference to a callable. It is + * intended for use as the type of a function parameter that is not used after + * the function in question returns. + * + * This class does not own the callable, so in general it is unsafe to store a + * FunctionRef. + */ +template +class FunctionRef; + +template +class FunctionRef { + union Payload; + + // |FunctionRef| stores an adaptor function pointer, determined by the + // arguments passed to the constructor. That adaptor will perform the steps + // needed to invoke the callable passed at construction time. + using Adaptor = Ret (*)(const Payload& aPayload, Params... aParams); + + // If |FunctionRef|'s callable can be stored as a function pointer, that + // function pointer is stored after being cast to this *different* function + // pointer type. |mAdaptor| then casts back to the original type to call it. + // ([expr.reinterpret.cast]p6 guarantees that A->B->A function pointer casts + // produce the original function pointer value.) An outlandish signature is + // used to emphasize that the exact function pointer type doesn't matter. + using FuncPtr = Payload***** (*)(Payload*****); + + /** + * An adaptor function (used by this class's function call operator) that + * invokes the callable in |mPayload|, forwarding arguments and converting + * return type as needed. + */ + const Adaptor mAdaptor; + + /** Storage for the wrapped callable value. */ + union Payload { + // This arm is used if |FunctionRef| is passed a compatible function pointer + // or a lambda/callable that converts to a compatible function pointer. + FuncPtr mFuncPtr; + + // This arm is used if |FunctionRef| is passed some other callable or + // |nullptr|. + void* mObject; + } mPayload; + + template + static Ret CallFunctionPointer(const Payload& aPayload, + Params... aParams) noexcept { + auto func = reinterpret_cast(aPayload.mFuncPtr); + return static_cast(func(static_cast(aParams)...)); + } + + template + FunctionRef(detail::MatchingFunctionPointerTag, Ret2 (*aFuncPtr)(Params2...)) + : mAdaptor(&CallFunctionPointer) { + ::new (KnownNotNull, &mPayload.mFuncPtr) + FuncPtr(reinterpret_cast(aFuncPtr)); + } + + public: + /** + * Construct a |FunctionRef| that's like a null function pointer that can't be + * called. + */ + MOZ_IMPLICIT FunctionRef(std::nullptr_t) noexcept : mAdaptor(nullptr) { + // This is technically unnecessary, but it seems best to always initialize + // a union arm. + ::new (KnownNotNull, &mPayload.mObject) void*(nullptr); + } + + FunctionRef() : FunctionRef(nullptr) {} + + /** + * Constructs a |FunctionRef| from an object callable with |Params| arguments, + * that returns a type convertible to |Ret|, where the callable isn't + * convertible to function pointer (often because it contains some internal + * state). For example: + * + * int x = 5; + * auto doSideEffect = [&x]{ x++; }; // state is captured reference to |x| + * FunctionRef f(doSideEffect); + */ + template < + typename Callable, + typename = detail::EnableFunctionTag, + typename std::enable_if_t>, + FunctionRef>>* = nullptr> + MOZ_IMPLICIT FunctionRef(Callable& aCallable) noexcept + : mAdaptor([](const Payload& aPayload, Params... aParams) { + auto& func = *static_cast(aPayload.mObject); + return static_cast(func(static_cast(aParams)...)); + }) { + ::new (KnownNotNull, &mPayload.mObject) void*(&aCallable); + } + + /** + * Constructs a |FunctionRef| from an value callable with |Params| arguments, + * that returns a type convertible to |Ret|, where the callable is stateless + * and is (or is convertible to) a function pointer. For example: + * + * // Exact match + * double twice(double d) { return d * 2; } + * FunctionRef func1(&twice); + * + * // Compatible match + * float thrice(long double d) { return static_cast(d) * 3; } + * FunctionRef func2(&thrice); + * + * // Non-generic lambdas that don't capture anything have a conversion + * // function to the appropriate function pointer type. + * FunctionRef f([](long double){ return 'c'; }); + */ + template > + MOZ_IMPLICIT FunctionRef(const Callable& aCallable) noexcept + : FunctionRef(detail::MatchingFunctionPointerTag{}, +aCallable) {} + + /** Call the callable stored in this with the given arguments. */ + Ret operator()(Params... params) const { + return mAdaptor(mPayload, static_cast(params)...); + } + + /** Return true iff this wasn't created from |nullptr|. */ + explicit operator bool() const noexcept { return mAdaptor != nullptr; } +}; + +} /* namespace mozilla */ + +#endif /* mozilla_FunctionRef_h */ diff --git a/mfbt/moz.build b/mfbt/moz.build index 54b13f21b9..5dc890cdbc 100644 --- a/mfbt/moz.build +++ b/mfbt/moz.build @@ -42,6 +42,7 @@ EXPORTS.mozilla = [ 'FastBernoulliTrial.h', 'FloatingPoint.h', 'Function.h', + 'FunctionRef.h', 'GuardObjects.h', 'HashFunctions.h', 'IndexSequence.h', From 79c7489bb0cfcb828b1e67164c5593142bfe3351 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Thu, 7 Mar 2024 12:24:06 +0100 Subject: [PATCH 3/9] Issue #2472 - Part 3: Refactor Fullscreen stack to Top Layer stack Fullscreen stack isn't part of the spec anymore; it's been changed to a more generic version called Top Layer stack, which is being used by both full-screen APIs and dialog elements. This patch refactors it to Top Layer stack so that it can be reused for dialog elements. Top Layer stack spec: https://fullscreen.spec.whatwg.org/#new-stacking-layer --- dom/base/DocumentOrShadowRoot.cpp | 2 +- dom/base/nsDocument.cpp | 204 ++++++++++++++++++----------- dom/base/nsDocument.h | 43 +++--- dom/base/nsIDocument.h | 10 +- layout/generic/nsViewportFrame.cpp | 4 +- 5 files changed, 163 insertions(+), 100 deletions(-) diff --git a/dom/base/DocumentOrShadowRoot.cpp b/dom/base/DocumentOrShadowRoot.cpp index f420ff6a28..2850bdb2d6 100644 --- a/dom/base/DocumentOrShadowRoot.cpp +++ b/dom/base/DocumentOrShadowRoot.cpp @@ -169,7 +169,7 @@ DocumentOrShadowRoot::GetFullscreenElement() return nullptr; } - Element* element = AsNode().OwnerDoc()->FullScreenStackTop(); + Element* element = AsNode().OwnerDoc()->GetUnretargetedFullScreenElement(); NS_ASSERTION(!element || element->State().HasState(NS_EVENT_STATE_FULL_SCREEN), "Fullscreen element should have fullscreen styles applied"); diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 0c6950e422..68dfd7eebd 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -8481,7 +8481,7 @@ nsDocument::OnPageHide(bool aPersisted, EnumerateActivityObservers(NotifyActivityChanged, nullptr); ClearPendingFullscreenRequests(this); - if (FullScreenStackTop()) { + if (GetUnretargetedFullScreenElement()) { // If this document was fullscreen, we should exit fullscreen in this // doctree branch. This ensures that if the user navigates while in // fullscreen mode we don't leave its still visible ancestor documents @@ -10112,6 +10112,19 @@ nsDocument::GetFullscreenRoot() return root; } +size_t +nsDocument::CountFullscreenElements() const { + size_t count = 0; + for (const nsWeakPtr& ptr : mTopLayer) { + if (nsCOMPtr elem = do_QueryReferent(ptr)) { + if (elem->State().HasState(NS_EVENT_STATE_FULL_SCREEN)) { + count++; + } + } + } + return count; +} + void nsDocument::SetFullscreenRoot(nsIDocument* aRoot) { @@ -10175,7 +10188,7 @@ nsIDocument::AsyncExitFullscreen(nsIDocument* aDoc) static bool CountFullscreenSubDocuments(nsIDocument* aDoc, void* aData) { - if (aDoc->FullScreenStackTop()) { + if (aDoc->GetUnretargetedFullScreenElement()) { uint32_t* count = static_cast(aData); (*count)++; } @@ -10195,7 +10208,7 @@ nsDocument::IsFullscreenLeaf() { // A fullscreen leaf document is fullscreen, and has no fullscreen // subdocuments. - if (!FullScreenStackTop()) { + if (!GetUnretargetedFullScreenElement()) { return false; } return CountFullscreenSubDocuments(this) == 0; @@ -10204,11 +10217,11 @@ nsDocument::IsFullscreenLeaf() static bool ResetFullScreen(nsIDocument* aDocument, void* aData) { - if (aDocument->FullScreenStackTop()) { + if (aDocument->GetUnretargetedFullScreenElement()) { NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1, "Should have at most 1 fullscreen subdocument."); static_cast(aDocument)->CleanupFullscreenState(); - NS_ASSERTION(!aDocument->FullScreenStackTop(), + NS_ASSERTION(!aDocument->GetUnretargetedFullScreenElement(), "Should reset full-screen"); auto changed = reinterpret_cast*>(aData); changed->AppendElement(aDocument); @@ -10256,7 +10269,7 @@ nsIDocument::ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc) UnlockPointer(); nsCOMPtr root = aMaybeNotARootDoc->GetFullscreenRoot(); - if (!root || !root->FullScreenStackTop()) { + if (!root || !root->GetUnretargetedFullScreenElement()) { // If a document was detached before exiting from fullscreen, it is // possible that the root had left fullscreen state. In this case, // we would not get anything from the ResetFullScreen() call. Root's @@ -10285,7 +10298,7 @@ nsIDocument::ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc) DispatchFullScreenChange(changed[changed.Length() - i - 1]); } - NS_ASSERTION(!root->FullScreenStackTop(), + NS_ASSERTION(!root->GetUnretargetedFullScreenElement(), "Fullscreen root should no longer be a fullscreen doc..."); // Move the top-level window out of fullscreen mode. @@ -10302,7 +10315,7 @@ GetFullscreenLeaf(nsIDocument* aDoc, void* aData) nsIDocument** result = static_cast(aData); *result = aDoc; return false; - } else if (aDoc->FullScreenStackTop()) { + } else if (aDoc->GetUnretargetedFullScreenElement()) { aDoc->EnumerateSubDocuments(GetFullscreenLeaf, aData); } return true; @@ -10321,7 +10334,7 @@ GetFullscreenLeaf(nsIDocument* aDoc) nsIDocument* root = nsContentUtils::GetRootDocument(aDoc); // Check that the root is actually fullscreen so we don't waste time walking // around its descendants. - if (!root->FullScreenStackTop()) { + if (!root->GetUnretargetedFullScreenElement()) { return nullptr; } GetFullscreenLeaf(root, &leaf); @@ -10331,10 +10344,10 @@ GetFullscreenLeaf(nsIDocument* aDoc) void nsDocument::RestorePreviousFullScreenState() { - NS_ASSERTION(!FullScreenStackTop() || !FullscreenRoots::IsEmpty(), + NS_ASSERTION(!GetUnretargetedFullScreenElement() || !FullscreenRoots::IsEmpty(), "Should have at least 1 fullscreen root when fullscreen!"); - if (!FullScreenStackTop() || !GetWindow() || FullscreenRoots::IsEmpty()) { + if (!GetUnretargetedFullScreenElement() || !GetWindow() || FullscreenRoots::IsEmpty()) { return; } @@ -10350,10 +10363,10 @@ nsDocument::RestorePreviousFullScreenState() // Collect all ancestor documents which we are going to change. for (; doc; doc = doc->GetParentDocument()) { nsDocument* theDoc = static_cast(doc); - MOZ_ASSERT(!theDoc->mFullScreenStack.IsEmpty(), + Element* top = theDoc->GetUnretargetedFullScreenElement(); + MOZ_ASSERT(!top, "Ancestor of fullscreen document must also be in fullscreen"); if (doc != this) { - Element* top = theDoc->FullScreenStackTop(); if (top->IsHTMLElement(nsGkAtoms::iframe)) { if (static_cast(top)->FullscreenFlag()) { // If this is an iframe, and it explicitly requested @@ -10363,14 +10376,14 @@ nsDocument::RestorePreviousFullScreenState() } } exitDocs.AppendElement(theDoc); - if (theDoc->mFullScreenStack.Length() > 1) { + if (theDoc->CountFullscreenElements() > 1) { break; } } nsDocument* lastDoc = exitDocs.LastElement(); - if (!lastDoc->GetParentDocument() && - lastDoc->mFullScreenStack.Length() == 1) { + size_t fullscreenCount = lastDoc->CountFullscreenElements(); + if (!lastDoc->GetParentDocument() && fullscreenCount == 1) { // If we are fully exiting fullscreen, don't touch anything here, // just wait for the window to get out from fullscreen first. AskWindowToExitFullscreen(this); @@ -10387,8 +10400,8 @@ nsDocument::RestorePreviousFullScreenState() // The last document will either rollback one fullscreen element, or // completely exit from the fullscreen state as well. nsIDocument* newFullscreenDoc; - if (lastDoc->mFullScreenStack.Length() > 1) { - lastDoc->FullScreenStackPop(); + if (fullscreenCount > 1) { + lastDoc->UnsetFullscreenElement(); newFullscreenDoc = lastDoc; } else { lastDoc->CleanupFullscreenState(); @@ -10482,93 +10495,131 @@ ClearFullscreenStateOnElement(Element* aElement) void nsDocument::CleanupFullscreenState() { - // Iterate the fullscreen stack and clear the fullscreen states. + // Iterate the top layer and clear the fullscreen states. // Since we also need to clear the fullscreen-ancestor state, and // currently fullscreen elements can only be placed in hierarchy // order in the stack, reversely iterating the stack could be more // efficient. NOTE that fullscreen-ancestor state would be removed // in bug 1199529, and the elements may not in hierarchy order // after bug 1195213. - for (nsWeakPtr& weakPtr : Reversed(mFullScreenStack)) { - if (nsCOMPtr element = do_QueryReferent(weakPtr)) { - ClearFullscreenStateOnElement(element); + mTopLayer.RemoveElementsBy([&](const nsWeakPtr& weakPtr) { + nsCOMPtr element(do_QueryReferent(weakPtr)); + if (!element || !element->IsInComposedDoc() || + element->OwnerDoc() != this) { + return true; } - } - mFullScreenStack.Clear(); + + if (element->State().HasState(NS_EVENT_STATE_FULL_SCREEN)) { + ClearFullscreenStateOnElement(element); + return true; + } + return false; + }); + mFullscreenRoot = nullptr; UpdateViewportScrollbarOverrideForFullscreen(this); } -bool -nsDocument::FullScreenStackPush(Element* aElement) -{ - NS_ASSERTION(aElement, "Must pass non-null to FullScreenStackPush()"); - Element* top = FullScreenStackTop(); - if (top == aElement || !aElement) { - return false; - } - EventStateManager::SetFullScreenState(aElement, true); - mFullScreenStack.AppendElement(do_GetWeakReference(aElement)); - NS_ASSERTION(FullScreenStackTop() == aElement, "Should match"); +void +nsDocument::UnsetFullscreenElement() { + Element* removedElement = TopLayerPop([](Element* element) -> bool { + return element->State().HasState(NS_EVENT_STATE_FULL_SCREEN); + }); + + MOZ_ASSERT(removedElement->State().HasState(NS_EVENT_STATE_FULL_SCREEN)); + ClearFullscreenStateOnElement(removedElement); UpdateViewportScrollbarOverrideForFullscreen(this); +} + +bool +nsDocument::SetFullscreenElement(Element* aElement) { + if (TopLayerPush(aElement)) { + EventStateManager::SetFullScreenState(aElement, true); + UpdateViewportScrollbarOverrideForFullscreen(this); + return true; + } + return false; +} + +bool +nsDocument::TopLayerPush(Element* aElement) { + NS_ASSERTION(aElement, "Must pass non-null to TopLayerPush()"); + auto predictFunc = [&aElement](Element* element) { + return element == aElement; + }; + TopLayerPop(predictFunc); + + mTopLayer.AppendElement(do_GetWeakReference(aElement)); + NS_ASSERTION(GetTopLayerTop() == aElement, "Should match"); return true; } -void -nsDocument::FullScreenStackPop() -{ - if (mFullScreenStack.IsEmpty()) { - return; +Element* +nsDocument::TopLayerPop(FunctionRef aPredicateFunc) { + if (mTopLayer.IsEmpty()) { + return nullptr; } - ClearFullscreenStateOnElement(FullScreenStackTop()); - - // Remove top element. Note the remaining top element in the stack - // will not have full-screen style bits set, so we will need to restore - // them on the new top element before returning. - uint32_t last = mFullScreenStack.Length() - 1; - mFullScreenStack.RemoveElementAt(last); + // Remove the topmost element that qualifies aPredicate; This + // is required is because the top layer contains not only + // fullscreen elements, but also dialog elements. + Element* removedElement; + for (auto i : Reversed(MakeRange(mTopLayer.Length()))) { + nsCOMPtr element(do_QueryReferent(mTopLayer[i])); + if (element && aPredicateFunc(element)) { + removedElement = element; + mTopLayer.RemoveElementAt(i); + break; + } + } // Pop from the stack null elements (references to elements which have // been GC'd since they were added to the stack) and elements which are // no longer in this document. - while (!mFullScreenStack.IsEmpty()) { - Element* element = FullScreenStackTop(); + while (!mTopLayer.IsEmpty()) { + Element* element = GetTopLayerTop(); if (!element || !element->IsInUncomposedDoc() || element->OwnerDoc() != this) { - NS_ASSERTION(!element->State().HasState(NS_EVENT_STATE_FULL_SCREEN), - "Should have already removed full-screen styles"); - uint32_t last = mFullScreenStack.Length() - 1; - mFullScreenStack.RemoveElementAt(last); + mTopLayer.RemoveElementAt(mTopLayer.Length() - 1); } else { // The top element of the stack is now an in-doc element. Return here. break; } } - UpdateViewportScrollbarOverrideForFullscreen(this); + return removedElement; } Element* -nsDocument::FullScreenStackTop() -{ - if (mFullScreenStack.IsEmpty()) { +nsDocument::GetTopLayerTop() { + if (mTopLayer.IsEmpty()) { return nullptr; } - uint32_t last = mFullScreenStack.Length() - 1; - nsCOMPtr element(do_QueryReferent(mFullScreenStack[last])); - NS_ASSERTION(element, "Should have full-screen element!"); - NS_ASSERTION(element->IsInComposedDoc(), "Full-screen element should be in doc"); - NS_ASSERTION(element->OwnerDoc() == this, "Full-screen element should be in this doc"); + uint32_t last = mTopLayer.Length() - 1; + nsCOMPtr element(do_QueryReferent(mTopLayer[last])); + NS_ASSERTION(element, "Should have a top layer element!"); + NS_ASSERTION(element->IsInComposedDoc(), "Top layer element should be in doc"); + NS_ASSERTION(element->OwnerDoc() == this, "Top layer element should be in this doc"); return element; } -/* virtual */ nsTArray -nsDocument::GetFullscreenStack() const -{ +Element* +nsDocument::GetUnretargetedFullScreenElement() { + for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) { + nsCOMPtr element(do_QueryReferent(weakPtr)); + // Per spec, the fullscreen element is the topmost element in the document's + // top layer of which the fullscreen flag is set, if any, and null otherwise. + if (element && element->State().HasState(NS_EVENT_STATE_FULL_SCREEN)) { + return element; + } + } + return nullptr; +} + +nsTArray +nsDocument::GetTopLayer() const { nsTArray elements; - for (const nsWeakPtr& ptr : mFullScreenStack) { + for (const nsWeakPtr& ptr : mTopLayer) { if (nsCOMPtr elem = do_QueryReferent(ptr)) { - MOZ_ASSERT(elem->State().HasState(NS_EVENT_STATE_FULL_SCREEN)); elements.AppendElement(elem); } } @@ -10683,7 +10734,8 @@ nsDocument::FullscreenElementReadyCheck(Element* aElement, { NS_ASSERTION(aElement, "Must pass non-null element to nsDocument::RequestFullScreen"); - if (!aElement || aElement == FullScreenStackTop()) { + Element* fullscreenElement = GetUnretargetedFullScreenElement(); + if (!aElement || aElement == fullscreenElement) { return false; } if (!aElement->IsInComposedDoc()) { @@ -10712,9 +10764,9 @@ nsDocument::FullscreenElementReadyCheck(Element* aElement, } //XXXsmaug Note, we don't follow the latest fullscreen spec here. // This whole check could be probably removed. - if (FullScreenStackTop() && + if (fullscreenElement && !nsContentUtils::ContentIsHostIncludingDescendantOf(aElement, - FullScreenStackTop())) { + fullscreenElement)) { // If this document is full-screen, only grant full-screen requests from // a descendant of the current full-screen element. DispatchFullscreenError("FullscreenDeniedNotDescendant"); @@ -10871,7 +10923,7 @@ ShouldApplyFullscreenDirectly(nsIDocument* aDoc, // If we are in the content process, we can apply the fullscreen // state directly only if we have been in DOM fullscreen, because // otherwise we always need to notify the chrome. - return !!nsContentUtils::GetRootDocument(aDoc)->GetFullscreenElement(); + return !!nsContentUtils::GetRootDocument(aDoc)->GetUnretargetedFullScreenElement(); } else { // If we are in the chrome process, and the window has not been in // fullscreen, we certainly need to make that fullscreen first. @@ -10994,8 +11046,8 @@ nsDocument::ApplyFullscreen(const FullscreenRequest& aRequest) // Set the full-screen element. This sets the full-screen style on the // element, and the full-screen-ancestor styles on ancestors of the element // in this document. - DebugOnly x = FullScreenStackPush(elem); - NS_ASSERTION(x, "Full-screen state of requesting doc should always change!"); + DebugOnly x = SetFullscreenElement(elem); + MOZ_ASSERT(x, "Full-screen state of requesting doc should always change!"); // Set the iframe fullscreen flag. if (elem->IsHTMLElement(nsGkAtoms::iframe)) { static_cast(elem)->SetFullscreenFlag(true); @@ -11017,14 +11069,14 @@ nsDocument::ApplyFullscreen(const FullscreenRequest& aRequest) } nsIDocument* parent = child->GetParentDocument(); Element* element = parent->FindContentForSubDocument(child)->AsElement(); - if (static_cast(parent)->FullScreenStackPush(element)) { + if (static_cast(parent)->SetFullscreenElement(element)) { changed.AppendElement(parent); child = parent; } else { // We've reached either the root, or a point in the doctree where the // new full-screen element container is the same as the previous // full-screen element's container. No more changes need to be made - // to the full-screen stacks of documents further up the tree. + // to the top layer of documents further up the tree. break; } } @@ -11294,7 +11346,7 @@ PointerLockRequest::Run() } // If it is neither user input initiated, nor requested in fullscreen, // it should be rejected. - if (!error && !mUserInputOrChromeCaller && !doc->GetFullscreenElement()) { + if (!error && !mUserInputOrChromeCaller && !doc->GetUnretargetedFullScreenElement()) { error = "PointerLockDeniedNotInputDriven"; } if (!error && !d->SetPointerLock(e, NS_STYLE_CURSOR_NONE)) { diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 22157500a0..bb05a4fb9b 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -56,6 +56,7 @@ #include "imgIRequest.h" #include "mozilla/EventListenerManager.h" #include "mozilla/EventStates.h" +#include "mozilla/FunctionRef.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PendingAnimationTracker.h" #include "mozilla/dom/DOMImplementation.h" @@ -77,6 +78,7 @@ #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2) #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3) +using namespace mozilla; class nsDOMStyleSheetSetList; class nsDocument; @@ -638,7 +640,7 @@ public: using mozilla::dom::DocumentOrShadowRoot::GetElementsByTagName; using mozilla::dom::DocumentOrShadowRoot::GetElementsByTagNameNS; using mozilla::dom::DocumentOrShadowRoot::GetElementsByClassName; - + // nsIDOMEventTarget virtual nsresult GetEventTargetParent( mozilla::EventChainPreVisitor& aVisitor) override; @@ -850,7 +852,7 @@ public: virtual Element* FindImageMap(const nsAString& aNormalizedMapName) override; - virtual nsTArray GetFullscreenStack() const override; + virtual nsTArray GetTopLayer() const override; virtual void AsyncRequestFullScreen( mozilla::UniquePtr&& aRequest) override; virtual void RestorePreviousFullScreenState() override; @@ -860,6 +862,7 @@ public: virtual nsresult RemoteFrameFullscreenReverted() override; virtual nsIDocument* GetFullscreenRoot() override; + virtual size_t CountFullscreenElements() const override; virtual void SetFullscreenRoot(nsIDocument* aRoot) override; // Returns the size of the mBlockedTrackingNodes array. (nsIDocument.h) @@ -896,23 +899,29 @@ public: // to move this document into full-screen mode if allowed. void RequestFullScreen(mozilla::UniquePtr&& aRequest); - // Removes all elements from the full-screen stack, removing full-scren - // styles from the top element in the stack. + // Removes all elements with the full-screen flag set from the top layer, and + // clears their full-screen flag. void CleanupFullscreenState(); - // Pushes aElement onto the full-screen stack, and removes full-screen styles - // from the former full-screen stack top, and its ancestors, and applies the - // styles to aElement. aElement becomes the new "full-screen element". - bool FullScreenStackPush(Element* aElement); + // Pushes aElement onto the top layer + bool TopLayerPush(Element* aElement); - // Remove the top element from the full-screen stack. Removes the full-screen - // styles from the former top element, and applies them to the new top - // element, if there is one. - void FullScreenStackPop(); + // Removes the topmost element which have aPredicate return true from the top + // layer. The removed element, if any, is returned. + Element* TopLayerPop(FunctionRef aPredicateFunc); + + // Pops the fullscreen element from the top layer and clears its + // fullscreen flag. + void UnsetFullscreenElement(); + + // Pushes the given element into the top of top layer and set fullscreen + // flag. + bool SetFullscreenElement(Element* aElement); // Returns the top element from the full-screen stack. - Element* FullScreenStackTop() override; - + Element* GetTopLayerTop() override; + // Return the fullscreen element in the top layer + Element* GetUnretargetedFullScreenElement() override; // DOM-exposed fullscreen API bool FullscreenEnabled() override; @@ -1193,10 +1202,8 @@ protected: // is a weak reference to avoid leaks due to circular references. nsWeakPtr mScopeObject; - // Stack of full-screen elements. When we request full-screen we push the - // full-screen element onto this stack, and when we cancel full-screen we - // pop one off this stack, restoring the previous full-screen state - nsTArray mFullScreenStack; + // Stack of top layer elements. + nsTArray mTopLayer; // The root of the doc tree in which this document is in. This is only // non-null when this document is in fullscreen mode. diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index 90c0668e2e..81b5b51c8e 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -33,6 +33,7 @@ #include "mozilla/UniquePtr.h" #include "mozilla/CORSMode.h" #include "mozilla/dom/DocumentOrShadowRoot.h" +#include "mozilla/FunctionRef.h" #include "mozilla/LinkedList.h" #include "mozilla/StyleSheet.h" #include "mozilla/TimeStamp.h" @@ -1273,9 +1274,9 @@ public: virtual void RemoveFromNameTable(Element* aElement, nsIAtom* aName) = 0; /** - * Returns all elements in the fullscreen stack in the insertion order. + * Returns all elements in the top layer in the insertion order. */ - virtual nsTArray GetFullscreenStack() const = 0; + virtual nsTArray GetTopLayer() const = 0; /** * Asynchronously requests that the document make aElement the fullscreen @@ -1331,6 +1332,8 @@ public: */ virtual nsIDocument* GetFullscreenRoot() = 0; + virtual size_t CountFullscreenElements() const = 0; + /** * Sets the fullscreen root to aRoot. This stores a weak reference to aRoot * in this document. @@ -2607,7 +2610,8 @@ public: nsIURI* GetDocumentURIObject() const; // Not const because all the full-screen goop is not const virtual bool FullscreenEnabled() = 0; - virtual Element* FullScreenStackTop() = 0; + virtual Element* GetTopLayerTop() = 0; + virtual Element* GetUnretargetedFullScreenElement() = 0; bool Fullscreen() { return !!GetFullscreenElement(); diff --git a/layout/generic/nsViewportFrame.cpp b/layout/generic/nsViewportFrame.cpp index ec933b84e3..ab3bfc88c3 100644 --- a/layout/generic/nsViewportFrame.cpp +++ b/layout/generic/nsViewportFrame.cpp @@ -123,8 +123,8 @@ ViewportFrame::BuildDisplayListForTopLayer(nsDisplayListBuilder* aBuilder, nsDisplayList* aList) { nsIDocument* doc = PresContext()->Document(); - nsTArray fullscreenStack = doc->GetFullscreenStack(); - for (Element* elem : fullscreenStack) { + nsTArray toplayer = doc->GetTopLayer(); + for (Element* elem : toplayer) { if (nsIFrame* frame = elem->GetPrimaryFrame()) { // There are two cases where an element in fullscreen is not in // the top layer: From 7a04afab4ab72301308ca293c43699c40e268baf Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sat, 4 May 2024 18:16:28 +0200 Subject: [PATCH 4/9] Issue #2472 - Part 4: Make TopLayer{Push|Pop} part of nsIDocument. --- dom/base/nsDocument.h | 4 ++-- dom/base/nsIDocument.h | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index bb05a4fb9b..16b93d245d 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -904,11 +904,11 @@ public: void CleanupFullscreenState(); // Pushes aElement onto the top layer - bool TopLayerPush(Element* aElement); + bool TopLayerPush(Element* aElement) override; // Removes the topmost element which have aPredicate return true from the top // layer. The removed element, if any, is returned. - Element* TopLayerPop(FunctionRef aPredicateFunc); + Element* TopLayerPop(FunctionRef aPredicateFunc) override; // Pops the fullscreen element from the top layer and clears its // fullscreen flag. diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index 81b5b51c8e..2a18c2259e 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -100,6 +100,8 @@ class nsIGlobalObject; class nsStyleSet; struct nsCSSSelectorList; +using mozilla::FunctionRef; + namespace mozilla { class CSSStyleSheet; class ErrorResult; @@ -1339,6 +1341,13 @@ public: * in this document. */ virtual void SetFullscreenRoot(nsIDocument* aRoot) = 0; + + /** + * Push elements to and pop elements from the top layer. + * Currently in use for fullscreen and modal version of . + */ + virtual bool TopLayerPush(Element* aElement) = 0; + virtual Element* TopLayerPop(FunctionRef aPredicateFunc) = 0; /** * Synchronously cleans up the fullscreen state on the given document. From 7d34b285e0b223475c0ba87c49d8d6255ddc2476 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sat, 4 May 2024 18:28:27 +0200 Subject: [PATCH 5/9] Issue #2472 - Part 5: Make modal versions of work, and tune styles. This adds a pseudo-class `:-moz-modal-dialog` to be able to track whether the dialog is in the top layer in CSS. This implements default styling for modal dialogs following the HTML standard. Slightly off-extreme color scheme chosen for unstyled elements. --- dom/events/EventStates.h | 5 ++++- dom/html/HTMLDialogElement.cpp | 27 +++++++++++++++++++++++++++ dom/html/HTMLDialogElement.h | 7 +++++++ layout/style/nsCSSPseudoClassList.h | 6 ++++++ layout/style/res/html.css | 16 +++++++++++++--- 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/dom/events/EventStates.h b/dom/events/EventStates.h index de0f651bd4..eef0ca1002 100644 --- a/dom/events/EventStates.h +++ b/dom/events/EventStates.h @@ -291,6 +291,8 @@ private: #define NS_EVENT_STATE_PLACEHOLDERSHOWN NS_DEFINE_EVENT_STATE_MACRO(52) // Element has focus-within. #define NS_EVENT_STATE_FOCUS_WITHIN NS_DEFINE_EVENT_STATE_MACRO(53) +// Modal element +#define NS_EVENT_STATE_MODAL_DIALOG NS_DEFINE_EVENT_STATE_MACRO(54) #define DIR_ATTR_STATES (NS_EVENT_STATE_HAS_DIR_ATTR | \ NS_EVENT_STATE_DIR_ATTR_LTR | \ @@ -309,7 +311,8 @@ private: #define ESM_MANAGED_STATES (DIR_ATTR_STATES | NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS | \ NS_EVENT_STATE_HOVER | NS_EVENT_STATE_DRAGOVER | \ NS_EVENT_STATE_URLTARGET | NS_EVENT_STATE_FOCUSRING | \ - NS_EVENT_STATE_FULL_SCREEN | NS_EVENT_STATE_FOCUS_WITHIN) + NS_EVENT_STATE_FULL_SCREEN | NS_EVENT_STATE_FOCUS_WITHIN | \ + NS_EVENT_STATE_MODAL_DIALOG) #define INTRINSIC_STATES (~ESM_MANAGED_STATES) diff --git a/dom/html/HTMLDialogElement.cpp b/dom/html/HTMLDialogElement.cpp index a178e5afea..795b44dc1a 100644 --- a/dom/html/HTMLDialogElement.cpp +++ b/dom/html/HTMLDialogElement.cpp @@ -56,6 +56,9 @@ HTMLDialogElement::Close(const mozilla::dom::Optional& aReturnValue) ErrorResult ignored; SetOpen(false, ignored); ignored.SuppressException(); + + RemoveFromTopLayerIfNeeded(); + RefPtr eventDispatcher = new AsyncEventDispatcher(this, NS_LITERAL_STRING("close"), false); eventDispatcher->PostDOMEvent(); @@ -72,6 +75,26 @@ HTMLDialogElement::Show() ignored.SuppressException(); } +bool HTMLDialogElement::IsInTopLayer() const { + return State().HasState(NS_EVENT_STATE_MODAL_DIALOG); +} + +void HTMLDialogElement::RemoveFromTopLayerIfNeeded() { + if (!IsInTopLayer()) { + return; + } + auto predictFunc = [&](Element* element) { return element == this; }; + + DebugOnly removedElement = OwnerDoc()->TopLayerPop(predictFunc); + MOZ_ASSERT(removedElement == this); + RemoveStates(NS_EVENT_STATE_MODAL_DIALOG); +} + +void HTMLDialogElement::UnbindFromTree(bool aNullParent) { + RemoveFromTopLayerIfNeeded(); + nsGenericHTMLElement::UnbindFromTree(aNullParent); +} + void HTMLDialogElement::ShowModal(ErrorResult& aError) { @@ -79,6 +102,10 @@ HTMLDialogElement::ShowModal(ErrorResult& aError) aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } + + if (!IsInTopLayer() && OwnerDoc()->TopLayerPush(this)) { + AddStates(NS_EVENT_STATE_MODAL_DIALOG); + } SetOpen(true, aError); aError.SuppressException(); diff --git a/dom/html/HTMLDialogElement.h b/dom/html/HTMLDialogElement.h index 0520ef56b0..6386452d91 100644 --- a/dom/html/HTMLDialogElement.h +++ b/dom/html/HTMLDialogElement.h @@ -41,10 +41,14 @@ public: { mReturnValue = aReturnValue; } + + void UnbindFromTree(bool aNullParent = true) /*override*/; void Close(const mozilla::dom::Optional& aReturnValue); void Show(); void ShowModal(ErrorResult& aError); + + bool IsInTopLayer() const; nsString mReturnValue; @@ -52,6 +56,9 @@ protected: virtual ~HTMLDialogElement(); JSObject* WrapNode(JSContext* aCx, JS::Handle aGivenProto) override; + +private: + void RemoveFromTopLayerIfNeeded(); }; } // namespace dom diff --git a/layout/style/nsCSSPseudoClassList.h b/layout/style/nsCSSPseudoClassList.h index 7bdb244c0a..9b1b4355e6 100644 --- a/layout/style/nsCSSPseudoClassList.h +++ b/layout/style/nsCSSPseudoClassList.h @@ -190,6 +190,12 @@ CSS_STATE_PSEUDO_CLASS(fullscreen, ":fullscreen", NS_EVENT_STATE_FULL_SCREEN) CSS_STATE_PSEUDO_CLASS(mozFullScreen, ":-moz-full-screen", 0, "", NS_EVENT_STATE_FULL_SCREEN) +// Matches if a element is modal +CSS_STATE_PSEUDO_CLASS(mozModalDialog, ":-moz-modal-dialog", + CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS, + "", + NS_EVENT_STATE_MODAL_DIALOG) + // Matches if the element is focused and should show a focus ring CSS_STATE_PSEUDO_CLASS(mozFocusRing, ":-moz-focusring", 0, "", NS_EVENT_STATE_FOCUSRING) diff --git a/layout/style/res/html.css b/layout/style/res/html.css index 93539c0890..7f792a06bc 100644 --- a/layout/style/res/html.css +++ b/layout/style/res/html.css @@ -811,14 +811,15 @@ dialog { position: absolute; inset-inline-start: 0; inset-inline-end: 0; - color: black; margin: auto; border-width: initial; border-style: solid; - border-color: initial; + border-color: dimgrey; border-image: initial; + border-radius: 5px; padding: 1em; - background: white; + color: #222; /*CanvasText*/ + background: snow; /*Canvas*/ width: -moz-fit-content; } @@ -826,6 +827,15 @@ dialog:not([open]) { display: none; } +dialog:-moz-modal-dialog { + -moz-top-layer: top !important; +} + +/* https://html.spec.whatwg.org/#flow-content-3 */ +dialog::backdrop { + background: rgba(0, 0, 0, 0.1); +} + /* emulation of non-standard HTML tag */ marquee { inline-size: -moz-available; From 87a7cf733238bf16a1321104958a65abd486c285 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 6 May 2024 12:33:09 +0200 Subject: [PATCH 6/9] 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; From a3826565a7020034ff225ca57286ecaf6aa7ec80 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 6 May 2024 21:21:53 +0200 Subject: [PATCH 7/9] No issue - Regenerate devtools css database We were missing ::slotted, apparently. --- devtools/shared/css/generated/properties-db.js | 1 + 1 file changed, 1 insertion(+) diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js index fb95ffd1f1..fd8635bbfa 100644 --- a/devtools/shared/css/generated/properties-db.js +++ b/devtools/shared/css/generated/properties-db.js @@ -9826,6 +9826,7 @@ exports.CSS_PROPERTIES = { exports.PSEUDO_ELEMENTS = [ ":after", ":before", + ":slotted", ":backdrop", ":first-letter", ":first-line", From c77b4b0badd092f16e1426f595c2c78f3a3f7fb9 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 6 May 2024 22:09:18 +0200 Subject: [PATCH 8/9] Issue #2510 - Unprefix -moz-fit-content No aliasing since this has been out for a while. Devtools db regenerated with `mach devtools-css-db` Resolves #2510 --- .../shared/css/generated/properties-db.js | 28 +++++++++---------- layout/base/nsLayoutUtils.cpp | 8 +++--- layout/style/nsCSSKeywordList.h | 1 - layout/style/nsCSSProps.cpp | 4 +-- layout/style/res/forms.css | 2 +- layout/style/res/html.css | 2 +- layout/tables/BasicTableLayoutStrategy.cpp | 4 +-- layout/tables/FixedTableLayoutStrategy.cpp | 4 +-- 8 files changed, 26 insertions(+), 27 deletions(-) diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js index fd8635bbfa..587f5704d1 100644 --- a/devtools/shared/css/generated/properties-db.js +++ b/devtools/shared/css/generated/properties-db.js @@ -2208,10 +2208,10 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "auto", "calc", "content", + "fit-content", "inherit", "initial", "max-content", @@ -2231,10 +2231,10 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "auto", "calc", "content", + "fit-content", "inherit", "initial", "max-content", @@ -3282,7 +3282,6 @@ exports.CSS_PROPERTIES = { "-moz-crisp-edges", "-moz-deck", "-moz-element", - "-moz-fit-content", "-moz-grid", "-moz-grid-group", "-moz-grid-line", @@ -3424,6 +3423,7 @@ exports.CSS_PROPERTIES = { "extra-expanded", "fill", "fill-box", + "fit-content", "fixed", "flat", "flex", @@ -6000,10 +6000,10 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "auto", "calc", "content", + "fit-content", "inherit", "initial", "max-content", @@ -6023,10 +6023,10 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "auto", "calc", "content", + "fit-content", "inherit", "initial", "max-content", @@ -6816,9 +6816,9 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "auto", "calc", + "fit-content", "inherit", "initial", "max-content", @@ -6903,9 +6903,9 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "auto", "calc", + "fit-content", "inherit", "initial", "max-content", @@ -7782,8 +7782,8 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "calc", + "fit-content", "inherit", "initial", "max-content", @@ -7804,8 +7804,8 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "calc", + "fit-content", "inherit", "initial", "max-content", @@ -7826,8 +7826,8 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "calc", + "fit-content", "inherit", "initial", "max-content", @@ -7866,9 +7866,9 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "auto", "calc", + "fit-content", "inherit", "initial", "max-content", @@ -7888,9 +7888,9 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "auto", "calc", + "fit-content", "inherit", "initial", "max-content", @@ -7910,9 +7910,9 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "auto", "calc", + "fit-content", "inherit", "initial", "max-content", @@ -9710,9 +9710,9 @@ exports.CSS_PROPERTIES = { "values": [ "-moz-available", "-moz-calc", - "-moz-fit-content", "auto", "calc", + "fit-content", "inherit", "initial", "max-content", diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index e156e432d3..fb7390753d 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -4726,8 +4726,8 @@ GetDefiniteSizeTakenByBoxSizing(StyleBoxSizing aBoxSizing, } // Handles only max-content and min-content, and -// -moz-fit-content for min-width and max-width, since the others -// (-moz-fit-content for width, and -moz-available) have no effect on +// fit-content for min-width and max-width, since the others +// (fit-content for width, and -moz-available) have no effect on // intrinsic widths. enum eWidthProperty { PROP_WIDTH, PROP_MAX_WIDTH, PROP_MIN_WIDTH }; static bool @@ -5038,7 +5038,7 @@ nsLayoutUtils::IntrinsicForAxis(PhysicalAxis aAxis, if (styleISize.GetUnit() == eStyleUnit_Enumerated && (styleISize.GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT || styleISize.GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT)) { - // -moz-fit-content and -moz-available enumerated widths compute intrinsic + // fit-content and -moz-available enumerated widths compute intrinsic // widths just like auto. // For max-content and min-content, we handle them like // specified widths, but ignore box-sizing. @@ -5054,7 +5054,7 @@ nsLayoutUtils::IntrinsicForAxis(PhysicalAxis aAxis, ++gNoiseIndent; #endif if (aType != MIN_ISIZE) { - // At this point, |styleISize| is auto/-moz-fit-content/-moz-available or + // At this point, |styleISize| is auto/fit-content/-moz-available or // has a percentage. The intrinisic size for those under a max-content // constraint is the max-content contribution which we shouldn't clamp. aMarginBoxMinSizeClamp = NS_MAXSIZE; diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index bbe25893dd..033ca5db17 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -63,7 +63,6 @@ CSS_KEY(-moz-element, _moz_element) CSS_KEY(-moz-eventreerow, _moz_eventreerow) CSS_KEY(-moz-field, _moz_field) CSS_KEY(-moz-fieldtext, _moz_fieldtext) -CSS_KEY(-moz-fit-content, _moz_fit_content) CSS_KEY(-moz-fixed, _moz_fixed) CSS_KEY(-moz-grabbing, _moz_grabbing) CSS_KEY(-moz-grab, _moz_grab) diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index 7f1fe2c18f..9acc69daf1 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -2260,7 +2260,7 @@ const KTableEntry nsCSSProps::kWhitespaceKTable[] = { const KTableEntry nsCSSProps::kWidthKTable[] = { { eCSSKeyword_max_content, NS_STYLE_WIDTH_MAX_CONTENT }, { eCSSKeyword_min_content, NS_STYLE_WIDTH_MIN_CONTENT }, - { eCSSKeyword__moz_fit_content, NS_STYLE_WIDTH_FIT_CONTENT }, + { eCSSKeyword_fit_content, NS_STYLE_WIDTH_FIT_CONTENT }, { eCSSKeyword__moz_available, NS_STYLE_WIDTH_AVAILABLE }, { eCSSKeyword_UNKNOWN, -1 } }; @@ -2269,7 +2269,7 @@ const KTableEntry nsCSSProps::kWidthKTable[] = { const KTableEntry nsCSSProps::kFlexBasisKTable[] = { { eCSSKeyword_max_content, NS_STYLE_WIDTH_MAX_CONTENT }, { eCSSKeyword_min_content, NS_STYLE_WIDTH_MIN_CONTENT }, - { eCSSKeyword__moz_fit_content, NS_STYLE_WIDTH_FIT_CONTENT }, + { eCSSKeyword_fit_content, NS_STYLE_WIDTH_FIT_CONTENT }, { eCSSKeyword__moz_available, NS_STYLE_WIDTH_AVAILABLE }, { eCSSKeyword_content, NS_STYLE_FLEX_BASIS_CONTENT }, { eCSSKeyword_UNKNOWN, -1 } diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css index fcf5874829..6d0a5503ae 100644 --- a/layout/style/res/forms.css +++ b/layout/style/res/forms.css @@ -55,7 +55,7 @@ fieldset > legend { padding-inline-start: 2px; padding-inline-end: 2px; - inline-size: -moz-fit-content; + inline-size: fit-content; } legend { diff --git a/layout/style/res/html.css b/layout/style/res/html.css index 7f792a06bc..38814a8354 100644 --- a/layout/style/res/html.css +++ b/layout/style/res/html.css @@ -820,7 +820,7 @@ dialog { padding: 1em; color: #222; /*CanvasText*/ background: snow; /*Canvas*/ - width: -moz-fit-content; + width: fit-content; } dialog:not([open]) { diff --git a/layout/tables/BasicTableLayoutStrategy.cpp b/layout/tables/BasicTableLayoutStrategy.cpp index 8ece42fcb3..c599142252 100644 --- a/layout/tables/BasicTableLayoutStrategy.cpp +++ b/layout/tables/BasicTableLayoutStrategy.cpp @@ -179,7 +179,7 @@ GetISizeInfo(nsRenderingContext *aRenderingContext, if (!aIsCell || maxISize.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) { maxISize.SetNoneValue(); } else if (maxISize.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) { - // for 'max-inline-size', '-moz-fit-content' is like + // for 'max-inline-size', 'fit-content' is like // 'max-content' maxISize.SetIntValue(NS_STYLE_WIDTH_MAX_CONTENT, eStyleUnit_Enumerated); @@ -206,7 +206,7 @@ GetISizeInfo(nsRenderingContext *aRenderingContext, if (!aIsCell || minISize.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) { minISize.SetCoordValue(0); } else if (minISize.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) { - // for 'min-inline-size', '-moz-fit-content' is like + // for 'min-inline-size', 'fit-content' is like // 'min-content' minISize.SetIntValue(NS_STYLE_WIDTH_MIN_CONTENT, eStyleUnit_Enumerated); diff --git a/layout/tables/FixedTableLayoutStrategy.cpp b/layout/tables/FixedTableLayoutStrategy.cpp index 072be701a8..d1b30b9059 100644 --- a/layout/tables/FixedTableLayoutStrategy.cpp +++ b/layout/tables/FixedTableLayoutStrategy.cpp @@ -111,7 +111,7 @@ FixedTableLayoutStrategy::GetMinISize(nsRenderingContext* aRenderingContext) result -= spacing * (colSpan - 1); } } - // else, for 'auto', '-moz-available', '-moz-fit-content', + // else, for 'auto', '-moz-available', 'fit-content', // and 'calc()' with percentages, do nothing } } @@ -266,7 +266,7 @@ FixedTableLayoutStrategy::ComputeColumnISizes(const ReflowInput& aReflowInput) colFrame->AddPrefPercent(pct); pctTotal += pct; } else { - // 'auto', '-moz-available', '-moz-fit-content', and 'calc()' + // 'auto', '-moz-available', 'fit-content', and 'calc()' // with percentages colISize = unassignedMarker; } From de8213f561e41d353f6b5cdaa48bafa629cfda85 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 7 May 2024 11:30:59 +0200 Subject: [PATCH 9/9] Issue #2472 - Part 8: Fix conflicting defines on Mac. --- dom/events/TextComposition.cpp | 13 ------------- dom/plugins/base/npapi.h | 7 +++++++ dom/plugins/base/nsPluginInstanceOwner.h | 7 +++++++ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/dom/events/TextComposition.cpp b/dom/events/TextComposition.cpp index bd7ebbc468..3b3dc6505b 100644 --- a/dom/events/TextComposition.cpp +++ b/dom/events/TextComposition.cpp @@ -21,21 +21,8 @@ #include "mozilla/Unused.h" #include "mozilla/dom/TabParent.h" -#ifdef XP_MACOSX -// Some defiens will be conflict with OSX SDK -#define TextRange _TextRange -#define TextRangeArray _TextRangeArray -#define Comment _Comment -#endif - #include "nsPluginInstanceOwner.h" -#ifdef XP_MACOSX -#undef TextRange -#undef TextRangeArray -#undef Comment -#endif - using namespace mozilla::widget; namespace mozilla { diff --git a/dom/plugins/base/npapi.h b/dom/plugins/base/npapi.h index aa6a66b2bd..996df80844 100644 --- a/dom/plugins/base/npapi.h +++ b/dom/plugins/base/npapi.h @@ -34,7 +34,14 @@ #endif #if defined(XP_MACOSX) +// Some defines will conflict with OSX SDK +#define TextRange _TextRange +#define TextRangeArray _TextRangeArray +#define Comment _Comment #include +#undef TextRange +#undef TextRangeArray +#undef Comment #include #ifndef NP_NO_CARBON #include diff --git a/dom/plugins/base/nsPluginInstanceOwner.h b/dom/plugins/base/nsPluginInstanceOwner.h index 03977b047f..0eba706cb4 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.h +++ b/dom/plugins/base/nsPluginInstanceOwner.h @@ -20,7 +20,14 @@ #ifdef XP_MACOSX #include "mozilla/gfx/QuartzSupport.h" +// Some defines will conflict with OSX SDK +#define TextRange _TextRange +#define TextRangeArray _TextRangeArray +#define Comment _Comment #include +#undef TextRange +#undef TextRangeArray +#undef Comment #endif class nsIInputStream;