Bug 1510204, bug 1516289 - Fix composedPath implementation when slots are present. r=smaug
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sat, 05 Jan 2019 11:27:09 +0100
changeset 509745 e4f3ad7a95af8aa3ae7950cee19b79992d89f2c4
parent 509738 ab2bdfc3132a2f88658b554ca12fae9fd2977928
child 509746 c1eea02ba0c0df7868b43ab4c30e57655938969e
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1510204, 1516289
milestone66.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 1510204, bug 1516289 - Fix composedPath implementation when slots are present. r=smaug
dom/base/crashtests/1516289.html
dom/base/crashtests/crashtests.list
dom/events/EventDispatcher.cpp
testing/web-platform/meta/shadow-dom/Extensions-to-Event-Interface.html.ini
testing/web-platform/meta/shadow-dom/event-composed-path.html.ini
new file mode 100644
--- /dev/null
+++ b/dom/base/crashtests/1516289.html
@@ -0,0 +1,14 @@
+<script>
+function eh1() {
+  a.remove()
+  window.event.composedPath()
+}
+function eh2() {
+  b.addEventListener("DOMNodeInserted", eh1)
+  c.insertAdjacentElement("afterBegin", document.createElement("s"))
+}
+</script>
+<image srcset="A" onerror="eh2()"></image>
+<marquee id="a">
+<time id="b">
+<dialog id="c">AA</dialog>
--- a/dom/base/crashtests/crashtests.list
+++ b/dom/base/crashtests/crashtests.list
@@ -242,8 +242,9 @@ load 1449601.html
 load 1445670.html
 load 1458016.html
 load 1459688.html
 load 1460794.html
 load 1505875.html
 load 1505811.html
 load 1508845.html
 load 1516560.html
+load 1516289.html
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -180,37 +180,37 @@ class EventTargetChainItem {
     for (int32_t i = aChain.Length() - 1; i >= 0; --i) {
       if (!aChain[i].PreHandleEventOnly()) {
         return &aChain[i];
       }
     }
     return nullptr;
   }
 
