Bug 1412775 - Implement Event.composedPath, r=stone
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Mon, 18 Dec 2017 18:07:36 +0200
changeset 448491 9c0350b68863ed20379ae03781bbd9a74f46f733
parent 448490 bb89831b9aadfc4b6d736b89d781a5c04b35f369
child 448492 45ed85bd1b6ab5e85afc870969173e5f3b29cfd0
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersstone
bugs1412775
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1412775 - Implement Event.composedPath, r=stone
dom/base/FragmentOrElement.cpp
dom/base/ShadowRoot.cpp
dom/base/nsDocument.cpp
dom/base/nsGlobalWindowInner.cpp
dom/base/nsInProcessTabChildGlobal.cpp
dom/base/nsWindowRoot.cpp
dom/events/DOMEventTargetHelper.cpp
dom/events/Event.cpp
dom/events/Event.h
dom/events/EventDispatcher.cpp
dom/events/EventDispatcher.h
dom/indexedDB/IDBFileHandle.cpp
dom/indexedDB/IDBFileRequest.cpp
dom/indexedDB/IDBRequest.cpp
dom/indexedDB/IDBTransaction.cpp
dom/webidl/Event.webidl
dom/workers/SharedWorker.cpp
dom/xul/nsXULElement.cpp
testing/web-platform/meta/shadow-dom/Extensions-to-Event-Interface.html.ini
testing/web-platform/meta/shadow-dom/event-composed-path-after-dom-mutation.html.ini
testing/web-platform/meta/shadow-dom/event-composed-path-with-related-target.html.ini
testing/web-platform/meta/shadow-dom/event-composed-path.html.ini
testing/web-platform/meta/shadow-dom/event-composed.html.ini
testing/web-platform/meta/shadow-dom/event-post-dispatch.html.ini
widget/BasicEvents.h
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -975,16 +975,17 @@ nsIContent::GetEventTargetParent(EventCh
 {
   //FIXME! Document how this event retargeting works, Bug 329124.
   aVisitor.mCanHandle = true;
   aVisitor.mMayHaveListenerManager = HasListenerManager();
 
   // Don't propagate mouseover and mouseout events when mouse is moving
   // inside chrome access only content.
   bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree();
+  aVisitor.mRootOfClosedTree = isAnonForEvents;
   if ((aVisitor.mEvent->mMessage == eMouseOver ||
        aVisitor.mEvent->mMessage == eMouseOut ||
        aVisitor.mEvent->mMessage == ePointerOver ||
        aVisitor.mEvent->mMessage == ePointerOut) &&
       // Check if we should stop event propagation when event has just been
       // dispatched or when we're about to propagate from
       // chrome access only subtree or if we are about to propagate out of
       // a shadow root to a shadow root host.
@@ -996,17 +997,17 @@ nsIContent::GetEventTargetParent(EventCh
         relatedTarget->OwnerDoc() == OwnerDoc()) {
 
       // In the web components case, we may need to stop propagation of events
       // at shadow root host.
       if (GetShadowRoot()) {
         nsIContent* adjustedTarget =
           Event::GetShadowRelatedTarget(this, relatedTarget);
         if (this == adjustedTarget) {
-          aVisitor.mParentTarget = nullptr;
+          aVisitor.SetParentTarget(nullptr, false);
           aVisitor.mCanHandle = false;
           return NS_OK;
         }
       }
 
       // If current target is anonymous for events or we know that related
       // target is descendant of an element which is anonymous for events,
       // we may want to stop event propagation.
@@ -1053,17 +1054,17 @@ nsIContent::GetEventTargetParent(EventCh
                      NS_ConvertUTF16toUTF8(rt).get(),
                      relatedTarget->ChromeOnlyAccess()
                        ? "(is in native anonymous subtree)" : "",
                      (originalTarget &&
                       relatedTarget->FindFirstNonChromeOnlyAccessContent() ==
                         originalTarget->FindFirstNonChromeOnlyAccessContent())
                        ? "" : "Wrong event propagation!?!\n");
 #endif
-              aVisitor.mParentTarget = nullptr;
+              aVisitor.SetParentTarget(nullptr, false);
               // Event should not propagate to non-anon content.
               aVisitor.mCanHandle = isAnonForEvents;
               return NS_OK;
             }
           }
         }
       }
     }
