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