Bug 1529237 - Use matrix instead of offset for converting between chrome and content process event coordinates in TabParent. r=kats
authorHenri Sivonen <hsivonen@hsivonen.fi>
Mon, 04 Mar 2019 17:35:47 +0000
changeset 520141 e544d9948446156682f0ae8870308fdde72916ea
parent 520140 3d9f5f3d386bab092634df66e49f684d433e2507
child 520142 7cc86fd0d3865f54f91a92e01b21a62aa0125d8b
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1529237
milestone67.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 1529237 - Use matrix instead of offset for converting between chrome and content process event coordinates in TabParent. r=kats Differential Revision: https://phabricator.services.mozilla.com/D20634
dom/browser-element/BrowserElementParent.jsm
dom/browser-element/mochitest/browserElement_OpenTab.js
dom/browser-element/mochitest/browserElement_SendEvent.js
dom/interfaces/base/nsITabParent.idl
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
--- a/dom/browser-element/BrowserElementParent.jsm
+++ b/dom/browser-element/BrowserElementParent.jsm
@@ -501,34 +501,21 @@ BrowserElementParent.prototype = {
     }
     else {
       debug("Got error in gotAsyncResult.");
       p.reject(new this._window.DOMException(
         Cu.cloneInto(data.json.errorMsg, this._window)));
     }
   },
 