@@ -1105,21 +1106,27 @@ nsIContent::GetEventTargetParent(EventCh
     if (insertionParent) {
       parent = insertionParent;
     }
   }
 
   if (!aVisitor.mEvent->mFlags.mComposedInNativeAnonymousContent &&
       IsRootOfNativeAnonymousSubtree() && OwnerDoc() &&
       OwnerDoc()->GetWindow()) {
-    aVisitor.mParentTarget = OwnerDoc()->GetWindow()->GetParentTarget();
+    aVisitor.SetParentTarget(OwnerDoc()->GetWindow()->GetParentTarget(), true);
   } else if (parent) {
-    aVisitor.mParentTarget = parent;
+    aVisitor.SetParentTarget(parent, false);
+    if (slot) {
+      ShadowRoot* root = slot->GetContainingShadow();
+      if (root && root->IsClosed()) {
+        aVisitor.mParentIsSlotInClosedTree = true;
+      }
+    }
   } else {
-    aVisitor.mParentTarget = GetComposedDoc();
+    aVisitor.SetParentTarget(GetComposedDoc(), false);
   }
   return NS_OK;
 }
 
 bool
 nsIContent::GetAttr(int32_t aNameSpaceID, nsAtom* aName,
                     nsAString& aResult) const
 {
--- a/dom/base/ShadowRoot.cpp
+++ b/dom/base/ShadowRoot.cpp
@@ -302,37 +302,38 @@ ShadowRoot::GetElementsByClassName(const
 {
   return nsContentUtils::GetElementsByClassName(this, aClasses);
 }
 
 nsresult
 ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   aVisitor.mCanHandle = true;
+  aVisitor.mRootOfClosedTree = IsClosed();
 
   // https://dom.spec.whatwg.org/#ref-for-get-the-parent%E2%91%A6
   if (!aVisitor.mEvent->mFlags.mComposed) {
     nsCOMPtr<nsIContent> originalTarget =
       do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
     if (originalTarget->GetContainingShadow() == this) {
       // If we do stop propagation, we still want to propagate
       // the event to chrome (nsPIDOMWindow::GetParentTarget()).
       // The load event is special in that we don't ever propagate it
       // to chrome.
       nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
       EventTarget* parentTarget = win && aVisitor.mEvent->mMessage != eLoad
         ? win->GetParentTarget() : nullptr;
 
-      aVisitor.mParentTarget = parentTarget;
+      aVisitor.SetParentTarget(parentTarget, true);
       return NS_OK;
     }
   }
 
   nsIContent* shadowHost = GetHost();
-  aVisitor.mParentTarget = shadowHost;
+  aVisitor.SetParentTarget(shadowHost, false);
 
   if (aVisitor.mOriginalTargetIsInAnon) {
     nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->mTarget));
     if (content && content->GetBindingParent() == shadowHost) {
       aVisitor.mEventTargetAtParent = shadowHost;
     }
  }
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -8204,18 +8204,18 @@ nsDocument::GetEventTargetParent(EventCh
   aVisitor.mCanHandle = true;
    // FIXME! This is a hack to make middle mouse paste working also in Editor.
    // Bug 329119
   aVisitor.mForceContentDispatch = true;
 
   // Load events must not propagate to |window| object, see bug 335251.
   if (aVisitor.mEvent->mMessage != eLoad) {
     nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
-    aVisitor.mParentTarget =
-      window ? window->GetTargetForEventTargetChain() : nullptr;
+    aVisitor.SetParentTarget(
+      window ? window->GetTargetForEventTargetChain() : nullptr, false);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocument::CreateEvent(const nsAString& aEventType, nsIDOMEvent** aReturn)
 {
   NS_ENSURE_ARG_POINTER(aReturn);
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -1944,17 +1944,17 @@ nsGlobalWindowInner::GetEventTargetParen
         do_GetService("@mozilla.org/widget/dragservice;1");
       if (ds) {
         sDragServiceDisabled = false;
         ds->Unsuppress();
       }
     }
   }
 
-  aVisitor.mParentTarget = GetParentTarget();
+  aVisitor.SetParentTarget(GetParentTarget(), true);
 
   // Handle 'active' event.
   if (!mIdleObservers.IsEmpty() &&
       aVisitor.mEvent->IsTrusted() &&
       (aVisitor.mEvent->HasMouseEventMessage() ||
        aVisitor.mEvent->HasDragEventMessage())) {
     mAddActiveEventFuzzTime = false;
   }
--- a/dom/base/nsInProcessTabChildGlobal.cpp
+++ b/dom/base/nsInProcessTabChildGlobal.cpp
@@ -272,29 +272,31 @@ nsInProcessTabChildGlobal::GetEventTarge
                    "Wrong event target!");
       NS_ASSERTION(fl->mMessageManager == mChromeMessageManager,
                    "Wrong message manager!");
     }
   }
 #endif
 
   if (mPreventEventsEscaping) {
-    aVisitor.mParentTarget = nullptr;
+    aVisitor.SetParentTarget(nullptr, false);
     return NS_OK;
   }
 
   if (mIsBrowserFrame &&
       (!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) {
     if (mOwner) {
       if (nsPIDOMWindowInner* innerWindow = mOwner->OwnerDoc()->GetInnerWindow()) {
-        aVisitor.mParentTarget = innerWindow->GetParentTarget();
+        // 'this' is already a "chrome handler", so we consider window's
+        // parent target to be part of that same part of the event path.
+        aVisitor.SetParentTarget(innerWindow->GetParentTarget(), false);
       }
     }
   } else {
-    aVisitor.mParentTarget = mOwner;
+    aVisitor.SetParentTarget(mOwner, false);
   }
 
   return NS_OK;
 }
 
 nsresult
 nsInProcessTabChildGlobal::InitTabChildGlobal()
 {
--- a/dom/base/nsWindowRoot.cpp
+++ b/dom/base/nsWindowRoot.cpp
@@ -169,17 +169,17 @@ nsWindowRoot::GetContextForEventHandlers
 
 nsresult
 nsWindowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   aVisitor.mCanHandle = true;
   aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119
   // To keep mWindow alive
   aVisitor.mItemData = static_cast<nsISupports *>(mWindow);
-  aVisitor.mParentTarget = mParent;
+  aVisitor.SetParentTarget(mParent, false);
   return NS_OK;
 }
 
 nsresult
 nsWindowRoot::PostHandleEvent(EventChainPostVisitor& aVisitor)
 {
   return NS_OK;
 }
