Skip to content

Commit

Permalink
Bug 1773865 - Dispatch an event on the window document when a pinch z…
Browse files Browse the repository at this point in the history
…oom gesture ends. r=botond,smaug,NeilDeakin

Differential Revision: https://phabricator.services.mozilla.com/D149283
  • Loading branch information
mikeconley committed Jun 22, 2022
1 parent e04d423 commit af9dec9
Show file tree
Hide file tree
Showing 18 changed files with 194 additions and 14 deletions.
26 changes: 19 additions & 7 deletions browser/base/content/browser-fullZoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
}
},

Expand Down Expand Up @@ -323,7 +330,17 @@ 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.
*
* @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) {
Expand All @@ -340,7 +357,7 @@ var FullZoom = {
}

let resetCmd = document.getElementById("cmd_fullZoomReset");
if (zoomLevel == 1) {
if (zoomLevel == 1 && !forceResetEnabled) {
resetCmd.setAttribute("disabled", "true");
} else {
resetCmd.removeAttribute("disabled");
Expand All @@ -352,11 +369,6 @@ var FullZoom = {
} else {
fullZoomCmd.setAttribute("checked", "false");
}
let event = new CustomEvent("ZoomCommandsUpdated", {
bubbles: false,
cancelable: false,
});
window.dispatchEvent(event);
},

// Setting & Pref Manipulation
Expand Down
1 change: 1 addition & 0 deletions browser/base/content/test/zoom/browser_zoom_commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ add_task(async () => {
// and the other without.
for (let textZoom of [true, false]) {
info(`Running variation with textZoom set to ${textZoom}`);

await SpecialPowers.pushPrefEnv({
set: [["browser.zoom.full", !textZoom]],
});
Expand Down
18 changes: 11 additions & 7 deletions browser/base/content/test/zoom/head.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,7 @@ var FullZoomHelper = {
*/

let parsedZoomValue = parseFloat((parseInt(newZoom) / 100).toFixed(2));

let commandsUpdated = BrowserTestUtils.waitForEvent(
window,
"ZoomCommandsUpdated"
);
let completion = new Promise(resolve => {
await new Promise(resolve => {
gContentPrefs.setGlobal(
FullZoom.name,
parsedZoomValue,
Expand All @@ -49,7 +44,16 @@ var FullZoomHelper = {
}
);
});
await Promise.all([commandsUpdated, completion]);
// The zoom level is used to update the commands associated with
// increasing, decreasing or resetting the Zoom levels. There are
// a series of async things we need to wait for (writing the content
// pref to the database, and then reading that content pref back out
// again and reacting to it), so waiting for the zoom level to reach
// the expected level is actually simplest to make sure we're okay to
// proceed.
await TestUtils.waitForCondition(() => {
return ZoomManager.zoom == parsedZoomValue;
});
},

async getGlobalValue() {
Expand Down
3 changes: 3 additions & 0 deletions gfx/layers/apz/public/GeckoContentController.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand Down
9 changes: 9 additions & 0 deletions gfx/layers/apz/src/AsyncPanZoomController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,7 @@ AsyncPanZoomController::AsyncPanZoomController(
kViewportMaxScale / ParentLayerToScreenScale(1)),
mLastSampleTime(GetFrameTime()),
mLastCheckerboardReport(GetFrameTime()),
mLastNotifiedZoom(),
mOverscrollEffect(MakeUnique<OverscrollEffect>(*this)),
mState(NOTHING),
mX(this),
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 4 additions & 0 deletions gfx/layers/apz/src/AsyncPanZoomController.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<AsyncPanZoomAnimation> mAnimation;

UniquePtr<OverscrollEffectBase> mOverscrollEffect;
Expand Down
2 changes: 2 additions & 0 deletions gfx/layers/apz/test/gtest/APZTestCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
36 changes: 36 additions & 0 deletions gfx/layers/apz/util/APZCCallbackHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -869,6 +873,38 @@ void APZCCallbackHelper::CancelAutoscroll(
data.get());
}

/* static */
void APZCCallbackHelper::NotifyScaleGestureComplete(
const nsCOMPtr<nsIWidget>& 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<JS::Value> detail(cx, JS::Float32Value(aScale));
RefPtr<dom::CustomEvent> 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,
Expand Down
2 changes: 2 additions & 0 deletions gfx/layers/apz/util/APZCCallbackHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ class APZCCallbackHelper {
const ScrollableLayerGuid::ViewID& aScrollId);

static void CancelAutoscroll(const ScrollableLayerGuid::ViewID& aScrollId);
static void NotifyScaleGestureComplete(const nsCOMPtr<nsIWidget>& aWidget,
float aScale);

/*
* Check if the scrollable frame is currently in the middle of a main thread
Expand Down
20 changes: 20 additions & 0 deletions gfx/layers/apz/util/ChromeProcessController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ScrollableLayerGuid, float>(
"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));
}
}
2 changes: 2 additions & 0 deletions gfx/layers/apz/util/ChromeProcessController.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }

Expand Down
6 changes: 6 additions & 0 deletions gfx/layers/apz/util/ContentProcessController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
3 changes: 3 additions & 0 deletions gfx/layers/apz/util/ContentProcessController.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Runnable> aTask) override;
Expand Down
14 changes: 14 additions & 0 deletions gfx/layers/ipc/APZCTreeManagerChild.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions gfx/layers/ipc/APZCTreeManagerChild.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions gfx/layers/ipc/PAPZCTreeManager.ipdl
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ child:
Modifiers aModifiers);

async CancelAutoscroll(ViewID aScrollId);

async NotifyScaleGestureComplete(ViewID aScrollId, float aScale);
};

} // namespace gfx
Expand Down
50 changes: 50 additions & 0 deletions gfx/layers/ipc/RemoteContentController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ScrollableLayerGuid, float>(
"layers::RemoteContentController::NotifyScaleGestureCompleteInProcess",
this, &RemoteContentController::NotifyScaleGestureCompleteInProcess,
aGuid, aScale));
return;
}

RefPtr<GeckoContentController> 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<ScrollableLayerGuid, float>(
"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.
Expand Down
Loading

0 comments on commit af9dec9

Please sign in to comment.