Bug 1586144 - Factor dynamic toolbar max height into layout metrics. r=emilio,botond
authorHiroyuki Ikezoe <hikezoe.birchill@mozilla.com>
Thu, 14 Nov 2019 06:00:05 +0000
changeset 501905 9f96406e2da19b6d4e9f89163ff51c4e529779ae
parent 501904 b79f9f171fb110ee5469a8b6a645da77a44bfde8
child 501906 1f6692cbcebad058f1c4d322e3306314e5d534eb
push id100490
push userhikezoe.birchill@mozilla.com
push dateThu, 14 Nov 2019 06:02:24 +0000
treeherderautoland@828246fa2bdf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio, botond
bugs1586144
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1586144 - Factor dynamic toolbar max height into layout metrics. r=emilio,botond Now * nsPresContext::mVisibleArea is excluding the toolbar max height so that ICB is now static regardless of the dynamic toolbar transition * nsPresContext::mSizeForViewportUnits is introduced to resolve viewport units which is including the toolbar max height That means that with the dynamic toolbar max height; mVisibleArea < mSizeForViewportUnits See https://github.com/bokand/URLBarSizing for more detail backgrounds of this change. Depends on D50417 Differential Revision: https://phabricator.services.mozilla.com/D50418
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
dom/ipc/BrowserChild.h
layout/base/MobileViewportManager.cpp
layout/base/nsLayoutUtils.cpp
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/base/tests/file_dynamic_toolbar_max_height.html
layout/base/tests/mochitest.ini
layout/base/tests/test_dynamic_toolbar_max_height.html
servo/components/style/gecko/media_queries.rs
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1478,16 +1478,34 @@ nsDOMWindowUtils::GetVisualViewportOffse
   nsPoint offset = presShell->GetVisualViewportOffset();
   *aOffsetX = nsPresContext::AppUnitsToIntCSSPixels(offset.x);
   *aOffsetY = nsPresContext::AppUnitsToIntCSSPixels(offset.y);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::SetDynamicToolbarMaxHeight(uint32_t aHeightInScreen) {
+  if (aHeightInScreen > INT32_MAX) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  RefPtr<nsPresContext> presContext = GetPresContext();
+  if (!presContext) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(presContext->IsRootContentDocumentCrossProcess());
+
+  presContext->SetDynamicToolbarMaxHeight(ScreenIntCoord(aHeightInScreen));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::GetScrollbarSize(bool aFlushLayout, int32_t* aWidth,
                                    int32_t* aHeight) {
   *aWidth = 0;
   *aHeight = 0;
 
   nsCOMPtr<Document> doc = GetDocument();
   NS_ENSURE_STATE(doc);
 
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -862,16 +862,22 @@ interface nsIDOMWindowUtils : nsISupport
   void getVisualViewportOffsetRelativeToLayoutViewport(out float aOffsetX,
                                                        out float aOffsetY);
 
   /**
    * Returns the scroll position of the window's visual viewport.
    */
   void getVisualViewportOffset(out long aOffsetX, out long aOffsetY);
 
+  /**
+   * Sets the maximum height of the dynamic toolbar in Screen pixel units.
+   */
+  [can_run_script]
+  void setDynamicToolbarMaxHeight(in uint32_t aHeightInScreen);
+
   const long FLUSH_NONE = -1;
   const long FLUSH_STYLE = 0;
   const long FLUSH_LAYOUT = 1;
   const long FLUSH_DISPLAY = 2;
 
   /**
    * Returns true if a flush of the given type is needed.
    */
--- a/dom/ipc/BrowserChild.h
+++ b/dom/ipc/BrowserChild.h
@@ -281,16 +281,17 @@ class BrowserChild final : public nsMess
   mozilla::ipc::IPCResult RecvUpdateDimensions(
       const mozilla::dom::DimensionInfo& aDimensionInfo);
   mozilla::ipc::IPCResult RecvSizeModeChanged(const nsSizeMode& aSizeMode);
 
   mozilla::ipc::IPCResult RecvChildToParentMatrix(
       const mozilla::Maybe<mozilla::gfx::Matrix4x4>& aMatrix,
       const mozilla::ScreenRect& aRemoteDocumentRect);
 
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   mozilla::ipc::IPCResult RecvDynamicToolbarMaxHeightChanged(
       const mozilla::ScreenIntCoord& aHeight);
 
   mozilla::ipc::IPCResult RecvActivate();
 
   mozilla::ipc::IPCResult RecvDeactivate();
 
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
--- a/layout/base/MobileViewportManager.cpp
+++ b/layout/base/MobileViewportManager.cpp
@@ -455,16 +455,19 @@ void MobileViewportManager::UpdateResolu
 }
 
 ScreenIntSize MobileViewportManager::GetCompositionSize(
     const ScreenIntSize& aDisplaySize) const {
   if (!mContext) {
     return ScreenIntSize();
   }
 
+  // FIXME: Bug 1586986 - To update VisualViewport in response to the dynamic
+  // toolbar transition we probably need to include the dynamic toolbar
+  // _current_ height.
   ScreenIntSize compositionSize(aDisplaySize);
   ScreenMargin scrollbars =
       mContext->ScrollbarAreaToExcludeFromCompositionBounds()
       // Scrollbars are not subject to resolution scaling, so LD pixels =
       // Screen pixels for them.
       * LayoutDeviceToScreenScale(1.0f);
 
   compositionSize.width =
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -8419,16 +8419,29 @@ bool nsLayoutUtils::GetContentViewerSize
   nsCOMPtr<nsIContentViewer> cv;
   docShell->GetContentViewer(getter_AddRefs(cv));
   if (!cv) {
     return false;
   }
 
   nsIntRect bounds;
   cv->GetBounds(bounds);
+
+  if (aPresContext->HasDynamicToolbar() && !bounds.IsEmpty()) {
+    MOZ_ASSERT(aPresContext->IsRootContentDocumentCrossProcess());
+    MOZ_ASSERT(bounds.height > aPresContext->GetDynamicToolbarMaxHeight());
+    bounds.height -= aPresContext->GetDynamicToolbarMaxHeight();
+    // Collapse the size in the case the dynamic toolbar max height is greater
+    // than the content bound height so that hopefully embedders of GeckoView
+    // may notice they set wrong dynamic toolbar max height.
+    if (bounds.height < 0) {
+      bounds.height = 0;
+    }
+  }
+
   aOutSize = LayoutDeviceIntRect::FromUnknownRect(bounds).Size();
   return true;
 }
 
 static bool UpdateCompositionBoundsForRCDRSF(ParentLayerRect& aCompBounds,
                                              nsPresContext* aPresContext,
                                              bool aScaleContentViewerSize) {
   nsIFrame* rootFrame = aPresContext->PresShell()->GetRootFrame();
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -82,16 +82,17 @@
 #include "mozilla/StaticPrefs_layout.h"
 #include "mozilla/StaticPrefs_zoom.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/dom/Performance.h"
 #include "mozilla/dom/PerformanceTiming.h"
 #include "mozilla/layers/APZThreadUtils.h"
+#include "MobileViewportManager.h"
 
 // Needed for Start/Stop of Image Animation
 #include "imgIContainer.h"
 #include "nsIImageLoadingContent.h"
 
 #include "nsBidiUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsBidi.h"
@@ -417,16 +418,22 @@ void nsPresContext::AppUnitsPerDevPixelC
   }
 
   MediaFeatureValuesChanged({RestyleHint::RecascadeSubtree(),
                              NS_STYLE_HINT_REFLOW,
                              MediaFeatureChangeReason::ResolutionChange});
 
   mCurAppUnitsPerDevPixel = mDeviceContext->AppUnitsPerDevPixel();
 
+  // Recompute the size for vh units since it's changed by the dynamic toolbar
+  // max height which is stored in screen coord.
+  if (IsRootContentDocumentCrossProcess()) {
+    AdjustSizeForViewportUnits();
+  }
+
   // nsSubDocumentFrame uses a AppUnitsPerDevPixel difference between parent and
   // child document to determine if it needs to build a nsDisplayZoom item. So
   // if we that changes then we need to invalidate the subdoc frame so that
   // item gets created/removed.
   if (mPresShell) {
     if (nsIFrame* frame = mPresShell->GetRootFrame()) {
       frame = nsLayoutUtils::GetCrossDocParentFrame(frame);
       if (frame) {
@@ -2461,23 +2468,76 @@ void nsPresContext::FlushFontFeatureValu
 
   if (mFontFeatureValuesDirty) {
     ServoStyleSet* styleSet = mPresShell->StyleSet();
     mFontFeatureValuesLookup = styleSet->BuildFontFeatureValueSet();
     mFontFeatureValuesDirty = false;
   }
 }
 
+void nsPresContext::SetVisibleArea(const nsRect& r) {
+  if (!r.IsEqualEdges(mVisibleArea)) {
+    mVisibleArea = r;
+    mSizeForViewportUnits = mVisibleArea.Size();
+    if (IsRootContentDocumentCrossProcess()) {
+      AdjustSizeForViewportUnits();
+    }
+    // Visible area does not affect media queries when paginated.
+    if (!IsPaginated()) {
+      MediaFeatureValuesChanged(
+          {mozilla::MediaFeatureChangeReason::ViewportChange});
+    }
+  }
+}
+
 void nsPresContext::SetDynamicToolbarMaxHeight(ScreenIntCoord aHeight) {
   MOZ_ASSERT(IsRootContentDocumentCrossProcess());
 
   if (mDynamicToolbarMaxHeight == aHeight) {
     return;
   }
   mDynamicToolbarMaxHeight = aHeight;
+
+  AdjustSizeForViewportUnits();
+
+  if (RefPtr<mozilla::PresShell> presShell = mPresShell) {
+    // Changing the max height of the dynamic toolbar changes the ICB size, we
+    // need to kick a reflow with the current window dimensions since the max
+    // height change doesn't change the window dimensions but
+    // PresShell::ResizeReflow ends up subtracting the new dynamic toolbar
+    // height from the window dimensions and kick a reflow with the proper ICB
+    // size.
+    nscoord currentWidth, currentHeight;
+    presShell->GetViewManager()->GetWindowDimensions(&currentWidth,
+                                                     &currentHeight);
+    presShell->ResizeReflow(currentWidth, currentHeight,
+                            ResizeReflowOptions::NoOption);
+  }
+}
+
+void nsPresContext::AdjustSizeForViewportUnits() {
+  MOZ_ASSERT(IsRootContentDocumentCrossProcess());
+  if (mVisibleArea.height == NS_UNCONSTRAINEDSIZE) {
+    // Ignore `NS_UNCONSTRAINEDSIZE` since it's a temporary state during a
+    // reflow. We will end up calling this function again with a proper size in
+    // the same reflow.
+    return;
+  }
+
+  if (MOZ_UNLIKELY(mVisibleArea.height +
+                       NSIntPixelsToAppUnits(mDynamicToolbarMaxHeight,
+                                             mCurAppUnitsPerDevPixel) >
+                   nscoord_MAX)) {
+    MOZ_ASSERT_UNREACHABLE("The dynamic toolbar max height is probably wrong");
+    return;
+  }
+
+  mSizeForViewportUnits.height =
+      mVisibleArea.height +
+      NSIntPixelsToAppUnits(mDynamicToolbarMaxHeight, mCurAppUnitsPerDevPixel);
 }
 
 #ifdef DEBUG
 
 void nsPresContext::ValidatePresShellAndDocumentReleation() const {
   NS_ASSERTION(!mPresShell || !mPresShell->GetDocument() ||
                    mPresShell->GetDocument() == mDocument,
                "nsPresContext doesn't have the same document as nsPresShell!");
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -364,32 +364,37 @@ class nsPresContext : public nsISupports
    * nscoord units (as scaled by the device context).
    */
   nsRect GetVisibleArea() const { return mVisibleArea; }
 
   /**
    * Set the currently visible area. The units for r are standard
    * nscoord units (as scaled by the device context).
    */
-  void SetVisibleArea(const nsRect& r) {
-    if (!r.IsEqualEdges(mVisibleArea)) {
-      mVisibleArea = r;
-      // Visible area does not affect media queries when paginated.
-      if (!IsPaginated()) {
-        MediaFeatureValuesChanged(
-            {mozilla::MediaFeatureChangeReason::ViewportChange});
-      }
-    }
-  }
+  void SetVisibleArea(const nsRect& r);
 
   /**
    * Set the maximum height of the dynamic toolbar in nscoord units.
    */
+  MOZ_CAN_RUN_SCRIPT
   void SetDynamicToolbarMaxHeight(mozilla::ScreenIntCoord aHeight);
 
+  mozilla::ScreenIntCoord GetDynamicToolbarMaxHeight() const {
+    MOZ_ASSERT(IsRootContentDocumentCrossProcess());
+    return mDynamicToolbarMaxHeight;
+  }
+
+  /**
+   * Returns true if we are using the dynamic toolbar.
+   */
+  bool HasDynamicToolbar() const {
+    MOZ_ASSERT(IsRootContentDocumentCrossProcess());
+    return mDynamicToolbarMaxHeight > 0;
+  }
+
   /**
    * Return true if this presentation context is a paginated
    * context.
    */
   bool IsPaginated() const { return mPaginated; }
 
   /**
    * Sets whether the presentation context can scroll for a paginated
@@ -1109,16 +1114,20 @@ class nsPresContext : public nsISupports
 
   struct TransactionInvalidations {
     TransactionId mTransactionId;
     nsTArray<nsRect> mInvalidations;
     bool mIsWaitingForPreviousTransaction = false;
   };
   TransactionInvalidations* GetInvalidations(TransactionId aTransactionId);
 
+  // This should be called only when we update mVisibleArea or
+  // mDynamicToolbarMaxHeight or `app units per device pixels` changes.
+  void AdjustSizeForViewportUnits();
+
   // IMPORTANT: The ownership implicit in the following member variables
   // has been explicitly checked.  If you add any members to this class,
   // please make the ownership explicit (pinkerton, scc).
 
   nsPresContextType mType;
   // the PresShell owns a strong reference to the nsPresContext, and is
   // responsible for nulling this pointer before it is destroyed
   mozilla::PresShell* MOZ_NON_OWNING_REF mPresShell;  // [WEAK]
@@ -1168,16 +1177,20 @@ class nsPresContext : public nsISupports
   AutoTArray<TransactionInvalidations, 4> mTransactions;
 
   // text performance metrics
   mozilla::UniquePtr<gfxTextPerfMetrics> mTextPerf;
 
   mozilla::UniquePtr<gfxMissingFontRecorder> mMissingFonts;
 
   nsRect mVisibleArea;
+  // This value is used to resolve viewport units.
+  // On mobile this size is including the dynamic toolbar maximum height below.
+  // On desktops this size is pretty much the same as |mVisibleArea|.
+  nsSize mSizeForViewportUnits;
   // The maximum height of the dynamic toolbar on mobile.
   mozilla::ScreenIntCoord mDynamicToolbarMaxHeight;
   nsSize mPageSize;
   float mPageScale;
   float mPPScale;
 
   // This is a non-owning pointer. May be null. If non-null, it's guaranteed to
   // be pointing to an element that's still alive, because we'll reset it in
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/file_dynamic_toolbar_max_height.html
@@ -0,0 +1,59 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
+<title>Tests metrics with dynamic toolbar</title>
+<script>
+const ok = opener.ok.bind(opener);
+const is = opener.is.bind(opener);
+const original_finish = opener.SimpleTest.finish;
+const SimpleTest = opener.SimpleTest;
+const add_task = opener.add_task;
+SimpleTest.finish = function finish() {
+  self.close();
+  original_finish();
+}
+</script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<style>
+html {
+  scrollbar-width: none;
+}
+#percent {
+  position: absolute;
+  height: 100%;
+}
+#vh {
+  position: absolute;
+  height: 100vh;
+}
+</style>
+<div id="percent"></div>
+<div id="vh"></div>
+<script>
+'use strict';
+
+SpecialPowers.DOMWindowUtils.setDynamicToolbarMaxHeight(0);
+
+let percentHeight = getComputedStyle(percent).height;
+let vhHeight = getComputedStyle(vh).height;
+is(percentHeight, vhHeight,
+   "%-units and vh-units should be the same when the dynamic toolbar max " +
+   "height is zero");
+
+SpecialPowers.DOMWindowUtils.setDynamicToolbarMaxHeight(50);
+
+percentHeight = getComputedStyle(percent).height;
+vhHeight = getComputedStyle(vh).height;
+is(parseInt(percentHeight) + 50, parseInt(vhHeight),
+   "vh units should be 50px greater than %-units");
+is(document.documentElement.clientHeight, parseInt(percentHeight),
+   "documentElement.clientHeight should equal to %-units");
+is(window.innerHeight, parseInt(percentHeight),
+   "window.innerHeight should equal to %-units when the dynamic toolbar is " +
+   "visible");
+ok(matchMedia(`(height: ${percentHeight})`).matches,
+   "Media Queries' height is not including the dynamic toolbar max height");
+
+SimpleTest.finish();
+
+</script>
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -134,16 +134,18 @@ support-files =
 [test_bug1226904.html]
 support-files = bug1226904.html
 [test_bug1246622.html]
 [test_bug1278021.html]
 [test_bug1448730.html]
 support-files = bug1448730.html
 [test_bug1515822.html]
 [test_bug1550869_video.html]
+[test_dynamic_toolbar_max_height.html]
+support-files = file_dynamic_toolbar_max_height.html
 [test_emulateMedium.html]
 [test_emulate_color_scheme.html]
 [test_event_target_iframe_oop.html]
 skip-if = e10s # bug 1020135, nested oop iframes not supported
 support-files = bug921928_event_target_iframe_apps_oop.html
 [test_event_target_radius.html]
 skip-if = toolkit == 'android' # Bug 1355836
 [test_flush_on_paint.html]
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/test_dynamic_toolbar_max_height.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<div id="log"></div>
+<script>
+"use strict";
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+  {
+    set: [
+      ["dom.meta-viewport.enabled", true],
+      ["apz.allow_zooming", true],
+    ],
+  },
+  function() {
+    // We need to open a new window because the API to set the dynamic toolbar
+    // max height works only in the top content document.
+    window.open("file_dynamic_toolbar_max_height.html");
+  }
+);
+</script>
+</html>
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -232,17 +232,23 @@ impl Device {
         let area = &pc.mVisibleArea;
         Size2D::new(Au(area.width), Au(area.height))
     }
 
     /// Returns the current viewport size in app units, recording that it's been
     /// used for viewport unit resolution.
     pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D<Au> {
         self.used_viewport_size.store(true, Ordering::Relaxed);
-        self.au_viewport_size()
+
+        let pc = match self.pres_context() {
+            Some(pc) => pc,
+            None => return Size2D::new(Au(0), Au(0)),
+        };
+        let size = &pc.mSizeForViewportUnits;
+        Size2D::new(Au(size.width), Au(size.height))
     }
 
     /// Returns whether we ever looked up the viewport size of the Device.
     pub fn used_viewport_size(&self) -> bool {
         self.used_viewport_size.load(Ordering::Relaxed)
     }
 
     /// Returns the device pixel ratio.