--- a/dom/events/DOMEventTargetHelper.cpp
+++ b/dom/events/DOMEventTargetHelper.cpp
@@ -302,17 +302,17 @@ DOMEventTargetHelper::DispatchTrustedEve
   bool dummy;
   return DispatchEvent(event, &dummy);
 }
 
 nsresult
 DOMEventTargetHelper::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   aVisitor.mCanHandle = true;
-  aVisitor.mParentTarget = nullptr;
+  aVisitor.SetParentTarget(nullptr, false);
   return NS_OK;
 }
 
 nsresult
 DOMEventTargetHelper::PostHandleEvent(EventChainPostVisitor& aVisitor)
 {
   return NS_OK;
 }
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -268,16 +268,22 @@ Event::GetTarget(nsIDOMEventTarget** aTa
 }
 
 EventTarget*
 Event::GetCurrentTarget() const
 {
   return mEvent->GetCurrentDOMEventTarget();
 }
 
+void
+Event::ComposedPath(nsTArray<RefPtr<EventTarget>>& aPath)
+{
+  EventDispatcher::GetComposedPathFor(mEvent, aPath);
+}
+
 NS_IMETHODIMP
 Event::GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget)
 {
   NS_IF_ADDREF(*aCurrentTarget = GetCurrentTarget());
   return NS_OK;
 }
 
 //
--- a/dom/events/Event.h
+++ b/dom/events/Event.h
@@ -153,16 +153,18 @@ public:
                                              ErrorResult& aRv);
 
   // Implemented as xpidl method
   // void GetType(nsString& aRetval) {}
 
   EventTarget* GetTarget() const;
   EventTarget* GetCurrentTarget() const;
 
+  void ComposedPath(nsTArray<RefPtr<EventTarget>>& aPath);
+
   uint16_t EventPhase() const;
 
   // xpidl implementation
   // void StopPropagation();
 
   // xpidl implementation
   // void StopImmediatePropagation();
 
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -241,16 +241,46 @@ public:
     mFlags.mPreHandleEventOnly = aWants;
   }
 
   bool PreHandleEventOnly()
   {
     return mFlags.mPreHandleEventOnly;
   }
 
