Issue #595 - Implement window.event

This MSIE extension is still technically part of the standard*, although its use is discouraged. This API will also likely never go away based on some comments at this issue on MDN content**.

Note that this uses a different approach for getting the inner window.

* https://dom.spec.whatwg.org/#interface-window-extensions
** https://github.com/mdn/content/issues/21848

Spec PR: https://github.com/whatwg/dom/pull/407
Spec discussion: https://github.com/whatwg/dom/issues/334

Partially based on https://bugzilla.mozilla.org/show_bug.cgi?id=218415
This commit is contained in:
FranklinDM 2023-03-26 18:34:04 +08:00 • committed by roytam1
commit 31283d993b
10 changed files with 110 additions and 6 deletions

View file

@ -824,6 +824,10 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor)
aVisitor.mCanHandle = true; aVisitor.mCanHandle = true;
aVisitor.mMayHaveListenerManager = HasListenerManager(); aVisitor.mMayHaveListenerManager = HasListenerManager();
if (IsInShadowTree()) {
aVisitor.mItemInShadowTree = true;
}
// Don't propagate mouseover and mouseout events when mouse is moving // Don't propagate mouseover and mouseout events when mouse is moving
// inside chrome access only content. // inside chrome access only content.
bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree(); bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree();

View file

@ -909,7 +909,8 @@ nsPIDOMWindow<T>::nsPIDOMWindow(nsPIDOMWindowOuter *aOuterWindow)
mOuterWindow(aOuterWindow), mOuterWindow(aOuterWindow),
// Make sure no actual window ends up with mWindowID == 0 // Make sure no actual window ends up with mWindowID == 0
mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false), mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false),
mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false) mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false),
mEvent(nullptr)
{} {}
template<class T> template<class T>
@ -5245,6 +5246,16 @@ nsGlobalWindow::SetOpener(JSContext* aCx, JS::Handle<JS::Value> aOpener,
SetOpenerWindow(outer, false); SetOpenerWindow(outer, false);
} }
void
nsGlobalWindow::GetEvent(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval)
{
if (mEvent) {
Unused << nsContentUtils::WrapNative(aCx, mEvent, aRetval);
} else {
aRetval.setUndefined();
}
}
void void
nsGlobalWindow::GetStatusOuter(nsAString& aStatus) nsGlobalWindow::GetStatusOuter(nsAString& aStatus)
{ {

View file

@ -846,6 +846,7 @@ public:
already_AddRefed<nsPIDOMWindowOuter> GetOpener() override; already_AddRefed<nsPIDOMWindowOuter> GetOpener() override;
void SetOpener(JSContext* aCx, JS::Handle<JS::Value> aOpener, void SetOpener(JSContext* aCx, JS::Handle<JS::Value> aOpener,
mozilla::ErrorResult& aError); mozilla::ErrorResult& aError);
void GetEvent(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval);
already_AddRefed<nsPIDOMWindowOuter> GetParentOuter(); already_AddRefed<nsPIDOMWindowOuter> GetParentOuter();
already_AddRefed<nsPIDOMWindowOuter> GetParent(mozilla::ErrorResult& aError); already_AddRefed<nsPIDOMWindowOuter> GetParent(mozilla::ErrorResult& aError);
already_AddRefed<nsPIDOMWindowOuter> GetParent() override; already_AddRefed<nsPIDOMWindowOuter> GetParent() override;

View file

@ -194,6 +194,10 @@ protected:
// we have what it takes to do so. // we have what it takes to do so.
void MaybeCreateDoc(); void MaybeCreateDoc();
// The event dispatch code sets and unsets this while keeping
// the event object alive.
nsIDOMEvent* mEvent;
public: public:
inline bool IsLoadingOrRunningTimeout() const; inline bool IsLoadingOrRunningTimeout() const;
@ -789,6 +793,17 @@ public:
return mInnerObjectsFreed; 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. * Check whether this window is a secure context.
*/ */

View file

@ -264,6 +264,16 @@ public:
return mFlags.mRootOfClosedTree; return mFlags.mRootOfClosedTree;
} }
void SetItemInShadowTree(bool aSet)
{
mFlags.mItemInShadowTree = aSet;
}
bool IsItemInShadowTree()
{
return mFlags.mItemInShadowTree;
}
void SetIsSlotInClosedTree(bool aSet) void SetIsSlotInClosedTree(bool aSet)
{ {
mFlags.mIsSlotInClosedTree = aSet; mFlags.mIsSlotInClosedTree = aSet;
@ -351,7 +361,8 @@ public:
mManager->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent, mManager->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent,
&aVisitor.mDOMEvent, &aVisitor.mDOMEvent,
CurrentTarget(), CurrentTarget(),
&aVisitor.mEventStatus); &aVisitor.mEventStatus,
IsItemInShadowTree());
NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr, NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr,
"CurrentTarget should be null!"); "CurrentTarget should be null!");
} }
@ -384,6 +395,7 @@ private:
bool mWantsPreHandleEvent : 1; bool mWantsPreHandleEvent : 1;
bool mPreHandleEventOnly : 1; bool mPreHandleEventOnly : 1;
bool mRootOfClosedTree : 1; bool mRootOfClosedTree : 1;
bool mItemInShadowTree : 1;
bool mIsSlotInClosedTree : 1; bool mIsSlotInClosedTree : 1;
bool mIsChromeHandler : 1; bool mIsChromeHandler : 1;
private: private:
@ -433,6 +445,7 @@ EventTargetChainItem::GetEventTargetParent(EventChainPreVisitor& aVisitor)
SetWantsPreHandleEvent(aVisitor.mWantsPreHandleEvent); SetWantsPreHandleEvent(aVisitor.mWantsPreHandleEvent);
SetPreHandleEventOnly(aVisitor.mWantsPreHandleEvent && !aVisitor.mCanHandle); SetPreHandleEventOnly(aVisitor.mWantsPreHandleEvent && !aVisitor.mCanHandle);
SetRootOfClosedTree(aVisitor.mRootOfClosedTree); SetRootOfClosedTree(aVisitor.mRootOfClosedTree);
SetItemInShadowTree(aVisitor.mItemInShadowTree);
SetRetargetedRelatedTarget(aVisitor.mRetargetedRelatedTarget); SetRetargetedRelatedTarget(aVisitor.mRetargetedRelatedTarget);
mItemFlags = aVisitor.mItemFlags; mItemFlags = aVisitor.mItemFlags;
mItemData = aVisitor.mItemData; mItemData = aVisitor.mItemData;

