MobileViewportManager.cpp (30371B)
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 "MobileViewportManager.h" 8 9 #include "UnitTransforms.h" 10 #include "gfxPlatform.h" 11 #include "mozilla/PresShell.h" 12 #include "mozilla/ToString.h" 13 #include "mozilla/dom/Document.h" 14 #include "mozilla/dom/Event.h" 15 #include "mozilla/dom/EventTarget.h" 16 #include "mozilla/dom/InteractiveWidget.h" 17 #include "nsIFrame.h" 18 #include "nsLayoutUtils.h" 19 #include "nsViewportInfo.h" 20 21 mozilla::LazyLogModule MobileViewportManager::gLog("apz.mobileviewport"); 22 #define MVM_LOG(...) \ 23 MOZ_LOG(MobileViewportManager::gLog, LogLevel::Debug, (__VA_ARGS__)) 24 25 NS_IMPL_ISUPPORTS(MobileViewportManager, nsIDOMEventListener, nsIObserver) 26 27 #define DOM_META_ADDED u"DOMMetaAdded"_ns 28 #define DOM_META_CHANGED u"DOMMetaChanged"_ns 29 #define FULLSCREEN_CHANGED u"fullscreenchange"_ns 30 #define LOAD u"load"_ns 31 #define BEFORE_FIRST_PAINT "before-first-paint"_ns 32 33 using namespace mozilla; 34 using namespace mozilla::dom; 35 using namespace mozilla::layers; 36 37 MobileViewportManager::MobileViewportManager(MVMContext* aContext, 38 ManagerType aType) 39 : mContext(aContext), 40 mManagerType(aType), 41 mIsFirstPaint(false), 42 mPainted(false), 43 mInvalidViewport(false) { 44 MOZ_ASSERT(mContext); 45 46 MVM_LOG("%p: creating with context %p\n", this, mContext.get()); 47 48 mContext->AddEventListener(DOM_META_ADDED, this, false); 49 mContext->AddEventListener(DOM_META_CHANGED, this, false); 50 mContext->AddEventListener(FULLSCREEN_CHANGED, this, false); 51 mContext->AddEventListener(LOAD, this, true); 52 53 mContext->AddObserver(this, BEFORE_FIRST_PAINT.Data(), false); 54 55 // We need to initialize the display size and the CSS viewport size before 56 // the initial reflow happens. 57 UpdateSizesBeforeReflow(); 58 } 59 60 MobileViewportManager::~MobileViewportManager() = default; 61 62 void MobileViewportManager::Destroy() { 63 MVM_LOG("%p: destroying\n", this); 64 65 mContext->RemoveEventListener(DOM_META_ADDED, this, false); 66 mContext->RemoveEventListener(DOM_META_CHANGED, this, false); 67 mContext->RemoveEventListener(FULLSCREEN_CHANGED, this, false); 68 mContext->RemoveEventListener(LOAD, this, true); 69 70 mContext->RemoveObserver(this, BEFORE_FIRST_PAINT.Data()); 71 72 mContext->Destroy(); 73 mContext = nullptr; 74 } 75 76 void MobileViewportManager::SetRestoreResolution( 77 float aResolution, LayoutDeviceIntSize aDisplaySize) { 78 SetRestoreResolution(aResolution); 79 ScreenIntSize restoreDisplaySize = ViewAs<ScreenPixel>( 80 aDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds); 81 mRestoreDisplaySize = Some(restoreDisplaySize); 82 } 83 84 void MobileViewportManager::SetRestoreResolution(float aResolution) { 85 mRestoreResolution = Some(aResolution); 86 } 87 88 float MobileViewportManager::ComputeIntrinsicResolution() const { 89 if (!mContext) { 90 return 1.f; 91 } 92 93 ScreenIntSize displaySize = GetLayoutDisplaySize(); 94 nsViewportInfo viewportInfo = mContext->GetViewportInfo(displaySize); 95 // Use the up-to-date CSS viewport size from viewportInfo rather than 96 // mMobileViewportSize, which may not have been updated yet. 97 CSSToScreenScale intrinsicScale = 98 ComputeIntrinsicScale(viewportInfo, displaySize, viewportInfo.GetSize()); 99 MVM_LOG("%p: intrinsic scale based on CSS viewport size is %f", this, 100 intrinsicScale.scale); 101 CSSToLayoutDeviceScale cssToDev = mContext->CSSToDevPixelScale(); 102 return (intrinsicScale / cssToDev).scale; 103 } 104 105 mozilla::CSSToScreenScale MobileViewportManager::ComputeIntrinsicScale( 106 const nsViewportInfo& aViewportInfo, 107 const mozilla::ScreenIntSize& aDisplaySize, 108 const mozilla::CSSSize& aViewportOrContentSize) const { 109 CSSToScreenScale intrinsicScale = 110 aViewportOrContentSize.IsEmpty() 111 ? CSSToScreenScale(1.0) 112 : MaxScaleRatio(ScreenSize(aDisplaySize), aViewportOrContentSize); 113 return ClampZoom(intrinsicScale, aViewportInfo); 114 } 115 116 void MobileViewportManager::RequestReflow(bool aForceAdjustResolution) { 117 MVM_LOG("%p: got a reflow request with force resolution: %d\n", this, 118 aForceAdjustResolution); 119 RefreshViewportSize(aForceAdjustResolution); 120 } 121 122 void MobileViewportManager::ResolutionUpdated( 123 mozilla::ResolutionChangeOrigin aOrigin) { 124 MVM_LOG("%p: resolution updated\n", this); 125 126 if (!mContext) { 127 return; 128 } 129 130 if ((!mPainted && 131 aOrigin == mozilla::ResolutionChangeOrigin::MainThreadRestore) || 132 aOrigin == mozilla::ResolutionChangeOrigin::Test) { 133 // Save the value, so our default zoom calculation 134 // can take it into account later on. 135 SetRestoreResolution(mContext->GetResolution()); 136 } 137 RefreshVisualViewportSize(); 138 } 139 140 NS_IMETHODIMP 141 MobileViewportManager::HandleEvent(dom::Event* event) { 142 nsAutoString type; 143 event->GetType(type); 144 145 if (type.Equals(DOM_META_ADDED)) { 146 HandleDOMMetaAdded(); 147 } else if (type.Equals(DOM_META_CHANGED)) { 148 MVM_LOG("%p: got a dom-meta-changed event\n", this); 149 RefreshViewportSize(mPainted); 150 } else if (type.Equals(FULLSCREEN_CHANGED)) { 151 MVM_LOG("%p: got a fullscreenchange event\n", this); 152 RefreshViewportSize(mPainted); 153 } else if (type.Equals(LOAD)) { 154 MVM_LOG("%p: got a load event\n", this); 155 if (!mPainted) { 156 // Load event got fired before the before-first-paint message 157 SetInitialViewport(); 158 } 159 } 160 return NS_OK; 161 } 162 163 void MobileViewportManager::HandleDOMMetaAdded() { 164 MVM_LOG("%p: got a dom-meta-added event\n", this); 165 if (mPainted && mContext->IsDocumentLoading()) { 166 // It's possible that we get a DOMMetaAdded event after the page 167 // has already been painted, but before the document finishes loading. 168 // In such a case, we've already run SetInitialViewport() on 169 // "before-first-paint", and won't run it again on "load" (because 170 // mPainted=true). But that SetInitialViewport() call didn't know the 171 // "initial-scale" from this meta viewport tag. To ensure we respect 172 // the "initial-scale", call SetInitialViewport() again. 173 // Note: It's important that we only do this if mPainted=true. In the 174 // usual case, we get the DOMMetaAdded before the first paint, sometimes 175 // even before we have a frame tree, and calling SetInitialViewport() 176 // before we have a frame tree will skip some important steps (e.g. 177 // updating display port margins). 178 SetInitialViewport(); 179 } else { 180 RefreshViewportSize(mPainted); 181 } 182 } 183 184 NS_IMETHODIMP 185 MobileViewportManager::Observe(nsISupports* aSubject, const char* aTopic, 186 const char16_t* aData) { 187 if (!mContext) { 188 return NS_OK; 189 } 190 191 if (mContext->SubjectMatchesDocument(aSubject) && 192 BEFORE_FIRST_PAINT.EqualsASCII(aTopic)) { 193 MVM_LOG("%p: got a before-first-paint event\n", this); 194 if (!mPainted) { 195 // before-first-paint message arrived before load event 196 SetInitialViewport(); 197 } 198 } 199 return NS_OK; 200 } 201 202 void MobileViewportManager::SetInitialViewport() { 203 MVM_LOG("%p: setting initial viewport\n", this); 204 mIsFirstPaint = true; 205 mPainted = true; 206 RefreshViewportSize(false); 207 } 208 209 CSSToScreenScale MobileViewportManager::ClampZoom( 210 const CSSToScreenScale& aZoom, const nsViewportInfo& aViewportInfo) const { 211 CSSToScreenScale zoom = aZoom; 212 if (std::isnan(zoom.scale)) { 213 NS_ERROR("Don't pass NaN to ClampZoom; check caller for 0/0 division"); 214 zoom = CSSToScreenScale(1.0); 215 } 216 217 if (zoom < aViewportInfo.GetMinZoom()) { 218 zoom = aViewportInfo.GetMinZoom(); 219 MVM_LOG("%p: Clamped to %f\n", this, zoom.scale); 220 } 221 if (zoom > aViewportInfo.GetMaxZoom()) { 222 zoom = aViewportInfo.GetMaxZoom(); 223 MVM_LOG("%p: Clamped to %f\n", this, zoom.scale); 224 } 225 226 // Non-positive zoom factors can produce NaN or negative viewport sizes, 227 // so we better be sure we've got a positive zoom factor. Just for good 228 // measure, we check our min/max as well as the final clamped value. 229 MOZ_ASSERT(aViewportInfo.GetMinZoom() > CSSToScreenScale(0.0f), 230 "zoom factor must be positive"); 231 MOZ_ASSERT(aViewportInfo.GetMaxZoom() > CSSToScreenScale(0.0f), 232 "zoom factor must be positive"); 233 MOZ_ASSERT(zoom > CSSToScreenScale(0.0f), "zoom factor must be positive"); 234 return zoom; 235 } 236 237 CSSToScreenScale MobileViewportManager::ScaleZoomWithDisplayWidth( 238 const CSSToScreenScale& aZoom, const float& aDisplayWidthChangeRatio, 239 const CSSSize& aNewViewport, const CSSSize& aOldViewport) { 240 float inverseCssWidthChangeRatio = 241 (aNewViewport.width == 0) ? 1.0f 242 : aOldViewport.width / aNewViewport.width; 243 CSSToScreenScale newZoom(aZoom.scale * aDisplayWidthChangeRatio * 244 inverseCssWidthChangeRatio); 245 MVM_LOG("%p: Old zoom was %f, changed by %f * %f to %f\n", this, aZoom.scale, 246 aDisplayWidthChangeRatio, inverseCssWidthChangeRatio, newZoom.scale); 247 return newZoom; 248 } 249 250 CSSToScreenScale MobileViewportManager::ResolutionToZoom( 251 const LayoutDeviceToLayerScale& aResolution) const { 252 return ViewTargetAs<ScreenPixel>( 253 mContext->CSSToDevPixelScale() * aResolution / ParentLayerToLayerScale(1), 254 PixelCastJustification::ScreenIsParentLayerForRoot); 255 } 256 257 LayoutDeviceToLayerScale MobileViewportManager::ZoomToResolution( 258 const CSSToScreenScale& aZoom) const { 259 return ViewTargetAs<ParentLayerPixel>( 260 aZoom, PixelCastJustification::ScreenIsParentLayerForRoot) / 261 mContext->CSSToDevPixelScale() * ParentLayerToLayerScale(1); 262 } 263 264 void MobileViewportManager::UpdateResolutionForFirstPaint( 265 const CSSSize& aViewportSize) { 266 ScreenIntSize displaySize = GetLayoutDisplaySize(); 267 nsViewportInfo viewportInfo = mContext->GetViewportInfo(displaySize); 268 ScreenIntSize compositionSize = GetCompositionSize(displaySize); 269 270 if (mRestoreResolution) { 271 LayoutDeviceToLayerScale restoreResolution(*mRestoreResolution); 272 CSSToScreenScale restoreZoom = ResolutionToZoom(restoreResolution); 273 if (mRestoreDisplaySize) { 274 CSSSize prevViewport = 275 mContext->GetViewportInfo(*mRestoreDisplaySize).GetSize(); 276 float restoreDisplayWidthChangeRatio = 277 (mRestoreDisplaySize->width > 0) 278 ? (float)compositionSize.width / (float)mRestoreDisplaySize->width 279 : 1.0f; 280 281 restoreZoom = 282 ScaleZoomWithDisplayWidth(restoreZoom, restoreDisplayWidthChangeRatio, 283 aViewportSize, prevViewport); 284 } 285 MVM_LOG("%p: restored zoom is %f\n", this, restoreZoom.scale); 286 restoreZoom = ClampZoom(restoreZoom, viewportInfo); 287 288 ApplyNewZoom(restoreZoom); 289 return; 290 } 291 292 CSSToScreenScale defaultZoom = viewportInfo.GetDefaultZoom(); 293 MVM_LOG("%p: default zoom from viewport is %f\n", this, defaultZoom.scale); 294 if (!viewportInfo.IsDefaultZoomValid()) { 295 CSSSize contentSize = aViewportSize; 296 if (Maybe<CSSRect> scrollableRect = 297 mContext->CalculateScrollableRectForRSF()) { 298 contentSize = scrollableRect->Size(); 299 } 300 defaultZoom = 301 ComputeIntrinsicScale(viewportInfo, compositionSize, contentSize); 302 MVM_LOG( 303 "%p: overriding default zoom with intrinsic scale of %f based on " 304 "content size", 305 this, defaultZoom.scale); 306 } 307 MOZ_ASSERT(viewportInfo.GetMinZoom() <= defaultZoom && 308 defaultZoom <= viewportInfo.GetMaxZoom()); 309 310 ApplyNewZoom(defaultZoom); 311 } 312 313 void MobileViewportManager::UpdateResolutionForViewportSizeChange( 314 const CSSSize& aViewportSize, 315 const Maybe<float>& aDisplayWidthChangeRatio) { 316 ScreenIntSize displaySize = GetLayoutDisplaySize(); 317 nsViewportInfo viewportInfo = mContext->GetViewportInfo(displaySize); 318 319 CSSToScreenScale zoom = GetZoom(); 320 MVM_LOG("%p: current zoom level: %f", this, zoom.scale); 321 // Non-positive zoom factors can produce NaN or negative viewport sizes, 322 // so we better be sure we've got a positive zoom factor. 323 MOZ_ASSERT(zoom > CSSToScreenScale(0.0f), "zoom factor must be positive"); 324 325 MOZ_ASSERT(!mIsFirstPaint); 326 327 // If this is not a first paint, then in some cases we want to update the 328 // pre- existing resolution so as to maintain how much actual content is 329 // visible within the display width. Note that "actual content" may be 330 // different with respect to CSS pixels because of the CSS viewport size 331 // changing. 332 // 333 // aDisplayWidthChangeRatio is non-empty if: 334 // (a) The meta-viewport tag information changes, and so the CSS viewport 335 // might change as a result. If this happens after the content has 336 // been painted, we want to adjust the zoom to compensate. OR 337 // (b) The display size changed from a nonzero value to another 338 // nonzero value. This covers the case where e.g. the device was 339 // rotated, and again we want to adjust the zoom to compensate. 340 // Note in particular that aDisplayWidthChangeRatio will be None if all 341 // that happened was a change in the full-zoom. In this case, we still 342 // want to compute a new CSS and visual viewport, but we don't want to update 343 // the resolution. 344 // 345 // Given the above, the algorithm below accounts for all types of changes 346 // I can conceive of: 347 // 1. screen size changes, CSS viewport does not (pages with no meta 348 // viewport or a fixed size viewport) 349 // 2. screen size changes, CSS viewport also does (pages with a 350 // device-width viewport) 351 // 3. screen size remains constant, but CSS viewport changes (meta 352 // viewport tag is added or removed) 353 // 4. neither screen size nor CSS viewport changes 354 355 if (!aDisplayWidthChangeRatio || mContext->IsDocumentFullscreen()) { 356 UpdateVisualViewportSize(zoom); 357 return; 358 } 359 360 // One more complication is that our current zoom level may be the 361 // result of clamping to either the minimum or maximum zoom level 362 // allowed by the viewport. If we naively scale the zoom level with 363 // the change in the display width, we might be scaling one of these 364 // previously clamped values. What we really want to do is to make 365 // scaling of the zoom aware of these minimum and maximum clamping 366 // points for the existing content size, so that we keep display 367 // width changes completely reversible. 368 369 // We don't consider here if we are scaling to a zoom value outside 370 // of our viewport limits, because we'll clamp to the viewport limits 371 // as a final step. 372 373 // Because of the behavior of ShrinkToDisplaySizeIfNeeded, we are 374 // choosing zoom clamping points based on the content size of the 375 // scrollable rect, which might different from aViewportSize. 376 CSSSize contentSize = aViewportSize; 377 if (Maybe<CSSRect> scrollableRect = 378 mContext->CalculateScrollableRectForRSF()) { 379 contentSize = scrollableRect->Size(); 380 } 381 382 // We scale the sizes, though we only care about the scaled widths. 383 ScreenSize minZoomDisplaySize = contentSize * viewportInfo.GetMinZoom(); 384 ScreenSize maxZoomDisplaySize = contentSize * viewportInfo.GetMaxZoom(); 385 386 ScreenSize newDisplaySize(displaySize); 387 ScreenSize oldDisplaySize = newDisplaySize / *aDisplayWidthChangeRatio; 388 389 // To calculate an adjusted ratio, we use some combination of these 390 // four values: 391 float a(minZoomDisplaySize.width); 392 float b(maxZoomDisplaySize.width); 393 float c(oldDisplaySize.width); 394 float d(newDisplaySize.width); 395 396 // The oldDisplaySize value is in one of three "zones": 397 // 1) Less than or equal to minZoomDisplaySize. 398 // 2) Between minZoomDisplaySize and maxZoomDisplaySize. 399 // 3) Greater than or equal to maxZoomDisplaySize. 400 401 // Depending on which zone each are in, the adjusted ratio is shown in 402 // the table below (using the a-b-c-d coding from above): 403 404 // c +---+ 405 // | d | 406 // 1 | a | 407 // +---+ 408 // | d | 409 // 2 | c | 410 // +---+ 411 // | d | 412 // 3 | b | 413 // +---+ 414 415 // Conveniently, the denominator is c clamped to a..b. 416 float denominator = std::clamp(c, a, b); 417 418 float adjustedRatio = d / denominator; 419 CSSToScreenScale adjustedZoom = ScaleZoomWithDisplayWidth( 420 zoom, adjustedRatio, aViewportSize, mMobileViewportSize); 421 CSSToScreenScale newZoom = ClampZoom(adjustedZoom, viewportInfo); 422 MVM_LOG("%p: applying new zoom level: %f", this, newZoom.scale); 423 424 ApplyNewZoom(newZoom); 425 } 426 427 void MobileViewportManager::UpdateResolutionForContentSizeChange( 428 const CSSSize& aContentSize) { 429 ScreenIntSize displaySize = GetLayoutDisplaySize(); 430 nsViewportInfo viewportInfo = mContext->GetViewportInfo(displaySize); 431 432 CSSToScreenScale zoom = GetZoom(); 433 // Non-positive zoom factors can produce NaN or negative viewport sizes, 434 // so we better be sure we've got a positive zoom factor. 435 MOZ_ASSERT(zoom > CSSToScreenScale(0.0f), "zoom factor must be positive"); 436 437 ScreenIntSize compositionSize = GetCompositionSize(displaySize); 438 CSSToScreenScale intrinsicScale = 439 ComputeIntrinsicScale(viewportInfo, compositionSize, aContentSize); 440 MVM_LOG("%p: intrinsic scale based on content size is %f", this, 441 intrinsicScale.scale); 442 443 // We try to scale down the contents only IF the document has no 444 // initial-scale AND IF it's not restored documents AND IF the resolution 445 // has never been changed by APZ. 446 if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { 447 MVM_LOG("%p: conditions preventing shrink-to-fit: %d %d %d\n", this, 448 mRestoreResolution.isSome(), mContext->IsResolutionUpdatedByApz(), 449 viewportInfo.IsDefaultZoomValid()); 450 } 451 if (!mRestoreResolution && !mContext->IsResolutionUpdatedByApz() && 452 !viewportInfo.IsDefaultZoomValid()) { 453 if (zoom != intrinsicScale) { 454 ApplyNewZoom(intrinsicScale); 455 } 456 return; 457 } 458 459 // Even in other scenarios, we want to ensure that zoom level is 460 // not _smaller_ than the intrinsic scale, otherwise we might be 461 // trying to show regions where there is no content to show. 462 CSSToScreenScale clampedZoom = zoom; 463 464 if (clampedZoom < intrinsicScale) { 465 clampedZoom = intrinsicScale; 466 } 467 468 // Also clamp to the restrictions imposed by viewportInfo. 469 clampedZoom = ClampZoom(clampedZoom, viewportInfo); 470 471 if (clampedZoom != zoom) { 472 ApplyNewZoom(clampedZoom); 473 } 474 } 475 476 void MobileViewportManager::ApplyNewZoom(const CSSToScreenScale& aNewZoom) { 477 // If the zoom has changed, update the pres shell resolution accordingly. 478 // We characterize this as MainThreadAdjustment, because we don't want our 479 // change here to be remembered as a restore resolution. 480 481 // Non-positive zoom factors can produce NaN or negative viewport sizes, 482 // so we better be sure we've got a positive zoom factor. 483 MOZ_ASSERT(aNewZoom > CSSToScreenScale(0.0f), "zoom factor must be positive"); 484 485 LayoutDeviceToLayerScale resolution = ZoomToResolution(aNewZoom); 486 MVM_LOG("%p: setting resolution %f\n", this, resolution.scale); 487 mContext->SetResolutionAndScaleTo( 488 resolution.scale, ResolutionChangeOrigin::MainThreadAdjustment); 489 490 MVM_LOG("%p: New zoom is %f\n", this, aNewZoom.scale); 491 492 UpdateVisualViewportSize(aNewZoom); 493 } 494 495 ScreenIntSize MobileViewportManager::GetCompositionSize( 496 const ScreenIntSize& aDisplaySize) const { 497 if (!mContext) { 498 return ScreenIntSize(); 499 } 500 501 // FIXME: Bug 1586986 - To update VisualViewport in response to the dynamic 502 // toolbar transition we probably need to include the dynamic toolbar 503 // _current_ height. 504 ScreenIntSize compositionSize(aDisplaySize); 505 ScreenMargin scrollbars = 506 mContext->ScrollbarAreaToExcludeFromCompositionBounds() 507 // Scrollbars are not subject to resolution scaling, so LD pixels = 508 // Screen pixels for them. 509 * LayoutDeviceToScreenScale(1.0f); 510 511 compositionSize.width = 512 std::max(0.0f, compositionSize.width - scrollbars.LeftRight()); 513 compositionSize.height = 514 std::max(0.0f, compositionSize.height - scrollbars.TopBottom()); 515 516 return compositionSize; 517 } 518 519 void MobileViewportManager::UpdateVisualViewportSize( 520 const CSSToScreenScale& aZoom) { 521 if (!mContext) { 522 return; 523 } 524 525 ScreenIntSize displaySize = GetDisplaySizeForVisualViewport(); 526 if (displaySize.width == 0 || displaySize.height == 0) { 527 return; 528 } 529 530 ScreenSize compositionSize = ScreenSize(GetCompositionSize(displaySize)); 531 CSSSize compSize = compositionSize / aZoom; 532 MVM_LOG("%p: Setting VVPS %s\n", this, ToString(compSize).c_str()); 533 mContext->SetVisualViewportSize(compSize); 534 535 UpdateVisualViewportSizeByDynamicToolbar(mContext->GetDynamicToolbarOffset()); 536 } 537 538 CSSToScreenScale MobileViewportManager::GetZoom() const { 539 LayoutDeviceToLayerScale res(mContext->GetResolution()); 540 return ResolutionToZoom(res); 541 } 542 543 void MobileViewportManager::UpdateVisualViewportSizeByDynamicToolbar( 544 ScreenIntCoord aToolbarHeight) { 545 if (!mContext) { 546 return; 547 } 548 549 ScreenIntSize displaySize = GetDisplaySizeForVisualViewport(); 550 displaySize.height += aToolbarHeight; 551 nsSize compSize = CSSSize::ToAppUnits( 552 ScreenSize(GetCompositionSize(displaySize)) / GetZoom()); 553 554 if (mVisualViewportSizeUpdatedByDynamicToolbar == compSize) { 555 return; 556 } 557 558 mVisualViewportSizeUpdatedByDynamicToolbar = compSize; 559 560 mContext->PostVisualViewportResizeEventByDynamicToolbar(); 561 } 562 563 void MobileViewportManager:: 564 UpdateVisualViewportSizeForPotentialScrollbarChange() { 565 RefreshVisualViewportSize(); 566 } 567 568 void MobileViewportManager::UpdateDisplayPortMargins() { 569 if (!mContext) { 570 return; 571 } 572 mContext->UpdateDisplayPortMargins(); 573 } 574 575 void MobileViewportManager::RefreshVisualViewportSize() { 576 // This function is a subset of RefreshViewportSize, and only updates the 577 // visual viewport size. 578 if (!mContext) { 579 return; 580 } 581 582 UpdateVisualViewportSize(GetZoom()); 583 } 584 585 void MobileViewportManager::UpdateSizesBeforeReflow() { 586 if (Maybe<LayoutDeviceIntSize> newDisplaySize = 587 mContext->GetDocumentViewerSize()) { 588 mDisplaySize = *newDisplaySize; 589 MVM_LOG("%p: Reflow starting, display size updated to %s\n", this, 590 ToString(mDisplaySize).c_str()); 591 592 if (mDisplaySize.width == 0 || mDisplaySize.height == 0) { 593 return; 594 } 595 596 nsViewportInfo viewportInfo = 597 mContext->GetViewportInfo(GetLayoutDisplaySize()); 598 mMobileViewportSize = viewportInfo.GetSize(); 599 MVM_LOG("%p: MVSize updated to %s\n", this, 600 ToString(mMobileViewportSize).c_str()); 601 } 602 } 603 604 void MobileViewportManager::RefreshViewportSize(bool aForceAdjustResolution) { 605 // This function gets called by the various triggers that may result in a 606 // change of the CSS viewport. In some of these cases (e.g. the meta-viewport 607 // tag changes) we want to update the resolution and in others (e.g. the full 608 // zoom changing) we don't want to update the resolution. See the comment in 609 // UpdateResolutionForViewportSizeChange for some more detail on this. 610 // An important assumption we 611 // make here is that this RefreshViewportSize function will be called 612 // separately for each trigger that changes. For instance it should never get 613 // called such that both the full zoom and the meta-viewport tag have changed; 614 // instead it would get called twice - once after each trigger changes. This 615 // assumption is what allows the aForceAdjustResolution parameter to work as 616 // intended; if this assumption is violated then we will need to add extra 617 // complicated logic in UpdateResolutionForViewportSizeChange to ensure we 618 // only do the resolution update in the right scenarios. 619 620 if (!mContext) { 621 return; 622 } 623 624 Maybe<float> displayWidthChangeRatio; 625 if (Maybe<LayoutDeviceIntSize> newDisplaySize = 626 mContext->GetDocumentViewerSize()) { 627 // See the comment in UpdateResolutionForViewportSizeChange for why we're 628 // doing this. 629 if (mDisplaySize.width > 0) { 630 if (aForceAdjustResolution || 631 mDisplaySize.width != newDisplaySize->width) { 632 displayWidthChangeRatio = 633 Some((float)newDisplaySize->width / (float)mDisplaySize.width); 634 } 635 } else if (aForceAdjustResolution) { 636 displayWidthChangeRatio = Some(1.0f); 637 } 638 639 MVM_LOG("%p: Display width change ratio is %f\n", this, 640 displayWidthChangeRatio.valueOr(0.0f)); 641 mDisplaySize = *newDisplaySize; 642 } 643 644 MVM_LOG("%p: Computing CSS viewport using %d,%d\n", this, mDisplaySize.width, 645 mDisplaySize.height); 646 if (mDisplaySize.width == 0 || mDisplaySize.height == 0) { 647 // We can't do anything useful here, we should just bail out 648 return; 649 } 650 651 // Now it's time to update the keyboard height 652 if (mPendingKeyboardHeight) { 653 mKeyboardHeight = *mPendingKeyboardHeight; 654 mPendingKeyboardHeight.reset(); 655 } 656 657 nsViewportInfo viewportInfo = 658 mContext->GetViewportInfo(GetLayoutDisplaySize()); 659 MVM_LOG("%p: viewport info has zooms min=%f max=%f default=%f,valid=%d\n", 660 this, viewportInfo.GetMinZoom().scale, 661 viewportInfo.GetMaxZoom().scale, viewportInfo.GetDefaultZoom().scale, 662 viewportInfo.IsDefaultZoomValid()); 663 664 CSSSize viewport = viewportInfo.GetSize(); 665 MVM_LOG("%p: Computed CSS viewport %s\n", this, ToString(viewport).c_str()); 666 667 if (!mInvalidViewport && !mIsFirstPaint && mMobileViewportSize == viewport) { 668 // Nothing changed, so no need to do a reflow 669 return; 670 } 671 672 // If it's the first-paint or the viewport changed, we need to update 673 // various APZ properties (the zoom and some things that might depend on it) 674 MVM_LOG("%p: Updating properties because %d || %d\n", this, mIsFirstPaint, 675 mMobileViewportSize != viewport); 676 677 if (mManagerType == ManagerType::VisualAndMetaViewport && 678 (aForceAdjustResolution || mContext->AllowZoomingForDocument())) { 679 MVM_LOG("%p: Updating resolution because %d || %d\n", this, 680 aForceAdjustResolution, mContext->AllowZoomingForDocument()); 681 if (mIsFirstPaint) { 682 UpdateResolutionForFirstPaint(viewport); 683 } else { 684 UpdateResolutionForViewportSizeChange(viewport, displayWidthChangeRatio); 685 } 686 } else { 687 // Even without zoom, we need to update that the visual viewport size 688 // has changed. 689 MVM_LOG("%p: Updating VV size\n", this); 690 RefreshVisualViewportSize(); 691 } 692 if (gfxPlatform::AsyncPanZoomEnabled()) { 693 UpdateDisplayPortMargins(); 694 } 695 696 // Update internal state. 697 mMobileViewportSize = viewport; 698 699 if (mManagerType == ManagerType::VisualViewportOnly) { 700 MVM_LOG("%p: Visual-only, so aborting before reflow\n", this); 701 mIsFirstPaint = false; 702 return; 703 } 704 705 RefPtr<MobileViewportManager> strongThis(this); 706 707 // Kick off a reflow. 708 MVM_LOG("%p: Triggering reflow with viewport %s\n", this, 709 ToString(viewport).c_str()); 710 mContext->Reflow(viewport); 711 712 // We are going to fit the content to the display width if the initial-scale 713 // is not specied and if the content is still wider than the display width. 714 ShrinkToDisplaySizeIfNeeded(); 715 716 mIsFirstPaint = false; 717 mInvalidViewport = false; 718 } 719 720 void MobileViewportManager::ShrinkToDisplaySizeIfNeeded() { 721 if (!mContext) { 722 return; 723 } 724 725 if (mManagerType == ManagerType::VisualViewportOnly) { 726 MVM_LOG("%p: Visual-only, so aborting ShrinkToDisplaySizeIfNeeded\n", this); 727 return; 728 } 729 730 if (!mContext->AllowZoomingForDocument() || mContext->IsInReaderMode()) { 731 // If zoom is disabled, we don't scale down wider contents to fit them 732 // into device screen because users won't be able to zoom out the tiny 733 // contents. 734 // We special-case reader mode, because it doesn't allow zooming, but 735 // the restriction is often not yet in place at the time this logic 736 // runs. 737 return; 738 } 739 740 if (Maybe<CSSRect> scrollableRect = 741 mContext->CalculateScrollableRectForRSF()) { 742 MVM_LOG("%p: ShrinkToDisplaySize using scrollableRect %s\n", this, 743 ToString(scrollableRect->Size()).c_str()); 744 UpdateResolutionForContentSizeChange(scrollableRect->Size()); 745 } 746 } 747 748 CSSSize MobileViewportManager::GetIntrinsicCompositionSize() const { 749 // TODO: Should we use GetDisplaySizeForVisualViewport() for computing the 750 // intrinsic composition size? 751 ScreenIntSize displaySize = GetLayoutDisplaySize(); 752 ScreenIntSize compositionSize = GetCompositionSize(displaySize); 753 CSSToScreenScale intrinsicScale = 754 ComputeIntrinsicScale(mContext->GetViewportInfo(displaySize), 755 compositionSize, mMobileViewportSize); 756 757 return ScreenSize(compositionSize) / intrinsicScale; 758 } 759 760 ParentLayerSize MobileViewportManager::GetCompositionSizeWithoutDynamicToolbar() 761 const { 762 return ViewAs<ParentLayerPixel>( 763 ScreenSize(GetCompositionSize(GetDisplaySizeForVisualViewport())), 764 PixelCastJustification::ScreenIsParentLayerForRoot); 765 } 766 767 void MobileViewportManager::UpdateKeyboardHeight( 768 ScreenIntCoord aKeyboardHeight) { 769 if (mPendingKeyboardHeight == Some(aKeyboardHeight)) { 770 return; 771 } 772 773 mPendingKeyboardHeight = Some(aKeyboardHeight); 774 mInvalidViewport = true; 775 } 776 777 ScreenIntSize MobileViewportManager::GetLayoutDisplaySize() const { 778 ScreenIntSize displaySize = ViewAs<ScreenPixel>( 779 mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds); 780 switch (mContext->GetInteractiveWidgetMode()) { 781 case InteractiveWidget::ResizesContent: 782 break; 783 case InteractiveWidget::OverlaysContent: 784 case InteractiveWidget::ResizesVisual: 785 displaySize.height += mKeyboardHeight; 786 break; 787 } 788 return displaySize; 789 } 790 791 ScreenIntSize MobileViewportManager::GetDisplaySizeForVisualViewport() const { 792 ScreenIntSize displaySize = ViewAs<ScreenPixel>( 793 mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds); 794 switch (mContext->GetInteractiveWidgetMode()) { 795 case InteractiveWidget::ResizesContent: 796 case InteractiveWidget::ResizesVisual: 797 break; 798 case InteractiveWidget::OverlaysContent: 799 displaySize.height += mKeyboardHeight; 800 break; 801 } 802 return displaySize; 803 } 804 805 nsRect MobileViewportManager::InitialVisibleArea() { 806 UpdateSizesBeforeReflow(); 807 808 // Basically mMobileViewportSize should not be empty, but we somehow create 809 // a MobileViewportManager for the transient about blank document of each 810 // window actor, in such cases the document viewer size is empty, thus we 811 // return an empty rectangle here. 812 return nsRect(nsPoint(), CSSSize::ToAppUnits(mMobileViewportSize)); 813 }