-  bool IsValid() {
+  bool IsValid() const {
     NS_WARNING_ASSERTION(!!(mTarget), "Event target is not valid!");
     return !!(mTarget);
   }
 
-  EventTarget* GetNewTarget() { return mNewTarget; }
+  EventTarget* GetNewTarget() const { return mNewTarget; }
 
   void SetNewTarget(EventTarget* aNewTarget) { mNewTarget = aNewTarget; }
 
   EventTarget* GetRetargetedRelatedTarget() { return mRetargetedRelatedTarget; }
 
   void SetRetargetedRelatedTarget(EventTarget* aTarget) {
     mRetargetedRelatedTarget = aTarget;
   }
 
   void SetRetargetedTouchTarget(
       Maybe<nsTArray<RefPtr<EventTarget>>>&& aTargets) {
     mRetargetedTouchTargets = std::move(aTargets);
   }
 
-  bool HasRetargetTouchTargets() {
+  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.");
@@ -248,59 +248,59 @@ class EventTargetChainItem {
       Maybe<nsTArray<RefPtr<dom::Touch>>>&& aInitialTargetTouches) {
     mInitialTargetTouches = std::move(aInitialTargetTouches);
   }
 
   void SetForceContentDispatch(bool aForce) {
     mFlags.mForceContentDispatch = aForce;
   }
 
-  bool ForceContentDispatch() { return mFlags.mForceContentDispatch; }
+  bool ForceContentDispatch() const { return mFlags.mForceContentDispatch; }
 
   void SetWantsWillHandleEvent(bool aWants) {
     mFlags.mWantsWillHandleEvent = aWants;
   }
 
-  bool WantsWillHandleEvent() { return mFlags.mWantsWillHandleEvent; }
+  bool WantsWillHandleEvent() const { return mFlags.mWantsWillHandleEvent; }
 
   void SetWantsPreHandleEvent(bool aWants) {
     mFlags.mWantsPreHandleEvent = aWants;
   }
 
-  bool WantsPreHandleEvent() { return mFlags.mWantsPreHandleEvent; }
+  bool WantsPreHandleEvent() const { return mFlags.mWantsPreHandleEvent; }
 
   void SetPreHandleEventOnly(bool aWants) {
     mFlags.mPreHandleEventOnly = aWants;
   }
 
-  bool PreHandleEventOnly() { return mFlags.mPreHandleEventOnly; }
+  bool PreHandleEventOnly() const { return mFlags.mPreHandleEventOnly; }
 
   void SetRootOfClosedTree(bool aSet) { mFlags.mRootOfClosedTree = aSet; }
 
-  bool IsRootOfClosedTree() { return mFlags.mRootOfClosedTree; }
+  bool IsRootOfClosedTree() const { return mFlags.mRootOfClosedTree; }
 
   void SetItemInShadowTree(bool aSet) { mFlags.mItemInShadowTree = aSet; }
 
-  bool IsItemInShadowTree() { return mFlags.mItemInShadowTree; }
+  bool IsItemInShadowTree() const { return mFlags.mItemInShadowTree; }
 
   void SetIsSlotInClosedTree(bool aSet) { mFlags.mIsSlotInClosedTree = aSet; }
 
-  bool IsSlotInClosedTree() { return mFlags.mIsSlotInClosedTree; }
+  bool IsSlotInClosedTree() const { return mFlags.mIsSlotInClosedTree; }
 
   void SetIsChromeHandler(bool aSet) { mFlags.mIsChromeHandler = aSet; }
 
-  bool IsChromeHandler() { return mFlags.mIsChromeHandler; }
+  bool IsChromeHandler() const { return mFlags.mIsChromeHandler; }
 
   void SetMayHaveListenerManager(bool aMayHave) {
     mFlags.mMayHaveManager = aMayHave;
   }
 
   bool MayHaveListenerManager() { return mFlags.mMayHaveManager; }
 
-  EventTarget* CurrentTarget() { return mTarget; }
+  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.
    */
   static void HandleEventTargetChain(nsTArray<EventTargetChainItem>& aChain,
@@ -1290,67 +1290,120 @@ static bool ShouldClearTargets(WidgetEve
   }
 
   // NEW EVENT TYPES SHOULD NOT BE ADDED HERE; THEY SHOULD USE ONLY EVENT
   // CONSTRUCTORS
 
   return nullptr;
 }
 
-// static
+struct CurrentTargetPathInfo {
+  uint32_t mIndex;
+  int32_t mHiddenSubtreeLevel;
+};
+
+static CurrentTargetPathInfo TargetPathInfo(
+    const nsTArray<EventTargetChainItem>& 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<RefPtr<EventTarget>>& aPath) {
+  MOZ_ASSERT(aPath.IsEmpty());
   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;
-    }
+  CurrentTargetPathInfo currentTargetInfo =
+      TargetPathInfo(*path, *currentTarget);
 
-    if (!hasSeenCurrentTarget && currentTarget == item.CurrentTarget()) {
-      hasSeenCurrentTarget = true;
-    } else if (hasSeenCurrentTarget && item.IsRootOfClosedTree()) {
-      ++hiddenSubtreeLevel;
-    }
+  {
+    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 (hiddenSubtreeLevel == 0) {
-      reversedComposedPath.AppendElement(item.CurrentTarget());
-    }
+      if (item.IsRootOfClosedTree()) {
+        currentHiddenLevel++;
+      }
 
-    if (item.IsSlotInClosedTree() && hiddenSubtreeLevel > 0) {
-      --hiddenSubtreeLevel;
-    }
+      if (currentHiddenLevel <= maxHiddenLevel) {
+        aPath.AppendElement(item.CurrentTarget()->GetTargetForDOMEvent());
+      }
 
-    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.
+      if (item.IsChromeHandler()) {
         break;
       }
 
-      // Need to start all over to collect the composed path on content side.
-      reversedComposedPath.Clear();
+      if (item.IsSlotInClosedTree()) {
+        currentHiddenLevel--;
+        maxHiddenLevel = std::min(maxHiddenLevel, currentHiddenLevel);
+      }
     }
+
+    aPath.Reverse();
   }
 
-  aPath.SetCapacity(reversedComposedPath.Length());
-  for (uint32_t i = reversedComposedPath.Length(); i;) {
-    --i;
-    aPath.AppendElement(reversedComposedPath[i]->GetTargetForDOMEvent());
+  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
deleted file mode 100644
--- a/testing/web-platform/meta/shadow-dom/Extensions-to-Event-Interface.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[Extensions-to-Event-Interface.html]
-  [The event must not propagate out of closed mode shadow tree of the target but must propagate out of inner shadow trees when the scoped flag is set]
-    expected: FAIL
-
-  [composedPath() must contain and only contain the unclosed nodes of target in closed mode shadow trees]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/shadow-dom/event-composed-path.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[event-composed-path.html]
-  [Event Path with a slot in a closed Shadow Root.]
-    expected: FAIL
-
-  [Event Path with slots in nested ShadowRoots: closed > closed.]
-    expected: FAIL
-