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 defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT)
57 return rootFrame
->GetNearestWidget();
59 if (nsView
* view
= rootFrame
->GetView()) {
60 return view
->GetWidget();
67 void ZoomConstraintsClient::Destroy() {
68 if (!(mPresShell
&& mDocument
)) {
72 ZCC_LOG("Destroying %p\n", this);
75 mEventTarget
->RemoveEventListener(DOM_META_ADDED
, this, false);
76 mEventTarget
->RemoveEventListener(DOM_META_CHANGED
, this, false);
77 mEventTarget
->RemoveSystemEventListener(FULLSCREEN_CHANGED
, this, false);
78 mEventTarget
= nullptr;
81 nsCOMPtr
<nsIObserverService
> observerService
=
82 mozilla::services::GetObserverService();
83 if (observerService
) {
84 observerService
->RemoveObserver(this, BEFORE_FIRST_PAINT
.Data());
85 observerService
->RemoveObserver(this, COMPOSITOR_REINITIALIZED
.Data());
88 Preferences::RemoveObserver(this, "browser.ui.zoom.force-user-scalable");
91 if (nsIWidget
* widget
= GetWidget(mPresShell
)) {
92 ZCC_LOG("Sending null constraints in %p for { %u, %" PRIu64
" }\n", this,
93 mGuid
->mPresShellId
, mGuid
->mScrollId
);
94 widget
->UpdateZoomConstraints(mGuid
->mPresShellId
, mGuid
->mScrollId
,
101 mPresShell
= nullptr;
104 void ZoomConstraintsClient::Init(PresShell
* aPresShell
, Document
* aDocument
) {
105 if (!(aPresShell
&& aDocument
)) {
109 mPresShell
= aPresShell
;
110 mDocument
= aDocument
;
112 if (nsCOMPtr
<nsPIDOMWindowOuter
> window
= mDocument
->GetWindow()) {
113 mEventTarget
= window
->GetParentTarget();
116 mEventTarget
->AddEventListener(DOM_META_ADDED
, this, false);
117 mEventTarget
->AddEventListener(DOM_META_CHANGED
, this, false);
118 mEventTarget
->AddSystemEventListener(FULLSCREEN_CHANGED
, this, false);
121 nsCOMPtr
<nsIObserverService
> observerService
=
122 mozilla::services::GetObserverService();
123 if (observerService
) {
124 observerService
->AddObserver(this, BEFORE_FIRST_PAINT
.Data(), false);
125 observerService
->AddObserver(this, COMPOSITOR_REINITIALIZED
.Data(), false);
128 Preferences::AddStrongObserver(this, "browser.ui.zoom.force-user-scalable");
132 ZoomConstraintsClient::HandleEvent(dom::Event
* event
) {
134 event
->GetType(type
);
136 if (type
.Equals(DOM_META_ADDED
)) {
137 ZCC_LOG("Got a dom-meta-added event in %p\n", this);
138 RefreshZoomConstraints();
139 } else if (type
.Equals(DOM_META_CHANGED
)) {
140 ZCC_LOG("Got a dom-meta-changed event in %p\n", this);
141 RefreshZoomConstraints();
142 } else if (type
.Equals(FULLSCREEN_CHANGED
)) {
143 ZCC_LOG("Got a fullscreen-change event in %p\n", this);
144 RefreshZoomConstraints();
151 ZoomConstraintsClient::Observe(nsISupports
* aSubject
, const char* aTopic
,
152 const char16_t
* aData
) {
153 if (SameCOMIdentity(aSubject
, ToSupports(mDocument
)) &&
154 BEFORE_FIRST_PAINT
.EqualsASCII(aTopic
)) {
155 ZCC_LOG("Got a before-first-paint event in %p\n", this);
156 RefreshZoomConstraints();
157 } else if (COMPOSITOR_REINITIALIZED
.EqualsASCII(aTopic
)) {
158 ZCC_LOG("Got a compositor-reinitialized notification in %p\n", this);
159 RefreshZoomConstraints();
160 } else if (NS_PREF_CHANGED
.EqualsASCII(aTopic
)) {
161 ZCC_LOG("Got a pref-change event in %p\n", this);
162 // We need to run this later because all the pref change listeners need
163 // to execute before we can be guaranteed that
164 // StaticPrefs::browser_ui_zoom_force_user_scalable() returns the updated
167 RefPtr
<nsRunnableMethod
<ZoomConstraintsClient
>> event
=
168 NewRunnableMethod("ZoomConstraintsClient::RefreshZoomConstraints", this,
169 &ZoomConstraintsClient::RefreshZoomConstraints
);
170 mDocument
->Dispatch(TaskCategory::Other
, event
.forget());
175 void ZoomConstraintsClient::ScreenSizeChanged() {
176 ZCC_LOG("Got a screen-size change notification in %p\n", this);
177 RefreshZoomConstraints();
180 static mozilla::layers::ZoomConstraints
ComputeZoomConstraintsFromViewportInfo(
181 const nsViewportInfo
& aViewportInfo
, Document
* aDocument
) {
182 mozilla::layers::ZoomConstraints constraints
;
183 constraints
.mAllowZoom
= aViewportInfo
.IsZoomAllowed() &&
184 nsLayoutUtils::AllowZoomingForDocument(aDocument
);
185 constraints
.mAllowDoubleTapZoom
=
186 constraints
.mAllowZoom
&& StaticPrefs::apz_allow_double_tap_zooming();
187 if (constraints
.mAllowZoom
) {
188 constraints
.mMinZoom
.scale
= aViewportInfo
.GetMinZoom().scale
;
189 constraints
.mMaxZoom
.scale
= aViewportInfo
.GetMaxZoom().scale
;
191 constraints
.mMinZoom
.scale
= aViewportInfo
.GetDefaultZoom().scale
;
192 constraints
.mMaxZoom
.scale
= aViewportInfo
.GetDefaultZoom().scale
;
197 void ZoomConstraintsClient::RefreshZoomConstraints() {
198 mZoomConstraints
= ZoomConstraints(false, false, CSSToParentLayerScale(1.f
),
199 CSSToParentLayerScale(1.f
));
201 nsIWidget
* widget
= GetWidget(mPresShell
);
206 uint32_t presShellId
= 0;
207 ScrollableLayerGuid::ViewID viewId
= ScrollableLayerGuid::NULL_SCROLL_ID
;
208 bool scrollIdentifiersValid
=
209 APZCCallbackHelper::GetOrCreateScrollIdentifiers(
210 mDocument
->GetDocumentElement(), &presShellId
, &viewId
);
211 if (!scrollIdentifiersValid
) {
215 LayoutDeviceIntSize screenSize
;
216 if (!nsLayoutUtils::GetContentViewerSize(mPresShell
->GetPresContext(),
221 nsViewportInfo viewportInfo
= mDocument
->GetViewportInfo(ViewAs
<ScreenPixel
>(
222 screenSize
, PixelCastJustification::LayoutDeviceIsScreenForBounds
));
225 ComputeZoomConstraintsFromViewportInfo(viewportInfo
, mDocument
);
227 if (mDocument
->Fullscreen()) {
228 ZCC_LOG("%p is in fullscreen, disallowing zooming\n", this);
229 mZoomConstraints
.mAllowZoom
= false;
230 mZoomConstraints
.mAllowDoubleTapZoom
= false;
233 if (mDocument
->IsStaticDocument()) {
234 ZCC_LOG("%p is in print or print preview, disallowing double tap zooming\n",
236 mZoomConstraints
.mAllowDoubleTapZoom
= false;
239 if (nsContentUtils::IsPDFJS(mDocument
->GetPrincipal())) {
240 ZCC_LOG("%p is pdf.js viewer, disallowing double tap zooming\n", this);
241 mZoomConstraints
.mAllowDoubleTapZoom
= false;
244 // On macOS the OS can send us a double tap zoom event from the touchpad and
245 // there are no touch screen macOS devices so we never wait to see if a second
246 // tap is coming so we can always allow double tap zooming on mac. We need
247 // this because otherwise the width check usually disables it.
248 bool allow_double_tap_always
= false;
250 allow_double_tap_always
=
251 StaticPrefs::apz_mac_enable_double_tap_zoom_touchpad_gesture();
253 if (!allow_double_tap_always
&& mZoomConstraints
.mAllowDoubleTapZoom
) {
254 // If the CSS viewport is narrower than the screen (i.e. width <=
255 // device-width) then we disable double-tap-to-zoom behaviour.
256 CSSToLayoutDeviceScale scale
=
257 mPresShell
->GetPresContext()->CSSToDevPixelScale();
258 if ((viewportInfo
.GetSize() * scale
).width
<= screenSize
.width
) {
259 mZoomConstraints
.mAllowDoubleTapZoom
= false;
263 // We only ever create a ZoomConstraintsClient for an RCD, so the RSF of
264 // the presShell must be the RCD-RSF (if it exists).
265 MOZ_ASSERT(mPresShell
->GetPresContext()->IsRootContentDocumentCrossProcess());
266 if (nsIScrollableFrame
* rcdrsf
=
267 mPresShell
->GetRootScrollFrameAsScrollable()) {
268 ZCC_LOG("Notifying RCD-RSF that it is zoomable: %d\n",
269 mZoomConstraints
.mAllowZoom
);
270 rcdrsf
->SetZoomableByAPZ(mZoomConstraints
.mAllowZoom
);
273 ScrollableLayerGuid
newGuid(LayersId
{0}, presShellId
, viewId
);
274 if (mGuid
&& mGuid
.value() != newGuid
) {
275 ZCC_LOG("Clearing old constraints in %p for { %u, %" PRIu64
" }\n", this,
276 mGuid
->mPresShellId
, mGuid
->mScrollId
);
277 // If the guid changes, send a message to clear the old one
278 widget
->UpdateZoomConstraints(mGuid
->mPresShellId
, mGuid
->mScrollId
,
281 mGuid
= Some(newGuid
);
282 ZCC_LOG("Sending constraints %s in %p for { %u, %" PRIu64
" }\n",
283 ToString(mZoomConstraints
).c_str(), this, presShellId
, viewId
);
284 widget
->UpdateZoomConstraints(presShellId
, viewId
, Some(mZoomConstraints
));