View file

@ -125,6 +125,7 @@ public:
, mMayHaveListenerManager(true) , mMayHaveListenerManager(true)
, mWantsPreHandleEvent(false) , mWantsPreHandleEvent(false)
, mRootOfClosedTree(false) , mRootOfClosedTree(false)
, mItemInShadowTree(false)
, mParentIsSlotInClosedTree(false) , mParentIsSlotInClosedTree(false)
, mParentIsChromeHandler(false) , mParentIsChromeHandler(false)
, mRelatedTargetRetargetedInCurrentScope(false) , mRelatedTargetRetargetedInCurrentScope(false)
@ -147,6 +148,7 @@ public:
mMayHaveListenerManager = true; mMayHaveListenerManager = true;
mWantsPreHandleEvent = false; mWantsPreHandleEvent = false;
mRootOfClosedTree = false; mRootOfClosedTree = false;
mItemInShadowTree = false;
mParentIsSlotInClosedTree = false; mParentIsSlotInClosedTree = false;
mParentIsChromeHandler = false; mParentIsChromeHandler = false;
// Note, we don't clear mRelatedTargetRetargetedInCurrentScope explicitly, // Note, we don't clear mRelatedTargetRetargetedInCurrentScope explicitly,
@ -236,6 +238,12 @@ public:
*/ */
bool mRootOfClosedTree; 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 * True if mParentTarget is HTMLSlotElement in a closed shadow tree and the
* current target is assigned to that slot. * current target is assigned to that slot.

View file

