ZoomConstraintsClient.cpp (10931B)
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/. */ 6 7 #include "ZoomConstraintsClient.h" 8 9 #include <inttypes.h> 10 11 #include "UnitTransforms.h" 12 #include "Units.h" 13 #include "mozilla/Preferences.h" 14 #include "mozilla/PresShell.h" 15 #include "mozilla/ScrollContainerFrame.h" 16 #include "mozilla/StaticPrefs_apz.h" 17 #include "mozilla/dom/Document.h" 18 #include "mozilla/dom/Event.h" 19 #include "mozilla/layers/APZCCallbackHelper.h" 20 #include "mozilla/layers/ScrollableLayerGuid.h" 21 #include "mozilla/layers/ZoomConstraints.h" 22 #include "nsIFrame.h" 23 #include "nsLayoutUtils.h" 24 #include "nsPoint.h" 25 #include "nsViewportInfo.h" 26 27 static mozilla::LazyLogModule sApzZoomLog("apz.zoom"); 28 #define ZCC_LOG(...) MOZ_LOG(sApzZoomLog, LogLevel::Debug, (__VA_ARGS__)) 29 30 NS_IMPL_ISUPPORTS(ZoomConstraintsClient, nsIDOMEventListener, nsIObserver) 31 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 38 39 using namespace mozilla; 40 using namespace mozilla::dom; 41 using namespace mozilla::layers; 42 43 ZoomConstraintsClient::ZoomConstraintsClient() 44 : mDocument(nullptr), 45 mPresShell(nullptr), 46 mZoomConstraints(false, false, CSSToParentLayerScale(1.f), 47 CSSToParentLayerScale(1.f)) {} 48 49 ZoomConstraintsClient::~ZoomConstraintsClient() = default; 50 51 static nsIWidget* GetWidget(PresShell* aPresShell) { 52 if (!aPresShell) { 53 return nullptr; 54 } 55 if (nsIFrame* rootFrame = aPresShell->GetRootFrame()) { 56 #if defined(MOZ_WIDGET_ANDROID) 57 // On Android in cases of about:XX pages loaded in the browser parent 58 // process we need to return the nearest widget since it's the widget owning 59 // an IAPZCTreeManager to communicate with the APZCTreeManager for the 60 // browser. 61 // In bug 1648427 we will apply this code to desktops as well to make 62 // about pages zoomable on desktops, but it will be involving more works, 63 // see https://bugzilla.mozilla.org/show_bug.cgi?id=1648427#c7 . 64 return rootFrame->GetNearestWidget(); 65 #else 66 return rootFrame->GetOwnWidget(); 67 #endif 68 } 69 return nullptr; 70 } 71 72 void ZoomConstraintsClient::Destroy() { 73 if (!(mPresShell && mDocument)) { 74 return; 75 } 76 77 ZCC_LOG("Destroying %p\n", this); 78 79 if (mEventTarget) { 80 mEventTarget->RemoveEventListener(DOM_META_ADDED, this, false); 81 mEventTarget->RemoveEventListener(DOM_META_CHANGED, this, false); 82 mEventTarget->RemoveSystemEventListener(FULLSCREEN_CHANGED, this, false); 83 mEventTarget = nullptr; 84 } 85 86 nsCOMPtr<nsIObserverService> observerService = 87 mozilla::services::GetObserverService(); 88 if (observerService) { 89 observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data()); 90 observerService->RemoveObserver(this, COMPOSITOR_REINITIALIZED.Data()); 91 } 92 93 Preferences::RemoveObserver(this, "browser.ui.zoom.force-user-scalable"); 94 95 if (mGuid) { 96 if (nsIWidget* widget = GetWidget(mPresShell)) { 97 ZCC_LOG("Sending null constraints in %p for { %u, %" PRIu64 " }\n", this, 98 mGuid->mPresShellId, mGuid->mScrollId); 99 widget->UpdateZoomConstraints(mGuid->mPresShellId, mGuid->mScrollId, 100 Nothing()); 101 mGuid = Nothing(); 102 } 103 } 104 105 mDocument = nullptr; 106 mPresShell = nullptr; 107 } 108 109 void ZoomConstraintsClient::Init(PresShell* aPresShell, Document* aDocument) { 110 if (!(aPresShell && aDocument)) { 111 return; 112 } 113 114 mPresShell = aPresShell; 115 mDocument = aDocument; 116 117 if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) { 118 mEventTarget = window->GetParentTarget(); 119 } 120 if (mEventTarget) { 121 mEventTarget->AddEventListener(DOM_META_ADDED, this, false); 122 mEventTarget->AddEventListener(DOM_META_CHANGED, this, false); 123 mEventTarget->AddSystemEventListener(FULLSCREEN_CHANGED, this, false); 124 } 125 126 nsCOMPtr<nsIObserverService> observerService = 127 mozilla::services::GetObserverService(); 128 if (observerService) { 129 observerService->AddObserver(this, BEFORE_FIRST_PAINT.Data(), false); 130 observerService->AddObserver(this, COMPOSITOR_REINITIALIZED.Data(), false); 131 } 132 133 Preferences::AddStrongObserver(this, "browser.ui.zoom.force-user-scalable"); 134 } 135 136 NS_IMETHODIMP 137 ZoomConstraintsClient::HandleEvent(dom::Event* event) { 138 nsAutoString type; 139 event->GetType(type); 140 141 if (type.Equals(DOM_META_ADDED)) { 142 ZCC_LOG("Got a dom-meta-added event in %p\n", this); 143 RefreshZoomConstraints(); 144 } else if (type.Equals(DOM_META_CHANGED)) { 145 ZCC_LOG("Got a dom-meta-changed event in %p\n", this); 146 RefreshZoomConstraints(); 147 } else if (type.Equals(FULLSCREEN_CHANGED)) { 148 ZCC_LOG("Got a fullscreen-change event in %p\n", this); 149 RefreshZoomConstraints(); 150 } 151 152 return NS_OK; 153 } 154 155 NS_IMETHODIMP 156 ZoomConstraintsClient::Observe(nsISupports* aSubject, const char* aTopic, 157 const char16_t* aData) { 158 if (SameCOMIdentity(aSubject, ToSupports(mDocument)) && 159 BEFORE_FIRST_PAINT.EqualsASCII(aTopic)) { 160 ZCC_LOG("Got a before-first-paint event in %p\n", this); 161 RefreshZoomConstraints(); 162 } else if (COMPOSITOR_REINITIALIZED.EqualsASCII(aTopic)) { 163 ZCC_LOG("Got a compositor-reinitialized notification in %p\n", this); 164 RefreshZoomConstraints(); 165 } else if (NS_PREF_CHANGED.EqualsASCII(aTopic)) { 166 ZCC_LOG("Got a pref-change event in %p\n", this); 167 // We need to run this later because all the pref change listeners need 168 // to execute before we can be guaranteed that 169 // StaticPrefs::browser_ui_zoom_force_user_scalable() returns the updated 170 // value. 171 172 RefPtr<nsRunnableMethod<ZoomConstraintsClient>> event = 173 NewRunnableMethod("ZoomConstraintsClient::RefreshZoomConstraints", this, 174 &ZoomConstraintsClient::RefreshZoomConstraints); 175 mDocument->Dispatch(event.forget()); 176 } 177 return NS_OK; 178 } 179 180 void ZoomConstraintsClient::ScreenSizeChanged() { 181 ZCC_LOG("Got a screen-size change notification in %p\n", this); 182 RefreshZoomConstraints(); 183 } 184 185 static mozilla::layers::ZoomConstraints ComputeZoomConstraintsFromViewportInfo( 186 const nsViewportInfo& aViewportInfo, Document* aDocument) { 187 mozilla::layers::ZoomConstraints constraints; 188 constraints.mAllowZoom = aViewportInfo.IsZoomAllowed() && 189 nsLayoutUtils::AllowZoomingForDocument(aDocument); 190 constraints.mAllowDoubleTapZoom = 191 constraints.mAllowZoom && StaticPrefs::apz_allow_double_tap_zooming(); 192 if (constraints.mAllowZoom) { 193 constraints.mMinZoom.scale = aViewportInfo.GetMinZoom().scale; 194 constraints.mMaxZoom.scale = aViewportInfo.GetMaxZoom().scale; 195 } else { 196 constraints.mMinZoom.scale = aViewportInfo.GetDefaultZoom().scale; 197 constraints.mMaxZoom.scale = aViewportInfo.GetDefaultZoom().scale; 198 } 199 return constraints; 200 } 201 202 void ZoomConstraintsClient::RefreshZoomConstraints() { 203 mZoomConstraints = ZoomConstraints(false, false, CSSToParentLayerScale(1.f), 204 CSSToParentLayerScale(1.f)); 205 206 nsIWidget* widget = GetWidget(mPresShell); 207 if (!widget) { 208 return; 209 } 210 211 // Ignore documents which has been removed from the doc shell. 212 if (!mDocument->IsActive()) { 213 return; 214 } 215 216 uint32_t presShellId = 0; 217 ScrollableLayerGuid::ViewID viewId = ScrollableLayerGuid::NULL_SCROLL_ID; 218 bool scrollIdentifiersValid = 219 APZCCallbackHelper::GetOrCreateScrollIdentifiers( 220 mDocument->GetDocumentElement(), &presShellId, &viewId); 221 if (!scrollIdentifiersValid) { 222 return; 223 } 224 225 LayoutDeviceIntSize screenSize; 226 if (!nsLayoutUtils::GetDocumentViewerSize(mPresShell->GetPresContext(), 227 screenSize)) { 228 return; 229 } 230 231 nsViewportInfo viewportInfo = mDocument->GetViewportInfo(ViewAs<ScreenPixel>( 232 screenSize, PixelCastJustification::LayoutDeviceIsScreenForBounds)); 233 234 mZoomConstraints = 235 ComputeZoomConstraintsFromViewportInfo(viewportInfo, mDocument); 236 237 if (mDocument->Fullscreen()) { 238 ZCC_LOG("%p is in fullscreen, disallowing zooming\n", this); 239 mZoomConstraints.mAllowZoom = false; 240 mZoomConstraints.mAllowDoubleTapZoom = false; 241 } 242 243 if (mDocument->IsStaticDocument()) { 244 ZCC_LOG("%p is in print or print preview, disallowing double tap zooming\n", 245 this); 246 mZoomConstraints.mAllowDoubleTapZoom = false; 247 } 248 249 if (nsContentUtils::IsPDFJS(mDocument->GetPrincipal())) { 250 ZCC_LOG("%p is pdf.js viewer, disallowing double tap zooming\n", this); 251 mZoomConstraints.mAllowDoubleTapZoom = false; 252 } 253 254 // On macOS the OS can send us a double tap zoom event from the touchpad and 255 // there are no touch screen macOS devices so we never wait to see if a second 256 // tap is coming so we can always allow double tap zooming on mac. We need 257 // this because otherwise the width check usually disables it. 258 bool allow_double_tap_always = false; 259 #ifdef XP_MACOSX 260 allow_double_tap_always = 261 StaticPrefs::apz_mac_enable_double_tap_zoom_touchpad_gesture(); 262 #endif 263 if (!allow_double_tap_always && mZoomConstraints.mAllowDoubleTapZoom) { 264 // If the CSS viewport is narrower than the screen (i.e. width <= 265 // device-width) then we disable double-tap-to-zoom behaviour. 266 CSSToLayoutDeviceScale scale = 267 mPresShell->GetPresContext()->CSSToDevPixelScale(); 268 if ((viewportInfo.GetSize() * scale).width <= screenSize.width) { 269 mZoomConstraints.mAllowDoubleTapZoom = false; 270 } 271 } 272 273 // We only ever create a ZoomConstraintsClient for an RCD, so the RSF of 274 // the presShell must be the RCD-RSF (if it exists). 275 MOZ_ASSERT(mPresShell->GetPresContext()->IsRootContentDocumentCrossProcess()); 276 if (ScrollContainerFrame* rcdrsf = 277 mPresShell->GetRootScrollContainerFrame()) { 278 ZCC_LOG("Notifying RCD-RSF that it is zoomable: %d\n", 279 mZoomConstraints.mAllowZoom); 280 rcdrsf->SetZoomableByAPZ(mZoomConstraints.mAllowZoom); 281 } 282 283 ScrollableLayerGuid newGuid(LayersId{0}, presShellId, viewId); 284 if (mGuid && mGuid.value() != newGuid) { 285 ZCC_LOG("Clearing old constraints in %p for { %u, %" PRIu64 " }\n", this, 286 mGuid->mPresShellId, mGuid->mScrollId); 287 // If the guid changes, send a message to clear the old one 288 widget->UpdateZoomConstraints(mGuid->mPresShellId, mGuid->mScrollId, 289 Nothing()); 290 } 291 mGuid = Some(newGuid); 292 ZCC_LOG("Sending constraints %s in %p for { %u, %" PRIu64 " }\n", 293 ToString(mZoomConstraints).c_str(), this, presShellId, viewId); 294 widget->UpdateZoomConstraints(presShellId, viewId, Some(mZoomConstraints)); 295 }