/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ #include "nsPresContext.h" #include "nsContentUtils.h" #include "nsDocShell.h" #include "nsError.h" #include #include "nsIContent.h" #include "nsIContentInlines.h" #include "mozilla/dom/Document.h" #include "nsINode.h" #include "nsIScriptObjectPrincipal.h" #include "nsPIDOMWindow.h" #include "nsRefreshDriver.h" #include "AnimationEvent.h" #include "BeforeUnloadEvent.h" #include "ClipboardEvent.h" #include "CommandEvent.h" #include "CompositionEvent.h" #include "DeviceMotionEvent.h" #include "DragEvent.h" #include "KeyboardEvent.h" #include "mozilla/BasePrincipal.h" #include "mozilla/ContentEvents.h" #include "mozilla/dom/CloseEvent.h" #include "mozilla/dom/CustomEvent.h" #include "mozilla/dom/DeviceOrientationEvent.h" #include "mozilla/dom/EventTarget.h" #include "mozilla/dom/FocusEvent.h" #include "mozilla/dom/HashChangeEvent.h" #include "mozilla/dom/InputEvent.h" #include "mozilla/dom/MessageEvent.h" #include "mozilla/dom/MouseScrollEvent.h" #include "mozilla/dom/MutationEvent.h" #include "mozilla/dom/NotifyPaintEvent.h" #include "mozilla/dom/PageTransitionEvent.h" #include "mozilla/dom/PerformanceEventTiming.h" #include "mozilla/dom/PointerEvent.h" #include "mozilla/dom/RootedDictionary.h" #include "mozilla/dom/ScrollAreaEvent.h" #include "mozilla/dom/SimpleGestureEvent.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/StorageEvent.h" #include "mozilla/dom/TimeEvent.h" #include "mozilla/dom/TouchEvent.h" #include "mozilla/dom/TransitionEvent.h" #include "mozilla/dom/WheelEvent.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/XULCommandEvent.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventListenerManager.h" #include "mozilla/InternalMutationEvent.h" #include "mozilla/ipc/MessageChannel.h" #include "mozilla/MiscEvents.h" #include "mozilla/MouseEvents.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/ProfilerMarkers.h" #include "mozilla/ScopeExit.h" #include "mozilla/Telemetry.h" #include "mozilla/TextEvents.h" #include "mozilla/TouchEvents.h" #include "mozilla/Unused.h" namespace mozilla { using namespace dom; class ELMCreationDetector { public: ELMCreationDetector() // We can do this optimization only in the main thread. : mNonMainThread(!NS_IsMainThread()), mInitialCount(mNonMainThread ? 0 : EventListenerManager::sMainThreadCreatedCount) {} bool MayHaveNewListenerManager() { return mNonMainThread || mInitialCount != EventListenerManager::sMainThreadCreatedCount; } bool IsMainThread() { return !mNonMainThread; } private: bool mNonMainThread; uint32_t mInitialCount; }; static bool IsEventTargetChrome(EventTarget* aEventTarget, Document** aDocument = nullptr) { if (aDocument) { *aDocument = nullptr; } Document* doc = nullptr; if (nsINode* node = nsINode::FromEventTargetOrNull(aEventTarget)) { doc = node->OwnerDoc(); } else if (nsPIDOMWindowInner* window = nsPIDOMWindowInner::FromEventTargetOrNull(aEventTarget)) { doc = window->GetExtantDoc(); } // nsContentUtils::IsChromeDoc is null-safe. bool isChrome = false; if (doc) { isChrome = nsContentUtils::IsChromeDoc(doc); if (aDocument) { nsCOMPtr retVal = doc; retVal.swap(*aDocument); } } else if (nsCOMPtr sop = do_QueryInterface(aEventTarget->GetOwnerGlobal())) { isChrome = sop->GetPrincipal()->IsSystemPrincipal(); } return isChrome; } // EventTargetChainItem represents a single item in the event target chain. class EventTargetChainItem { public: explicit EventTargetChainItem(EventTarget* aTarget) : mTarget(aTarget), mItemFlags(0) { MOZ_COUNT_CTOR(EventTargetChainItem); } MOZ_COUNTED_DTOR(EventTargetChainItem) static EventTargetChainItem* Create(nsTArray& aChain, EventTarget* aTarget, EventTargetChainItem* aChild = nullptr) { // The last item which can handle the event must be aChild. MOZ_ASSERT(GetLastCanHandleEventTarget(aChain) == aChild); MOZ_ASSERT(!aTarget || aTarget == aTarget->GetTargetForEventTargetChain()); EventTargetChainItem* etci = aChain.AppendElement(aTarget); return etci; } static void DestroyLast(nsTArray& aChain, EventTargetChainItem* aItem) { MOZ_ASSERT(&aChain.LastElement() == aItem); aChain.RemoveLastElement(); } static EventTargetChainItem* GetFirstCanHandleEventTarget( nsTArray& aChain) { return &aChain[GetFirstCanHandleEventTargetIdx(aChain)]; } static uint32_t GetFirstCanHandleEventTargetIdx( nsTArray& aChain) { // aChain[i].PreHandleEventOnly() = true only when the target element wants // PreHandleEvent and set mCanHandle=false. So we find the first element // which can handle the event. for (uint32_t i = 0; i < aChain.Length(); ++i) { if (!aChain[i].PreHandleEventOnly()) { return i; } } MOZ_ASSERT(false); return 0; } static EventTargetChainItem* GetLastCanHandleEventTarget( nsTArray& aChain) { // Fine the last item which can handle the event. for (int32_t i = aChain.Length() - 1; i >= 0; --i) { if (!aChain[i].PreHandleEventOnly()) { return &aChain[i]; } } return nullptr; } bool IsValid() const { NS_WARNING_ASSERTION(!!(mTarget), "Event target is not valid!"); return !!(mTarget); } EventTarget* GetNewTarget() const { return mNewTarget; } void SetNewTarget(EventTarget* aNewTarget) { mNewTarget = aNewTarget; } EventTarget* GetRetargetedRelatedTarget() { return mRetargetedRelatedTarget; } void SetRetargetedRelatedTarget(EventTarget* aTarget) { mRetargetedRelatedTarget = aTarget; } void SetRetargetedTouchTarget( Maybe>>&& aTargets) { mRetargetedTouchTargets = std::move(aTargets); } bool HasRetargetTouchTargets() const { return mRetargetedTouchTargets.isSome() || mInitialTargetTouches.isSome(); } void RetargetTouchTargets(WidgetTouchEvent* aTouchEvent, Event* aDOMEvent) { MOZ_ASSERT(HasRetargetTouchTargets()); MOZ_ASSERT(aTouchEvent, "mRetargetedTouchTargets should be empty when dispatching " "non-touch events."); if (mRetargetedTouchTargets.isSome()) { WidgetTouchEvent::TouchArray& touches = aTouchEvent->mTouches; MOZ_ASSERT(!touches.Length() || touches.Length() == mRetargetedTouchTargets->Length()); for (uint32_t i = 0; i < touches.Length(); ++i) { touches[i]->mTarget = mRetargetedTouchTargets->ElementAt(i); } } if (aDOMEvent) { // The number of touch objects in targetTouches list may change depending // on the retargeting. TouchEvent* touchDOMEvent = static_cast(aDOMEvent); TouchList* targetTouches = touchDOMEvent->GetExistingTargetTouches(); if (targetTouches) { targetTouches->Clear(); if (mInitialTargetTouches.isSome()) { for (uint32_t i = 0; i < mInitialTargetTouches->Length(); ++i) { Touch* touch = mInitialTargetTouches->ElementAt(i); if (touch) { touch->mTarget = touch->mOriginalTarget; } targetTouches->Append(touch); } } } } } void SetInitialTargetTouches( Maybe>>&& aInitialTargetTouches) { mInitialTargetTouches = std::move(aInitialTargetTouches); } void SetForceContentDispatch(bool aForce) { mFlags.mForceContentDispatch = aForce; } bool ForceContentDispatch() const { return mFlags.mForceContentDispatch; } void SetWantsWillHandleEvent(bool aWants) { mFlags.mWantsWillHandleEvent = aWants; } bool WantsWillHandleEvent() const { return mFlags.mWantsWillHandleEvent; } void SetWantsPreHandleEvent(bool aWants) { mFlags.mWantsPreHandleEvent = aWants; } bool WantsPreHandleEvent() const { return mFlags.mWantsPreHandleEvent; } void SetPreHandleEventOnly(bool aWants) { mFlags.mPreHandleEventOnly = aWants; } bool PreHandleEventOnly() const { return mFlags.mPreHandleEventOnly; } void SetRootOfClosedTree(bool aSet) { mFlags.mRootOfClosedTree = aSet; } bool IsRootOfClosedTree() const { return mFlags.mRootOfClosedTree; } void SetItemInShadowTree(bool aSet) { mFlags.mItemInShadowTree = aSet; } bool IsItemInShadowTree() const { return mFlags.mItemInShadowTree; } void SetIsSlotInClosedTree(bool aSet) { mFlags.mIsSlotInClosedTree = aSet; } bool IsSlotInClosedTree() const { return mFlags.mIsSlotInClosedTree; } void SetIsChromeHandler(bool aSet) { mFlags.mIsChromeHandler = aSet; } bool IsChromeHandler() const { return mFlags.mIsChromeHandler; } void SetMayHaveListenerManager(bool aMayHave) { mFlags.mMayHaveManager = aMayHave; } bool MayHaveListenerManager() { return mFlags.mMayHaveManager; } EventTarget* CurrentTarget() const { return mTarget; } /** * Dispatches event through the event target chain. * Handles capture, target and bubble phases both in default * and system event group and calls also PostHandleEvent for each * item in the chain. */ MOZ_CAN_RUN_SCRIPT static void HandleEventTargetChain(nsTArray& aChain, EventChainPostVisitor& aVisitor, EventDispatchingCallback* aCallback, ELMCreationDetector& aCd); /** * Resets aVisitor object and calls GetEventTargetParent. * Copies mItemFlags and mItemData to the current EventTargetChainItem. */ void GetEventTargetParent(EventChainPreVisitor& aVisitor); /** * Calls PreHandleEvent for those items which called SetWantsPreHandleEvent. */ void PreHandleEvent(EventChainVisitor& aVisitor); /** * If the current item in the event target chain has an event listener * manager, this method calls EventListenerManager::HandleEvent(). */ void HandleEvent(EventChainPostVisitor& aVisitor, ELMCreationDetector& aCd) { if (WantsWillHandleEvent()) { mTarget->WillHandleEvent(aVisitor); } if (aVisitor.mEvent->PropagationStopped()) { return; } if (aVisitor.mEvent->mFlags.mOnlySystemGroupDispatch && !aVisitor.mEvent->mFlags.mInSystemGroup) { return; } if (aVisitor.mEvent->mFlags.mOnlySystemGroupDispatchInContent && !aVisitor.mEvent->mFlags.mInSystemGroup && !IsCurrentTargetChrome()) { return; } if (!mManager) { if (!MayHaveListenerManager() && !aCd.MayHaveNewListenerManager()) { return; } mManager = mTarget->GetExistingListenerManager(); } if (mManager) { NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr, "CurrentTarget should be null!"); mManager->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent, &aVisitor.mDOMEvent, CurrentTarget(), &aVisitor.mEventStatus, IsItemInShadowTree()); NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr, "CurrentTarget should be null!"); } } /** * Copies mItemFlags and mItemData to aVisitor and calls PostHandleEvent. */ MOZ_CAN_RUN_SCRIPT void PostHandleEvent(EventChainPostVisitor& aVisitor); private: const nsCOMPtr mTarget; nsCOMPtr mRetargetedRelatedTarget; Maybe>> mRetargetedTouchTargets; Maybe>> mInitialTargetTouches; class EventTargetChainFlags { public: explicit EventTargetChainFlags() { SetRawFlags(0); } // Cached flags for each EventTargetChainItem which are set when calling // GetEventTargetParent to create event target chain. They are used to // manage or speedup event dispatching. bool mForceContentDispatch : 1; bool mWantsWillHandleEvent : 1; bool mMayHaveManager : 1; bool mChechedIfChrome : 1; bool mIsChromeContent : 1; bool mWantsPreHandleEvent : 1; bool mPreHandleEventOnly : 1; bool mRootOfClosedTree : 1; bool mItemInShadowTree : 1; bool mIsSlotInClosedTree : 1; bool mIsChromeHandler : 1; private: using RawFlags = uint32_t; void SetRawFlags(RawFlags aRawFlags) { static_assert( sizeof(EventTargetChainFlags) <= sizeof(RawFlags), "EventTargetChainFlags must not be bigger than the RawFlags"); memcpy(this, &aRawFlags, sizeof(EventTargetChainFlags)); } } mFlags; uint16_t mItemFlags; nsCOMPtr mItemData; // Event retargeting must happen whenever mNewTarget is non-null. nsCOMPtr mNewTarget; // Cache mTarget's event listener manager. RefPtr mManager; bool IsCurrentTargetChrome() { if (!mFlags.mChechedIfChrome) { mFlags.mChechedIfChrome = true; if (IsEventTargetChrome(mTarget)) { mFlags.mIsChromeContent = true; } } return mFlags.mIsChromeContent; } }; void EventTargetChainItem::GetEventTargetParent( EventChainPreVisitor& aVisitor) { aVisitor.Reset(); mTarget->GetEventTargetParent(aVisitor); SetForceContentDispatch(aVisitor.mForceContentDispatch); SetWantsWillHandleEvent(aVisitor.mWantsWillHandleEvent); SetMayHaveListenerManager(aVisitor.mMayHaveListenerManager); SetWantsPreHandleEvent(aVisitor.mWantsPreHandleEvent); SetPreHandleEventOnly(aVisitor.mWantsPreHandleEvent && !aVisitor.mCanHandle); SetRootOfClosedTree(aVisitor.mRootOfClosedTree); SetItemInShadowTree(aVisitor.mItemInShadowTree); SetRetargetedRelatedTarget(aVisitor.mRetargetedRelatedTarget); SetRetargetedTouchTarget(std::move(aVisitor.mRetargetedTouchTargets)); mItemFlags = aVisitor.mItemFlags; mItemData = aVisitor.mItemData; } void EventTargetChainItem::PreHandleEvent(EventChainVisitor& aVisitor) { if (!WantsPreHandleEvent()) { return; } aVisitor.mItemFlags = mItemFlags; aVisitor.mItemData = mItemData; Unused << mTarget->PreHandleEvent(aVisitor); } void EventTargetChainItem::PostHandleEvent(EventChainPostVisitor& aVisitor) { aVisitor.mItemFlags = mItemFlags; aVisitor.mItemData = mItemData; mTarget->PostHandleEvent(aVisitor); } void EventTargetChainItem::HandleEventTargetChain( nsTArray& aChain, EventChainPostVisitor& aVisitor, EventDispatchingCallback* aCallback, ELMCreationDetector& aCd) { // Save the target so that it can be restored later. nsCOMPtr firstTarget = aVisitor.mEvent->mTarget; nsCOMPtr firstRelatedTarget = aVisitor.mEvent->mRelatedTarget; Maybe, 10>> firstTouchTargets; WidgetTouchEvent* touchEvent = nullptr; if (aVisitor.mEvent->mClass == eTouchEventClass) { touchEvent = aVisitor.mEvent->AsTouchEvent(); if (!aVisitor.mEvent->mFlags.mInSystemGroup) { firstTouchTargets.emplace(); WidgetTouchEvent* touchEvent = aVisitor.mEvent->AsTouchEvent(); WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; for (uint32_t i = 0; i < touches.Length(); ++i) { firstTouchTargets->AppendElement(touches[i]->mTarget); } } } uint32_t chainLength = aChain.Length(); EventTargetChainItem* chain = aChain.Elements(); uint32_t firstCanHandleEventTargetIdx = EventTargetChainItem::GetFirstCanHandleEventTargetIdx(aChain); // Capture aVisitor.mEvent->mFlags.mInCapturePhase = true; aVisitor.mEvent->mFlags.mInBubblingPhase = false; for (uint32_t i = chainLength - 1; i > firstCanHandleEventTargetIdx; --i) { EventTargetChainItem& item = chain[i]; if (item.PreHandleEventOnly()) { continue; } if ((!aVisitor.mEvent->mFlags.mNoContentDispatch || item.ForceContentDispatch()) && !aVisitor.mEvent->PropagationStopped()) { item.HandleEvent(aVisitor, aCd); } if (item.GetNewTarget()) { // item is at anonymous boundary. Need to retarget for the child items. for (uint32_t j = i; j > 0; --j) { uint32_t childIndex = j - 1; EventTarget* newTarget = chain[childIndex].GetNewTarget(); if (newTarget) { aVisitor.mEvent->mTarget = newTarget; break; } } } // https://dom.spec.whatwg.org/#dispatching-events // Step 14.2 // "Set event's relatedTarget to tuple's relatedTarget." // Note, the initial retargeting was done already when creating // event target chain, so we need to do this only after calling // HandleEvent, not before, like in the specification. if (item.GetRetargetedRelatedTarget()) { bool found = false; for (uint32_t j = i; j > 0; --j) { uint32_t childIndex = j - 1; EventTarget* relatedTarget = chain[childIndex].GetRetargetedRelatedTarget(); if (relatedTarget) { found = true; aVisitor.mEvent->mRelatedTarget = relatedTarget; break; } } if (!found) { aVisitor.mEvent->mRelatedTarget = aVisitor.mEvent->mOriginalRelatedTarget; } } if (item.HasRetargetTouchTargets()) { bool found = false; for (uint32_t j = i; j > 0; --j) { uint32_t childIndex = j - 1; if (chain[childIndex].HasRetargetTouchTargets()) { found = true; chain[childIndex].RetargetTouchTargets(touchEvent, aVisitor.mDOMEvent); break; } } if (!found) { WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; for (uint32_t i = 0; i < touches.Length(); ++i) { touches[i]->mTarget = touches[i]->mOriginalTarget; } } } } // Target aVisitor.mEvent->mFlags.mInBubblingPhase = true; EventTargetChainItem& targetItem = chain[firstCanHandleEventTargetIdx]; // Need to explicitly retarget touch targets so that initial targets get set // properly in case nothing else retargeted touches. if (targetItem.HasRetargetTouchTargets()) { targetItem.RetargetTouchTargets(touchEvent, aVisitor.mDOMEvent); } if (!aVisitor.mEvent->PropagationStopped() && (!aVisitor.mEvent->mFlags.mNoContentDispatch || targetItem.ForceContentDispatch())) { targetItem.HandleEvent(aVisitor, aCd); } if (aVisitor.mEvent->mFlags.mInSystemGroup) { targetItem.PostHandleEvent(aVisitor); } // Bubble aVisitor.mEvent->mFlags.mInCapturePhase = false; for (uint32_t i = firstCanHandleEventTargetIdx + 1; i < chainLength; ++i) { EventTargetChainItem& item = chain[i]; if (item.PreHandleEventOnly()) { continue; } EventTarget* newTarget = item.GetNewTarget(); if (newTarget) { // Item is at anonymous boundary. Need to retarget for the current item // and for parent items. aVisitor.mEvent->mTarget = newTarget; } // https://dom.spec.whatwg.org/#dispatching-events // Step 15.2 // "Set event's relatedTarget to tuple's relatedTarget." EventTarget* relatedTarget = item.GetRetargetedRelatedTarget(); if (relatedTarget) { aVisitor.mEvent->mRelatedTarget = relatedTarget; } if (item.HasRetargetTouchTargets()) { item.RetargetTouchTargets(touchEvent, aVisitor.mDOMEvent); } if (aVisitor.mEvent->mFlags.mBubbles || newTarget) { if ((!aVisitor.mEvent->mFlags.mNoContentDispatch || item.ForceContentDispatch()) && !aVisitor.mEvent->PropagationStopped()) { item.HandleEvent(aVisitor, aCd); } if (aVisitor.mEvent->mFlags.mInSystemGroup) { item.PostHandleEvent(aVisitor); } } } aVisitor.mEvent->mFlags.mInBubblingPhase = false; if (!aVisitor.mEvent->mFlags.mInSystemGroup && aVisitor.mEvent->IsAllowedToDispatchInSystemGroup()) { // Dispatch to the system event group. Make sure to clear the // STOP_DISPATCH flag since this resets for each event group. aVisitor.mEvent->mFlags.mPropagationStopped = false; aVisitor.mEvent->mFlags.mImmediatePropagationStopped = false; // Setting back the original target of the event. aVisitor.mEvent->mTarget = aVisitor.mEvent->mOriginalTarget; aVisitor.mEvent->mRelatedTarget = aVisitor.mEvent->mOriginalRelatedTarget; if (firstTouchTargets) { WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; for (uint32_t i = 0; i < touches.Length(); ++i) { touches[i]->mTarget = touches[i]->mOriginalTarget; } } // Special handling if PresShell (or some other caller) // used a callback object. if (aCallback) { aCallback->HandleEvent(aVisitor); } // Retarget for system event group (which does the default handling too). // Setting back the target which was used also for default event group. aVisitor.mEvent->mTarget = firstTarget; aVisitor.mEvent->mRelatedTarget = firstRelatedTarget; if (firstTouchTargets) { WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; for (uint32_t i = 0; i < firstTouchTargets->Length(); ++i) { touches[i]->mTarget = firstTouchTargets->ElementAt(i); } } aVisitor.mEvent->mFlags.mInSystemGroup = true; HandleEventTargetChain(aChain, aVisitor, aCallback, aCd); aVisitor.mEvent->mFlags.mInSystemGroup = false; // After dispatch, clear all the propagation flags so that // system group listeners don't affect to the event. aVisitor.mEvent->mFlags.mPropagationStopped = false; aVisitor.mEvent->mFlags.mImmediatePropagationStopped = false; } } // There are often 2 nested event dispatches ongoing at the same time, so // have 2 separate caches. static const uint32_t kCachedMainThreadChainSize = 128; struct CachedChains { nsTArray mChain1; nsTArray mChain2; }; static CachedChains* sCachedMainThreadChains = nullptr; /* static */ void EventDispatcher::Shutdown() { delete sCachedMainThreadChains; sCachedMainThreadChains = nullptr; } EventTargetChainItem* EventTargetChainItemForChromeTarget( nsTArray& aChain, nsINode* aNode, EventTargetChainItem* aChild = nullptr) { if (!aNode->IsInComposedDoc()) { return nullptr; } nsPIDOMWindowInner* win = aNode->OwnerDoc()->GetInnerWindow(); EventTarget* piTarget = win ? win->GetParentTarget() : nullptr; NS_ENSURE_TRUE(piTarget, nullptr); EventTargetChainItem* etci = EventTargetChainItem::Create( aChain, piTarget->GetTargetForEventTargetChain(), aChild); if (!etci->IsValid()) { EventTargetChainItem::DestroyLast(aChain, etci); return nullptr; } return etci; } /* static */ EventTargetChainItem* MayRetargetToChromeIfCanNotHandleEvent( nsTArray& aChain, EventChainPreVisitor& aPreVisitor, EventTargetChainItem* aTargetEtci, EventTargetChainItem* aChildEtci, nsINode* aContent) { if (!aPreVisitor.mWantsPreHandleEvent) { // Keep EventTargetChainItem if we need to call PreHandleEvent on it. EventTargetChainItem::DestroyLast(aChain, aTargetEtci); } if (aPreVisitor.mAutomaticChromeDispatch && aContent) { aPreVisitor.mRelatedTargetRetargetedInCurrentScope = false; // Event target couldn't handle the event. Try to propagate to chrome. EventTargetChainItem* chromeTargetEtci = EventTargetChainItemForChromeTarget(aChain, aContent, aChildEtci); if (chromeTargetEtci) { // If we propagate to chrome, need to ensure we mark // EventTargetChainItem to be chrome handler so that event.composedPath() // can return the right value. chromeTargetEtci->SetIsChromeHandler(true); chromeTargetEtci->GetEventTargetParent(aPreVisitor); return chromeTargetEtci; } } return nullptr; } static bool ShouldClearTargets(WidgetEvent* aEvent) { if (nsIContent* finalTarget = nsIContent::FromEventTargetOrNull(aEvent->mTarget)) { if (finalTarget->SubtreeRoot()->IsShadowRoot()) { return true; } } if (nsIContent* finalRelatedTarget = nsIContent::FromEventTargetOrNull(aEvent->mRelatedTarget)) { if (finalRelatedTarget->SubtreeRoot()->IsShadowRoot()) { return true; } } // XXXsmaug Check also all the touch objects. return false; } static void DescribeEventTargetForProfilerMarker(const EventTarget* aTarget, nsACString& aDescription) { auto* node = aTarget->GetAsNode(); if (node) { if (node->IsElement()) { nsAutoString nodeDescription; node->AsElement()->Describe(nodeDescription, true); aDescription = NS_ConvertUTF16toUTF8(nodeDescription); } else if (node->IsDocument()) { aDescription.AssignLiteral("document"); } else if (node->IsText()) { aDescription.AssignLiteral("text"); } else if (node->IsDocumentFragment()) { aDescription.AssignLiteral("document fragment"); } } else if (aTarget->IsInnerWindow() || aTarget->IsOuterWindow()) { aDescription.AssignLiteral("window"); } else if (aTarget->IsRootWindow()) { aDescription.AssignLiteral("root window"); } else { // Probably something that inherits from DOMEventTargetHelper. } } /* static */ nsresult EventDispatcher::Dispatch(nsISupports* aTarget, nsPresContext* aPresContext, WidgetEvent* aEvent, Event* aDOMEvent, nsEventStatus* aEventStatus, EventDispatchingCallback* aCallback, nsTArray* aTargets) { AUTO_PROFILER_LABEL("EventDispatcher::Dispatch", OTHER); NS_ASSERTION(aEvent, "Trying to dispatch without WidgetEvent!"); NS_ENSURE_TRUE(!aEvent->mFlags.mIsBeingDispatched, NS_ERROR_DOM_INVALID_STATE_ERR); NS_ASSERTION(!aTargets || !aEvent->mMessage, "Wrong parameters!"); // If we're dispatching an already created DOMEvent object, make // sure it is initialized! // If aTargets is non-null, the event isn't going to be dispatched. NS_ENSURE_TRUE(aEvent->mMessage || !aDOMEvent || aTargets, NS_ERROR_DOM_INVALID_STATE_ERR); // Events shall not be fired while we are in stable state to prevent anything // visible from the scripts. MOZ_ASSERT(!nsContentUtils::IsInStableOrMetaStableState()); NS_ENSURE_TRUE(!nsContentUtils::IsInStableOrMetaStableState(), NS_ERROR_DOM_INVALID_STATE_ERR); nsCOMPtr target = do_QueryInterface(aTarget); RefPtr eventTimingEntry; // Similar to PerformancePaintTiming, we don't need to // expose them for printing documents if (aPresContext && !aPresContext->IsPrintingOrPrintPreview()) { eventTimingEntry = PerformanceEventTiming::TryGenerateEventTiming(target, aEvent); } bool retargeted = false; if (aEvent->mFlags.mRetargetToNonNativeAnonymous) { nsIContent* content = nsIContent::FromEventTargetOrNull(target); if (content && content->IsInNativeAnonymousSubtree()) { nsCOMPtr newTarget = content->FindFirstNonChromeOnlyAccessContent(); NS_ENSURE_STATE(newTarget); aEvent->mOriginalTarget = target; target = newTarget; retargeted = true; } } if (aEvent->mFlags.mOnlyChromeDispatch) { nsCOMPtr doc; if (!IsEventTargetChrome(target, getter_AddRefs(doc)) && doc) { nsPIDOMWindowInner* win = doc->GetInnerWindow(); // If we can't dispatch the event to chrome, do nothing. EventTarget* piTarget = win ? win->GetParentTarget() : nullptr; if (!piTarget) { return NS_OK; } // Set the target to be the original dispatch target, aEvent->mTarget = target; // but use chrome event handler or BrowserChildMessageManager for event // target chain. target = piTarget; } else if (NS_WARN_IF(!doc)) { return NS_ERROR_UNEXPECTED; } } #ifdef DEBUG if (NS_IsMainThread() && aEvent->mMessage != eVoidEvent && !nsContentUtils::IsSafeToRunScript()) { static const auto warn = [](bool aIsSystem) { if (aIsSystem) { NS_WARNING("Fix the caller!"); } else { MOZ_CRASH("This is unsafe! Fix the caller!"); } }; if (nsINode* node = nsINode::FromEventTargetOrNull(target)) { // If this is a node, it's possible that this is some sort of DOM tree // that is never accessed by script (for example an SVG image or XBL // binding document or whatnot). We really only want to warn/assert here // if there might be actual scripted listeners for this event, so restrict // the warnings/asserts to the case when script can or once could touch // this node's document. Document* doc = node->OwnerDoc(); bool hasHadScriptHandlingObject; nsIGlobalObject* global = doc->GetScriptHandlingObject(hasHadScriptHandlingObject); if (global || hasHadScriptHandlingObject) { warn(nsContentUtils::IsChromeDoc(doc)); } } else if (nsCOMPtr global = target->GetOwnerGlobal()) { warn(global->PrincipalOrNull()->IsSystemPrincipal()); } } if (aDOMEvent) { WidgetEvent* innerEvent = aDOMEvent->WidgetEventPtr(); NS_ASSERTION(innerEvent == aEvent, "The inner event of aDOMEvent is not the same as aEvent!"); } #endif nsresult rv = NS_OK; bool externalDOMEvent = !!(aDOMEvent); // If we have a PresContext, make sure it doesn't die before // event dispatching is finished. RefPtr kungFuDeathGrip(aPresContext); ELMCreationDetector cd; nsTArray chain; if (cd.IsMainThread()) { if (!sCachedMainThreadChains) { sCachedMainThreadChains = new CachedChains(); } if (sCachedMainThreadChains->mChain1.Capacity() == kCachedMainThreadChainSize) { chain = std::move(sCachedMainThreadChains->mChain1); } else if (sCachedMainThreadChains->mChain2.Capacity() == kCachedMainThreadChainSize) { chain = std::move(sCachedMainThreadChains->mChain2); } else { chain.SetCapacity(kCachedMainThreadChainSize); } } // Create the event target chain item for the event target. EventTargetChainItem* targetEtci = EventTargetChainItem::Create( chain, target->GetTargetForEventTargetChain()); MOZ_ASSERT(&chain[0] == targetEtci); if (!targetEtci->IsValid()) { EventTargetChainItem::DestroyLast(chain, targetEtci); return NS_ERROR_FAILURE; } // Make sure that Event::target and Event::originalTarget // point to the last item in the chain. if (!aEvent->mTarget) { // Note, CurrentTarget() points always to the object returned by // GetTargetForEventTargetChain(). aEvent->mTarget = targetEtci->CurrentTarget(); } else { // XXX But if the target is already set, use that. This is a hack // for the 'load', 'beforeunload' and 'unload' events, // which are dispatched to |window| but have document as their target. // // Make sure that the event target points to the right object. aEvent->mTarget = aEvent->mTarget->GetTargetForEventTargetChain(); NS_ENSURE_STATE(aEvent->mTarget); } if (retargeted) { aEvent->mOriginalTarget = aEvent->mOriginalTarget->GetTargetForEventTargetChain(); NS_ENSURE_STATE(aEvent->mOriginalTarget); } else { aEvent->mOriginalTarget = aEvent->mTarget; } aEvent->mOriginalRelatedTarget = aEvent->mRelatedTarget; bool clearTargets = false; nsCOMPtr content = nsIContent::FromEventTargetOrNull(aEvent->mOriginalTarget); bool isInAnon = content && content->IsInNativeAnonymousSubtree(); aEvent->mFlags.mIsBeingDispatched = true; // Create visitor object and start event dispatching. // GetEventTargetParent for the original target. nsEventStatus status = aDOMEvent && aDOMEvent->DefaultPrevented() ? nsEventStatus_eConsumeNoDefault : aEventStatus ? *aEventStatus : nsEventStatus_eIgnore; nsCOMPtr targetForPreVisitor = aEvent->mTarget; EventChainPreVisitor preVisitor(aPresContext, aEvent, aDOMEvent, status, isInAnon, targetForPreVisitor); targetEtci->GetEventTargetParent(preVisitor); if (!preVisitor.mCanHandle) { targetEtci = MayRetargetToChromeIfCanNotHandleEvent( chain, preVisitor, targetEtci, nullptr, content); } if (!preVisitor.mCanHandle) { // The original target and chrome target (mAutomaticChromeDispatch=true) // can not handle the event but we still have to call their PreHandleEvent. for (uint32_t i = 0; i < chain.Length(); ++i) { chain[i].PreHandleEvent(preVisitor); } clearTargets = ShouldClearTargets(aEvent); } else { // At least the original target can handle the event. // Setting the retarget to the |target| simplifies retargeting code. nsCOMPtr t = aEvent->mTarget; targetEtci->SetNewTarget(t); // In order to not change the targetTouches array passed to TouchEvents // when dispatching events from JS, we need to store the initial Touch // objects on the list. if (aEvent->mClass == eTouchEventClass && aDOMEvent) { TouchEvent* touchEvent = static_cast(aDOMEvent); TouchList* targetTouches = touchEvent->GetExistingTargetTouches(); if (targetTouches) { Maybe>> initialTargetTouches; initialTargetTouches.emplace(); for (uint32_t i = 0; i < targetTouches->Length(); ++i) { initialTargetTouches->AppendElement(targetTouches->Item(i)); } targetEtci->SetInitialTargetTouches(std::move(initialTargetTouches)); targetTouches->Clear(); } } EventTargetChainItem* topEtci = targetEtci; targetEtci = nullptr; while (preVisitor.GetParentTarget()) { EventTarget* parentTarget = preVisitor.GetParentTarget(); EventTargetChainItem* parentEtci = EventTargetChainItem::Create(chain, parentTarget, topEtci); if (!parentEtci->IsValid()) { EventTargetChainItem::DestroyLast(chain, parentEtci); rv = NS_ERROR_FAILURE; break; } parentEtci->SetIsSlotInClosedTree(preVisitor.mParentIsSlotInClosedTree); parentEtci->SetIsChromeHandler(preVisitor.mParentIsChromeHandler); // Item needs event retargetting. if (preVisitor.mEventTargetAtParent) { // Need to set the target of the event // so that also the next retargeting works. preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget; preVisitor.mEvent->mTarget = preVisitor.mEventTargetAtParent; parentEtci->SetNewTarget(preVisitor.mEventTargetAtParent); } if (preVisitor.mRetargetedRelatedTarget) { preVisitor.mEvent->mRelatedTarget = preVisitor.mRetargetedRelatedTarget; } parentEtci->GetEventTargetParent(preVisitor); if (preVisitor.mCanHandle) { preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget; topEtci = parentEtci; } else { bool ignoreBecauseOfShadowDOM = preVisitor.mIgnoreBecauseOfShadowDOM; nsCOMPtr disabledTarget = nsINode::FromEventTargetOrNull(parentTarget); parentEtci = MayRetargetToChromeIfCanNotHandleEvent( chain, preVisitor, parentEtci, topEtci, disabledTarget); if (parentEtci && preVisitor.mCanHandle) { preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget; EventTargetChainItem* item = EventTargetChainItem::GetFirstCanHandleEventTarget(chain); if (!ignoreBecauseOfShadowDOM) { // If we ignored the target because of Shadow DOM retargeting, we // shouldn't treat the target to be in the event path at all. item->SetNewTarget(parentTarget); } topEtci = parentEtci; continue; } break; } } if (NS_SUCCEEDED(rv)) { if (aTargets) { aTargets->Clear(); uint32_t numTargets = chain.Length(); EventTarget** targets = aTargets->AppendElements(numTargets); for (uint32_t i = 0; i < numTargets; ++i) { targets[i] = chain[i].CurrentTarget()->GetTargetForDOMEvent(); } } else { // Event target chain is created. PreHandle the chain. for (uint32_t i = 0; i < chain.Length(); ++i) { chain[i].PreHandleEvent(preVisitor); } RefPtr refreshDriver; if (aEvent->IsTrusted() && (aEvent->mMessage == eKeyPress || aEvent->mMessage == eMouseClick) && aPresContext && aPresContext->GetRootPresContext()) { refreshDriver = aPresContext->GetRootPresContext()->RefreshDriver(); if (refreshDriver) { refreshDriver->EnterUserInputProcessing(); } } auto cleanup = MakeScopeExit([&] { if (refreshDriver) { refreshDriver->ExitUserInputProcessing(); } }); clearTargets = ShouldClearTargets(aEvent); // Handle the chain. EventChainPostVisitor postVisitor(preVisitor); MOZ_RELEASE_ASSERT(!aEvent->mPath); aEvent->mPath = &chain; if (profiler_is_active()) { // Add a profiler label and a profiler marker for the actual // dispatch of the event. // This is a very hot code path, so we need to make sure not to // do this extra work when we're not profiling. if (!postVisitor.mDOMEvent) { // This is tiny bit slow, but happens only once per event. // Similar code also in EventListenerManager. nsCOMPtr et = aEvent->mOriginalTarget; RefPtr event = EventDispatcher::CreateEvent(et, aPresContext, aEvent, u""_ns); event.swap(postVisitor.mDOMEvent); } nsAutoString typeStr; postVisitor.mDOMEvent->GetType(typeStr); AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING( "EventDispatcher::Dispatch", OTHER, typeStr); nsCOMPtr docShell; docShell = nsContentUtils::GetDocShellForEventTarget(aEvent->mTarget); MarkerInnerWindowId innerWindowId; if (nsCOMPtr inner = do_QueryInterface(aEvent->mTarget->GetOwnerGlobal())) { innerWindowId = MarkerInnerWindowId{inner->WindowID()}; } struct DOMEventMarker { static constexpr Span MarkerTypeName() { return MakeStringSpan("DOMEvent"); } static void StreamJSONMarkerData( baseprofiler::SpliceableJSONWriter& aWriter, const ProfilerString16View& aEventType, const nsCString& aTarget, const TimeStamp& aStartTime, const TimeStamp& aEventTimeStamp) { aWriter.StringProperty("eventType", NS_ConvertUTF16toUTF8(aEventType)); if (!aTarget.IsEmpty()) { aWriter.StringProperty("target", aTarget); } // This is the event processing latency, which is the time from // when the event was created, to when it was started to be // processed. Note that the computation of this latency is // deferred until serialization time, at the expense of some extra // memory. aWriter.DoubleProperty( "latency", (aStartTime - aEventTimeStamp).ToMilliseconds()); } static MarkerSchema MarkerTypeDisplay() { using MS = MarkerSchema; MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable, MS::Location::TimelineOverview}; schema.SetChartLabel("{marker.data.eventType}"); schema.SetTooltipLabel("{marker.data.eventType} - DOMEvent"); schema.SetTableLabel( "{marker.data.eventType} - {marker.data.target}"); schema.AddKeyLabelFormatSearchable("target", "Event Target", MS::Format::String, MS::Searchable::Searchable); schema.AddKeyLabelFormat("latency", "Latency", MS::Format::Duration); schema.AddKeyLabelFormatSearchable("eventType", "Event Type", MS::Format::String, MS::Searchable::Searchable); return schema; } }; nsAutoCString target; DescribeEventTargetForProfilerMarker(aEvent->mTarget, target); auto startTime = TimeStamp::Now(); profiler_add_marker("DOMEvent", geckoprofiler::category::DOM, {MarkerTiming::IntervalStart(), MarkerInnerWindowId(innerWindowId)}, DOMEventMarker{}, typeStr, target, startTime, aEvent->mTimeStamp); EventTargetChainItem::HandleEventTargetChain(chain, postVisitor, aCallback, cd); profiler_add_marker( "DOMEvent", geckoprofiler::category::DOM, {MarkerTiming::IntervalEnd(), std::move(innerWindowId)}, DOMEventMarker{}, typeStr, target, startTime, aEvent->mTimeStamp); } else { EventTargetChainItem::HandleEventTargetChain(chain, postVisitor, aCallback, cd); } aEvent->mPath = nullptr; if (aEvent->IsTrusted() && (aEvent->mMessage == eKeyPress || aEvent->mMessage == eMouseClick) && aPresContext && aPresContext->GetRootPresContext()) { nsRefreshDriver* driver = aPresContext->GetRootPresContext()->RefreshDriver(); if (driver && driver->HasPendingTick()) { switch (aEvent->mMessage) { case eKeyPress: driver->RegisterCompositionPayload( {layers::CompositionPayloadType::eKeyPress, aEvent->mTimeStamp}); break; case eMouseClick: { if (aEvent->AsMouseEvent()->mInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE || aEvent->AsMouseEvent()->mInputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH) { driver->RegisterCompositionPayload( {layers::CompositionPayloadType::eMouseUpFollowedByClick, aEvent->mTimeStamp}); } break; } default: break; } } } preVisitor.mEventStatus = postVisitor.mEventStatus; // If the DOM event was created during event flow. if (!preVisitor.mDOMEvent && postVisitor.mDOMEvent) { preVisitor.mDOMEvent = postVisitor.mDOMEvent; } } } } // Note, EventTargetChainItem objects are deleted when the chain goes out of // the scope. aEvent->mFlags.mIsBeingDispatched = false; aEvent->mFlags.mDispatchedAtLeastOnce = true; if (eventTimingEntry) { eventTimingEntry->FinalizeEventTiming(aEvent->mTarget); } // https://dom.spec.whatwg.org/#concept-event-dispatch // step 10. If clearTargets, then: // 1. Set event's target to null. // 2. Set event's relatedTarget to null. // 3. Set event's touch target list to the empty list. if (clearTargets) { aEvent->mTarget = nullptr; aEvent->mOriginalTarget = nullptr; aEvent->mRelatedTarget = nullptr; aEvent->mOriginalRelatedTarget = nullptr; // XXXsmaug Check also all the touch objects. } if (!externalDOMEvent && preVisitor.mDOMEvent) { // A dom::Event was created while dispatching the event. // Duplicate private data if someone holds a pointer to it. nsrefcnt rc = 0; NS_RELEASE2(preVisitor.mDOMEvent, rc); if (preVisitor.mDOMEvent) { preVisitor.mDOMEvent->DuplicatePrivateData(); } } if (aEventStatus) { *aEventStatus = preVisitor.mEventStatus; } if (cd.IsMainThread() && chain.Capacity() == kCachedMainThreadChainSize && sCachedMainThreadChains) { if (sCachedMainThreadChains->mChain1.Capacity() != kCachedMainThreadChainSize) { chain.ClearAndRetainStorage(); chain.SwapElements(sCachedMainThreadChains->mChain1); } else if (sCachedMainThreadChains->mChain2.Capacity() != kCachedMainThreadChainSize) { chain.ClearAndRetainStorage(); chain.SwapElements(sCachedMainThreadChains->mChain2); } } return rv; } /* static */ nsresult EventDispatcher::DispatchDOMEvent(nsISupports* aTarget, WidgetEvent* aEvent, Event* aDOMEvent, nsPresContext* aPresContext, nsEventStatus* aEventStatus) { if (aDOMEvent) { WidgetEvent* innerEvent = aDOMEvent->WidgetEventPtr(); NS_ENSURE_TRUE(innerEvent, NS_ERROR_ILLEGAL_VALUE); // Don't modify the event if it's being dispatched right now. if (innerEvent->mFlags.mIsBeingDispatched) { return NS_ERROR_DOM_INVALID_STATE_ERR; } bool dontResetTrusted = false; if (innerEvent->mFlags.mDispatchedAtLeastOnce) { innerEvent->mTarget = nullptr; innerEvent->mOriginalTarget = nullptr; } else { dontResetTrusted = aDOMEvent->IsTrusted(); } if (!dontResetTrusted) { // Check security state to determine if dispatcher is trusted bool trusted = NS_IsMainThread() ? nsContentUtils::LegacyIsCallerChromeOrNativeCode() : IsCurrentThreadRunningChromeWorker(); aDOMEvent->SetTrusted(trusted); } return EventDispatcher::Dispatch(aTarget, aPresContext, innerEvent, aDOMEvent, aEventStatus); } else if (aEvent) { return EventDispatcher::Dispatch(aTarget, aPresContext, aEvent, aDOMEvent, aEventStatus); } return NS_ERROR_ILLEGAL_VALUE; } /* static */ already_AddRefed EventDispatcher::CreateEvent( EventTarget* aOwner, nsPresContext* aPresContext, WidgetEvent* aEvent, const nsAString& aEventType, CallerType aCallerType) { if (aEvent) { switch (aEvent->mClass) { case eMutationEventClass: return NS_NewDOMMutationEvent(aOwner, aPresContext, aEvent->AsMutationEvent()); case eGUIEventClass: case eScrollPortEventClass: case eUIEventClass: return NS_NewDOMUIEvent(aOwner, aPresContext, aEvent->AsGUIEvent()); case eScrollAreaEventClass: return NS_NewDOMScrollAreaEvent(aOwner, aPresContext, aEvent->AsScrollAreaEvent()); case eKeyboardEventClass: return NS_NewDOMKeyboardEvent(aOwner, aPresContext, aEvent->AsKeyboardEvent()); case eCompositionEventClass: return NS_NewDOMCompositionEvent(aOwner, aPresContext, aEvent->AsCompositionEvent()); case eMouseEventClass: return NS_NewDOMMouseEvent(aOwner, aPresContext, aEvent->AsMouseEvent()); case eFocusEventClass: return NS_NewDOMFocusEvent(aOwner, aPresContext, aEvent->AsFocusEvent()); case eMouseScrollEventClass: return NS_NewDOMMouseScrollEvent(aOwner, aPresContext, aEvent->AsMouseScrollEvent()); case eWheelEventClass: return NS_NewDOMWheelEvent(aOwner, aPresContext, aEvent->AsWheelEvent()); case eEditorInputEventClass: return NS_NewDOMInputEvent(aOwner, aPresContext, aEvent->AsEditorInputEvent()); case eDragEventClass: return NS_NewDOMDragEvent(aOwner, aPresContext, aEvent->AsDragEvent()); case eClipboardEventClass: return NS_NewDOMClipboardEvent(aOwner, aPresContext, aEvent->AsClipboardEvent()); case eSMILTimeEventClass: return NS_NewDOMTimeEvent(aOwner, aPresContext, aEvent->AsSMILTimeEvent()); case eCommandEventClass: return NS_NewDOMCommandEvent(aOwner, aPresContext, aEvent->AsCommandEvent()); case eSimpleGestureEventClass: return NS_NewDOMSimpleGestureEvent(aOwner, aPresContext, aEvent->AsSimpleGestureEvent()); case ePointerEventClass: return NS_NewDOMPointerEvent(aOwner, aPresContext, aEvent->AsPointerEvent()); case eTouchEventClass: return NS_NewDOMTouchEvent(aOwner, aPresContext, aEvent->AsTouchEvent()); case eTransitionEventClass: return NS_NewDOMTransitionEvent(aOwner, aPresContext, aEvent->AsTransitionEvent()); case eAnimationEventClass: return NS_NewDOMAnimationEvent(aOwner, aPresContext, aEvent->AsAnimationEvent()); default: // For all other types of events, create a vanilla event object. return NS_NewDOMEvent(aOwner, aPresContext, aEvent); } } // And if we didn't get an event, check the type argument. if (aEventType.LowerCaseEqualsLiteral("mouseevent") || aEventType.LowerCaseEqualsLiteral("mouseevents")) { return NS_NewDOMMouseEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("dragevent")) { return NS_NewDOMDragEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("keyboardevent")) { return NS_NewDOMKeyboardEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("compositionevent") || aEventType.LowerCaseEqualsLiteral("textevent")) { return NS_NewDOMCompositionEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("mutationevent") || aEventType.LowerCaseEqualsLiteral("mutationevents")) { return NS_NewDOMMutationEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("deviceorientationevent")) { DeviceOrientationEventInit init; RefPtr event = DeviceOrientationEvent::Constructor(aOwner, u""_ns, init); event->MarkUninitialized(); return event.forget(); } if (aEventType.LowerCaseEqualsLiteral("devicemotionevent")) { return NS_NewDOMDeviceMotionEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("uievent") || aEventType.LowerCaseEqualsLiteral("uievents")) { return NS_NewDOMUIEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("event") || aEventType.LowerCaseEqualsLiteral("events") || aEventType.LowerCaseEqualsLiteral("htmlevents") || aEventType.LowerCaseEqualsLiteral("svgevents")) { return NS_NewDOMEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("messageevent")) { RefPtr event = new MessageEvent(aOwner, aPresContext, nullptr); return event.forget(); } if (aEventType.LowerCaseEqualsLiteral("beforeunloadevent")) { return NS_NewDOMBeforeUnloadEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("touchevent") && TouchEvent::LegacyAPIEnabled( nsContentUtils::GetDocShellForEventTarget(aOwner), aCallerType == CallerType::System)) { return NS_NewDOMTouchEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("hashchangeevent")) { HashChangeEventInit init; RefPtr event = HashChangeEvent::Constructor(aOwner, u""_ns, init); event->MarkUninitialized(); return event.forget(); } if (aEventType.LowerCaseEqualsLiteral("customevent")) { return NS_NewDOMCustomEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("storageevent")) { RefPtr event = StorageEvent::Constructor(aOwner, u""_ns, StorageEventInit()); event->MarkUninitialized(); return event.forget(); } if (aEventType.LowerCaseEqualsLiteral("focusevent")) { RefPtr event = NS_NewDOMFocusEvent(aOwner, aPresContext, nullptr); event->MarkUninitialized(); return event.forget(); } // Only allow these events for chrome if (aCallerType == CallerType::System) { if (aEventType.LowerCaseEqualsLiteral("simplegestureevent")) { return NS_NewDOMSimpleGestureEvent(aOwner, aPresContext, nullptr); } if (aEventType.LowerCaseEqualsLiteral("xulcommandevent") || aEventType.LowerCaseEqualsLiteral("xulcommandevents")) { return NS_NewDOMXULCommandEvent(aOwner, aPresContext, nullptr); } } // NEW EVENT TYPES SHOULD NOT BE ADDED HERE; THEY SHOULD USE ONLY EVENT // CONSTRUCTORS return nullptr; } struct CurrentTargetPathInfo { uint32_t mIndex; int32_t mHiddenSubtreeLevel; }; static CurrentTargetPathInfo TargetPathInfo( const nsTArray& aEventPath, const EventTarget& aCurrentTarget) { int32_t currentTargetHiddenSubtreeLevel = 0; for (uint32_t index = aEventPath.Length(); index--;) { const EventTargetChainItem& item = aEventPath.ElementAt(index); if (item.PreHandleEventOnly()) { continue; } if (item.IsRootOfClosedTree()) { currentTargetHiddenSubtreeLevel++; } if (item.CurrentTarget() == &aCurrentTarget) { return {index, currentTargetHiddenSubtreeLevel}; } if (item.IsSlotInClosedTree()) { currentTargetHiddenSubtreeLevel--; } } MOZ_ASSERT_UNREACHABLE("No target found?"); return {0, 0}; } // https://dom.spec.whatwg.org/#dom-event-composedpath void EventDispatcher::GetComposedPathFor(WidgetEvent* aEvent, nsTArray>& aPath) { MOZ_ASSERT(aPath.IsEmpty()); nsTArray* path = aEvent->mPath; if (!path || path->IsEmpty() || !aEvent->mCurrentTarget) { return; } EventTarget* currentTarget = aEvent->mCurrentTarget->GetTargetForEventTargetChain(); if (!currentTarget) { return; } CurrentTargetPathInfo currentTargetInfo = TargetPathInfo(*path, *currentTarget); { int32_t maxHiddenLevel = currentTargetInfo.mHiddenSubtreeLevel; int32_t currentHiddenLevel = currentTargetInfo.mHiddenSubtreeLevel; for (uint32_t index = currentTargetInfo.mIndex; index--;) { EventTargetChainItem& item = path->ElementAt(index); if (item.PreHandleEventOnly()) { continue; } if (item.IsRootOfClosedTree()) { currentHiddenLevel++; } if (currentHiddenLevel <= maxHiddenLevel) { aPath.AppendElement(item.CurrentTarget()->GetTargetForDOMEvent()); } if (item.IsChromeHandler()) { break; } if (item.IsSlotInClosedTree()) { currentHiddenLevel--; maxHiddenLevel = std::min(maxHiddenLevel, currentHiddenLevel); } } aPath.Reverse(); } aPath.AppendElement(currentTarget->GetTargetForDOMEvent()); { int32_t maxHiddenLevel = currentTargetInfo.mHiddenSubtreeLevel; int32_t currentHiddenLevel = currentTargetInfo.mHiddenSubtreeLevel; for (uint32_t index = currentTargetInfo.mIndex + 1; index < path->Length(); ++index) { EventTargetChainItem& item = path->ElementAt(index); if (item.PreHandleEventOnly()) { continue; } if (item.IsSlotInClosedTree()) { currentHiddenLevel++; } if (item.IsChromeHandler()) { break; } if (currentHiddenLevel <= maxHiddenLevel) { aPath.AppendElement(item.CurrentTarget()->GetTargetForDOMEvent()); } if (item.IsRootOfClosedTree()) { currentHiddenLevel--; maxHiddenLevel = std::min(maxHiddenLevel, currentHiddenLevel); } } } } } // namespace mozilla