Bug 1420589 Part9: Dispatch pointer events to the capturing target even it's frame is destroyed. r=smaug.
authorStone Shih <sshih@mozilla.com>
Thu, 30 Nov 2017 16:10:03 +0800
changeset 448727 2155af8d88c3bd116305378d7e22b947db339c55
parent 448726 49491891f6b1119387bad90bb9a10fd9468d382c
child 448728 d0538cb619eb80b400dc439a59579e8cf9e895b4
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)
reviewerssmaug
bugs1420589
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 1420589 Part9: Dispatch pointer events to the capturing target even it's frame is destroyed. r=smaug. MozReview-Commit-ID: DxNx3ByTdCW
dom/events/PointerEventHandler.cpp
dom/events/PointerEventHandler.h
dom/events/test/pointerevents/mochitest.ini
dom/events/test/pointerevents/test_remove_frame_when_got_pointer_capture.html
layout/base/PresShell.cpp
--- a/dom/events/PointerEventHandler.cpp
+++ b/dom/events/PointerEventHandler.cpp
@@ -326,42 +326,32 @@ PointerEventHandler::GetPointerCapturing
 {
   PointerCaptureInfo* pointerCaptureInfo = GetPointerCaptureInfo(aPointerId);
   if (pointerCaptureInfo) {
     return pointerCaptureInfo->mOverrideContent;
   }
   return nullptr;
 }
 