+  void SetRootOfClosedTree(bool aSet)
+  {
+    mFlags.mRootOfClosedTree = aSet;
+  }
+
+  bool IsRootOfClosedTree()
+  {
+    return mFlags.mRootOfClosedTree;
+  }
+
+  void SetIsSlotInClosedTree(bool aSet)
+  {
+    mFlags.mIsSlotInClosedTree = aSet;
+  }
+
+  bool IsSlotInClosedTree()
+  {
+    return mFlags.mIsSlotInClosedTree;
+  }
+
+  void SetIsChromeHandler(bool aSet)
+  {
+    mFlags.mIsChromeHandler = aSet;
+  }
+
+  bool IsChromeHandler()
+  {
+    return mFlags.mIsChromeHandler;
+  }
+
   void SetMayHaveListenerManager(bool aMayHave)
   {
     mFlags.mMayHaveManager = aMayHave;
   }
 
   bool MayHaveListenerManager()
   {
     return mFlags.mMayHaveManager;
@@ -339,16 +369,19 @@ private:
     // 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 mIsSlotInClosedTree : 1;
+    bool mIsChromeHandler : 1;
   private:
     typedef uint32_t RawFlags;
     void SetRawFlags(RawFlags aRawFlags)
     {
       static_assert(sizeof(EventTargetChainFlags) <= sizeof(RawFlags),
         "EventTargetChainFlags must not be bigger than the RawFlags");
       memcpy(this, &aRawFlags, sizeof(EventTargetChainFlags));
     }
@@ -385,16 +418,17 @@ EventTargetChainItem::GetEventTargetPare
 {
   aVisitor.Reset();
   Unused << mTarget->GetEventTargetParent(aVisitor);
   SetForceContentDispatch(aVisitor.mForceContentDispatch);
   SetWantsWillHandleEvent(aVisitor.mWantsWillHandleEvent);
   SetMayHaveListenerManager(aVisitor.mMayHaveListenerManager);
   SetWantsPreHandleEvent(aVisitor.mWantsPreHandleEvent);
   SetPreHandleEventOnly(aVisitor.mWantsPreHandleEvent && !aVisitor.mCanHandle);
+  SetRootOfClosedTree(aVisitor.mRootOfClosedTree);
   mItemFlags = aVisitor.mItemFlags;
   mItemData = aVisitor.mItemData;
 }
 
 void
 EventTargetChainItem::PreHandleEvent(EventChainVisitor& aVisitor)
 {
   if (!WantsPreHandleEvent()) {
@@ -765,26 +799,29 @@ EventDispatcher::Dispatch(nsISupports* a
     }
   } else {
     // At least the original target can handle the event.
     // Setting the retarget to the |target| simplifies retargeting code.
     nsCOMPtr<EventTarget> t = do_QueryInterface(aEvent->mTarget);
     targetEtci->SetNewTarget(t);
     EventTargetChainItem* topEtci = targetEtci;
     targetEtci = nullptr;
-    while (preVisitor.mParentTarget) {
-      EventTarget* parentTarget = preVisitor.mParentTarget;
+    while (preVisitor.GetParentTarget()) {
+      EventTarget* parentTarget = preVisitor.GetParentTarget();
       EventTargetChainItem* parentEtci =
-        EventTargetChainItem::Create(chain, preVisitor.mParentTarget, topEtci);
+        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.mEvent->mTarget = preVisitor.mEventTargetAtParent;
         parentEtci->SetNewTarget(preVisitor.mEventTargetAtParent);
       }
 
@@ -818,18 +855,21 @@ EventDispatcher::Dispatch(nsISupports* a
         }
       } else {
         // Event target chain is created. PreHandle the chain.
         for (uint32_t i = 0; i < chain.Length(); ++i) {
           chain[i].PreHandleEvent(preVisitor);
         }
         // Handle the chain.
         EventChainPostVisitor postVisitor(preVisitor);
+        MOZ_RELEASE_ASSERT(!aEvent->mPath);
+        aEvent->mPath = &chain;
         EventTargetChainItem::HandleEventTargetChain(chain, postVisitor,
                                                      aCallback, cd);
+        aEvent->mPath = nullptr;
 
         preVisitor.mEventStatus = postVisitor.mEventStatus;
         // If the DOM event was created during event flow.
         if (!preVisitor.mDOMEvent && postVisitor.mDOMEvent) {
           preVisitor.mDOMEvent = postVisitor.mDOMEvent;
         }
       }
     }
@@ -1112,9 +1152,69 @@ EventDispatcher::CreateEvent(EventTarget
 #undef LOG_EVENT_CREATION
 
   // NEW EVENT TYPES SHOULD NOT BE ADDED HERE; THEY SHOULD USE ONLY EVENT
   // CONSTRUCTORS
 
   return nullptr;
 }
 
+// static
+void
+EventDispatcher::GetComposedPathFor(WidgetEvent* aEvent,
+                                    nsTArray<RefPtr<EventTarget>>& aPath)
+{
+  nsTArray<EventTargetChainItem>* path = aEvent->mPath;
+  if (!path || path->IsEmpty() || !aEvent->mCurrentTarget) {
+    return;
+  }
+
+  EventTarget* currentTarget =
+    aEvent->mCurrentTarget->GetTargetForEventTargetChain();
+  if (!currentTarget) {
+    return;
+  }
+
+  AutoTArray<EventTarget*, 128> reversedComposedPath;
+  bool hasSeenCurrentTarget = false;
+  uint32_t hiddenSubtreeLevel = 0;
+  for (uint32_t i = path->Length(); i; ) {
+    --i;
+
+    EventTargetChainItem& item = path->ElementAt(i);
+    if (item.PreHandleEventOnly()) {
+      continue;
+    }
+
+    if (!hasSeenCurrentTarget && currentTarget == item.CurrentTarget()) {
+      hasSeenCurrentTarget = true;
+    } else if (hasSeenCurrentTarget && item.IsRootOfClosedTree()) {
+      ++hiddenSubtreeLevel;
+    }
+
+    if (hiddenSubtreeLevel == 0) {
+      reversedComposedPath.AppendElement(item.CurrentTarget());
+    }
+
+    if (item.IsSlotInClosedTree() && hiddenSubtreeLevel > 0) {
+      --hiddenSubtreeLevel;
+    }
+
+    if (item.IsChromeHandler()) {
+      if (hasSeenCurrentTarget) {
+        // The current behavior is to include only EventTargets from
+        // either chrome side of event path or content side, not from both.
+        break;
+      }
+
+      // Need to start all over to collect the composed path on content side.
+      reversedComposedPath.Clear();
+    }
+  }
+
+  aPath.SetCapacity(reversedComposedPath.Length());
+  for (uint32_t i = reversedComposedPath.Length(); i; ) {
+    --i;
+    aPath.AppendElement(reversedComposedPath[i]->GetTargetForDOMEvent());
+  }
+}
+
 } // namespace mozilla
--- a/dom/events/EventDispatcher.h
+++ b/dom/events/EventDispatcher.h
@@ -120,35 +120,54 @@ public:
     , mCanHandle(true)
     , mAutomaticChromeDispatch(true)
     , mForceContentDispatch(false)
     , mRelatedTargetIsInAnon(false)
     , mOriginalTargetIsInAnon(aIsInAnon)
     , mWantsWillHandleEvent(false)
     , mMayHaveListenerManager(true)
     , mWantsPreHandleEvent(false)
+    , mRootOfClosedTree(false)
+    , mParentIsSlotInClosedTree(false)
+    , mParentIsChromeHandler(false)
     , mParentTarget(nullptr)
     , mEventTargetAtParent(nullptr)
   {
   }
 
   void Reset()
   {
     mItemFlags = 0;
     mItemData = nullptr;
     mCanHandle = true;
     mAutomaticChromeDispatch = true;
     mForceContentDispatch = false;
     mWantsWillHandleEvent = false;
     mMayHaveListenerManager = true;
     mWantsPreHandleEvent = false;
+    mRootOfClosedTree = false;
+    mParentIsSlotInClosedTree = false;
+    mParentIsChromeHandler = false;
     mParentTarget = nullptr;
     mEventTargetAtParent = nullptr;
   }
 
+  dom::EventTarget* GetParentTarget()
+  {
+    return mParentTarget;
+  }
+
+  void SetParentTarget(dom::EventTarget* aParentTarget, bool aIsChromeHandler)
+  {
+    mParentTarget = aParentTarget;
+    if (mParentTarget) {
+      mParentIsChromeHandler = aIsChromeHandler;
+    }
+  }
+
   /**
    * Member that must be set in GetEventTargetParent by event targets. If set to
    * false, indicates that this event target will not be handling the event and
    * construction of the event target chain is complete. The target that sets
    * mCanHandle to false is NOT included in the event target chain.
    */
   bool                  mCanHandle;
 
@@ -191,20 +210,39 @@ public:
 
   /**
    * Whether or not nsIDOMEventTarget::PreHandleEvent will be called. Default is
    * false;
    */
   bool mWantsPreHandleEvent;
 
   /**
+   * True if the current target is either closed ShadowRoot or root of
+   * chrome only access tree (for example native anonymous content).
+   */
+  bool mRootOfClosedTree;
+
+  /**
+   * True if mParentTarget is HTMLSlotElement in a closed shadow tree and the
+   * current target is assigned to that slot.
+   */
+  bool mParentIsSlotInClosedTree;
+
+  /**
+   * True if mParentTarget is a chrome handler in the event path.
+   */
+  bool mParentIsChromeHandler;
+
+private:
+  /**
    * Parent item in the event target chain.
    */
   dom::EventTarget* mParentTarget;
 
+public:
   /**
    * If the event needs to be retargeted, this is the event target,
    * which should be used when the event is handled at mParentTarget.
    */
   dom::EventTarget* mEventTargetAtParent;
 };
 
 class EventChainPostVisitor : public mozilla::EventChainVisitor
