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 490557 a7172b2e62e0cdcf7c492c06b21f08be13d093b4
parent 490556 9217bce3d56119840999271a3627f8a4b55b9d9d
child 490558 d94fc104be3b3cc4d0d9b08678648863932ca269
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersbotond
bugs1494422
milestone64.0a1
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();