-/* static */ nsIFrame*
-PointerEventHandler::GetPointerCapturingFrame(WidgetGUIEvent* aEvent)
+/* static */ nsIContent*
+PointerEventHandler::GetPointerCapturingContent(WidgetGUIEvent* aEvent)
 {
   if (!IsPointerEventEnabled() || (aEvent->mClass != ePointerEventClass &&
                                    aEvent->mClass != eMouseEventClass) ||
       aEvent->mMessage == ePointerDown || aEvent->mMessage == eMouseDown) {
     // Pointer capture should only be applied to all pointer events and mouse
     // events except ePointerDown and eMouseDown;
     return nullptr;
   }
 
   WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
   if (!mouseEvent) {
     return nullptr;
   }
-
-  // Find the content which captures the pointer.
-  nsIContent* capturingContent =
-    GetPointerCapturingContent(mouseEvent->pointerId);
-
-  if (!capturingContent) {
-    return nullptr;
-  }
-  // Return the content's primary frame as the target frame.
-  nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
-  return capturingFrame ? capturingFrame : nullptr;
+  return GetPointerCapturingContent(mouseEvent->pointerId);
 }
 
 /* static */ void
 PointerEventHandler::ReleaseIfCaptureByDescendant(nsIContent* aContent)
 {
   // We should check that aChild does not contain pointer capturing elements.
   // If it does we should release the pointer capture for the elements.
   for (auto iter = sPointerCaptureList->Iter(); !iter.Done(); iter.Next()) {
--- a/dom/events/PointerEventHandler.h
+++ b/dom/events/PointerEventHandler.h
@@ -84,27 +84,26 @@ public:
   static void ProcessPointerCaptureForTouch(WidgetTouchEvent* aEvent);
   static void CheckPointerCaptureState(WidgetPointerEvent* aEvent);
 
   // Implicitly get and release capture of current pointer for touch.
   static void ImplicitlyCapturePointer(nsIFrame* aFrame, WidgetEvent* aEvent);
   static void ImplicitlyReleasePointerCapture(WidgetEvent* aEvent);
 
   /**
-   * GetPointerCapturingFrame returns a target frame of aEvent. If the event is
-   * a mouse or pointer event (except mousedown and pointerdown), the pointer
-   * may be captured by a content. This method returns the capturing content's
-   * primary frame. Otherwise, nullptr.
+   * GetPointerCapturingContent returns a target content which captures the
+   * pointer. It's applied to mouse or pointer event (except mousedown and
+   * pointerdown). When capturing, return the content. Otherwise, nullptr.
    *
    * @param aEvent               A mouse event or pointer event which may be
    *                             captured.
    *
-   * @return                     Target frame for aEvent.
+   * @return                     Target content for aEvent.
    */
-  static nsIFrame* GetPointerCapturingFrame(WidgetGUIEvent* aEvent);
+  static nsIContent* GetPointerCapturingContent(WidgetGUIEvent* aEvent);
 
   static nsIContent* GetPointerCapturingContent(uint32_t aPointerId);
 
   // Release pointer capture if captured by the specified content or it's
   // descendant. This is called to handle the case that the pointer capturing
   // content or it's parent is removed from the document.
   static void ReleaseIfCaptureByDescendant(nsIContent* aContent);
 
--- a/dom/events/test/pointerevents/mochitest.ini
+++ b/dom/events/test/pointerevents/mochitest.ini
@@ -140,12 +140,13 @@ support-files =
     pointerevent_touch-action-pan-down-css_touch-manual.html
     pointerevent_touch-action-pan-left-css_touch-manual.html
     pointerevent_touch-action-pan-right-css_touch-manual.html
     pointerevent_touch-action-pan-up-css_touch-manual.html
 [test_trigger_fullscreen_by_pointer_events.html]
   support-files =
     file_test_trigger_fullscreen.html
 [test_trigger_popup_by_pointer_events.html]
+[test_remove_frame_when_got_pointer_capture.html]
 [test_getCoalescedEvents.html]
   skip-if = !e10s
   support-files =
     ../../../../gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/test_remove_frame_when_got_pointer_capture.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for triggering popup by pointer events</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="div1" style="width: 50px; height: 50px; background: green"></div>
+<div id="div2" style="width: 50px; height: 50px; background: green"></div>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+function startTest() {
+  let div1 = document.getElementById("div1");
+  let divEvents = [
+    "pointerdown",
+    "gotpointercapture",
+    "pointermove",
+    "pointerup",
+    "lostpointercapture",
+    "mousedown",
+    "mousemove",
+    "mouseup",
+  ];
+
+  let documentEvents = [
+    "pointerdown",
+    "pointermove",
+    "pointerup",
+    "mousedown",
+    "mousemove",
+    "mouseup",
+  ];
+
+  divEvents.forEach((event) => {
+    div1.addEventListener(event, (e) => {
+      ok(divEvents.indexOf(e.type) >= 0, " don't expect " + e.type);
+      divEvents = divEvents.filter(item => item !== e.type);
+    }, { once: true });
+  });
+
+  documentEvents.forEach((event) => {
+    document.addEventListener(event, (e) => {
+      is(e.target, div1, e.type + " should be dispatched to div1");
+    }, { once: true });
+  });
+
+  div1.addEventListener("pointerdown", (e) => {
+    div1.setPointerCapture(e.pointerId);
+  });
+
+  div1.addEventListener("gotpointercapture", (e) => {
+    div1.style.display = "none";
+  });
+
+  synthesizeMouseAtCenter(div1, {type: "mousedown"});
+  synthesizeMouseAtCenter(div2, {type: "mousemove"});
+  synthesizeMouseAtCenter(div2, {type: "mouseup"});
+
+  ok(divEvents.length == 0, " expect " + divEvents);
+
+  divEvents = [
+    "pointerdown",
+    "gotpointercapture",
+    "pointermove",
+    "pointerup",
+    "lostpointercapture",
+    "touchstart",
+    "touchmove",
+    "touchend",
+  ];
+
+  documentEvents = [
+    "pointerdown",
+    "pointermove",
+    "pointerup",
+    "touchstart",
+    "touchmove",
+    "touchend",
+  ];
+  divEvents.forEach((event) => {
+    div1.addEventListener(event, (e) => {
+      ok(divEvents.indexOf(e.type) >= 0, " don't expect " + e.type);
+      divEvents = divEvents.filter(item => item !== e.type);
+    }, { once: true });
+  });
+
+  documentEvents.forEach((event) => {
+    document.addEventListener(event, (e) => {
+      is(e.target, div1, e.type + " should be dispatched to div1");
+    }, { once: true });
+  });
+
+  div1.style.display = "block";
+  synthesizeMouseAtCenter(div1, {type: "mousemove"});
+  synthesizeTouch(div1, 5, 5, { type: "touchstart" });
+  synthesizeTouch(div2, 5, 5, { type: "touchmove" });
+  synthesizeTouch(div2, 5, 5, { type: "touchend" });
+
+  ok(divEvents.length == 0, " expect " + divEvents);
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(() => {
+  SpecialPowers.pushPrefEnv({
+    "set": [["dom.w3c_pointer_events.enabled", true]]
+  }, startTest);
+});
+
+</script>
+</body>
+</html>
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -7183,33 +7183,58 @@ PresShell::HandleEvent(nsIFrame* aFrame,
       PointerEventHandler::MaybeProcessPointerCapture(aEvent);
       // Prevent application crashes, in case damaged frame.
       if (!frameKeeper.IsAlive()) {
         NS_WARNING("Nothing to handle this event!");
         return NS_OK;
       }
     }
 
-    nsIFrame* pointerCapturingFrame =
-      PointerEventHandler::GetPointerCapturingFrame(aEvent);
-
-    if (pointerCapturingFrame) {
-      frame = pointerCapturingFrame;
+    // Only capture mouse events and pointer events.
+    nsIContent* pointerCapturingContent =
+      PointerEventHandler::GetPointerCapturingContent(aEvent);
+
+    if (pointerCapturingContent) {
+      nsIFrame* pointerCapturingFrame =
+        pointerCapturingContent->GetPrimaryFrame();
+
+      if (!pointerCapturingFrame) {
+        // Dispatch events to the capturing content even it's frame is
+        // destroyed.
+        PointerEventHandler::DispatchPointerFromMouseOrTouch(
+          this, nullptr, pointerCapturingContent, aEvent, false, aEventStatus,
+          nullptr);
+
+        PresShell* shell = GetShellForEventTarget(nullptr,
+                                                  pointerCapturingContent);
+
+        if (!shell) {
+          // The capturing element could be changed when dispatch pointer
+          // events.
+          return NS_OK;
+        }
+        return shell->HandleEventWithTarget(aEvent, nullptr,
+                                            pointerCapturingContent,
+                                            aEventStatus, true);
+      } else {
+        frame = pointerCapturingFrame;
+      }
     }
 
     WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
     bool isWindowLevelMouseExit = (aEvent->mMessage == eMouseExitFromWidget) &&
       (mouseEvent && mouseEvent->mExitFrom == WidgetMouseEvent::eTopLevel);
 
     // Get the frame at the event point. However, don't do this if we're
     // capturing and retargeting the event because the captured frame will
     // be used instead below. Also keep using the root frame if we're dealing
     // with a window-level mouse exit event since we want to start sending
     // mouse out events at the root EventStateManager.
-    if (!captureRetarget && !isWindowLevelMouseExit && !pointerCapturingFrame) {
+    if (!captureRetarget && !isWindowLevelMouseExit &&
+        !pointerCapturingContent) {
       if (aEvent->mClass == eTouchEventClass) {
         frame = TouchManager::SetupTarget(aEvent->AsTouchEvent(), frame);
       } else {
         uint32_t flags = 0;
         nsPoint eventPoint =
           nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, frame);
 
         if (mouseEvent && mouseEvent->mClass == eMouseEventClass &&
@@ -7223,17 +7248,17 @@ PresShell::HandleEvent(nsIFrame* aFrame,
         }
       }
     }
 
     // if a node is capturing the mouse, check if the event needs to be
     // retargeted at the capturing content instead. This will be the case when
     // capture retargeting is being used, no frame was found or the frame's
     // content is not a descendant of the capturing content.
-    if (capturingContent && !pointerCapturingFrame &&
+    if (capturingContent && !pointerCapturingContent &&
         (gCaptureInfo.mRetargetToElement || !frame->GetContent() ||
          !nsContentUtils::ContentIsCrossDocDescendantOf(frame->GetContent(),
                                                         capturingContent))) {
       // A check was already done above to ensure that capturingContent is
       // in this presshell.
       NS_ASSERTION(capturingContent->GetComposedDoc() == GetDocument(),
                    "Unexpected document");
       nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();