Bug 1494422 - Introduce extend-to-zoom and resolve width and height using it. r=botond
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Fri, 19 Oct 2018 04:19:21 +0000
changeset 500721 a7172b2e62e0cdcf7c492c06b21f08be13d093b4
parent 500720 9217bce3d56119840999271a3627f8a4b55b9d9d
child 500722 d94fc104be3b3cc4d0d9b08678648863932ca269
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond
bugs1494422
milestone64.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 1494422 - Introduce extend-to-zoom and resolve width and height using it. r=botond The relevant parts of the spec are: https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height https://drafts.csswg.org/css-device-adapt/#resolve-width https://drafts.csswg.org/css-device-adapt/#resolve-height This patch also introduces the parsing steps for width and height values in viewport meta tag. https://drafts.csswg.org/css-device-adapt/#width-and-height-properties Differential Revision: https://phabricator.services.mozilla.com/D8690
dom/base/nsDocument.cpp
dom/base/nsIDocument.h
dom/base/nsViewportInfo.cpp
dom/base/nsViewportInfo.h
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -7223,16 +7223,79 @@ nsIDocument::AdoptNode(nsINode& aAdopted
   }
 
   NS_ASSERTION(adoptedNode->OwnerDoc() == this,
                "Should still be in the document we just got adopted into");
 
   return adoptedNode;
 }
 