@ -1120,6 +1120,38 @@ EventListenerManager::GetLegacyEventMessage(EventMessage aEventMessage) const
} }
} }
already_AddRefed<nsPIDOMWindowInner>
EventListenerManager::WindowFromListener(Listener* aListener,
bool aItemInShadowTree)
{
nsCOMPtr<nsPIDOMWindowInner> 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. * Causes a check for event listeners and processing by them if they exist.
* @param an event listener * @param an event listener
@ -1130,7 +1162,8 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext,
WidgetEvent* aEvent, WidgetEvent* aEvent,
nsIDOMEvent** aDOMEvent, nsIDOMEvent** aDOMEvent,
EventTarget* aCurrentTarget, EventTarget* aCurrentTarget,
nsEventStatus* aEventStatus) nsEventStatus* aEventStatus,
bool aItemInShadowTree)
{ {
//Set the value of the internal PreventDefault flag properly based on aEventStatus //Set the value of the internal PreventDefault flag properly based on aEventStatus
if (!aEvent->DefaultPrevented() && if (!aEvent->DefaultPrevented() &&
@ -1222,9 +1255,18 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext,
listener = listenerHolder.ptr(); listener = listenerHolder.ptr();
hasRemovedListener = true; hasRemovedListener = true;
} }
nsCOMPtr<nsPIDOMWindowInner> innerWindow =
WindowFromListener(listener, aItemInShadowTree);
nsIDOMEvent* oldWindowEvent = nullptr;
if (innerWindow) {
oldWindowEvent = innerWindow->SetEvent(*aDOMEvent);
}
if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent, aCurrentTarget))) { if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent, aCurrentTarget))) {
aEvent->mFlags.mExceptionWasRaised = true; aEvent->mFlags.mExceptionWasRaised = true;
} }
if (innerWindow) {
Unused << innerWindow->SetEvent(oldWindowEvent);
}
aEvent->mFlags.mInPassiveListener = false; aEvent->mFlags.mInPassiveListener = false;
if (needsEndEventMarker) { if (needsEndEventMarker) {

View file

@ -350,7 +350,8 @@ public:
WidgetEvent* aEvent, WidgetEvent* aEvent,
nsIDOMEvent** aDOMEvent, nsIDOMEvent** aDOMEvent,
dom::EventTarget* aCurrentTarget, dom::EventTarget* aCurrentTarget,
nsEventStatus* aEventStatus) nsEventStatus* aEventStatus,
bool aItemInShadowTree)
{ {
if (mListeners.IsEmpty() || aEvent->PropagationStopped()) { if (mListeners.IsEmpty() || aEvent->PropagationStopped()) {
return; return;
@ -371,7 +372,7 @@ public:
return; return;
} }
HandleEventInternal(aPresContext, aEvent, aDOMEvent, aCurrentTarget, HandleEventInternal(aPresContext, aEvent, aDOMEvent, aCurrentTarget,
aEventStatus); aEventStatus, aItemInShadowTree);
} }
/** /**
@ -482,7 +483,8 @@ protected:
WidgetEvent* aEvent, WidgetEvent* aEvent,
nsIDOMEvent** aDOMEvent, nsIDOMEvent** aDOMEvent,
dom::EventTarget* aCurrentTarget, dom::EventTarget* aCurrentTarget,
nsEventStatus* aEventStatus); nsEventStatus* aEventStatus,
bool aItemInShadowTree);
nsresult HandleEventSubType(Listener* aListener, nsresult HandleEventSubType(Listener* aListener,
nsIDOMEvent* aDOMEvent, nsIDOMEvent* aDOMEvent,
@ -573,6 +575,10 @@ public:
return typedHandler ? typedHandler->OnBeforeUnloadEventHandler() : nullptr; return typedHandler ? typedHandler->OnBeforeUnloadEventHandler() : nullptr;
} }
private:
already_AddRefed<nsPIDOMWindowInner> WindowFromListener(Listener* aListener,
bool aItemInShadowTree);
protected: protected:
/** /**
* Helper method for implementing the various Get*EventHandler above. Will * Helper method for implementing the various Get*EventHandler above. Will

View file

@ -50,6 +50,7 @@ interface nsIDOMCrypto;
[Throws] void stop(); [Throws] void stop();
[Throws, CrossOriginCallable, UnsafeInPrerendering] void focus(); [Throws, CrossOriginCallable, UnsafeInPrerendering] void focus();
[Throws, CrossOriginCallable] void blur(); [Throws, CrossOriginCallable] void blur();
[Replaceable, Pref="dom.window.event.enabled"] readonly attribute any event;
// other browsing contexts // other browsing contexts
[Replaceable, Throws, CrossOriginReadable] readonly attribute WindowProxy frames; [Replaceable, Throws, CrossOriginReadable] readonly attribute WindowProxy frames;

View file

@ -5244,6 +5244,9 @@ pref("prompts.content_handling_dialog_modal.enabled", false);
// Whether module scripts (<script type="module">) are enabled for content. // Whether module scripts (<script type="module">) are enabled for content.
pref("dom.moduleScripts.enabled", true); pref("dom.moduleScripts.enabled", true);
// Whether read-only Window property event is enabled for content.
pref("dom.window.event.enabled", false);
// Report details when a media source error occurs? // Report details when a media source error occurs?
// Enabled by default in debug builds, otherwise should be explicitly enabled // Enabled by default in debug builds, otherwise should be explicitly enabled
// by the user to prevent XO leaking of the response status (CVE-2020-15666) // by the user to prevent XO leaking of the response status (CVE-2020-15666)