Bug 1282902 - Part 3 - Let the MobileViewportManager recalculate the saved resolution if the display width changed before restoring. r=kats draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Fri, 01 Jul 2016 21:23:25 +0200
changeset 385094 658dbd26d2c38e35e32bc074badbc1bb733f131f
parent 385093 63bd999df89e57d28bbde06182ed3beeefac6cd6
child 385095 5de1086915e8146e75ec30f82db43b0d115f13b3
push id22427
push usermozilla@buttercookie.de
push dateThu, 07 Jul 2016 17:55:47 +0000
reviewerskats
bugs1282902
milestone50.0a1
Bug 1282902 - Part 3 - Let the MobileViewportManager recalculate the saved resolution if the display width changed before restoring. r=kats The mobile session store saves the current document resolution in order to restore the previous zoom level when restoring a page. If the display width has changed since the session data was captured (e.g. because the device was rotated), the resolution might have to be scaled appropriately. Currently, the session store does this scaling by itself by comparing the stored and current window widths, however this implementation is slightly simplified and doesn't cover all use cases, which means some pages can be restored at a wrong zoom level after rotation. To correctly cover all cases, the session store would have to compare viewport widths, too. Because the MobileViewportManager doesn't wait for the session store to set the restore resolution, the latter has to call setRestoreResolution() as early as possible in order to guarantee that the restore resolution is set before the first paint of the document. Therefore the session store currently calls this after receiving a LocationChange notification. However at that time, the correct viewport for the current document is not yet available, which means the resolution cannot be recalculated by the session store at that point. Therefore, this patch changes the approach taken and lets the MVM handle all resolution calculations instead. The session store now simply passes the stored previous display dimensions along with the previous document resolution to the MVM, which can then compare them to the current display and viewport widths and scale the resolution appropriately before using it during first paint. MozReview-Commit-ID: IGxWw87yftK
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
layout/base/MobileViewportManager.cpp
layout/base/MobileViewportManager.h
layout/base/nsIPresShell.h
layout/base/nsPresShell.cpp
layout/base/nsPresShell.h
mobile/android/components/SessionStore.js
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -551,24 +551,27 @@ nsDOMWindowUtils::SetResolutionAndScaleT
   }
 
   presShell->SetResolutionAndScaleTo(aResolution);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::SetRestoreResolution(float aResolution)
+nsDOMWindowUtils::SetRestoreResolution(float aResolution,
+                                       uint32_t aDisplayWidth,
+                                       uint32_t aDisplayHeight)
 {
   nsIPresShell* presShell = GetPresShell();
   if (!presShell) {
     return NS_ERROR_FAILURE;
   }
 
-  presShell->SetRestoreResolution(aResolution);
+  presShell->SetRestoreResolution(aResolution,
+    LayoutDeviceIntSize(aDisplayWidth, aDisplayHeight));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::GetResolution(float* aResolution)
 {
   nsIPresShell* presShell = GetPresShell();
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -224,20 +224,25 @@ interface nsIDOMWindowUtils : nsISupport
    * for pinch-zoom on mobile platforms.
    *
    * The caller of this method must have chrome privileges.
    */
   void setResolutionAndScaleTo(in float aResolution);
 
   /**
    * Set a resolution on the presShell which is the "restored" from history.
+   * The display dimensions are compared to their current values and used
+   * to scale the resolution value if necessary, e.g. if the device was
+   * rotated between saving and restoring of the session data.
    * This resolution should be used when painting for the first time. Calling
    * this too late may have no effect.
    */
-  void setRestoreResolution(in float aResolution);
+  void setRestoreResolution(in float aResolution,
+                            in uint32_t aDisplayWidth,
+                            in uint32_t aDisplayHeight);
 
   /**
    * Whether the resolution has been set by the user.
    * This gives a way to check whether the provided resolution is the default
    * value or restored from a previous session.
    *
    * Can only be accessed with chrome privileges.
    */
--- a/layout/base/MobileViewportManager.cpp
+++ b/layout/base/MobileViewportManager.cpp
@@ -80,19 +80,23 @@ MobileViewportManager::Destroy()
     observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data());
   }
 
   mDocument = nullptr;
   mPresShell = nullptr;
 }
 
 void