@@ -279,16 +317,19 @@ public:
    */
   static already_AddRefed<dom::Event> CreateEvent(dom::EventTarget* aOwner,
                                                   nsPresContext* aPresContext,
                                                   WidgetEvent* aEvent,
                                                   const nsAString& aEventType,
                                                   dom::CallerType aCallerType =
                                                     dom::CallerType::System);
 
+  static void GetComposedPathFor(WidgetEvent* aEvent,
+                                 nsTArray<RefPtr<dom::EventTarget>>& aPath);
+
   /**
    * Called at shutting down.
    */
   static void Shutdown();
 };
 
 } // namespace mozilla
 
--- a/dom/indexedDB/IDBFileHandle.cpp
+++ b/dom/indexedDB/IDBFileHandle.cpp
@@ -781,17 +781,17 @@ IDBFileHandle::Run()
 }
 
 nsresult
 IDBFileHandle::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   AssertIsOnOwningThread();
 
   aVisitor.mCanHandle = true;
-  aVisitor.mParentTarget = mMutableFile;
+  aVisitor.SetParentTarget(mMutableFile, false);
   return NS_OK;
 }
 
 // virtual
 JSObject*
 IDBFileHandle::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   AssertIsOnOwningThread();
--- a/dom/indexedDB/IDBFileRequest.cpp
+++ b/dom/indexedDB/IDBFileRequest.cpp
@@ -108,17 +108,17 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(IDBFi
                                    mFileHandle)
 
 nsresult
 IDBFileRequest::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   AssertIsOnOwningThread();
 
   aVisitor.mCanHandle = true;
