commit 90c4ed44a7184238502a52af28fd04b73e081822 parent 5d3c32565550318ec526f927f31744024d81f781 Author: Keith Cirkel <keithamus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:28:59 +0000 Bug 1968987 - Part 2 - Set ToggleEvent source on Popover/Dialog r=smaug,dom-core This uses the implicit "invoker" from each element to set the ToggleEvent Source, so that toggle events dispatched from Dialogs & Popovers include the `source` attribute. Differential Revision: https://phabricator.services.mozilla.com/D265435 Diffstat:
16 files changed, 133 insertions(+), 120 deletions(-)
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp @@ -16008,7 +16008,8 @@ void Document::HideAllPopoversUntil(nsINode& aEndpoint, auto closeAllOpenPopovers = [&aFocusPreviousElement, &aFireEvents, this]() MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { while (RefPtr<Element> topmost = GetTopmostAutoPopover()) { - HidePopover(*topmost, aFocusPreviousElement, aFireEvents, IgnoreErrors()); + HidePopover(*topmost, aFocusPreviousElement, aFireEvents, + /* aSource */ nullptr, IgnoreErrors()); } }; @@ -16054,7 +16055,8 @@ void Document::HideAllPopoversUntil(nsINode& aEndpoint, if (!topmost) { break; } - HidePopover(*topmost, aFocusPreviousElement, fireEvents, IgnoreErrors()); + HidePopover(*topmost, aFocusPreviousElement, fireEvents, + /* aSource */ nullptr, IgnoreErrors()); } repeatingHide = needRepeatingHide(); @@ -16066,7 +16068,8 @@ void Document::HideAllPopoversUntil(nsINode& aEndpoint, // https://html.spec.whatwg.org/#hide-popover-algorithm void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement, - bool aFireEvents, ErrorResult& aRv) { + bool aFireEvents, Element* aSource, + ErrorResult& aRv) { RefPtr<nsGenericHTMLElement> popoverHTMLEl = nsGenericHTMLElement::FromNode(aPopover); NS_ASSERTION(popoverHTMLEl, "Not a HTML element"); @@ -16134,7 +16137,6 @@ void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement, auto* data = popoverHTMLEl->GetPopoverData(); MOZ_ASSERT(data, "Should have popover data"); - data->SetInvoker(nullptr); // 9. If fireEvents is true: // Fire beforetoggle event and re-check popover validity. @@ -16144,8 +16146,8 @@ void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement, // initialized to "closed" at element. Intentionally ignore the return value // here as only on open event for beforetoggle the cancelable attribute is // initialized to true. - popoverHTMLEl->FireToggleEvent(u"open"_ns, u"closed"_ns, - u"beforetoggle"_ns); + popoverHTMLEl->FireToggleEvent(u"open"_ns, u"closed"_ns, u"beforetoggle"_ns, + aSource); // 9.2. If autoPopoverListContainsElement is true and document's showing // auto popover list's last item is not element, then run hide all popovers @@ -16167,7 +16169,7 @@ void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement, // 9.4. XXX: See below // 9.5. Set element's implicit anchor element to null. - // (TODO) + data->SetInvoker(nullptr); } // 9.4. Request an element to be removed from the top layer given element. @@ -16176,7 +16178,7 @@ void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement, RemovePopoverFromTopLayer(aPopover); // 11. Set element's popover invoker to null. - // (TODO) + data->SetInvoker(nullptr); // 12. Set element's opened in popover mode to null. // 13. Set element's popover visibility state to hidden. @@ -16187,7 +16189,8 @@ void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement, // 14. If fireEvents is true, then queue a popover toggle event task given // element, "open", and "closed". Queue popover toggle event task. if (fireEvents) { - popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing); + popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing, + aSource); } // 15. Let previouslyFocusedElement be element's previously focused element. diff --git a/dom/base/Document.h b/dom/base/Document.h @@ -3494,7 +3494,8 @@ class Document : public nsINode, // https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm MOZ_CAN_RUN_SCRIPT void HidePopover(Element& popover, bool aFocusPreviousElement, - bool aFireEvents, ErrorResult& aRv); + bool aFireEvents, Element* aSource, + ErrorResult& aRv); // Returns a list of all the elements in the Document's top layer whose // popover attribute is in the auto state. diff --git a/dom/base/PopoverData.cpp b/dom/base/PopoverData.cpp @@ -74,19 +74,23 @@ void PopoverData::DestroyCloseWatcher() { }; PopoverToggleEventTask::PopoverToggleEventTask(nsWeakPtr aElement, + nsWeakPtr aSource, PopoverVisibilityState aOldState) : Runnable("PopoverToggleEventTask"), mElement(std::move(aElement)), + mSource(std::move(aSource)), mOldState(aOldState) {} NS_IMETHODIMP PopoverToggleEventTask::Run() { nsCOMPtr<Element> element = do_QueryReferent(mElement); + nsCOMPtr<Element> source = do_QueryReferent(mSource); if (!element) { return NS_OK; } if (auto* htmlElement = nsGenericHTMLElement::FromNode(element)) { - MOZ_KnownLive(htmlElement)->RunPopoverToggleEventTask(this, mOldState); + MOZ_KnownLive(htmlElement) + ->RunPopoverToggleEventTask(this, mOldState, source); } return NS_OK; }; diff --git a/dom/base/PopoverData.h b/dom/base/PopoverData.h @@ -32,7 +32,7 @@ enum class PopoverVisibilityState : uint8_t { class PopoverToggleEventTask : public Runnable { public: - explicit PopoverToggleEventTask(nsWeakPtr aElement, + explicit PopoverToggleEventTask(nsWeakPtr aElement, nsWeakPtr aSource, PopoverVisibilityState aOldState); // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See @@ -41,8 +41,11 @@ class PopoverToggleEventTask : public Runnable { PopoverVisibilityState GetOldState() const { return mOldState; } + Element* GetSource() const; + private: nsWeakPtr mElement; + nsWeakPtr mSource; PopoverVisibilityState mOldState; }; diff --git a/dom/events/ToggleEvent.cpp b/dom/events/ToggleEvent.cpp @@ -6,6 +6,8 @@ #include "mozilla/dom/ToggleEvent.h" +#include "nsContentUtils.h" +#include "mozilla/dom/Element.h" #include "mozilla/MiscEvents.h" namespace mozilla::dom { diff --git a/dom/html/HTMLDetailsElement.cpp b/dom/html/HTMLDetailsElement.cpp @@ -60,8 +60,9 @@ void HTMLDetailsElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, } else { oldState.Assign(stringForState(wasOpen)); } - RefPtr<ToggleEvent> toggleEvent = CreateToggleEvent( - u"toggle"_ns, oldState, stringForState(isOpen), Cancelable::eNo); + RefPtr<ToggleEvent> toggleEvent = + CreateToggleEvent(u"toggle"_ns, oldState, stringForState(isOpen), + Cancelable::eNo, nullptr); mToggleEventDispatcher = new AsyncEventDispatcher(this, toggleEvent.forget()); mToggleEventDispatcher->PostDOMEvent(); diff --git a/dom/html/HTMLDialogElement.cpp b/dom/html/HTMLDialogElement.cpp @@ -48,12 +48,16 @@ class DialogCloseWatcherListener : public nsIDOMEventListener { mDialog = do_GetWeakReference(aDialog); } + // https://html.spec.whatwg.org/#set-the-dialog-close-watcher NS_IMETHODIMP HandleEvent(Event* aEvent) override { RefPtr<nsINode> node = do_QueryReferent(mDialog); if (HTMLDialogElement* dialog = HTMLDialogElement::FromNodeOrNull(node)) { nsAutoString eventType; aEvent->GetType(eventType); if (eventType.EqualsLiteral("cancel")) { + // 3. - cancelAction given canPreventClose being to return the result of + // firing an event named cancel at dialog, with the cancelable attribute + // initialized to canPreventClose. bool defaultAction = true; auto cancelable = aEvent->Cancelable() ? Cancelable::eYes : Cancelable::eNo; @@ -64,9 +68,13 @@ class DialogCloseWatcherListener : public nsIDOMEventListener { aEvent->PreventDefault(); } } else if (eventType.EqualsLiteral("close")) { + // 3. - closeAction being to close the dialog given dialog, dialog's + // request close return value, and dialog's request close source + // element. Optional<nsAString> retValue; dialog->GetRequestCloseReturnValue(retValue); - dialog->Close(retValue); + RefPtr<Element> source = dialog->GetRequestCloseSourceElement(); + dialog->Close(source, retValue); } } return NS_OK; @@ -137,7 +145,7 @@ bool HTMLDialogElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, // https://html.spec.whatwg.org/#dom-dialog-close // https://html.spec.whatwg.org/#close-the-dialog void HTMLDialogElement::Close( - const mozilla::dom::Optional<nsAString>& aReturnValue) { + Element* aSource, const mozilla::dom::Optional<nsAString>& aReturnValue) { // 1. If subject does not have an open attribute, then return. if (!Open()) { return; @@ -146,7 +154,7 @@ void HTMLDialogElement::Close( // 2. Fire an event named beforetoggle, using ToggleEvent, with the oldState // attribute initialized to "open", the newState attribute initialized to // "closed", and the source attribute initialized to source at subject. - FireToggleEvent(u"open"_ns, u"closed"_ns, u"beforetoggle"_ns); + FireToggleEvent(u"open"_ns, u"closed"_ns, u"beforetoggle"_ns, aSource); // 3. If subject does not have an open attribute, then return. if (!Open()) { @@ -155,7 +163,7 @@ void HTMLDialogElement::Close( // 4. Queue a dialog toggle event task given subject, "open", "closed", and // source. - QueueToggleEventTask(); + QueueToggleEventTask(aSource); // 5. Remove subject's open attribute. SetOpen(false, IgnoreErrors()); @@ -176,7 +184,7 @@ void HTMLDialogElement::Close( ClearRequestCloseReturnValue(); // 11. Set subject's request close source element to null. - // TODO(keithamus) Source element? + mRequestCloseSourceElement = nullptr; MOZ_ASSERT(!OwnerDoc()->DialogIsInOpenDialogsList(*this), "Dialog should not being in Open Dialog List"); @@ -210,9 +218,8 @@ void HTMLDialogElement::Close( // https://html.spec.whatwg.org/#dom-dialog-requestclose // https://html.spec.whatwg.org/#dialog-request-close void HTMLDialogElement::RequestClose( - const mozilla::dom::Optional<nsAString>& aReturnValue) { + Element* aSource, const mozilla::dom::Optional<nsAString>& aReturnValue) { RefPtr closeWatcher = mCloseWatcher; - // 1. If subject does not have an open attribute, then return. if (!Open()) { return; @@ -243,7 +250,7 @@ void HTMLDialogElement::RequestClose( } // 6. Set subject's request close source element to source. - // TODO(keithamus): Source Element? + mRequestCloseSourceElement = do_GetWeakReference(aSource); // 7. Request to close subject's close watcher with false. if (StaticPrefs::dom_closewatcher_enabled()) { @@ -262,6 +269,10 @@ void HTMLDialogElement::RequestClose( } } +RefPtr<Element> HTMLDialogElement::GetRequestCloseSourceElement() { + return do_QueryReferent(mRequestCloseSourceElement); +} + // https://html.spec.whatwg.org/#dom-dialog-show void HTMLDialogElement::Show(ErrorResult& aError) { // 1. If this has an open attribute and is modal of this is false, then @@ -281,7 +292,7 @@ void HTMLDialogElement::Show(ErrorResult& aError) { // with the cancelable attribute initialized to true, the oldState attribute // initialized to "closed", and the newState attribute initialized to "open" // at this is false, then return. - if (FireToggleEvent(u"closed"_ns, u"open"_ns, u"beforetoggle"_ns)) { + if (FireToggleEvent(u"closed"_ns, u"open"_ns, u"beforetoggle"_ns, nullptr)) { return; } @@ -291,7 +302,7 @@ void HTMLDialogElement::Show(ErrorResult& aError) { } // 5. Queue a dialog toggle event task given this, "closed", and "open". - QueueToggleEventTask(); + QueueToggleEventTask(nullptr); // 6. Add an open attribute to this, whose value is the empty string. SetOpen(true, IgnoreErrors()); @@ -408,7 +419,7 @@ void HTMLDialogElement::UnbindFromTree(UnbindContext& aContext) { } // https://html.spec.whatwg.org/#show-a-modal-dialog -void HTMLDialogElement::ShowModal(ErrorResult& aError) { +void HTMLDialogElement::ShowModal(Element* aSource, ErrorResult& aError) { // 1. If subject has an open attribute and is modal of subject is true, then // return. if (Open()) { @@ -446,7 +457,7 @@ void HTMLDialogElement::ShowModal(ErrorResult& aError) { // ToggleEvent, with the cancelable attribute initialized to true, the // oldState attribute initialized to "closed", and the newState attribute // initialized to "open" at subject is false, then return. - if (FireToggleEvent(u"closed"_ns, u"open"_ns, u"beforetoggle"_ns)) { + if (FireToggleEvent(u"closed"_ns, u"open"_ns, u"beforetoggle"_ns, aSource)) { return; } @@ -458,7 +469,7 @@ void HTMLDialogElement::ShowModal(ErrorResult& aError) { } // 10. Queue a dialog toggle event task given subject, "closed", and "open". - QueueToggleEventTask(); + QueueToggleEventTask(aSource); // 11. Add an open attribute to subject, whose value is the empty string. SetOpen(true, aError); @@ -613,7 +624,8 @@ void HTMLDialogElement::RunCancelDialogSteps() { if (defaultAction) { Optional<nsAString> retValue; GetRequestCloseReturnValue(retValue); - Close(retValue); + RefPtr<Element> source = GetRequestCloseSourceElement(); + Close(source, retValue); } } @@ -643,23 +655,23 @@ bool HTMLDialogElement::HandleCommandInternal(Element* aSource, } } if (aCommand == Command::Close) { - Close(retValueOpt); + Close(aSource, retValueOpt); } else { MOZ_ASSERT(aCommand == Command::RequestClose); - RequestClose(retValueOpt); + RequestClose(aSource, retValueOpt); } return true; } if (IsInComposedDoc() && !Open() && aCommand == Command::ShowModal) { - ShowModal(aRv); + ShowModal(aSource, aRv); return true; } return false; } -void HTMLDialogElement::QueueToggleEventTask() { +void HTMLDialogElement::QueueToggleEventTask(Element* aSource) { nsAutoString oldState; auto newState = Open() ? u"closed"_ns : u"open"_ns; if (mToggleEventDispatcher) { @@ -670,8 +682,8 @@ void HTMLDialogElement::QueueToggleEventTask() { } else { oldState.Assign(Open() ? u"open"_ns : u"closed"_ns); } - RefPtr<ToggleEvent> toggleEvent = - CreateToggleEvent(u"toggle"_ns, oldState, newState, Cancelable::eNo); + RefPtr<ToggleEvent> toggleEvent = CreateToggleEvent( + u"toggle"_ns, oldState, newState, Cancelable::eNo, aSource); mToggleEventDispatcher = new AsyncEventDispatcher(this, toggleEvent.forget()); mToggleEventDispatcher->PostDOMEvent(); } diff --git a/dom/html/HTMLDialogElement.h b/dom/html/HTMLDialogElement.h @@ -29,7 +29,8 @@ class HTMLDialogElement final : public nsGenericHTMLElement { explicit HTMLDialogElement( already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) : nsGenericHTMLElement(std::move(aNodeInfo)), - mPreviouslyFocusedElement(nullptr) {} + mPreviouslyFocusedElement(nullptr), + mRequestCloseSourceElement(nullptr) {} NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLDialogElement, dialog) @@ -72,11 +73,26 @@ class HTMLDialogElement final : public nsGenericHTMLElement { void UnbindFromTree(UnbindContext&) override; MOZ_CAN_RUN_SCRIPT_BOUNDARY void Close( - const mozilla::dom::Optional<nsAString>& aReturnValue); + const mozilla::dom::Optional<nsAString>& aReturnValue) { + return Close(nullptr, aReturnValue); + } + MOZ_CAN_RUN_SCRIPT_BOUNDARY void Close( + Element* aSource, const mozilla::dom::Optional<nsAString>& aReturnValue); MOZ_CAN_RUN_SCRIPT void RequestClose( - const mozilla::dom::Optional<nsAString>& aReturnValue); + const mozilla::dom::Optional<nsAString>& aReturnValue) { + RequestClose(nullptr, aReturnValue); + } + MOZ_CAN_RUN_SCRIPT_BOUNDARY void RequestClose( + Element* aSource, const mozilla::dom::Optional<nsAString>& aReturnValue); + + RefPtr<Element> GetRequestCloseSourceElement(); + MOZ_CAN_RUN_SCRIPT void Show(ErrorResult& aError); - MOZ_CAN_RUN_SCRIPT void ShowModal(ErrorResult& aError); + + MOZ_CAN_RUN_SCRIPT void ShowModal(Element* aSource, ErrorResult& aError); + MOZ_CAN_RUN_SCRIPT void ShowModal(ErrorResult& aError) { + return ShowModal(nullptr, aError); + } void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, @@ -110,7 +126,7 @@ class HTMLDialogElement final : public nsGenericHTMLElement { void AddToTopLayerIfNeeded(); void RemoveFromTopLayerIfNeeded(); void StorePreviouslyFocusedElement(); - MOZ_CAN_RUN_SCRIPT_BOUNDARY void QueueToggleEventTask(); + MOZ_CAN_RUN_SCRIPT_BOUNDARY void QueueToggleEventTask(Element* aSource); void SetDialogCloseWatcherIfNeeded(); void SetCloseWatcherEnabledState(); @@ -119,6 +135,8 @@ class HTMLDialogElement final : public nsGenericHTMLElement { nsWeakPtr mPreviouslyFocusedElement; + nsWeakPtr mRequestCloseSourceElement; + RefPtr<AsyncEventDispatcher> mToggleEventDispatcher; // This won't need to be cycle collected as CloseWatcher only has strong diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp @@ -688,7 +688,8 @@ void nsGenericHTMLElement::AfterSetPopoverAttr() { if (IsPopoverOpen()) { HidePopoverInternal(/* aFocusPreviousElement = */ true, - /* aFireEvents = */ true, IgnoreErrors()); + /* aFireEvents = */ true, /* aSource*/ nullptr, + IgnoreErrors()); // Event handlers could have removed the popover attribute, or changed // its value. // https://github.com/whatwg/html/issues/9034 @@ -2866,7 +2867,7 @@ void nsGenericHTMLFormControlElementWithState::HandlePopoverTargetAction( bool shouldShow = canShow && !popover->IsPopoverOpen(); if (shouldHide) { - popover->HidePopover(IgnoreErrors()); + popover->HidePopoverInternal(true, true, this, IgnoreErrors()); } else if (shouldShow) { popover->ShowPopoverInternal(this, IgnoreErrors()); } @@ -2903,7 +2904,7 @@ MOZ_CAN_RUN_SCRIPT bool nsGenericHTMLElement::HandleCommandInternal( if (canHide && IsPopoverOpen()) { HidePopoverInternal(/* aFocusPreviousElement = */ true, - /* aFireEvents = */ true, IgnoreErrors()); + /* aFireEvents = */ true, aSource, IgnoreErrors()); return true; } @@ -3290,7 +3291,7 @@ void nsGenericHTMLElement::PopoverPseudoStateUpdate(bool aOpen, bool aNotify) { already_AddRefed<ToggleEvent> nsGenericHTMLElement::CreateToggleEvent( const nsAString& aEventType, const nsAString& aOldState, - const nsAString& aNewState, Cancelable aCancelable) { + const nsAString& aNewState, Cancelable aCancelable, Element* aSource) { ToggleEventInit init; init.mBubbles = false; init.mOldState = aOldState; @@ -3299,23 +3300,26 @@ already_AddRefed<ToggleEvent> nsGenericHTMLElement::CreateToggleEvent( RefPtr<ToggleEvent> event = ToggleEvent::Constructor(this, aEventType, init); event->SetTrusted(true); event->SetTarget(this); + event->SetSource(aSource); return event.forget(); } bool nsGenericHTMLElement::FireToggleEvent(const nsAString& aOldState, const nsAString& aNewState, - const nsAString& aType) { + const nsAString& aType, + Element* aSource) { const auto cancelable = aType == u"beforetoggle"_ns && aNewState == u"open"_ns ? Cancelable::eYes : Cancelable::eNo; - RefPtr event = CreateToggleEvent(aType, aOldState, aNewState, cancelable); + RefPtr event = + CreateToggleEvent(aType, aOldState, aNewState, cancelable, aSource); EventDispatcher::DispatchDOMEvent(this, nullptr, event, nullptr, nullptr); return event->DefaultPrevented(); } // https://html.spec.whatwg.org/#queue-a-popover-toggle-event-task void nsGenericHTMLElement::QueuePopoverEventTask( - PopoverVisibilityState aOldState) { + PopoverVisibilityState aOldState, Element* aSource) { auto* data = GetPopoverData(); MOZ_ASSERT(data, "Should have popover data"); @@ -3323,14 +3327,15 @@ void nsGenericHTMLElement::QueuePopoverEventTask( aOldState = queuedToggleEventTask->GetOldState(); } - auto task = - MakeRefPtr<PopoverToggleEventTask>(do_GetWeakReference(this), aOldState); + auto task = MakeRefPtr<PopoverToggleEventTask>( + do_GetWeakReference(this), do_GetWeakReference(aSource), aOldState); data->SetToggleEventTask(task); OwnerDoc()->Dispatch(task.forget()); } void nsGenericHTMLElement::RunPopoverToggleEventTask( - PopoverToggleEventTask* aTask, PopoverVisibilityState aOldState) { + PopoverToggleEventTask* aTask, PopoverVisibilityState aOldState, + Element* aSource) { auto* data = GetPopoverData(); if (!data) { return; @@ -3348,7 +3353,7 @@ void nsGenericHTMLElement::RunPopoverToggleEventTask( }; FireToggleEvent(stringForState(aOldState), stringForState(data->GetPopoverVisibilityState()), - u"toggle"_ns); + u"toggle"_ns, aSource); } // https://html.spec.whatwg.org/#dom-showpopover @@ -3361,7 +3366,7 @@ void nsGenericHTMLElement::ShowPopover(const ShowPopoverOptions& aOptions, return ShowPopoverInternal(MOZ_KnownLive(source), aRv); } -void nsGenericHTMLElement::ShowPopoverInternal(Element* aInvoker, +void nsGenericHTMLElement::ShowPopoverInternal(Element* aSource, ErrorResult& aRv) { if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, nullptr, aRv)) { return; @@ -3380,7 +3385,7 @@ void nsGenericHTMLElement::ShowPopoverInternal(Element* aInvoker, }); // Fire beforetoggle event and re-check popover validity. - if (FireToggleEvent(u"closed"_ns, u"open"_ns, u"beforetoggle"_ns)) { + if (FireToggleEvent(u"closed"_ns, u"open"_ns, u"beforetoggle"_ns, aSource)) { return; } if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, document, aRv)) { @@ -3391,7 +3396,7 @@ void nsGenericHTMLElement::ShowPopoverInternal(Element* aInvoker, nsWeakPtr originallyFocusedElement; if (IsAutoPopover()) { auto originalState = GetPopoverAttributeState(); - RefPtr<nsINode> ancestor = GetTopmostPopoverAncestor(aInvoker, true); + RefPtr<nsINode> ancestor = GetTopmostPopoverAncestor(aSource, true); if (!ancestor) { ancestor = document; } @@ -3432,9 +3437,9 @@ void nsGenericHTMLElement::ShowPopoverInternal(Element* aInvoker, { auto* popoverData = GetPopoverData(); popoverData->SetPopoverVisibilityState(PopoverVisibilityState::Showing); - popoverData->SetInvoker(aInvoker); - if (aInvoker && aInvoker->IsHTMLElement()) { - aInvoker->SetAssociatedPopover(*this); + popoverData->SetInvoker(aSource); + if (aSource && aSource->IsHTMLElement()) { + aSource->SetAssociatedPopover(*this); } } @@ -3446,24 +3451,28 @@ void nsGenericHTMLElement::ShowPopoverInternal(Element* aInvoker, } // Queue popover toggle event task. - QueuePopoverEventTask(PopoverVisibilityState::Hidden); + QueuePopoverEventTask(PopoverVisibilityState::Hidden, aSource); } void nsGenericHTMLElement::HidePopoverWithoutRunningScript() { HidePopoverInternal(/* aFocusPreviousElement = */ false, - /* aFireEvents = */ false, IgnoreErrors()); + /* aFireEvents = */ false, + /* aSource = */ nullptr, IgnoreErrors()); } // https://html.spec.whatwg.org/#dom-hidepopover void nsGenericHTMLElement::HidePopover(ErrorResult& aRv) { HidePopoverInternal(/* aFocusPreviousElement = */ true, - /* aFireEvents = */ true, aRv); + /* aFireEvents = */ true, + /* aSource = */ nullptr, aRv); } void nsGenericHTMLElement::HidePopoverInternal(bool aFocusPreviousElement, bool aFireEvents, + mozilla::dom::Element* aSource, ErrorResult& aRv) { - OwnerDoc()->HidePopover(*this, aFocusPreviousElement, aFireEvents, aRv); + OwnerDoc()->HidePopover(*this, aFocusPreviousElement, aFireEvents, aSource, + aRv); } void nsGenericHTMLElement::ForgetPreviouslyFocusedElementAfterHidingPopover() { diff --git a/dom/html/nsGenericHTMLElement.h b/dom/html/nsGenericHTMLElement.h @@ -199,16 +199,18 @@ class nsGenericHTMLElement : public nsGenericHTMLElementBase { Document* aExpectedDocument, ErrorResult& aRv); already_AddRefed<mozilla::dom::ToggleEvent> CreateToggleEvent( const nsAString& aEventType, const nsAString& aOldState, - const nsAString& aNewState, mozilla::Cancelable); + const nsAString& aNewState, mozilla::Cancelable, Element* aSource); /** Returns true if the event has been cancelled. */ MOZ_CAN_RUN_SCRIPT bool FireToggleEvent(const nsAString& aOldState, const nsAString& aNewState, - const nsAString& aType); + const nsAString& aType, + Element* aSource); MOZ_CAN_RUN_SCRIPT void QueuePopoverEventTask( - mozilla::dom::PopoverVisibilityState aOldState); + mozilla::dom::PopoverVisibilityState aOldState, Element* aSource); MOZ_CAN_RUN_SCRIPT void RunPopoverToggleEventTask( mozilla::dom::PopoverToggleEventTask* aTask, - mozilla::dom::PopoverVisibilityState aOldState); + mozilla::dom::PopoverVisibilityState aOldState, + mozilla::dom::Element* aSource); MOZ_CAN_RUN_SCRIPT void ShowPopover( const mozilla::dom::ShowPopoverOptions& aOptions, ErrorResult& aRv); MOZ_CAN_RUN_SCRIPT void ShowPopoverInternal(Element* aInvoker, @@ -216,6 +218,7 @@ class nsGenericHTMLElement : public nsGenericHTMLElementBase { MOZ_CAN_RUN_SCRIPT_BOUNDARY void HidePopoverWithoutRunningScript(); MOZ_CAN_RUN_SCRIPT void HidePopoverInternal(bool aFocusPreviousElement, bool aFireEvents, + mozilla::dom::Element* aSource, ErrorResult& aRv); MOZ_CAN_RUN_SCRIPT void HidePopover(ErrorResult& aRv); MOZ_CAN_RUN_SCRIPT bool TogglePopover( diff --git a/dom/webidl/ToggleEvent.webidl b/dom/webidl/ToggleEvent.webidl @@ -12,9 +12,11 @@ interface ToggleEvent : Event { constructor(DOMString type, optional ToggleEventInit eventInitDict = {}); readonly attribute DOMString oldState; readonly attribute DOMString newState; + readonly attribute Element? source; }; dictionary ToggleEventInit : EventInit { + Element? source = null; DOMString oldState = ""; DOMString newState = ""; }; diff --git a/testing/web-platform/meta/html/dom/idlharness.https.html.ini b/testing/web-platform/meta/html/dom/idlharness.https.html.ini @@ -1908,12 +1908,6 @@ prefs: [dom.security.featurePolicy.experimental.enabled:true, dom.security.featu [CustomElementRegistry interface: operation initialize(Node)] expected: FAIL - [ToggleEvent interface: attribute source] - expected: FAIL - - [ToggleEvent interface: new ToggleEvent("beforetoggle") must inherit property "source" with the proper type] - expected: FAIL - [CommandEvent interface: existence and properties of interface prototype object's "constructor" property] expected: [FAIL, PASS] diff --git a/testing/web-platform/meta/html/semantics/interactive-elements/the-details-element/details-toggle-source.html.ini b/testing/web-platform/meta/html/semantics/interactive-elements/the-details-element/details-toggle-source.html.ini @@ -1,9 +1,15 @@ [details-toggle-source.html] - [ToggleEvent.source on <details> elements: details.open.] - expected: FAIL + [ToggleEvent.source on <details> elements: click details.] + bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1991074 + expected: + if (os == "win") or (os == "mac") or (os == "android"): [PASS, FAIL] + PASS - [ToggleEvent.source on <details> elements: click summary.] - expected: FAIL + [ToggleEvent.source on <details> elements: command invokers.] + bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1991074 + expected: + if (os == "win") or (os == "mac") or (os == "android"): [PASS, FAIL] + PASS - [ToggleEvent.source on <details> elements: click details.] + [ToggleEvent.source on <details> elements: command invokers.] expected: FAIL diff --git a/testing/web-platform/meta/html/semantics/interactive-elements/the-dialog-element/dialog-toggle-source.html.ini b/testing/web-platform/meta/html/semantics/interactive-elements/the-dialog-element/dialog-toggle-source.html.ini @@ -1,18 +0,0 @@ -[dialog-toggle-source.html] - [ToggleEvent.source on <dialog> elements: dialog.showModal().] - expected: FAIL - - [ToggleEvent.soruce on <dialog> elements: open with button, close with dialog.close().] - expected: FAIL - - [ToggleEvent.source on <dialog> elements: open with showModal, close with request-close button.] - expected: FAIL - - [ToggleEvent.source on <dialog> elements: open with button, close with light dismiss.] - expected: FAIL - - [ToggleEvent.source on <dialog> elements: command button.] - expected: FAIL - - [ToggleEvent.source on <dialog> elements: open with showModal, close with button.] - expected: FAIL diff --git a/testing/web-platform/meta/html/semantics/popovers/popover-toggle-source.html.ini b/testing/web-platform/meta/html/semantics/popovers/popover-toggle-source.html.ini @@ -1,21 +0,0 @@ -[popover-toggle-source.html] - [ToggleEvent.source on popover elements: showPopover() without source.] - expected: FAIL - - [ToggleEvent.source on popover elements: showPopover() with source.] - expected: FAIL - - [ToggleEvent.source on popover elements: Calling click() on a popovertarget button.] - expected: FAIL - - [ToggleEvent.source on popover elements: Calling click() on a command button.] - expected: FAIL - - [ToggleEvent.source on popover elements: showPopover() then popovertarget button.] - expected: FAIL - - [ToggleEvent.source on popover elements: showPopover(invoker) then popovertarget button.] - expected: FAIL - - [ToggleEvent.source on popover elements: popovertarget button then hidePopover().] - expected: FAIL diff --git a/testing/web-platform/meta/html/semantics/the-button-element/command-and-commandfor/source-attribute-retargeting.html.ini b/testing/web-platform/meta/html/semantics/the-button-element/command-and-commandfor/source-attribute-retargeting.html.ini @@ -1,6 +0,0 @@ -[source-attribute-retargeting.html] - [CommandEvent.source and ToggleEvent.source should be retargeted during and after event dispatch.] - expected: FAIL - - [CommandEvent.source and ToggleEvent.source should not be set to null after dispatch without ShadowDOM.] - expected: FAIL