-  getChildProcessOffset: function() {
-    let offset = { x: 0, y: 0 };
-    let tabParent = this._frameLoader.tabParent;
-    if (tabParent) {
-      let offsetX = {};
-      let offsetY = {};
-      tabParent.getChildProcessOffset(offsetX, offsetY);
-      offset.x = offsetX.value;
-      offset.y = offsetY.value;
-    }
-    return offset;
-  },
-
   sendMouseEvent: defineNoReturnMethod(function(type, x, y, button, clickCount, modifiers) {
-    let offset = this.getChildProcessOffset();
-    x += offset.x;
-    y += offset.y;
-
+    // This method used to attempt to transform from the parent
+    // coordinate space to the child coordinate space, but the
+    // transform was always a no-op, because this._frameLoader.tabParent
+    // was null.
     this._sendAsyncMsg("send-mouse-event", {
       "type": type,
       "x": x,
       "y": y,
       "button": button,
       "clickCount": clickCount,
       "modifiers": modifiers
     });
--- a/dom/browser-element/mochitest/browserElement_OpenTab.js
+++ b/dom/browser-element/mochitest/browserElement_OpenTab.js
@@ -10,30 +10,24 @@ browserElementTestHelpers.addPermission(
 
 function runTest() {
   let iframe = document.createElement('iframe');
   iframe.setAttribute('mozbrowser', 'true');
   document.body.appendChild(iframe);
 
   let x = 2;
   let y = 2;
-  // First we force a reflow so that getChildProcessOffset actually returns
-  // meaningful data.
-  iframe.getBoundingClientRect();
-  // We need to make sure the event coordinates are actually inside the iframe,
-  // relative to the chome window.
-  let tabParent = SpecialPowers.wrap(iframe)
-                  .frameLoader.tabParent;
-  if (tabParent) {
-    let offsetX = {};
-    let offsetY = {};
-    tabParent.getChildProcessOffset(offsetX, offsetY);
-    x -= offsetX.value;
-    y -= offsetY.value;
-  }
+  // This test used to try to transform the coordinates from child
+  // to parent coordinate space by first calling
+  // iframe.getBoundingClientRect();
+  // to refresh offsets and then calling
+  // var tabParent = SpecialPowers.wrap(iframe)
+  //                .frameLoader.tabParent;
+  // and calling tabParent.getChildProcessOffset(offsetX, offsetY) if
+  // tabParent was not null, but tabParent was always null.
 
   let sendCtrlClick = () => {
     let nsIDOMWindowUtils = SpecialPowers.Ci.nsIDOMWindowUtils;
     let mod = nsIDOMWindowUtils.MODIFIER_META |
               nsIDOMWindowUtils.MODIFIER_CONTROL;
     iframe.sendMouseEvent('mousedown', x, y, 0, 1, mod);
     iframe.sendMouseEvent('mouseup', x, y, 0, 1, mod);
   }
--- a/dom/browser-element/mochitest/browserElement_SendEvent.js
+++ b/dom/browser-element/mochitest/browserElement_SendEvent.js
@@ -8,30 +8,24 @@ SimpleTest.waitForExplicitFinish();
 browserElementTestHelpers.setEnabledPref(true);
 
 function runTest() {
   var iframe = document.createElement("iframe");
   iframe.setAttribute('mozbrowser', 'true');
   document.body.appendChild(iframe);
   var x = 10;
   var y = 10;
-  // First we force a reflow so that getChildProcessOffset actually returns
-  // meaningful data.
-  iframe.getBoundingClientRect();
-  // We need to make sure the event coordinates are actually inside the iframe,
-  // relative to the chome window.
-  var tabParent = SpecialPowers.wrap(iframe)
-                  .frameLoader.tabParent;
-  if (tabParent) {
-    let offsetX = {};
-    let offsetY = {};
-    tabParent.getChildProcessOffset(offsetX, offsetY);
-    x -= offsetX.value;
-    y -= offsetY.value;
-  }
+  // This test used to try to transform the coordinates from child
+  // to parent coordinate space by first calling
+  // iframe.getBoundingClientRect();
+  // to refresh offsets and then calling
+  // var tabParent = SpecialPowers.wrap(iframe)
+  //                .frameLoader.tabParent;
+  // and calling tabParent.getChildProcessOffset(offsetX, offsetY) if
+  // tabParent was not null, but tabParent was always null.
 
   iframe.addEventListener("mozbrowserloadend", function onloadend(e) {
     iframe.sendMouseEvent("mousedown", x, y, 0, 1, 0);
   });
 
   iframe.addEventListener("mozbrowserlocationchange", function onlocchange(e) {
     var a = document.createElement("a");
     a.href = e.detail.url;
--- a/dom/interfaces/base/nsITabParent.idl
+++ b/dom/interfaces/base/nsITabParent.idl
@@ -6,18 +6,16 @@
 #include "domstubs.idl"
 
 interface nsIPrincipal;
 webidl Element;
 
 [builtinclass, scriptable, uuid(8e49f7b0-1f98-4939-bf91-e9c39cd56434)]
 interface nsITabParent : nsISupports
 {
-  void getChildProcessOffset(out int32_t aCssX, out int32_t aCssY);
-
   /**
    * Manages the docshell active state of the remote browser. Setting the
    * docShell to be active will also cause it to render layers and upload
    * them to the compositor. Setting the docShell as not active will clear
    * those layers.
    */
   attribute boolean docShellIsActive;
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1027,17 +1027,17 @@ void TabParent::SendMouseEvent(const nsA
                                              aIgnoreRootScrollFrame);
   }
 }
 
 void TabParent::SendRealMouseEvent(WidgetMouseEvent& aEvent) {
   if (mIsDestroyed) {
     return;
   }
-  aEvent.mRefPoint += GetChildProcessOffset();
+  aEvent.mRefPoint = TransformParentToChild(aEvent.mRefPoint);
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (widget) {
     // When we mouseenter the tab, the tab's cursor should
     // become the current cursor.  When we mouseexit, we stop.
     if (eMouseEnterIntoWidget == aEvent.mMessage) {
       mTabSetsCursor = true;
       if (mCursor != eCursorInvalid) {
@@ -1184,17 +1184,17 @@ bool TabParent::QueryDropLinksForVerific
 
 void TabParent::SendRealDragEvent(WidgetDragEvent& aEvent, uint32_t aDragAction,
                                   uint32_t aDropEffect,
                                   const IPC::Principal& aPrincipal) {
   if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
   MOZ_ASSERT(!Manager()->IsInputPriorityEventEnabled());
-  aEvent.mRefPoint += GetChildProcessOffset();
+  aEvent.mRefPoint = TransformParentToChild(aEvent.mRefPoint);
   if (aEvent.mMessage == eDrop) {
     if (!QueryDropLinksForVerification()) {
       return;
     }
   }
   DebugOnly<bool> ret = PBrowserParent::SendRealDragEvent(
       aEvent, aDragAction, aDropEffect, aPrincipal);
   NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealDragEvent() failed");
@@ -1204,17 +1204,17 @@ void TabParent::SendRealDragEvent(Widget
 void TabParent::SendMouseWheelEvent(WidgetWheelEvent& aEvent) {
   if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
 
   ScrollableLayerGuid guid;
   uint64_t blockId;
   ApzAwareEventRoutingToChild(&guid, &blockId, nullptr);
-  aEvent.mRefPoint += GetChildProcessOffset();
+  aEvent.mRefPoint = TransformParentToChild(aEvent.mRefPoint);
   DebugOnly<bool> ret =
       Manager()->IsInputPriorityEventEnabled()
           ? PBrowserParent::SendMouseWheelEvent(aEvent, guid, blockId)
           : PBrowserParent::SendNormalPriorityMouseWheelEvent(aEvent, guid,
                                                               blockId);
 
   NS_WARNING_ASSERTION(ret, "PBrowserParent::SendMouseWheelEvent() failed");
   MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess());
@@ -1224,47 +1224,47 @@ mozilla::ipc::IPCResult TabParent::RecvD
     const mozilla::WidgetWheelEvent& aEvent) {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return IPC_OK();
   }
 
   WidgetWheelEvent localEvent(aEvent);
   localEvent.mWidget = widget;
-  localEvent.mRefPoint -= GetChildProcessOffset();
+  localEvent.mRefPoint = TransformChildToParent(localEvent.mRefPoint);
 
   widget->DispatchInputEvent(&localEvent);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult TabParent::RecvDispatchMouseEvent(
     const mozilla::WidgetMouseEvent& aEvent) {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return IPC_OK();
   }
 
   WidgetMouseEvent localEvent(aEvent);
   localEvent.mWidget = widget;
-  localEvent.mRefPoint -= GetChildProcessOffset();
+  localEvent.mRefPoint = TransformChildToParent(localEvent.mRefPoint);
 
   widget->DispatchInputEvent(&localEvent);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult TabParent::RecvDispatchKeyboardEvent(
     const mozilla::WidgetKeyboardEvent& aEvent) {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return IPC_OK();
   }
 
   WidgetKeyboardEvent localEvent(aEvent);
   localEvent.mWidget = widget;
-  localEvent.mRefPoint -= GetChildProcessOffset();
+  localEvent.mRefPoint = TransformChildToParent(localEvent.mRefPoint);
 
   widget->DispatchInputEvent(&localEvent);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult TabParent::RecvRequestNativeKeyBindings(
     const uint32_t& aType, const WidgetKeyboardEvent& aEvent,
     nsTArray<CommandInt>* aCommands) {
@@ -1458,17 +1458,17 @@ TabParent::RecvResetPrefersReducedMotion
   }
   return IPC_OK();
 }
 
 void TabParent::SendRealKeyEvent(WidgetKeyboardEvent& aEvent) {
   if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
-  aEvent.mRefPoint += GetChildProcessOffset();
+  aEvent.mRefPoint = TransformParentToChild(aEvent.mRefPoint);
 
   if (aEvent.mMessage == eKeyPress) {
     // XXX Should we do this only when input context indicates an editor having
     //     focus and the key event won't cause inputting text?
     aEvent.InitAllEditCommands();
   } else {
     aEvent.PreventNativeKeyBindings();
   }
@@ -1502,19 +1502,19 @@ void TabParent::SendRealTouchEvent(Widge
   uint64_t blockId;
   nsEventStatus apzResponse;
   ApzAwareEventRoutingToChild(&guid, &blockId, &apzResponse);
 
   if (mIsDestroyed) {
     return;
   }
 
-  LayoutDeviceIntPoint offset = GetChildProcessOffset();
   for (uint32_t i = 0; i < aEvent.mTouches.Length(); i++) {
-    aEvent.mTouches[i]->mRefPoint += offset;
+    aEvent.mTouches[i]->mRefPoint =
+        TransformParentToChild(aEvent.mTouches[i]->mRefPoint);
   }
 
   bool inputPriorityEventEnabled = Manager()->IsInputPriorityEventEnabled();
 
   if (aEvent.mMessage == eTouchMove) {
     DebugOnly<bool> ret =
         inputPriorityEventEnabled
             ? PBrowserParent::SendRealTouchMoveEvent(aEvent, guid, blockId,
@@ -1559,22 +1559,23 @@ bool TabParent::SendHandleTap(TapType aT
         if (element) {
           fm->SetFocus(element, nsIFocusManager::FLAG_BYMOUSE |
                                     nsIFocusManager::FLAG_BYTOUCH |
                                     nsIFocusManager::FLAG_NOSCROLL);
         }
       }
     }
   }
-  LayoutDeviceIntPoint offset = GetChildProcessOffset();
   return Manager()->IsInputPriorityEventEnabled()
-             ? PBrowserParent::SendHandleTap(aType, aPoint + offset, aModifiers,
-                                             aGuid, aInputBlockId)
+             ? PBrowserParent::SendHandleTap(aType,
+                                             TransformParentToChild(aPoint),
+                                             aModifiers, aGuid, aInputBlockId)
              : PBrowserParent::SendNormalPriorityHandleTap(
-                   aType, aPoint + offset, aModifiers, aGuid, aInputBlockId);
+                   aType, TransformParentToChild(aPoint), aModifiers, aGuid,
+                   aInputBlockId);
 }
 
 mozilla::ipc::IPCResult TabParent::RecvSyncMessage(
     const nsString& aMessage, const ClonedMessageData& aData,
     InfallibleTArray<CpowEntry>&& aCpows, const IPC::Principal& aPrincipal,
     nsTArray<StructuredCloneData>* aRetVal) {
   AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING("TabParent::RecvSyncMessage",
                                              OTHER, aMessage);
@@ -1938,25 +1939,80 @@ mozilla::ipc::IPCResult TabParent::RecvE
     browser->EnableDisableCommandsRemoteOnly(
         aAction, aEnabledCommands.Length(), enabledCommands.get(),
         aDisabledCommands.Length(), disabledCommands.get());
   }
 
   return IPC_OK();
 }
 
-NS_IMETHODIMP
-TabParent::GetChildProcessOffset(int32_t* aOutCssX, int32_t* aOutCssY) {
-  NS_ENSURE_ARG(aOutCssX);
-  NS_ENSURE_ARG(aOutCssY);
-  CSSPoint offset =
-      LayoutDevicePoint(GetChildProcessOffset()) * GetLayoutDeviceToCSSScale();
-  *aOutCssX = offset.x;
-  *aOutCssY = offset.y;
-  return NS_OK;
+LayoutDeviceIntPoint TabParent::TransformPoint(
+    const LayoutDeviceIntPoint& aPoint,
+    const LayoutDeviceToLayoutDeviceMatrix4x4& aMatrix) {
+  LayoutDevicePoint floatPoint(aPoint);
+  LayoutDevicePoint floatTransformed = TransformPoint(floatPoint, aMatrix);
+  // The next line loses precision if an out-of-process iframe
+  // has been scaled or rotated.
+  return RoundedToInt(floatTransformed);
+}
+
+LayoutDevicePoint TabParent::TransformPoint(
+    const LayoutDevicePoint& aPoint,
+    const LayoutDeviceToLayoutDeviceMatrix4x4& aMatrix) {
+  return aMatrix.TransformPoint(aPoint);
+}
+
+LayoutDeviceIntPoint TabParent::TransformParentToChild(
+    const LayoutDeviceIntPoint& aPoint) {
+  LayoutDeviceToLayoutDeviceMatrix4x4 matrix =
+      GetChildToParentConversionMatrix();
+  if (!matrix.Invert()) {
+    return LayoutDeviceIntPoint(0, 0);
+  }
+  return TransformPoint(aPoint, matrix);
+}
+
+LayoutDevicePoint TabParent::TransformParentToChild(
+    const LayoutDevicePoint& aPoint) {
+  LayoutDeviceToLayoutDeviceMatrix4x4 matrix =
+      GetChildToParentConversionMatrix();
+  if (!matrix.Invert()) {
+    return LayoutDevicePoint(0.0, 0.0);
+  }
+  return TransformPoint(aPoint, matrix);
+}
+
+LayoutDeviceIntPoint TabParent::TransformChildToParent(
+    const LayoutDeviceIntPoint& aPoint) {
+  return TransformPoint(aPoint, GetChildToParentConversionMatrix());
+}
+
+LayoutDevicePoint TabParent::TransformChildToParent(
+    const LayoutDevicePoint& aPoint) {
+  return TransformPoint(aPoint, GetChildToParentConversionMatrix());
+}
+
+LayoutDeviceIntRect TabParent::TransformChildToParent(
+    const LayoutDeviceIntRect& aRect) {
+  LayoutDeviceToLayoutDeviceMatrix4x4 matrix =
+      GetChildToParentConversionMatrix();
+  LayoutDeviceRect floatRect(aRect);
+  // The outcome is not ideal if an out-of-process iframe has been rotated
+  LayoutDeviceRect floatTransformed = matrix.TransformBounds(floatRect);
+  // The next line loses precision if an out-of-process iframe
+  // has been scaled or rotated.
+  return RoundedToInt(floatTransformed);
+}
+
+LayoutDeviceToLayoutDeviceMatrix4x4
+TabParent::GetChildToParentConversionMatrix() {
+  // Placeholder: Replace this implementation with one that obtains the
+  // matrix from APZ/WebRender.
+  LayoutDevicePoint offset(-GetChildProcessOffset());
+  return LayoutDeviceToLayoutDeviceMatrix4x4::Translation(offset);
 }
 
 LayoutDeviceIntPoint TabParent::GetChildProcessOffset() {
   // The "toplevel widget" in child processes is always at position
   // 0,0.  Map the event coordinates to match that.
 
   LayoutDeviceIntPoint offset(0, 0);
   RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
@@ -2152,17 +2208,17 @@ bool TabParent::HandleQueryContentEvent(
     case eQueryCaretRect:
     case eQueryEditorRect: {
       nsCOMPtr<nsIWidget> widget = GetWidget();
       nsCOMPtr<nsIWidget> docWidget = GetDocWidget();
       if (widget != docWidget) {
         aEvent.mReply.mRect +=
             nsLayoutUtils::WidgetToWidgetOffset(widget, docWidget);
       }
-      aEvent.mReply.mRect -= GetChildProcessOffset();
+      aEvent.mReply.mRect = TransformChildToParent(aEvent.mReply.mRect);
       break;
     }
     default:
       break;
   }
   return true;
 }
 
@@ -3303,17 +3359,17 @@ mozilla::ipc::IPCResult TabParent::RecvL
     const nsString& aText, nsTArray<FontRange>&& aFontRangeArray,
     const bool& aIsVertical, const LayoutDeviceIntPoint& aPoint) {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return IPC_OK();
   }
 
   widget->LookUpDictionary(aText, aFontRangeArray, aIsVertical,
-                           aPoint - GetChildProcessOffset());
+                           TransformChildToParent(aPoint));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult TabParent::RecvShowCanvasPermissionPrompt(
     const nsCString& aFirstPartyURI, const bool& aHideDoorHanger) {
   nsCOMPtr<nsIBrowser> browser =
       mFrameElement ? mFrameElement->AsBrowser() : nullptr;
   if (!browser) {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -488,19 +488,47 @@ class TabParent final : public PBrowserP
   // Returns the closest widget for our frameloader's content.
   already_AddRefed<nsIWidget> GetWidget() const;
 
   // Returns the top-level widget for our frameloader's document.
   already_AddRefed<nsIWidget> GetDocWidget() const;
 
   const TabId GetTabId() const { return mTabId; }
 
+  // Helper for transforming a point
+  LayoutDeviceIntPoint TransformPoint(
+      const LayoutDeviceIntPoint& aPoint,
+      const LayoutDeviceToLayoutDeviceMatrix4x4& aMatrix);
+  LayoutDevicePoint TransformPoint(
+      const LayoutDevicePoint& aPoint,
+      const LayoutDeviceToLayoutDeviceMatrix4x4& aMatrix);
+
+  // Transform a coordinate from the parent process coordinate space to the
+  // child process coordinate space.
+  LayoutDeviceIntPoint TransformParentToChild(
+      const LayoutDeviceIntPoint& aPoint);
+  LayoutDevicePoint TransformParentToChild(const LayoutDevicePoint& aPoint);
+
+  // Transform a coordinate from the child process coordinate space to the
+  // parent process coordinate space.
+  LayoutDeviceIntPoint TransformChildToParent(
+      const LayoutDeviceIntPoint& aPoint);
+  LayoutDevicePoint TransformChildToParent(const LayoutDevicePoint& aPoint);
+  LayoutDeviceIntRect TransformChildToParent(const LayoutDeviceIntRect& aRect);
+
+  // Returns the matrix that transforms event coordinates from the coordinate
+  // space of the child process to the coordinate space of the parent process.
+  LayoutDeviceToLayoutDeviceMatrix4x4 GetChildToParentConversionMatrix();
+
   // Returns the offset from the origin of our frameloader's nearest widget to
   // the origin of its layout frame. This offset is used to translate event
   // coordinates relative to the PuppetWidget origin in the child process.
+  //
+  // GOING AWAY. PLEASE AVOID ADDING CALLERS. Use the above tranformation
+  // methods instead.
   LayoutDeviceIntPoint GetChildProcessOffset();
 
   // Returns the offset from the on-screen origin of our top-level window's
   // widget (including window decorations) to the origin of our frameloader's
   // nearest widget. This offset is used to translate coordinates from the
   // PuppetWidget's origin to absolute screen coordinates in the child.
   LayoutDeviceIntPoint GetClientOffset();