-  aVisitor.mParentTarget = mFileHandle;
+  aVisitor.SetParentTarget(mFileHandle, false);
   return NS_OK;
 }
 
 // virtual
 JSObject*
 IDBFileRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   AssertIsOnOwningThread();
--- a/dom/indexedDB/IDBRequest.cpp
+++ b/dom/indexedDB/IDBRequest.cpp
@@ -406,17 +406,17 @@ NS_IMPL_ADDREF_INHERITED(IDBRequest, IDB
 NS_IMPL_RELEASE_INHERITED(IDBRequest, IDBWrapperCache)
 
 nsresult
 IDBRequest::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   AssertIsOnOwningThread();
 
   aVisitor.mCanHandle = true;
-  aVisitor.mParentTarget = mTransaction;
+  aVisitor.SetParentTarget(mTransaction, false);
   return NS_OK;
 }
 
 class IDBOpenDBRequest::WorkerHolder final
   : public mozilla::dom::workers::WorkerHolder
 {
   WorkerPrivate* mWorkerPrivate;
 #ifdef DEBUG
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -1049,17 +1049,17 @@ IDBTransaction::WrapObject(JSContext* aC
 }
 
 nsresult
 IDBTransaction::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   AssertIsOnOwningThread();
 
   aVisitor.mCanHandle = true;
-  aVisitor.mParentTarget = mDatabase;
+  aVisitor.SetParentTarget(mDatabase, false);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBTransaction::Run()
 {
   AssertIsOnOwningThread();
 
--- a/dom/webidl/Event.webidl
+++ b/dom/webidl/Event.webidl
@@ -15,16 +15,18 @@
 interface Event {
   [Pure]
   readonly attribute DOMString type;
   [Pure]
   readonly attribute EventTarget? target;
   [Pure]
   readonly attribute EventTarget? currentTarget;
 
+  sequence<EventTarget> composedPath();
+
   const unsigned short NONE = 0;
   const unsigned short CAPTURING_PHASE = 1;
   const unsigned short AT_TARGET = 2;
   const unsigned short BUBBLING_PHASE = 3;
   [Pure]
   readonly attribute unsigned short eventPhase;
 
   void stopPropagation();
--- a/dom/workers/SharedWorker.cpp
+++ b/dom/workers/SharedWorker.cpp
@@ -196,14 +196,14 @@ SharedWorker::GetEventTargetParent(Event
       event = EventDispatcher::CreateEvent(aVisitor.mEvent->mOriginalTarget,
                                            aVisitor.mPresContext,
                                            aVisitor.mEvent, EmptyString());
     }
 
     QueueEvent(event);
 
     aVisitor.mCanHandle = false;
-    aVisitor.mParentTarget = nullptr;
+    aVisitor.SetParentTarget(nullptr, false);
     return NS_OK;
   }
 
   return DOMEventTargetHelper::GetEventTargetParent(aVisitor);
 }
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -1286,17 +1286,17 @@ nsXULElement::DispatchXULCommand(const E
 
 nsresult
 nsXULElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
     aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119
     if (IsEventStoppedFromAnonymousScrollbar(aVisitor.mEvent->mMessage)) {
         // Don't propagate these events from native anonymous scrollbar.
         aVisitor.mCanHandle = true;
-        aVisitor.mParentTarget = nullptr;
+        aVisitor.SetParentTarget(nullptr, false);
         return NS_OK;
     }
     if (aVisitor.mEvent->mMessage == eXULCommand &&
         aVisitor.mEvent->mClass == eInputEventClass &&
         aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
         !IsXULElement(nsGkAtoms::command)) {
         // Check that we really have an xul command event. That will be handled
         // in a special way.
--- a/testing/web-platform/meta/shadow-dom/Extensions-to-Event-Interface.html.ini
+++ b/testing/web-platform/meta/shadow-dom/Extensions-to-Event-Interface.html.ini
@@ -1,18 +1,10 @@
 [Extensions-to-Event-Interface.html]
   type: testharness
-  [composedPath() must exist on Event]
-    expected: FAIL
-
-  [composedPath() must return an empty array when the event has not been dispatched]
-    expected: FAIL
-
-  [composedPath() must return an empty array when the event is no longer dispatched]
-    expected: FAIL
 
   [The event must propagate out of open mode shadow boundaries when the composed flag is set]
     expected: FAIL
 
   [The event must propagate out of closed mode shadow boundaries when the composed flag is set]
     expected: FAIL
 
   [The event must not propagate out of open mode shadow boundaries when the composed flag is unset]
deleted file mode 100644
--- a/testing/web-platform/meta/shadow-dom/event-composed-path-after-dom-mutation.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[event-composed-path-after-dom-mutation.html]
-  [Event.composedPath() should return the same result even if DOM is mutated (1/2)]
-    expected: FAIL
-
-  [Event.composedPath() should return the same result even if DOM is mutated (2/2)]
-    expected: FAIL
-
--- a/testing/web-platform/meta/shadow-dom/event-composed-path-with-related-target.html.ini
+++ b/testing/web-platform/meta/shadow-dom/event-composed-path-with-related-target.html.ini
@@ -1,14 +1,10 @@
 [event-composed-path-with-related-target.html]
   type: testharness
-  expected: ERROR
-  [Event path for an event with a relatedTarget. relatedTarget != target.]
-    expected: FAIL
-
   [Event path for an event with a relatedTarget. Event shoul be dispatched if 1) target and relatedTarget are same, and 2) they are not in a shadow tree.]
     expected: FAIL
 
   [Event path for an event with a relatedTarget. Event should stop at the shadow root]
     expected: FAIL
 
   [Event path for an event with a relatedTarget. Event should not be dispatched if 1) target and relatedTarget are same, and 2) both are in a shadow tree.]
     expected: FAIL
