1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ZoomConstraintsClient.h"
10 #include "mozilla/layers/APZCCallbackHelper.h"
11 #include "mozilla/layers/ScrollableLayerGuid.h"
12 #include "mozilla/layers/ZoomConstraints.h"
13 #include "mozilla/Preferences.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/StaticPrefs_apz.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/Event.h"
19 #include "nsIScrollableFrame.h"
20 #include "nsLayoutUtils.h"
23 #include "nsViewportInfo.h"
25 #include "UnitTransforms.h"
27 static mozilla::LazyLogModule
sApzZoomLog("apz.zoom");
28 #define ZCC_LOG(...) MOZ_LOG(sApzZoomLog, LogLevel::Debug, (__VA_ARGS__))
30 NS_IMPL_ISUPPORTS(ZoomConstraintsClient
, nsIDOMEventListener
, nsIObserver
)
32 #define DOM_META_ADDED u"DOMMetaAdded"_ns
33 #define DOM_META_CHANGED u"DOMMetaChanged"_ns
34 #define FULLSCREEN_CHANGED u"fullscreenchange"_ns
35 #define BEFORE_FIRST_PAINT "before-first-paint"_ns
36 #define COMPOSITOR_REINITIALIZED "compositor-reinitialized"_ns
37 #define NS_PREF_CHANGED "nsPref:changed"_ns
39 using namespace mozilla
;
40 using namespace mozilla::dom
;
41 using namespace mozilla::layers
;
43 ZoomConstraintsClient::ZoomConstraintsClient()
46 mZoomConstraints(false, false, CSSToParentLayerScale(1.f
),
47 CSSToParentLayerScale(1.f
)) {}
49 ZoomConstraintsClient::~ZoomConstraintsClient() = default;
51 static nsIWidget
* GetWidget(PresShell
* aPresShell
) {
55 if (nsIFrame
* rootFrame
= aPresShell
->GetRootFrame()) {
56 if (nsView
* view
= rootFrame
->GetView()) {
57 return view
->GetWidget();
63 void ZoomConstraintsClient::Destroy() {
64 if (!(mPresShell
&& mDocument
)) {
68 ZCC_LOG("Destroying %p\n", this);
71 mEventTarget
->RemoveEventListener(DOM_META_ADDED
, this, false);
72 mEventTarget
->RemoveEventListener(DOM_META_CHANGED
, this, false);
73 mEventTarget
->RemoveSystemEventListener(FULLSCREEN_CHANGED
, this, false);
74 mEventTarget
= nullptr;
77 nsCOMPtr
<nsIObserverService
> observerService
=
78 mozilla::services::GetObserverService();
79 if (observerService
) {
80 observerService
->RemoveObserver(this, BEFORE_FIRST_PAINT
.Data());
81 observerService
->RemoveObserver(this, COMPOSITOR_REINITIALIZED
.Data());
84 Preferences::RemoveObserver(this, "browser.ui.zoom.force-user-scalable");
87 if (nsIWidget
* widget
= GetWidget(mPresShell
)) {
88 ZCC_LOG("Sending null constraints in %p for { %u, %" PRIu64
" }\n", this,
89 mGuid
->mPresShellId
, mGuid
->mScrollId
);
90 widget
->UpdateZoomConstraints(mGuid
->mPresShellId
, mGuid
->mScrollId
,
100 void ZoomConstraintsClient::Init(PresShell
* aPresShell
, Document
* aDocument
) {
101 if (!(aPresShell
&& aDocument
)) {
105 mPresShell
= aPresShell
;
106 mDocument
= aDocument
;
108 if (nsCOMPtr
<nsPIDOMWindowOuter
> window
= mDocument
->GetWindow()) {
109 mEventTarget
= window
->GetParentTarget();
112 mEventTarget
->AddEventListener(DOM_META_ADDED
, this, false);
113 mEventTarget
->AddEventListener(DOM_META_CHANGED
, this, false);
114 mEventTarget
->AddSystemEventListener(FULLSCREEN_CHANGED
, this, false);
117 nsCOMPtr
<nsIObserverService
> observerService
=
118 mozilla::services::GetObserverService();
119 if (observerService
) {
120 observerService
->AddObserver(this, BEFORE_FIRST_PAINT
.Data(), false);
121 observerService
->AddObserver(this, COMPOSITOR_REINITIALIZED
.Data(), false);
124 Preferences::AddStrongObserver(this, "browser.ui.zoom.force-user-scalable");
128 ZoomConstraintsClient::HandleEvent(dom::Event
* event
) {
130 event
->GetType(type
);
132 if (type
.Equals(DOM_META_ADDED
)) {
133 ZCC_LOG("Got a dom-meta-added event in %p\n", this);
134 RefreshZoomConstraints();
135 } else if (type
.Equals(DOM_META_CHANGED
)) {
136 ZCC_LOG("Got a dom-meta-changed event in %p\n", this);
137 RefreshZoomConstraints();
138 } else if (type
.Equals(FULLSCREEN_CHANGED
)) {
139 ZCC_LOG("Got a fullscreen-change event in %p\n", this);
140 RefreshZoomConstraints();
147 ZoomConstraintsClient::Observe(nsISupports
* aSubject
, const char* aTopic
,
148 const char16_t
* aData
) {
149 if (SameCOMIdentity(aSubject
, ToSupports(mDocument
)) &&
150 BEFORE_FIRST_PAINT
.EqualsASCII(aTopic
)) {
151 ZCC_LOG("Got a before-first-paint event in %p\n", this);
152 RefreshZoomConstraints();
153 } else if (COMPOSITOR_REINITIALIZED
.EqualsASCII(aTopic
)) {
154 ZCC_LOG("Got a compositor-reinitialized notification in %p\n", this);
155 RefreshZoomConstraints();
156 } else if (NS_PREF_CHANGED
.EqualsASCII(aTopic
)) {
157 ZCC_LOG("Got a pref-change event in %p\n", this);
158 // We need to run this later because all the pref change listeners need
159 // to execute before we can be guaranteed that
160 // StaticPrefs::browser_ui_zoom_force_user_scalable() returns the updated
163 RefPtr
<nsRunnableMethod
<ZoomConstraintsClient
>> event
=
164 NewRunnableMethod("ZoomConstraintsClient::RefreshZoomConstraints", this,
165 &ZoomConstraintsClient::RefreshZoomConstraints
);
166 mDocument
->Dispatch(event
.forget());
171 void ZoomConstraintsClient::ScreenSizeChanged() {
172 ZCC_LOG("Got a screen-size change notification in %p\n", this);
173 RefreshZoomConstraints();
176 static mozilla::layers::ZoomConstraints
ComputeZoomConstraintsFromViewportInfo(
177 const nsViewportInfo
& aViewportInfo
, Document
* aDocument
) {
178 mozilla::layers::ZoomConstraints constraints
;
179 constraints
.mAllowZoom
= aViewportInfo
.IsZoomAllowed() &&
180 nsLayoutUtils::AllowZoomingForDocument(aDocument
);
181 constraints
.mAllowDoubleTapZoom
=
182 constraints
.mAllowZoom
&& StaticPrefs::apz_allow_double_tap_zooming();
183 if (constraints
.mAllowZoom
) {
184 constraints
.mMinZoom
.scale
= aViewportInfo
.GetMinZoom().scale
;
185 constraints
.mMaxZoom
.scale
= aViewportInfo
.GetMaxZoom().scale
;
187 constraints
.mMinZoom
.scale
= aViewportInfo
.GetDefaultZoom().scale
;
188 constraints
.mMaxZoom
.scale
= aViewportInfo
.GetDefaultZoom().scale
;
193 void ZoomConstraintsClient::RefreshZoomConstraints() {
194 mZoomConstraints
= ZoomConstraints(false, false, CSSToParentLayerScale(1.f
),
195 CSSToParentLayerScale(1.f
));
197 nsIWidget
* widget
= GetWidget(mPresShell
);
202 uint32_t presShellId
= 0;
203 ScrollableLayerGuid::ViewID viewId
= ScrollableLayerGuid::NULL_SCROLL_ID
;
204 bool scrollIdentifiersValid
=
205 APZCCallbackHelper::GetOrCreateScrollIdentifiers(
206 mDocument
->GetDocumentElement(), &presShellId
, &viewId
);
207 if (!scrollIdentifiersValid
) {
211 LayoutDeviceIntSize screenSize
;
212 if (!nsLayoutUtils::GetDocumentViewerSize(mPresShell
->GetPresContext(),
217 nsViewportInfo viewportInfo
= mDocument
->GetViewportInfo(ViewAs
<ScreenPixel
>(
218 screenSize
, PixelCastJustification::LayoutDeviceIsScreenForBounds
));
221 ComputeZoomConstraintsFromViewportInfo(viewportInfo
, mDocument
);
223 if (mDocument
->Fullscreen()) {
224 ZCC_LOG("%p is in fullscreen, disallowing zooming\n", this);
225 mZoomConstraints
.mAllowZoom
= false;
226 mZoomConstraints
.mAllowDoubleTapZoom
= false;
229 if (mDocument
->IsStaticDocument()) {
230 ZCC_LOG("%p is in print or print preview, disallowing double tap zooming\n",
232 mZoomConstraints
.mAllowDoubleTapZoom
= false;
235 if (nsContentUtils::IsPDFJS(mDocument
->GetPrincipal())) {
236 ZCC_LOG("%p is pdf.js viewer, disallowing double tap zooming\n", this);
237 mZoomConstraints
.mAllowDoubleTapZoom
= false;
240 // On macOS the OS can send us a double tap zoom event from the touchpad and
241 // there are no touch screen macOS devices so we never wait to see if a second
242 // tap is coming so we can always allow double tap zooming on mac. We need
243 // this because otherwise the width check usually disables it.
244 bool allow_double_tap_always
= false;
246 allow_double_tap_always
=
247 StaticPrefs::apz_mac_enable_double_tap_zoom_touchpad_gesture();
249 if (!allow_double_tap_always
&& mZoomConstraints
.mAllowDoubleTapZoom
) {
250 // If the CSS viewport is narrower than the screen (i.e. width <=
251 // device-width) then we disable double-tap-to-zoom behaviour.
252 CSSToLayoutDeviceScale scale
=
253 mPresShell
->GetPresContext()->CSSToDevPixelScale();
254 if ((viewportInfo
.GetSize() * scale
).width
<= screenSize
.width
) {
255 mZoomConstraints
.mAllowDoubleTapZoom
= false;
259 // We only ever create a ZoomConstraintsClient for an RCD, so the RSF of
260 // the presShell must be the RCD-RSF (if it exists).
261 MOZ_ASSERT(mPresShell
->GetPresContext()->IsRootContentDocumentCrossProcess());
262 if (nsIScrollableFrame
* rcdrsf
=
263 mPresShell
->GetRootScrollFrameAsScrollable()) {
264 ZCC_LOG("Notifying RCD-RSF that it is zoomable: %d\n",
265 mZoomConstraints
.mAllowZoom
);
266 rcdrsf
->SetZoomableByAPZ(mZoomConstraints
.mAllowZoom
);
269 ScrollableLayerGuid
newGuid(LayersId
{0}, presShellId
, viewId
);
270 if (mGuid
&& mGuid
.value() != newGuid
) {
271 ZCC_LOG("Clearing old constraints in %p for { %u, %" PRIu64
" }\n", this,
272 mGuid
->mPresShellId
, mGuid
->mScrollId
);
273 // If the guid changes, send a message to clear the old one
274 widget
->UpdateZoomConstraints(mGuid
->mPresShellId
, mGuid
->mScrollId
,
277 mGuid
= Some(newGuid
);
278 ZCC_LOG("Sending constraints %s in %p for { %u, %" PRIu64
" }\n",
279 ToString(mZoomConstraints
).c_str(), this, presShellId
, viewId
);
280 widget
->UpdateZoomConstraints(presShellId
, viewId
, Some(mZoomConstraints
));