-MobileViewportManager::SetRestoreResolution(float aResolution)
+MobileViewportManager::SetRestoreResolution(float aResolution,
+                                            LayoutDeviceIntSize aDisplaySize)
 {
   mRestoreResolution = Some(aResolution);
+  ScreenIntSize restoreDisplaySize = ViewAs<ScreenPixel>(aDisplaySize,
+    PixelCastJustification::LayoutDeviceIsScreenForBounds);
+  mRestoreDisplaySize = Some(restoreDisplaySize);
 }
 
 void
 MobileViewportManager::RequestReflow()
 {
   MVM_LOG("%p: got a reflow request\n", this);
   RefreshViewportSize(false);
 }
@@ -190,17 +194,29 @@ MobileViewportManager::UpdateResolution(
 {
   CSSToLayoutDeviceScale cssToDev =
       mPresShell->GetPresContext()->CSSToDevPixelScale();
   LayoutDeviceToLayerScale res(mPresShell->GetResolution());
 
   if (mIsFirstPaint) {
     CSSToScreenScale defaultZoom;
     if (mRestoreResolution) {
-      defaultZoom = CSSToScreenScale(mRestoreResolution.value() * cssToDev.scale);
+    LayoutDeviceToLayerScale restoreResolution(mRestoreResolution.value());
+      if (mRestoreDisplaySize) {
+        CSSSize prevViewport = mDocument->GetViewportInfo(mRestoreDisplaySize.value()).GetSize();
+        float restoreDisplayWidthChangeRatio = (mRestoreDisplaySize.value().width > 0)
+          ? (float)aDisplaySize.width / (float)mRestoreDisplaySize.value().width : 1.0f;
+
+        restoreResolution =
+          ScaleResolutionWithDisplayWidth(restoreResolution,
+                                          restoreDisplayWidthChangeRatio,
+                                          aViewport,
+                                          prevViewport);
+      }
+      defaultZoom = CSSToScreenScale(restoreResolution.scale * cssToDev.scale);
       MVM_LOG("%p: restored zoom is %f\n", this, defaultZoom.scale);
       defaultZoom = ClampZoom(defaultZoom, aViewportInfo);
     } else {
       defaultZoom = aViewportInfo.GetDefaultZoom();
       MVM_LOG("%p: default zoom from viewport is %f\n", this, defaultZoom.scale);
       if (!aViewportInfo.IsDefaultZoomValid()) {
         defaultZoom = MaxScaleRatio(ScreenSize(aDisplaySize), aViewport);
         MVM_LOG("%p: Intrinsic computed zoom is %f\n", this, defaultZoom.scale);
--- a/layout/base/MobileViewportManager.h
+++ b/layout/base/MobileViewportManager.h
@@ -24,18 +24,22 @@ public:
   NS_DECL_NSIOBSERVER
 
   MobileViewportManager(nsIPresShell* aPresShell,
                         nsIDocument* aDocument);
   void Destroy();
 
   /* Provide a resolution to use during the first paint instead of the default
    * resolution computed from the viewport info metadata. This is in the same
-   * "units" as the argument to nsDOMWindowUtils::SetResolutionAndScaleTo. */
-  void SetRestoreResolution(float aResolution);
+   * "units" as the argument to nsDOMWindowUtils::SetResolutionAndScaleTo.
+   * Also takes the previous display dimensions as they were at the time the
+   * resolution was stored in order to correctly adjust the resolution if the
+   * device was rotated in the meantime. */
+  void SetRestoreResolution(float aResolution,
+                            mozilla::LayoutDeviceIntSize aDisplaySize);
 
   /* Notify the MobileViewportManager that a reflow was requested in the
    * presShell.*/
   void RequestReflow();
 
   /* Notify the MobileViewportManager that the resolution on the presShell was
    * updated, and the SPCSPS needs to be updated. */
   void ResolutionUpdated();
@@ -81,12 +85,13 @@ private:
   nsCOMPtr<nsIDocument> mDocument;
   nsIPresShell* MOZ_NON_OWNING_REF mPresShell; // raw ref since the presShell owns this
   nsCOMPtr<nsIDOMEventTarget> mEventTarget;
   bool mIsFirstPaint;
   bool mPainted;
   mozilla::LayoutDeviceIntSize mDisplaySize;
   mozilla::CSSSize mMobileViewportSize;
   mozilla::Maybe<float> mRestoreResolution;
+  mozilla::Maybe<mozilla::ScreenIntSize> mRestoreDisplaySize;
 };
 
 #endif
 
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -43,16 +43,17 @@
 #include "nsWeakReference.h"
 #include <stdio.h> // for FILE definition
 #include "nsChangeHint.h"
 #include "nsRefPtrHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsPresArena.h"
 #include "nsMargin.h"
 #include "nsFrameState.h"
+#include "Units.h"
 #include "Visibility.h"
 
 #ifdef MOZ_B2G
 #include "nsIHardwareKeyHandler.h"
 #endif
 
 class nsDocShell;
 class nsIDocument;
@@ -1441,17 +1442,18 @@ public:
    * SetResolutionAndScaleTo(), and set to false by a call to SetResolution().
    */
   virtual bool ScaleToResolution() const = 0;
 
   /**
    * Used by session restore code to restore a resolution before the first
    * paint.
    */
-  virtual void SetRestoreResolution(float aResolution) = 0;
+  virtual void SetRestoreResolution(float aResolution,
+                                    mozilla::LayoutDeviceIntSize aDisplaySize) = 0;
 
   /**
    * Returns whether we are in a DrawWindow() call that used the
    * DRAWWINDOW_DO_NOT_FLUSH flag.
    */
   bool InDrawWindowNotFlushing() const
   { return mRenderFlags & STATE_DRAWWINDOW_NOT_FLUSHING; }
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5628,20 +5628,21 @@ float PresShell::GetCumulativeNonRootSca
       currentShell = parentCtx->PresShell();
     } else {
       currentShell = nullptr;
     }
   }
   return resolution;
 }
 
-void PresShell::SetRestoreResolution(float aResolution)
+void PresShell::SetRestoreResolution(float aResolution,
+                                     LayoutDeviceIntSize aDisplaySize)
 {
   if (mMobileViewportManager) {
-    mMobileViewportManager->SetRestoreResolution(aResolution);
+    mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize);
   }
 }
 
 void PresShell::SetRenderingState(const RenderingState& aState)
 {
   if (mRenderFlags != aState.mRenderFlags) {
     // Rendering state changed in a way that forces us to flush any
     // retained layers we might already have.
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -235,17 +235,18 @@ public:
     return SetResolutionImpl(aResolution, /* aScaleToResolution = */ false);
   }
   virtual nsresult SetResolutionAndScaleTo(float aResolution) override {
     return SetResolutionImpl(aResolution, /* aScaleToResolution = */ true);
   }
   virtual bool ScaleToResolution() const override;
   virtual float GetCumulativeResolution() override;
   virtual float GetCumulativeNonRootScaleResolution() override;
-  virtual void SetRestoreResolution(float aResolution) override;
+  virtual void SetRestoreResolution(float aResolution,
+                                    mozilla::LayoutDeviceIntSize aDisplaySize) override;
 
   //nsIViewObserver interface
 
   virtual void Paint(nsView* aViewToPaint, const nsRegion& aDirtyRegion,
                      uint32_t aFlags) override;
   virtual nsresult HandleEvent(nsIFrame* aFrame,
                                mozilla::WidgetGUIEvent* aEvent,
                                bool aDontRetargetEvents,
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -739,47 +739,37 @@ SessionStore.prototype = {
     content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
       Ci.nsIDOMWindowUtils).getResolution(zoom);
     scrolldata.zoom = {};
     scrolldata.zoom.resolution = zoom.value;
     log("onTabScroll() zoom level: " + zoom.value);
 
     // Save some data that'll help in adjusting the zoom level
     // when restoring in a different screen orientation.
-    let viewportInfo = this._getViewportInfo(aWindow.outerWidth, aWindow.outerHeight, content);
-    scrolldata.zoom.autoSize = viewportInfo.autoSize;
-    log("onTabScroll() autoSize: " + scrolldata.zoom.autoSize);
-    scrolldata.zoom.windowWidth = aWindow.outerWidth;
-    log("onTabScroll() windowWidth: " + scrolldata.zoom.windowWidth);
+    scrolldata.zoom.displaySize = this._getContentViewerSize(content);
+    log("onTabScroll() displayWidth: " + scrolldata.zoom.displaySize.width);
 
     // Save zoom and scroll data.
     data.scrolldata = scrolldata;
     log("onTabScroll() ran for tab " + aWindow.BrowserApp.getTabForBrowser(aBrowser).id);
     let evt = new Event("SSTabScrollCaptured", {"bubbles":true, "cancelable":false});
     aBrowser.dispatchEvent(evt);
     this.saveStateDelayed();
   },
 
-  _getViewportInfo: function ss_getViewportInfo(aDisplayWidth, aDisplayHeight, aWindow) {
-    let viewportInfo = {};
-    let defaultZoom = {}, allowZoom = {}, minZoom = {}, maxZoom ={},
-        width = {}, height = {}, autoSize = {};
+  _getContentViewerSize: function ss_getContentViewerSize(aWindow) {
+    let displaySize = {};
+    let width = {}, height = {};
     aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
-      Ci.nsIDOMWindowUtils).getViewportInfo(aDisplayWidth, aDisplayHeight,
-        defaultZoom, allowZoom, minZoom, maxZoom, width, height, autoSize);
+      Ci.nsIDOMWindowUtils).getContentViewerSize(width, height);
 
-    viewportInfo.defaultZoom = defaultZoom.value;
-    viewportInfo.allowZoom = allowZoom.value;
-    viewportInfo.minZoom = maxZoom.value;
-    viewportInfo.maxZoom = maxZoom.value;
-    viewportInfo.width = width.value;
-    viewportInfo.height = height.value;
-    viewportInfo.autoSize = autoSize.value;
+    displaySize.width = width.value;
+    displaySize.height = height.value;
 
-    return viewportInfo;
+    return displaySize;
   },
 
   saveStateDelayed: function ss_saveStateDelayed() {
     if (!this._saveTimer) {
       // Interval until the next disk operation is allowed
       let minimalDelay = this._lastSaveTime + this._interval - Date.now();
 
       // If we have to wait, set a timer, otherwise saveState directly
@@ -1371,51 +1361,34 @@ SessionStore.prototype = {
   _restoreTextData: function ss_restoreTextData(aFormData, aBrowser) {
     if (aFormData) {
       log("_restoreTextData()");
       FormData.restoreTree(aBrowser.contentWindow, aFormData);
     }
   },
 
   /**
-  * Restores the zoom level of the window. This needs to be called before
-  * first paint/load (whichever comes first) to take any effect.
-  */
+   * Restores the zoom level of the window. This needs to be called before
+   * first paint/load (whichever comes first) to take any effect.
+   */
   _restoreZoom: function ss_restoreZoom(aScrollData, aBrowser) {
-    if (aScrollData && aScrollData.zoom) {
-      let recalculatedZoom = this._recalculateZoom(aScrollData.zoom);
-      log("_restoreZoom(), resolution: " + recalculatedZoom);
+    if (aScrollData && aScrollData.zoom && aScrollData.zoom.displaySize) {
+      log("_restoreZoom(), resolution: " + aScrollData.zoom.resolution +
+          ", old displayWidth: " + aScrollData.zoom.displaySize.width);
 
       let utils = aBrowser.contentWindow.QueryInterface(
         Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
       // Restore zoom level.
-      utils.setRestoreResolution(recalculatedZoom);
+      utils.setRestoreResolution(aScrollData.zoom.resolution,
+                                 aScrollData.zoom.displaySize.width,
+                                 aScrollData.zoom.displaySize.height);
     }
   },
 
   /**
-  * Recalculates the zoom level to account for a changed display width,
-  * e.g. because the device was rotated.
-  */
-  _recalculateZoom: function ss_recalculateZoom(aZoomData) {
-    let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
-
-    // Pages with "width=device-width" won't need any zoom level scaling.
-    if (!aZoomData.autoSize) {
-      let oldWidth = aZoomData.windowWidth;
-      let newWidth = browserWin.outerWidth;
-      if (oldWidth != newWidth && oldWidth > 0 && newWidth > 0) {
-        log("_recalculateZoom(), old resolution: " + aZoomData.resolution);
-        return newWidth / oldWidth * aZoomData.resolution;
-      }
-    }
-    return aZoomData.resolution;
-  },
-
-  /**
   * Takes serialized scroll positions and restores them into the given browser.
   */
   _restoreScrollPosition: function ss_restoreScrollPosition(aScrollData, aBrowser) {
     if (aScrollData) {
       log("_restoreScrollPosition()");
       ScrollPosition.restoreTree(aBrowser.contentWindow, aScrollData);
     }
   },