@@ -35,14 +31,11 @@
     expected: FAIL
 
   [Event path for an event with a relatedTarget. Event should be dispatched at every slots.]
     expected: FAIL
 
   [Event path for an event with a relatedTarget. Event should be dispatched at every slots. relatedTarget should be correctly retargeted.]
     expected: FAIL
 
-  [Event path for an event with a relatedTarget. Event should be dispatched even when target and relatedTarget are same.]
-    expected: FAIL
-
   [Event path for an event with a relatedTarget which is identical to target. Event should be dispatched and should stop at the shadow root.]
     expected: FAIL
 
--- a/testing/web-platform/meta/shadow-dom/event-composed-path.html.ini
+++ b/testing/web-platform/meta/shadow-dom/event-composed-path.html.ini
@@ -1,36 +1,9 @@
 [event-composed-path.html]
   type: testharness
-  expected: ERROR
-  [Event Path without ShadowRoots.]
-    expected: FAIL
-
-  [Event Path with an open ShadowRoot.]
-    expected: FAIL
-
-  [Event Path with a closed ShadowRoot.]
-    expected: FAIL
-
-  [Event Path with nested ShadowRoots: open > open.]
-    expected: FAIL
-
-  [Event Path with nested ShadowRoots: open > closed.]
-    expected: FAIL
-
-  [Event Path with nested ShadowRoots: closed > open.]
-    expected: FAIL
-
-  [Event Path with nested ShadowRoots: closed > closed.]
-    expected: FAIL
-
-  [Event Path with a slot in an open Shadow Root.]
-    expected: FAIL
 
   [Event Path with a slot in a closed Shadow Root.]
     expected: FAIL
 
-  [Event Path with slots in nested ShadowRoots: open > open.]
-    expected: FAIL
-
   [Event Path with slots in nested ShadowRoots: closed > closed.]
     expected: FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/shadow-dom/event-composed.html.ini