+void
+nsIDocument::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
+                                               const nsAString& aHeightString,
+                                               const nsAString& aScaleString)
+{
+  // The width and height properties
+  // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
+  //
+  // The width and height viewport <META> properties are translated into width
+  // and height descriptors, setting the min-width/min-height value to
+  // extend-to-zoom and the max-width/max-height value to the length from the
+  // viewport <META> property as follows:
+  //
+  // 1. Non-negative number values are translated to pixel lengths, clamped to
+  //    the range: [1px, 10000px]
+  // 2. Negative number values are dropped
+  // 3. device-width and device-height translate to 100vw and 100vh respectively
+  // 4. Other keywords and unknown values translate to 1px
+  mMinWidth = nsViewportInfo::Auto;
+  mMaxWidth = nsViewportInfo::Auto;
+  if (!aWidthString.IsEmpty()) {
+    mMinWidth = nsViewportInfo::ExtendToZoom;
+    if (aWidthString.EqualsLiteral("device-width")) {
+      mMaxWidth = nsViewportInfo::DeviceSize;
+    } else {
+      nsresult widthErrorCode;
+      mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
+      if (NS_FAILED(widthErrorCode)) {
+        mMaxWidth = 1.0f;
+      } else if (mMaxWidth >= 0.0f) {
+        mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
+      } else {
+        mMaxWidth = nsViewportInfo::Auto;
+      }
+    }
+  // FIXME: Check the scale is not 'auto' once we support auto value for it.
+  } else if (!aScaleString.IsEmpty()) {
+    if (aHeightString.IsEmpty()) {
+      mMinWidth = nsViewportInfo::ExtendToZoom;
+      mMaxWidth = nsViewportInfo::ExtendToZoom;
+    }
+  }
+
+  mMinHeight = nsViewportInfo::Auto;
+  mMaxHeight = nsViewportInfo::Auto;
+  if (!aHeightString.IsEmpty()) {
+    mMinHeight = nsViewportInfo::ExtendToZoom;
+    if (aHeightString.EqualsLiteral("device-height")) {
+      mMaxHeight = nsViewportInfo::DeviceSize;
+    } else {
+      nsresult heightErrorCode;
+      mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
+      if (NS_FAILED(heightErrorCode)) {
+        mMaxHeight = 1.0f;
+      } else if (mMaxHeight >= 0.0f) {
+        mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
+      } else {
+        mMaxHeight = nsViewportInfo::Auto;
+      }
+    }
+  }
+}
+
 nsViewportInfo
 nsIDocument::GetViewportInfo(const ScreenIntSize& aDisplaySize)
 {
   MOZ_ASSERT(mPresShell);
 
   // Compute the CSS-to-LayoutDevice pixel scale as the product of the
   // widget scale and the full zoom.
   nsPresContext* context = mPresShell->GetPresContext();
@@ -7347,37 +7410,35 @@ nsIDocument::GetViewportInfo(const Scree
     nsresult scaleErrorCode;
     mScaleFloat = LayoutDeviceToScreenScale(scaleStr.ToFloat(&scaleErrorCode));
 
     nsAutoString widthStr, heightStr;
 
     GetHeaderData(nsGkAtoms::viewport_height, heightStr);
     GetHeaderData(nsGkAtoms::viewport_width, widthStr);
 
+    // Parse width and height properties
+    // This function sets m{Min,Max}{Width,Height}.
+    ParseWidthAndHeightInMetaViewport(widthStr, heightStr, scaleStr);
+
     mAutoSize = false;
-
-    if (widthStr.EqualsLiteral("device-width")) {
+    if (mMaxWidth == nsViewportInfo::DeviceSize) {
       mAutoSize = true;
     }
-
     if (widthStr.IsEmpty() &&
-        (heightStr.EqualsLiteral("device-height") ||
+        (mMaxHeight == nsViewportInfo::DeviceSize ||
          (mScaleFloat.scale == 1.0)))
     {
       mAutoSize = true;
     }
 
-    nsresult widthErrorCode, heightErrorCode;
-    mViewportSize.width = widthStr.ToInteger(&widthErrorCode);
-    mViewportSize.height = heightStr.ToInteger(&heightErrorCode);
-
     // If width or height has not been set to a valid number by this point,
     // fall back to a default value.
-    mValidWidth = (!widthStr.IsEmpty() && NS_SUCCEEDED(widthErrorCode) && mViewportSize.width > 0);
-    mValidHeight = (!heightStr.IsEmpty() && NS_SUCCEEDED(heightErrorCode) && mViewportSize.height > 0);
+    mValidWidth = (!widthStr.IsEmpty() && mMaxWidth > 0);
+    mValidHeight = (!heightStr.IsEmpty() && mMaxHeight > 0);
 
     // If the width is set to some unrecognized value, and there is no
     // height set, treat it as if device-width were specified.
     if ((!mValidWidth && !widthStr.IsEmpty()) && !mValidHeight) {
       mAutoSize = true;
     }
 
     mAllowZoom = true;
@@ -7414,45 +7475,128 @@ nsIDocument::GetViewportInfo(const Scree
       // ForceUserScalable pref. Other codepaths that return nsViewportInfo
       // instances are all consistent with ForceUserScalable() already.
       effectiveMinScale = kViewportMinScale;
       effectiveMaxScale = kViewportMaxScale;
       effectiveValidMaxScale = true;
       effectiveAllowZoom = true;
     }
 
-    CSSSize size = mViewportSize;
-
-    if (!mValidWidth) {
-      if (mValidHeight && !aDisplaySize.IsEmpty()) {
-        size.width = size.height * aDisplaySize.width / aDisplaySize.height;
-      } else {
+    // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
+    auto ComputeExtendZoom = [&]() -> float {
+      if (mValidScaleFloat && effectiveValidMaxScale) {
+        return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
+      }
+      if (mValidScaleFloat) {
+        return mScaleFloat.scale;
+      }
+      if (effectiveValidMaxScale) {
+        return effectiveMaxScale.scale;
+      }
+      return nsViewportInfo::Auto;
+    };
+
+    // Resolving 'extend-to-zoom'
+    // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
+    float extendZoom = ComputeExtendZoom();
+
+    CSSCoord minWidth = mMinWidth;
+    CSSCoord maxWidth = mMaxWidth;
+    CSSCoord minHeight = mMinHeight;
+    CSSCoord maxHeight = mMaxHeight;
+
+    // aDisplaySize is in screen pixels; convert them to CSS pixels for the
+    // viewport size.
+    CSSToScreenScale defaultPixelScale =
+      layoutDeviceScale * LayoutDeviceToScreenScale(1.0f);
+    CSSSize displaySize = ScreenSize(aDisplaySize) / defaultPixelScale;
+
+    // Resolve device-width and device-height first.
+    if (maxWidth == nsViewportInfo::DeviceSize) {
+      maxWidth = displaySize.width;
+    }
+    if (maxHeight == nsViewportInfo::DeviceSize) {
+      maxHeight = displaySize.height;
+    }
+    if (extendZoom == nsViewportInfo::Auto) {
+      if (maxWidth == nsViewportInfo::ExtendToZoom) {
+        maxWidth = nsViewportInfo::Auto;
+      }
+      if (maxHeight == nsViewportInfo::ExtendToZoom) {
+        maxHeight = nsViewportInfo::Auto;
+      }
+      if (minWidth == nsViewportInfo::ExtendToZoom) {
+        minWidth = maxWidth;
+      }
+      if (minHeight == nsViewportInfo::ExtendToZoom) {
+        minHeight = maxHeight;
+      }
+    } else {
+      CSSSize extendSize = displaySize / extendZoom;
+      if (maxWidth == nsViewportInfo::ExtendToZoom) {
+        maxWidth = extendSize.width;
+      }
+      if (maxHeight == nsViewportInfo::ExtendToZoom) {
+        maxHeight = extendSize.height;
+      }
+      if (minWidth == nsViewportInfo::ExtendToZoom) {
+        minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
+      }
+      if (minHeight == nsViewportInfo::ExtendToZoom) {
+        minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
+      }
+    }
+    // Resolve initial width and height from min/max descriptors
+    // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
+    CSSCoord width = nsViewportInfo::Auto;
+    if (minWidth != nsViewportInfo::Auto || maxWidth != nsViewportInfo::Auto) {
+      width =
+        nsViewportInfo::Max(minWidth,
+                            nsViewportInfo::Min(maxWidth, displaySize.width));
+    }
+    CSSCoord height = nsViewportInfo::Auto;
+    if (minHeight != nsViewportInfo::Auto || maxHeight != nsViewportInfo::Auto) {
+      height =
+        nsViewportInfo::Max(minHeight,
+                            nsViewportInfo::Min(maxHeight, displaySize.height));
+    }
+
+    // Resolve width value
+    // https://drafts.csswg.org/css-device-adapt/#resolve-width
+    if (width == nsViewportInfo::Auto) {
+      if (height == nsViewportInfo::Auto ||
+          aDisplaySize.height == 0) {
         // Stretch CSS pixel size of viewport to keep device pixel size
         // unchanged after full zoom applied.
         // See bug 974242.
-        size.width = gfxPrefs::DesktopViewportWidth() / fullZoom;
-      }
-    }
-
-    if (!mValidHeight) {
-      if (!aDisplaySize.IsEmpty()) {
-        size.height = size.width * aDisplaySize.height / aDisplaySize.width;
+        width = gfxPrefs::DesktopViewportWidth() / fullZoom;
       } else {
-        size.height = size.width;
-      }
-    }
+        width = height * aDisplaySize.width / aDisplaySize.height;
+      }
+    }
+
+    // Resolve height value
+    // https://drafts.csswg.org/css-device-adapt/#resolve-height
+    if (height == nsViewportInfo::Auto) {
+      if (aDisplaySize.width == 0) {
+        height = aDisplaySize.height;
+      } else {
+        height = width * aDisplaySize.height / aDisplaySize.width;
+      }
+    }
+    MOZ_ASSERT(width != nsViewportInfo::Auto && height != nsViewportInfo::Auto);
+
+    CSSSize size(width, height);
 
     CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
     CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
     CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
 
     if (mAutoSize) {
-      // aDisplaySize is in screen pixels; convert them to CSS pixels for the viewport size.
-      CSSToScreenScale defaultPixelScale = layoutDeviceScale * LayoutDeviceToScreenScale(1.0f);
-      size = ScreenSize(aDisplaySize) / defaultPixelScale;
+      size = displaySize;
     }
 
     size.width = clamped(size.width, float(kViewportMinSize.width), float(kViewportMaxSize.width));
 
     // Also recalculate the default zoom, if it wasn't specified in the metadata,
     // and the width is specified.
     if (mScaleStrEmpty && !mWidthStrEmpty) {
       CSSToScreenScale defaultScale(float(aDisplaySize.width) / size.width);
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -3679,16 +3679,20 @@ protected:
    */
   void TriggerInitialDocumentTranslation();
 
   RefPtr<mozilla::dom::DocumentL10n> mDocumentL10n;
 
 private:
   void InitializeLocalization(nsTArray<nsString>& aResourceIds);
 
+  void ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
+                                         const nsAString& aHeightString,
+                                         const nsAString& aScaleString);
+
   nsTArray<nsString> mL10nResources;
 
 public:
   bool IsThirdParty();
 
   bool IsScopedStyleEnabled();
 
   nsINode* GetServoRestyleRoot() const
@@ -4703,17 +4707,21 @@ protected:
   nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
 
   // These member variables cache information about the viewport so we don't
   // have to recalculate it each time.
   mozilla::LayoutDeviceToScreenScale mScaleMinFloat;
   mozilla::LayoutDeviceToScreenScale mScaleMaxFloat;
   mozilla::LayoutDeviceToScreenScale mScaleFloat;
   mozilla::CSSToLayoutDeviceScale mPixelRatio;
-  mozilla::CSSSize mViewportSize;
+
+  mozilla::CSSCoord mMinWidth;
+  mozilla::CSSCoord mMaxWidth;
+  mozilla::CSSCoord mMinHeight;
+  mozilla::CSSCoord mMaxHeight;
 
   RefPtr<mozilla::EventListenerManager> mListenerManager;
 
   nsCOMPtr<nsIRunnable> mMaybeEndOutermostXBLUpdateRunner;
   nsCOMPtr<nsIRequest> mOnloadBlocker;
 
   nsTArray<RefPtr<mozilla::StyleSheet>> mAdditionalSheets[AdditionalSheetTypeCount];
 
--- a/dom/base/nsViewportInfo.cpp
+++ b/dom/base/nsViewportInfo.cpp
@@ -21,8 +21,41 @@ nsViewportInfo::ConstrainViewportValues(
     mDefaultZoomValid = false;
     mDefaultZoom = mMaxZoom;
   }
   if (mDefaultZoom < mMinZoom) {
     mDefaultZoomValid = false;
     mDefaultZoom = mMinZoom;
   }
 }
+
+static const float&
+MinOrMax(const float& aA, const float& aB,
+         const float& (*aMinOrMax)(const float&,
+                                   const float&))
+{
+  MOZ_ASSERT(aA != nsViewportInfo::ExtendToZoom &&
+             aA != nsViewportInfo::DeviceSize &&
+             aB != nsViewportInfo::ExtendToZoom &&
+             aB != nsViewportInfo::DeviceSize);
+  if (aA == nsViewportInfo::Auto) {
+    return aB;
+  }
+  if (aB == nsViewportInfo::Auto) {
+    return aA;
+  }
+  return aMinOrMax(aA, aB);
+}
+
+// static
+const float&
+nsViewportInfo::Min(const float& aA, const float& aB)
+{
+  return MinOrMax(aA, aB, std::min);
+}
+
+//static
+const float&
+nsViewportInfo::Max(const float& aA, const float& aB)
+{
+  return MinOrMax(aA, aB, std::max);
+}
+
--- a/dom/base/nsViewportInfo.h
+++ b/dom/base/nsViewportInfo.h
@@ -61,16 +61,28 @@ class MOZ_STACK_CLASS nsViewportInfo
     mozilla::CSSToScreenScale GetMinZoom() const { return mMinZoom; }
     mozilla::CSSToScreenScale GetMaxZoom() const { return mMaxZoom; }
 
     mozilla::CSSSize GetSize() const { return mSize; }
 
     bool IsAutoSizeEnabled() const { return mAutoSize; }
     bool IsZoomAllowed() const { return mAllowZoom; }
 
+    enum {
+      Auto = -1,
+      ExtendToZoom = -2,
+      DeviceSize = -3, // for device-width or device-height
+    };
+    // MIN/MAX computations where one of the arguments is auto resolve to the
+    // other argument. For instance, MIN(0.25, auto) = 0.25, and
+    // MAX(5, auto) = 5.
+    // https://drafts.csswg.org/css-device-adapt/#constraining-defs
+    static const float& Max(const float& aA, const float& aB);
+    static const float& Min(const float& aA, const float& aB);
+
   private:
 
     /**
      * Constrain the viewport calculations from the
      * nsIDocument::GetViewportInfo() function in order to always return
      * sane minimum/maximum values.
      */
     void ConstrainViewportValues();