Bug 1449560 - clear event.target and .relatedTarget in case they would otherwise reveal targets in shadow DOM, r=bz
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Sat, 05 May 2018 02:21:15 +0300
changeset 473148 7bd8b80c9a2956e16e6783e186eda7892b1976f5
parent 473147 270f1ccc00a08e0a2c89a9e76d6cbf416a603eca
child 473149 c1493d2344e9b085961d9d1684b250d710875d1c
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1449560
milestone61.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 1449560 - clear event.target and .relatedTarget in case they would otherwise reveal targets in shadow DOM, r=bz
dom/base/FragmentOrElement.cpp
dom/events/EventDispatcher.cpp
testing/web-platform/meta/dom/events/relatedTarget.window.js.ini
testing/web-platform/tests/dom/events/relatedTarget.window.js
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -1086,17 +1086,21 @@ nsIContent::GetEventTargetParent(EventCh
             //  parent to null."
             aVisitor.IgnoreCurrentTarget();
             // Old code relies on mTarget to point to the first element which
             // was not added to the event target chain because of mCanHandle
             // being false, but in Shadow DOM case mTarget really should
             // point to a node in Shadow DOM.
             aVisitor.mEvent->mTarget = aVisitor.mTargetInKnownToBeHandledScope;
             return;
-          } else {
+          } else if (targetInKnownToBeHandledScope) {
+            // Note, if targetInKnownToBeHandledScope is null,
+            // mTargetInKnownToBeHandledScope could be Window object in content
+            // page and we're in chrome document in the same process.
+
             // Step 11.6
             aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget;
           }
         }
       }
     }
   }
 
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -651,16 +651,36 @@ MayRetargetToChromeIfCanNotHandleEvent(
     if (chromeTargetEtci) {
       chromeTargetEtci->GetEventTargetParent(aPreVisitor);
       return chromeTargetEtci;
     }
   }
   return nullptr;
 }
 
+static bool
+ShouldClearTargets(WidgetEvent* aEvent)
+{
+  nsCOMPtr<nsIContent> finalTarget;
+  nsCOMPtr<nsIContent> finalRelatedTarget;
+  if ((finalTarget = do_QueryInterface(aEvent->mTarget)) &&
+      finalTarget->SubtreeRoot()->IsShadowRoot()) {
+    return true;
+  }
+
+  if ((finalRelatedTarget =
+         do_QueryInterface(aEvent->mRelatedTarget)) &&
+      finalRelatedTarget->SubtreeRoot()->IsShadowRoot()) {
+    return true;
+  }
+  //XXXsmaug Check also all the touch objects.
+
+  return false;
+}
+
 /* static */ nsresult
 EventDispatcher::Dispatch(nsISupports* aTarget,
                           nsPresContext* aPresContext,
                           WidgetEvent* aEvent,
                           Event* aDOMEvent,
                           nsEventStatus* aEventStatus,
                           EventDispatchingCallback* aCallback,
                           nsTArray<EventTarget*>* aTargets)
@@ -831,16 +851,18 @@ EventDispatcher::Dispatch(nsISupports* a
     NS_ENSURE_STATE(aEvent->mOriginalTarget);
   }
   else {
     aEvent->mOriginalTarget = aEvent->mTarget;
   }
 
   aEvent->mOriginalRelatedTarget = aEvent->mRelatedTarget;
 
+  bool clearTargets = false;
+
   nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->mOriginalTarget);
   bool isInAnon = content && content->IsInAnonymousSubtree();
 
   aEvent->mFlags.mIsBeingDispatched = true;
 
   // Create visitor object and start event dispatching.
   // GetEventTargetParent for the original target.
   nsEventStatus status = aEventStatus ? *aEventStatus : nsEventStatus_eIgnore;
@@ -854,16 +876,18 @@ EventDispatcher::Dispatch(nsISupports* a
                                                         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<EventTarget> t = do_QueryInterface(aEvent->mTarget);
     targetEtci->SetNewTarget(t);
     EventTargetChainItem* topEtci = targetEtci;
     targetEtci = nullptr;
     while (preVisitor.GetParentTarget()) {
@@ -922,16 +946,19 @@ EventDispatcher::Dispatch(nsISupports* a
         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);
         }
+
+        clearTargets = ShouldClearTargets(aEvent);
+
         // Handle the chain.
         EventChainPostVisitor postVisitor(preVisitor);
         MOZ_RELEASE_ASSERT(!aEvent->mPath);
         aEvent->mPath = &chain;
         EventTargetChainItem::HandleEventTargetChain(chain, postVisitor,
                                                      aCallback, cd);
         aEvent->mPath = nullptr;
 
@@ -946,25 +973,26 @@ EventDispatcher::Dispatch(nsISupports* a
 
   // Note, EventTargetChainItem objects are deleted when the chain goes out of
   // the scope.
 
   aEvent->mFlags.mIsBeingDispatched = false;
   aEvent->mFlags.mDispatchedAtLeastOnce = true;
 
   // https://dom.spec.whatwg.org/#concept-event-dispatch
-  // Step 18
-  // "If target's root is a shadow root, then set event's target attribute and
-  //  event's relatedTarget to null."
-  nsCOMPtr<nsIContent> finalTarget = do_QueryInterface(aEvent->mTarget);
-  if (finalTarget && finalTarget->SubtreeRoot()->IsShadowRoot()) {
+  // 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) {
     // An 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) {
--- a/testing/web-platform/meta/dom/events/relatedTarget.window.js.ini
+++ b/testing/web-platform/meta/dom/events/relatedTarget.window.js.ini
@@ -1,4 +1,5 @@
 [relatedTarget.window.html]
-  [Untitled]
+  prefs: [dom.webcomponents.shadowdom.enabled:true]
+  [Reset targets before activation behavior]
     expected: FAIL
 
--- a/testing/web-platform/tests/dom/events/relatedTarget.window.js
+++ b/testing/web-platform/tests/dom/events/relatedTarget.window.js
@@ -36,23 +36,25 @@ async_test(t => {
   assert_equals(event.target, null);
   assert_equals(event.relatedTarget, null);
   shadowChild.remove();
   t.done();
 }, "Reset if target pointed to a shadow tree pre-dispatch");
 
 async_test(t => {
   const shadowChild = shadow.appendChild(document.createElement("div"));
-  shadowChild.addEventListener("demo", t.step_func(() => document.body.appendChild(shadowChild)));
+  const shadowChild2 = shadow.appendChild(document.createElement("div"));
+  shadowChild2.addEventListener("demo", t.step_func(() => document.body.appendChild(shadowChild)));
   const event = new FocusEvent("demo", { relatedTarget: shadowChild });
-  document.body.dispatchEvent(event);
+  shadowChild2.dispatchEvent(event);
   assert_equals(shadowChild.parentNode, document.body);
   assert_equals(event.target, null);
   assert_equals(event.relatedTarget, null);
   shadowChild.remove();
+  shadowChild2.remove();
   t.done();
 }, "Reset if relatedTarget pointed to a shadow tree pre-dispatch");
 
 async_test(t => {
   const event = new FocusEvent("heya", { relatedTarget: shadow, cancelable: true }),
         callback = t.unreached_func();
   host.addEventListener("heya", callback);
   t.add_cleanup(() => host.removeEventListener("heya", callback));