diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp index 38be731811..6aea6820ef 100644 --- a/dom/base/FragmentOrElement.cpp +++ b/dom/base/FragmentOrElement.cpp @@ -824,6 +824,10 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) aVisitor.mCanHandle = true; aVisitor.mMayHaveListenerManager = HasListenerManager(); + if (IsInShadowTree()) { + aVisitor.mItemInShadowTree = true; + } + // Don't propagate mouseover and mouseout events when mouse is moving // inside chrome access only content. bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree(); diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index b53ad29769..c5ed38dc27 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -909,7 +909,8 @@ nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWindowOuter *aOuterWindow) mOuterWindow(aOuterWindow), // Make sure no actual window ends up with mWindowID == 0 mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false), - mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false) + mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false), + mEvent(nullptr) {} template @@ -5245,6 +5246,16 @@ nsGlobalWindow::SetOpener(JSContext* aCx, JS::Handle aOpener, SetOpenerWindow(outer, false); } +void +nsGlobalWindow::GetEvent(JSContext* aCx, JS::MutableHandle aRetval) +{ + if (mEvent) { + Unused << nsContentUtils::WrapNative(aCx, mEvent, aRetval); + } else { + aRetval.setUndefined(); + } +} + void nsGlobalWindow::GetStatusOuter(nsAString& aStatus) { diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index bc989ea2e4..63bb574dd4 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -846,6 +846,7 @@ public: already_AddRefed GetOpener() override; void SetOpener(JSContext* aCx, JS::Handle aOpener, mozilla::ErrorResult& aError); + void GetEvent(JSContext* aCx, JS::MutableHandle aRetval); already_AddRefed GetParentOuter(); already_AddRefed GetParent(mozilla::ErrorResult& aError); already_AddRefed GetParent() override; diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h index 21eb4cff7f..ffebf6570d 100644 --- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -194,6 +194,10 @@ protected: // we have what it takes to do so. void MaybeCreateDoc(); + // The event dispatch code sets and unsets this while keeping + // the event object alive. + nsIDOMEvent* mEvent; + public: inline bool IsLoadingOrRunningTimeout() const; @@ -789,6 +793,17 @@ public: return mInnerObjectsFreed; } + // Sets the event for window.event. Does NOT take ownership, so + // the caller is responsible for clearing the event before the + // event gets deallocated. Pass nullptr to set window.event to + // undefined. Returns the previous value. + nsIDOMEvent* SetEvent(nsIDOMEvent* aEvent) + { + nsIDOMEvent* old = mEvent; + mEvent = aEvent; + return old; + } + /** * Check whether this window is a secure context. */ diff --git a/dom/events/EventDispatcher.cpp b/dom/events/EventDispatcher.cpp index e2b5699241..001b458cf3 100644 --- a/dom/events/EventDispatcher.cpp +++ b/dom/events/EventDispatcher.cpp @@ -264,6 +264,16 @@ public: return mFlags.mRootOfClosedTree; } + void SetItemInShadowTree(bool aSet) + { + mFlags.mItemInShadowTree = aSet; + } + + bool IsItemInShadowTree() + { + return mFlags.mItemInShadowTree; + } + void SetIsSlotInClosedTree(bool aSet) { mFlags.mIsSlotInClosedTree = aSet; @@ -351,7 +361,8 @@ public: mManager->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent, &aVisitor.mDOMEvent, CurrentTarget(), - &aVisitor.mEventStatus); + &aVisitor.mEventStatus, + IsItemInShadowTree()); NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr, "CurrentTarget should be null!"); } @@ -384,6 +395,7 @@ private: bool mWantsPreHandleEvent : 1; bool mPreHandleEventOnly : 1; bool mRootOfClosedTree : 1; + bool mItemInShadowTree : 1; bool mIsSlotInClosedTree : 1; bool mIsChromeHandler : 1; private: @@ -433,6 +445,7 @@ EventTargetChainItem::GetEventTargetParent(EventChainPreVisitor& aVisitor) SetWantsPreHandleEvent(aVisitor.mWantsPreHandleEvent); SetPreHandleEventOnly(aVisitor.mWantsPreHandleEvent && !aVisitor.mCanHandle); SetRootOfClosedTree(aVisitor.mRootOfClosedTree); + SetItemInShadowTree(aVisitor.mItemInShadowTree); SetRetargetedRelatedTarget(aVisitor.mRetargetedRelatedTarget); mItemFlags = aVisitor.mItemFlags; mItemData = aVisitor.mItemData; diff --git a/dom/events/EventDispatcher.h b/dom/events/EventDispatcher.h index 403c472c5c..9eaa124137 100644 --- a/dom/events/EventDispatcher.h +++ b/dom/events/EventDispatcher.h @@ -125,6 +125,7 @@ public: , mMayHaveListenerManager(true) , mWantsPreHandleEvent(false) , mRootOfClosedTree(false) + , mItemInShadowTree(false) , mParentIsSlotInClosedTree(false) , mParentIsChromeHandler(false) , mRelatedTargetRetargetedInCurrentScope(false) @@ -147,6 +148,7 @@ public: mMayHaveListenerManager = true; mWantsPreHandleEvent = false; mRootOfClosedTree = false; + mItemInShadowTree = false; mParentIsSlotInClosedTree = false; mParentIsChromeHandler = false; // Note, we don't clear mRelatedTargetRetargetedInCurrentScope explicitly, @@ -236,6 +238,12 @@ public: */ bool mRootOfClosedTree; + /** + * If target is node and its root is a shadow root. + * https://dom.spec.whatwg.org/#event-path-item-in-shadow-tree + */ + bool mItemInShadowTree; + /** * True if mParentTarget is HTMLSlotElement in a closed shadow tree and the * current target is assigned to that slot. diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index 221b464773..af268f24bb 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -1120,6 +1120,38 @@ EventListenerManager::GetLegacyEventMessage(EventMessage aEventMessage) const } } +already_AddRefed +EventListenerManager::WindowFromListener(Listener* aListener, + bool aItemInShadowTree) +{ + nsCOMPtr innerWindow; + if (!aItemInShadowTree) { + if (aListener->mListener.HasWebIDLCallback()) { + CallbackObject* callback = aListener->mListener.GetWebIDLCallback(); + nsGlobalWindow* win; + if (callback) { + // Find the real underlying callback. + JSObject* realCallback = + js::UncheckedUnwrap(callback->CallbackPreserveColor()); + // Get the global for this callback. + win = mIsMainThreadELM ? + xpc::WindowGlobalOrNull(realCallback) : + nullptr; + } + if (win && win->IsInnerWindow()) { + innerWindow = win->AsInner(); // Can be nullptr + } + } else { + // Can't get the global from + // listener->mListener.GetXPCOMCallback(). + // In most cases, it would be the same as for + // the target, so let's do that. + innerWindow = GetInnerWindowForTarget(); // Can be nullptr + } + } + return innerWindow.forget(); +} + /** * Causes a check for event listeners and processing by them if they exist. * @param an event listener @@ -1130,7 +1162,8 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, WidgetEvent* aEvent, nsIDOMEvent** aDOMEvent, EventTarget* aCurrentTarget, - nsEventStatus* aEventStatus) + nsEventStatus* aEventStatus, + bool aItemInShadowTree) { //Set the value of the internal PreventDefault flag properly based on aEventStatus if (!aEvent->DefaultPrevented() && @@ -1222,9 +1255,18 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, listener = listenerHolder.ptr(); hasRemovedListener = true; } + nsCOMPtr innerWindow = + WindowFromListener(listener, aItemInShadowTree); + nsIDOMEvent* oldWindowEvent = nullptr; + if (innerWindow) { + oldWindowEvent = innerWindow->SetEvent(*aDOMEvent); + } if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent, aCurrentTarget))) { aEvent->mFlags.mExceptionWasRaised = true; } + if (innerWindow) { + Unused << innerWindow->SetEvent(oldWindowEvent); + } aEvent->mFlags.mInPassiveListener = false; if (needsEndEventMarker) { diff --git a/dom/events/EventListenerManager.h b/dom/events/EventListenerManager.h index 51373317d4..decd305f5e 100644 --- a/dom/events/EventListenerManager.h +++ b/dom/events/EventListenerManager.h @@ -350,7 +350,8 @@ public: WidgetEvent* aEvent, nsIDOMEvent** aDOMEvent, dom::EventTarget* aCurrentTarget, - nsEventStatus* aEventStatus) + nsEventStatus* aEventStatus, + bool aItemInShadowTree) { if (mListeners.IsEmpty() || aEvent->PropagationStopped()) { return; @@ -371,7 +372,7 @@ public: return; } HandleEventInternal(aPresContext, aEvent, aDOMEvent, aCurrentTarget, - aEventStatus); + aEventStatus, aItemInShadowTree); } /** @@ -482,7 +483,8 @@ protected: WidgetEvent* aEvent, nsIDOMEvent** aDOMEvent, dom::EventTarget* aCurrentTarget, - nsEventStatus* aEventStatus); + nsEventStatus* aEventStatus, + bool aItemInShadowTree); nsresult HandleEventSubType(Listener* aListener, nsIDOMEvent* aDOMEvent, @@ -573,6 +575,10 @@ public: return typedHandler ? typedHandler->OnBeforeUnloadEventHandler() : nullptr; } +private: + already_AddRefed WindowFromListener(Listener* aListener, + bool aItemInShadowTree); + protected: /** * Helper method for implementing the various Get*EventHandler above. Will diff --git a/dom/webidl/Window.webidl b/dom/webidl/Window.webidl index 59cc9689c2..2ba9f4f124 100644 --- a/dom/webidl/Window.webidl +++ b/dom/webidl/Window.webidl @@ -50,6 +50,7 @@ interface nsIDOMCrypto; [Throws] void stop(); [Throws, CrossOriginCallable, UnsafeInPrerendering] void focus(); [Throws, CrossOriginCallable] void blur(); + [Replaceable, Pref="dom.window.event.enabled"] readonly attribute any event; // other browsing contexts [Replaceable, Throws, CrossOriginReadable] readonly attribute WindowProxy frames; diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 51296a21ac..aad7c119d9 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -5244,6 +5244,9 @@ pref("prompts.content_handling_dialog_modal.enabled", false); // Whether module scripts (