From acc5c411752c8e4786e1a590f2cefe9c8a5676e7 Mon Sep 17 00:00:00 2001 From: Mike Conley Date: Wed, 22 Jun 2022 16:15:57 +0000 Subject: [PATCH] Bug 1773865 - Dispatch an event on the window document when a pinch zoom gesture ends. r=botond,smaug,NeilDeakin Differential Revision: https://phabricator.services.mozilla.com/D149283 --- browser/base/content/browser-fullZoom.js | 22 +++++++- .../test/zoom/browser_zoom_commands.js | 13 +++++ .../apz/public/GeckoContentController.h | 3 ++ gfx/layers/apz/src/AsyncPanZoomController.cpp | 9 ++++ gfx/layers/apz/src/AsyncPanZoomController.h | 4 ++ gfx/layers/apz/test/gtest/APZTestCommon.h | 2 + gfx/layers/apz/util/APZCCallbackHelper.cpp | 36 +++++++++++++ gfx/layers/apz/util/APZCCallbackHelper.h | 2 + .../apz/util/ChromeProcessController.cpp | 20 ++++++++ gfx/layers/apz/util/ChromeProcessController.h | 2 + .../apz/util/ContentProcessController.cpp | 6 +++ .../apz/util/ContentProcessController.h | 3 ++ gfx/layers/ipc/APZCTreeManagerChild.cpp | 14 ++++++ gfx/layers/ipc/APZCTreeManagerChild.h | 3 ++ gfx/layers/ipc/PAPZCTreeManager.ipdl | 2 + gfx/layers/ipc/RemoteContentController.cpp | 50 +++++++++++++++++++ gfx/layers/ipc/RemoteContentController.h | 7 +++ 17 files changed, 196 insertions(+), 2 deletions(-) diff --git a/browser/base/content/browser-fullZoom.js b/browser/base/content/browser-fullZoom.js index 8c85a7167aeb..4f22b6e92d6b 100644 --- a/browser/base/content/browser-fullZoom.js +++ b/browser/base/content/browser-fullZoom.js @@ -50,6 +50,7 @@ var FullZoom = { init: function FullZoom_init() { gBrowser.addEventListener("DoZoomEnlargeBy10", this); gBrowser.addEventListener("DoZoomReduceBy10", this); + window.addEventListener("MozScaleGestureComplete", this); // Register ourselves with the service so we know when our pref changes. this._cps2 = Cc["@mozilla.org/content-pref/service;1"].getService( @@ -86,6 +87,7 @@ var FullZoom = { this._cps2.removeObserverForName(this.name, this); gBrowser.removeEventListener("DoZoomEnlargeBy10", this); gBrowser.removeEventListener("DoZoomReduceBy10", this); + window.removeEventListener("MozScaleGestureComplete", this); }, // Event Handlers @@ -100,6 +102,11 @@ var FullZoom = { case "DoZoomReduceBy10": this.changeZoomBy(this._getTargetedBrowser(event), -0.1); break; + case "MozScaleGestureComplete": { + let nonDefaultScalingZoom = event.detail != 1.0; + this.updateCommands(nonDefaultScalingZoom); + break; + } } }, @@ -323,7 +330,18 @@ var FullZoom = { // update state of zoom menu items - updateCommands: function FullZoom_updateCommands() { + /** + * Updates the current windows Zoom commands for zooming in, zooming out + * and resetting the zoom level. Dispatches a ZoomCommandsUpdated event on + * the window when the commands have been updated. + * + * @param {boolean} [forceResetEnabled=false] + * Set to true if the zoom reset command should be enabled regardless of + * whether or not the ZoomManager.zoom level is at 1.0. This is specifically + * for when using scaling zoom via the pinch gesture which doesn't cause + * the ZoomManager.zoom level to change. + */ + updateCommands: function FullZoom_updateCommands(forceResetEnabled = false) { let zoomLevel = ZoomManager.zoom; let reduceCmd = document.getElementById("cmd_fullZoomReduce"); if (zoomLevel == ZoomManager.MIN) { @@ -340,7 +358,7 @@ var FullZoom = { } let resetCmd = document.getElementById("cmd_fullZoomReset"); - if (zoomLevel == 1) { + if (zoomLevel == 1 && !forceResetEnabled) { resetCmd.setAttribute("disabled", "true"); } else { resetCmd.removeAttribute("disabled"); diff --git a/browser/base/content/test/zoom/browser_zoom_commands.js b/browser/base/content/test/zoom/browser_zoom_commands.js index 14ed1f71b67f..53f6163279a8 100644 --- a/browser/base/content/test/zoom/browser_zoom_commands.js +++ b/browser/base/content/test/zoom/browser_zoom_commands.js @@ -60,6 +60,10 @@ add_task(async () => { const TEST_PAGE_URL = "data:text/html;charset=utf-8,test_zoom_levels"; + const winUtils = Services.wm.getMostRecentWindow("").windowUtils; + const layerManager = winUtils.layerManagerType; + const softwareWebRenderEnabled = layerManager == "WebRender (Software)"; + await BrowserTestUtils.withNewTab(TEST_PAGE_URL, async browser => { let currentZoom = await FullZoomHelper.getGlobalValue(); Assert.equal( @@ -79,6 +83,15 @@ add_task(async () => { // and the other without. for (let textZoom of [true, false]) { info(`Running variation with textZoom set to ${textZoom}`); + + if (!textZoom && softwareWebRenderEnabled) { + todo( + false, + "Skipping full zoom when using software WebRender (bug 1775498)" + ); + continue; + } + await SpecialPowers.pushPrefEnv({ set: [["browser.zoom.full", !textZoom]], }); diff --git a/gfx/layers/apz/public/GeckoContentController.h b/gfx/layers/apz/public/GeckoContentController.h index 36d751b1c20a..69c6da8945c5 100644 --- a/gfx/layers/apz/public/GeckoContentController.h +++ b/gfx/layers/apz/public/GeckoContentController.h @@ -147,6 +147,9 @@ class GeckoContentController { virtual void CancelAutoscroll(const ScrollableLayerGuid& aGuid) = 0; + virtual void NotifyScaleGestureComplete(const ScrollableLayerGuid& aGuid, + float aScale) = 0; + virtual void UpdateOverscrollVelocity(const ScrollableLayerGuid& aGuid, float aX, float aY, bool aIsRootContent) {} diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index bdf5d1219aeb..47edc252f636 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -738,6 +738,7 @@ AsyncPanZoomController::AsyncPanZoomController( kViewportMaxScale / ParentLayerToScreenScale(1)), mLastSampleTime(GetFrameTime()), mLastCheckerboardReport(GetFrameTime()), + mLastNotifiedZoom(), mOverscrollEffect(MakeUnique(*this)), mState(NOTHING), mX(this), @@ -4386,6 +4387,14 @@ void AsyncPanZoomController::RequestContentRepaint( RepaintRequest request(aFrameMetrics, aDisplayportMargins, aUpdateType, animationType, mScrollGeneration); + if (request.IsRootContent() && request.GetZoom() != mLastNotifiedZoom && + mState != PINCHING && mState != ANIMATING_ZOOM) { + controller->NotifyScaleGestureComplete( + GetGuid(), + (request.GetZoom() / request.GetDevPixelsPerCSSPixel()).scale); + mLastNotifiedZoom = request.GetZoom(); + } + // If we're trying to paint what we already think is painted, discard this // request since it's a pointless paint. if (request.GetDisplayPortMargins().WithinEpsilonOf( diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h index 7c297a14e450..8f4078e651cf 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -1081,6 +1081,10 @@ class AsyncPanZoomController { // to allow panning by moving multiple fingers (thus moving the focus point). ParentLayerPoint mLastZoomFocus; + // Stores the previous zoom level at which we last sent a ScaleGestureComplete + // notification. + CSSToParentLayerScale mLastNotifiedZoom; + RefPtr mAnimation; UniquePtr mOverscrollEffect; diff --git a/gfx/layers/apz/test/gtest/APZTestCommon.h b/gfx/layers/apz/test/gtest/APZTestCommon.h index 2419eff68d0f..b5b4c1f92c7c 100644 --- a/gfx/layers/apz/test/gtest/APZTestCommon.h +++ b/gfx/layers/apz/test/gtest/APZTestCommon.h @@ -154,6 +154,8 @@ class MockContentController : public GeckoContentController { MOCK_METHOD1(NotifyAsyncAutoscrollRejected, void(const ScrollableLayerGuid::ViewID&)); MOCK_METHOD1(CancelAutoscroll, void(const ScrollableLayerGuid&)); + MOCK_METHOD2(NotifyScaleGestureComplete, + void(const ScrollableLayerGuid&, float aScale)); }; class MockContentControllerDelayed : public MockContentController { diff --git a/gfx/layers/apz/util/APZCCallbackHelper.cpp b/gfx/layers/apz/util/APZCCallbackHelper.cpp index ff8bcd27d164..79a41fc5d582 100644 --- a/gfx/layers/apz/util/APZCCallbackHelper.cpp +++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp @@ -8,10 +8,13 @@ #include "gfxPlatform.h" // For gfxPlatform::UseTiling +#include "mozilla/AsyncEventDispatcher.h" #include "mozilla/EventForwards.h" +#include "mozilla/dom/CustomEvent.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/MouseEventBinding.h" #include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/layers/RepaintRequest.h" #include "mozilla/layers/WebRenderLayerManager.h" @@ -29,6 +32,7 @@ #include "nsIScrollableFrame.h" #include "nsLayoutUtils.h" #include "nsPrintfCString.h" +#include "nsPIDOMWindow.h" #include "nsRefreshDriver.h" #include "nsString.h" #include "nsView.h" @@ -869,6 +873,38 @@ void APZCCallbackHelper::CancelAutoscroll( data.get()); } +/* static */ +void APZCCallbackHelper::NotifyScaleGestureComplete( + const nsCOMPtr& aWidget, float aScale) { + MOZ_ASSERT(NS_IsMainThread()); + + if (nsView* view = nsView::GetViewFor(aWidget)) { + if (PresShell* presShell = view->GetPresShell()) { + dom::Document* doc = presShell->GetDocument(); + MOZ_ASSERT(doc); + if (nsPIDOMWindowInner* win = doc->GetInnerWindow()) { + dom::AutoJSAPI jsapi; + if (!jsapi.Init(win)) { + return; + } + + JSContext* cx = jsapi.cx(); + JS::Rooted detail(cx, JS::Float32Value(aScale)); + RefPtr event = + NS_NewDOMCustomEvent(doc, nullptr, nullptr); + event->InitCustomEvent(cx, u"MozScaleGestureComplete"_ns, + /* CanBubble */ true, + /* Cancelable */ false, detail); + event->SetTrusted(true); + AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(doc, event); + dispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes; + + dispatcher->PostDOMEvent(); + } + } + } +} + /* static */ void APZCCallbackHelper::NotifyPinchGesture( PinchGestureInput::PinchGestureType aType, diff --git a/gfx/layers/apz/util/APZCCallbackHelper.h b/gfx/layers/apz/util/APZCCallbackHelper.h index 89e3e0787ccc..2048bdde1502 100644 --- a/gfx/layers/apz/util/APZCCallbackHelper.h +++ b/gfx/layers/apz/util/APZCCallbackHelper.h @@ -162,6 +162,8 @@ class APZCCallbackHelper { const ScrollableLayerGuid::ViewID& aScrollId); static void CancelAutoscroll(const ScrollableLayerGuid::ViewID& aScrollId); + static void NotifyScaleGestureComplete(const nsCOMPtr& aWidget, + float aScale); /* * Check if the scrollable frame is currently in the middle of a main thread diff --git a/gfx/layers/apz/util/ChromeProcessController.cpp b/gfx/layers/apz/util/ChromeProcessController.cpp index e95e78e4fdb2..b594c5b7643c 100644 --- a/gfx/layers/apz/util/ChromeProcessController.cpp +++ b/gfx/layers/apz/util/ChromeProcessController.cpp @@ -330,3 +330,23 @@ void ChromeProcessController::CancelAutoscroll( APZCCallbackHelper::CancelAutoscroll(aGuid.mScrollId); } + +void ChromeProcessController::NotifyScaleGestureComplete( + const ScrollableLayerGuid& aGuid, float aScale) { + if (!mUIThread->IsOnCurrentThread()) { + mUIThread->Dispatch(NewRunnableMethod( + "layers::ChromeProcessController::NotifyScaleGestureComplete", this, + &ChromeProcessController::NotifyScaleGestureComplete, aGuid, aScale)); + return; + } + + if (mWidget) { + // Dispatch the call to APZCCallbackHelper::NotifyScaleGestureComplete + // to the main thread so that it runs asynchronously from the current call. + // This is because the call can run arbitrary JS code, which can also spin + // the event loop and cause undesirable re-entrancy in APZ. + mUIThread->Dispatch(NewRunnableFunction( + "layers::ChromeProcessController::NotifyScaleGestureComplete", + &APZCCallbackHelper::NotifyScaleGestureComplete, mWidget, aScale)); + } +} diff --git a/gfx/layers/apz/util/ChromeProcessController.h b/gfx/layers/apz/util/ChromeProcessController.h index 2a0a3051fda6..3239762129d9 100644 --- a/gfx/layers/apz/util/ChromeProcessController.h +++ b/gfx/layers/apz/util/ChromeProcessController.h @@ -75,6 +75,8 @@ class ChromeProcessController : public mozilla::layers::GeckoContentController { void NotifyAsyncAutoscrollRejected( const ScrollableLayerGuid::ViewID& aScrollId) override; void CancelAutoscroll(const ScrollableLayerGuid& aGuid) override; + void NotifyScaleGestureComplete(const ScrollableLayerGuid& aGuid, + float aScale) override; PresShell* GetTopLevelPresShell() const override { return GetPresShell(); } diff --git a/gfx/layers/apz/util/ContentProcessController.cpp b/gfx/layers/apz/util/ContentProcessController.cpp index 0c9071d30938..40bbe72be37c 100644 --- a/gfx/layers/apz/util/ContentProcessController.cpp +++ b/gfx/layers/apz/util/ContentProcessController.cpp @@ -97,6 +97,12 @@ void ContentProcessController::CancelAutoscroll( MOZ_ASSERT_UNREACHABLE("Unexpected message to content process"); } +void ContentProcessController::NotifyScaleGestureComplete( + const ScrollableLayerGuid& aGuid, float aScale) { + // This should never get called + MOZ_ASSERT_UNREACHABLE("Unexpected message to content process"); +} + bool ContentProcessController::IsRepaintThread() { return NS_IsMainThread(); } void ContentProcessController::DispatchToRepaintThread( diff --git a/gfx/layers/apz/util/ContentProcessController.h b/gfx/layers/apz/util/ContentProcessController.h index f91ea9a27978..94b3208deb6b 100644 --- a/gfx/layers/apz/util/ContentProcessController.h +++ b/gfx/layers/apz/util/ContentProcessController.h @@ -72,6 +72,9 @@ class ContentProcessController final : public GeckoContentController { void CancelAutoscroll(const ScrollableLayerGuid& aGuid) override; + void NotifyScaleGestureComplete(const ScrollableLayerGuid& aGuid, + float aScale) override; + bool IsRepaintThread() override; void DispatchToRepaintThread(already_AddRefed aTask) override; diff --git a/gfx/layers/ipc/APZCTreeManagerChild.cpp b/gfx/layers/ipc/APZCTreeManagerChild.cpp index d87a8c67943a..7d0d31e595c1 100644 --- a/gfx/layers/ipc/APZCTreeManagerChild.cpp +++ b/gfx/layers/ipc/APZCTreeManagerChild.cpp @@ -201,5 +201,19 @@ mozilla::ipc::IPCResult APZCTreeManagerChild::RecvCancelAutoscroll( return IPC_OK(); } +mozilla::ipc::IPCResult APZCTreeManagerChild::RecvNotifyScaleGestureComplete( + const ScrollableLayerGuid::ViewID& aScrollId, float aScale) { + // This will only get sent from the GPU process to the parent process, so + // this function should never get called in the content process. + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + if (mCompositorSession && mCompositorSession->GetWidget()) { + APZCCallbackHelper::NotifyScaleGestureComplete( + mCompositorSession->GetWidget(), aScale); + } + return IPC_OK(); +} + } // namespace layers } // namespace mozilla diff --git a/gfx/layers/ipc/APZCTreeManagerChild.h b/gfx/layers/ipc/APZCTreeManagerChild.h index 7d38fd23ba86..3266b6bbce0e 100644 --- a/gfx/layers/ipc/APZCTreeManagerChild.h +++ b/gfx/layers/ipc/APZCTreeManagerChild.h @@ -85,6 +85,9 @@ class APZCTreeManagerChild : public IAPZCTreeManager, mozilla::ipc::IPCResult RecvCancelAutoscroll( const ScrollableLayerGuid::ViewID& aScrollId); + mozilla::ipc::IPCResult RecvNotifyScaleGestureComplete( + const ScrollableLayerGuid::ViewID& aScrollId, float aScale); + virtual ~APZCTreeManagerChild(); private: diff --git a/gfx/layers/ipc/PAPZCTreeManager.ipdl b/gfx/layers/ipc/PAPZCTreeManager.ipdl index ff2c510ff665..b730513aba9b 100644 --- a/gfx/layers/ipc/PAPZCTreeManager.ipdl +++ b/gfx/layers/ipc/PAPZCTreeManager.ipdl @@ -83,6 +83,8 @@ child: Modifiers aModifiers); async CancelAutoscroll(ViewID aScrollId); + + async NotifyScaleGestureComplete(ViewID aScrollId, float aScale); }; } // namespace gfx diff --git a/gfx/layers/ipc/RemoteContentController.cpp b/gfx/layers/ipc/RemoteContentController.cpp index 8b110f13971d..b419be6e4c86 100644 --- a/gfx/layers/ipc/RemoteContentController.cpp +++ b/gfx/layers/ipc/RemoteContentController.cpp @@ -399,6 +399,56 @@ void RemoteContentController::CancelAutoscrollCrossProcess( } } +void RemoteContentController::NotifyScaleGestureComplete( + const ScrollableLayerGuid& aGuid, float aScale) { + if (XRE_GetProcessType() == GeckoProcessType_GPU) { + NotifyScaleGestureCompleteCrossProcess(aGuid, aScale); + } else { + NotifyScaleGestureCompleteInProcess(aGuid, aScale); + } +} + +void RemoteContentController::NotifyScaleGestureCompleteInProcess( + const ScrollableLayerGuid& aGuid, float aScale) { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(NewRunnableMethod( + "layers::RemoteContentController::NotifyScaleGestureCompleteInProcess", + this, &RemoteContentController::NotifyScaleGestureCompleteInProcess, + aGuid, aScale)); + return; + } + + RefPtr rootController = + CompositorBridgeParent::GetGeckoContentControllerForRoot(aGuid.mLayersId); + if (rootController) { + rootController->NotifyScaleGestureComplete(aGuid, aScale); + } +} + +void RemoteContentController::NotifyScaleGestureCompleteCrossProcess( + const ScrollableLayerGuid& aGuid, float aScale) { + MOZ_ASSERT(XRE_IsGPUProcess()); + + if (!mCompositorThread->IsOnCurrentThread()) { + mCompositorThread->Dispatch(NewRunnableMethod( + "layers::RemoteContentController::" + "NotifyScaleGestureCompleteCrossProcess", + this, &RemoteContentController::NotifyScaleGestureCompleteCrossProcess, + aGuid, aScale)); + return; + } + + // The raw pointer to APZCTreeManagerParent is ok here because we are on the + // compositor thread. + if (APZCTreeManagerParent* parent = + CompositorBridgeParent::GetApzcTreeManagerParentForRoot( + aGuid.mLayersId)) { + Unused << parent->SendNotifyScaleGestureComplete(aGuid.mScrollId, aScale); + } +} + void RemoteContentController::ActorDestroy(ActorDestroyReason aWhy) { // This controller could possibly be kept alive longer after this // by a RefPtr, but it is no longer valid to send messages. diff --git a/gfx/layers/ipc/RemoteContentController.h b/gfx/layers/ipc/RemoteContentController.h index 1f18323a3cad..c8f74365991a 100644 --- a/gfx/layers/ipc/RemoteContentController.h +++ b/gfx/layers/ipc/RemoteContentController.h @@ -81,6 +81,9 @@ class RemoteContentController : public GeckoContentController, void CancelAutoscroll(const ScrollableLayerGuid& aScrollId) override; + void NotifyScaleGestureComplete(const ScrollableLayerGuid& aGuid, + float aScale) override; + void ActorDestroy(ActorDestroyReason aWhy) override; void Destroy() override; @@ -106,6 +109,10 @@ class RemoteContentController : public GeckoContentController, void CancelAutoscrollInProcess(const ScrollableLayerGuid& aScrollId); void CancelAutoscrollCrossProcess(const ScrollableLayerGuid& aScrollId); + void NotifyScaleGestureCompleteInProcess(const ScrollableLayerGuid& aGuid, + float aScale); + void NotifyScaleGestureCompleteCrossProcess(const ScrollableLayerGuid& aGuid, + float aScale); }; } // namespace layers