+++ /dev/null
@@ -1,24 +0,0 @@
-[event-composed.html]
-  type: testharness
-  expected: ERROR
-  [An event should be scoped by default]
-    expected: FAIL
-
-  [An event should not be scoped if composed is specified]
-    expected: FAIL
-
-  [A synthetic MouseEvent should be scoped by default]
-    expected: FAIL
-
-  [A synthetic MouseEvent with composed=true should not be scoped]
-    expected: FAIL
-
-  [A synthetic FocusEvent should be scoped by default]
-    expected: FAIL
-
-  [A synthetic FocusEvent with composed=true should not be scoped]
-    expected: FAIL
-
-  [A UA click event should not be scoped]
-    expected: FAIL
-
--- a/testing/web-platform/meta/shadow-dom/event-post-dispatch.html.ini
+++ b/testing/web-platform/meta/shadow-dom/event-post-dispatch.html.ini
@@ -1,42 +1,25 @@
 [event-post-dispatch.html]
-  [Event properties post dispatch without ShadowRoots (composed: true).]
-    expected: FAIL
-
-  [Event properties post dispatch without ShadowRoots (composed: false).]
-    expected: FAIL
-
-  [Event properties post dispatch with an open ShadowRoot (composed: true).]
-    expected: FAIL
 
   [Event properties post dispatch with an open ShadowRoot (composed: false).]
     expected: FAIL
 
-  [Event properties post dispatch with a closed ShadowRoot (composed: true).]
-    expected: FAIL
-
   [Event properties post dispatch with a closed ShadowRoot (composed: false).]
     expected: FAIL
 
-  [Event properties post dispatch with nested ShadowRoots (composed: true).]
-    expected: FAIL
-
   [Event properties post dispatch with nested ShadowRoots (composed: false).]
     expected: FAIL
 
   [Event properties post dispatch with relatedTarget in the same shadow tree. (composed: true)]
     expected: FAIL
 
   [Event properties post dispatch with relatedTarget in the same shadow tree. (composed: false)]
     expected: FAIL
 
-  [Event properties post dispatch with relatedTarget in the document tree and the shadow tree. (composed: true)]
-    expected: FAIL
-
   [Event properties post dispatch with relatedTarget in the document tree and the shadow tree. (composed: false)]
     expected: FAIL
 
   [Event properties post dispatch with relatedTarget in the different shadow trees. (composed: true)]
     expected: FAIL
 
   [Event properties post dispatch with relatedTarget in the different shadow trees. (composed: false)]
     expected: FAIL
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -24,16 +24,18 @@
 
 namespace IPC {
 template<typename T>
 struct ParamTraits;
 } // namespace IPC
 
 namespace mozilla {
 
+class EventTargetChainItem;
+
 /******************************************************************************
  * mozilla::BaseEventFlags
  *
  * BaseEventFlags must be a POD struct for safe to use memcpy (including
  * in ParamTraits<BaseEventFlags>).  So don't make virtual methods, constructor,
  * destructor and operators.
  * This is necessary for VC which is NOT C++0x compiler.
  ******************************************************************************/
@@ -509,27 +511,29 @@ protected:
               EventClassID aEventClassID)
     : WidgetEventTime()
     , mClass(aEventClassID)
     , mMessage(aMessage)
     , mRefPoint(0, 0)
     , mLastRefPoint(0, 0)
     , mFocusSequenceNumber(0)
     , mSpecifiedEventType(nullptr)
+    , mPath(nullptr)
   {
     MOZ_COUNT_CTOR(WidgetEvent);
     mFlags.Clear();
     mFlags.mIsTrusted = aIsTrusted;
     SetDefaultCancelableAndBubbles();
     SetDefaultComposed();
     SetDefaultComposedInNativeAnonymousContent();
   }
 
   WidgetEvent()
     : WidgetEventTime()
+    , mPath(nullptr)
   {
     MOZ_COUNT_CTOR(WidgetEvent);
   }
 
 public:
   WidgetEvent(bool aIsTrusted, EventMessage aMessage)
     : WidgetEvent(aIsTrusted, aMessage, eBasicEventClass)
   {
@@ -583,16 +587,18 @@ public:
 
   // Event targets, needed by DOM Events
   // Note that when you need event target for DOM event, you should use
   // Get*DOMEventTarget() instead of accessing these members directly.
   nsCOMPtr<dom::EventTarget> mTarget;
   nsCOMPtr<dom::EventTarget> mCurrentTarget;
   nsCOMPtr<dom::EventTarget> mOriginalTarget;
 
+  nsTArray<EventTargetChainItem>* mPath;
+
   dom::EventTarget* GetDOMEventTarget() const;
   dom::EventTarget* GetCurrentDOMEventTarget() const;
   dom::EventTarget* GetOriginalDOMEventTarget() const;
 
   void AssignEventData(const WidgetEvent& aEvent, bool aCopyTargets)
   {
     // mClass should be initialized with the constructor.
     // mMessage should be initialized with the constructor.