Bug 1423017 - Add a telemetry for out-of-reach overflowing on root. r?botond draft
authorXidorn Quan <me@upsuper.org>
Fri, 01 Jun 2018 15:35:32 +1000
changeset 805789 9813870413dbf9f6990be6ffb818d1d9a04d0ac4
parent 805602 8d9e170939b8bf62f09b35739295b4b98df99c50
push id112755
push userxquan@mozilla.com
push dateFri, 08 Jun 2018 12:49:02 +0000
reviewersbotond
bugs1423017
milestone62.0a1
Bug 1423017 - Add a telemetry for out-of-reach overflowing on root. r?botond MozReview-Commit-ID: 2CyZTVBFP59
dom/base/nsDocument.cpp
dom/base/nsIDocument.h
layout/generic/nsGfxScrollFrame.cpp
toolkit/components/telemetry/Histograms.json
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1492,16 +1492,17 @@ nsIDocument::nsIDocument()
     mUseCounters(0),
     mChildDocumentUseCounters(0),
     mNotifiedPageForUseCounter(0),
     mUserHasInteracted(false),
     mUserHasActivatedInteraction(false),
     mStackRefCnt(0),
     mUpdateNestLevel(0),
     mViewportType(Unknown),
+    mViewportOverflowType(ViewportOverflowType::NoOverflow),
     mSubDocuments(nullptr),
     mHeaderData(nullptr),
     mFlashClassification(FlashClassification::Unclassified),
     mBoxObjectTable(nullptr),
     mCurrentOrientationAngle(0),
     mCurrentOrientationType(OrientationType::Portrait_primary),
     mServoRestyleRootDirtyBits(0),
     mThrowOnDynamicMarkupInsertionCounter(0),
@@ -3685,16 +3686,17 @@ nsIDocument::SetHeaderData(nsAtom* aHead
       aHeaderField == nsGkAtoms::handheldFriendly ||
       aHeaderField == nsGkAtoms::viewport_minimum_scale ||
       aHeaderField == nsGkAtoms::viewport_maximum_scale ||
       aHeaderField == nsGkAtoms::viewport_initial_scale ||
       aHeaderField == nsGkAtoms::viewport_height ||
       aHeaderField == nsGkAtoms::viewport_width ||
       aHeaderField ==  nsGkAtoms::viewport_user_scalable) {
     mViewportType = Unknown;
+    mViewportOverflowType = ViewportOverflowType::NoOverflow;
   }
 
   // Referrer policy spec says to ignore any empty referrer policies.
   if (aHeaderField == nsGkAtoms::referrer && !aData.IsEmpty()) {
      enum mozilla::net::ReferrerPolicy policy = mozilla::net::ReferrerPolicyFromString(aData);
     // If policy is not the empty string, then set element's node document's
     // referrer policy to policy
     if (policy != mozilla::net::RP_Unset) {
@@ -7232,16 +7234,17 @@ nsIDocument::GetViewportInfo(const Scree
     }
 
     mScaleStrEmpty = scaleStr.IsEmpty();
     mWidthStrEmpty = widthStr.IsEmpty();
     mValidScaleFloat = !scaleStr.IsEmpty() && NS_SUCCEEDED(scaleErrorCode);
     mValidMaxScale = !maxScaleStr.IsEmpty() && NS_SUCCEEDED(scaleMaxErrorCode);
 
     mViewportType = Specified;
+    mViewportOverflowType = ViewportOverflowType::NoOverflow;
     MOZ_FALLTHROUGH;
   }
   case Specified:
   default:
     LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
     LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
     bool effectiveValidMaxScale = mValidMaxScale;
     bool effectiveAllowZoom = mAllowZoom;
@@ -7313,16 +7316,68 @@ nsIDocument::GetViewportInfo(const Scree
       size.height = std::max(size.height, displaySize.height);
     }
 
     return nsViewportInfo(scaleFloat, scaleMinFloat, scaleMaxFloat, size,
                           mAutoSize, effectiveAllowZoom);
   }
 }
 
+void
+nsIDocument::UpdateViewportOverflowType(nscoord aScrolledWidth,
+                                        nscoord aScrollportWidth)
+{
+#ifdef DEBUG
+  MOZ_ASSERT(mPresShell);
+  nsPresContext* pc = GetPresContext();
+  MOZ_ASSERT(pc->GetViewportScrollbarStylesOverride().mHorizontal ==
+             NS_STYLE_OVERFLOW_HIDDEN,
+             "Should only be called when viewport has overflow-x: hidden");
+  MOZ_ASSERT(aScrolledWidth > aScrollportWidth,
+             "Should only be called when viewport is overflowed");
+  MOZ_ASSERT(IsTopLevelContentDocument(),
+             "Should only be called for top-level content document");
+#endif // DEBUG
+
+  if (!gfxPrefs::MetaViewportEnabled() ||
+      (GetWindow() && GetWindow()->IsDesktopModeViewport())) {
+    mViewportOverflowType = ViewportOverflowType::Desktop;
+    return;
+  }
+
+  if (mViewportType == Unknown) {
+    // The viewport info hasn't been initialized yet. Suppose we would
+    // get here again at some point after it's initialized.
+    return;
+  }
+
+  static const LayoutDeviceToScreenScale
+    kBlinkDefaultMinScale = LayoutDeviceToScreenScale(0.25f);
+  LayoutDeviceToScreenScale minScale;
+  if (mViewportType == DisplayWidthHeight) {
+    minScale = kBlinkDefaultMinScale;
+  } else {
+    if (mScaleMinFloat == kViewportMinScale) {
+      minScale = kBlinkDefaultMinScale;
+    } else {
+      minScale = mScaleMinFloat;
+    }
+  }
+
+  // If the content has overflowed with minimum scale applied, don't
+  // change it, otherwise update the overflow type.
+  if (mViewportOverflowType != ViewportOverflowType::MinScaleSize) {
+    if (aScrolledWidth * minScale.scale < aScrollportWidth) {
+      mViewportOverflowType = ViewportOverflowType::ButNotMinScaleSize;
+    } else {
+      mViewportOverflowType = ViewportOverflowType::MinScaleSize;
+    }
+  }
+}
+
 EventListenerManager*
 nsDocument::GetOrCreateListenerManager()
 {
   if (!mListenerManager) {
     mListenerManager =
       new EventListenerManager(static_cast<EventTarget*>(this));
     SetFlags(NODE_HAS_LISTENERMANAGER);
   }
@@ -12127,16 +12182,33 @@ nsIDocument::ReportUseCounters(UseCounte
             printf(": %d\n", value);
           }
 
           Telemetry::Accumulate(id, 1);
         }
       }
     }
   }
+
+  if (IsTopLevelContentDocument() && !IsResourceDoc()) {
+    using mozilla::Telemetry::LABELS_HIDDEN_VIEWPORT_OVERFLOW_TYPE;
+    LABELS_HIDDEN_VIEWPORT_OVERFLOW_TYPE label;
+    switch (mViewportOverflowType) {
+#define CASE_OVERFLOW_TYPE(t_)                            \
+      case ViewportOverflowType::t_:                      \
+        label = LABELS_HIDDEN_VIEWPORT_OVERFLOW_TYPE::t_; \
+        break;
+      CASE_OVERFLOW_TYPE(NoOverflow)
+      CASE_OVERFLOW_TYPE(Desktop)
+      CASE_OVERFLOW_TYPE(ButNotMinScaleSize)
+      CASE_OVERFLOW_TYPE(MinScaleSize)
+#undef CASE_OVERFLOW_TYPE
+    }
+    Telemetry::AccumulateCategorical(label);
+  }
 }
 
 void
 nsIDocument::UpdateIntersectionObservations()
 {
   if (mIntersectionObservers.IsEmpty()) {
     return;
   }
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -1195,16 +1195,27 @@ public:
    * document, in device pixels.
    *
    * NOTE: If the site is optimized for mobile (via the doctype), this
    * will return viewport information that specifies default information.
    */
   nsViewportInfo GetViewportInfo(const mozilla::ScreenIntSize& aDisplaySize);
 
   /**
+   * It updates the viewport overflow type with the given two widths
+   * and the viewport setting of the document.
+   * This should only be called when there is out-of-reach overflow
+   * happens on the viewport, i.e. the viewport should be using
+   * `overflow: hidden`. And it should only be called on a top level
+   * content document.
+   */
+  void UpdateViewportOverflowType(nscoord aScrolledWidth,
+                                  nscoord aScrollportWidth);
+
+  /**
    * True iff this doc will ignore manual character encoding overrides.
    */
   virtual bool WillIgnoreCharsetOverride() {
     return true;
   }
 
   /**
    * Return whether the document was created by a srcdoc iframe.
@@ -4266,24 +4277,48 @@ protected:
   // Weak reference to our sink for in case we no longer have a parser.  This
   // will allow us to flush out any pending stuff from the sink even if
   // EndLoad() has already happened.
   nsWeakPtr mWeakSink;
 
   // Our update nesting level
   uint32_t mUpdateNestLevel;
 
-  enum ViewportType {
+  enum ViewportType : uint8_t {
     DisplayWidthHeight,
     Specified,
     Unknown
   };
 
   ViewportType mViewportType;
 
+  // Enum for how content in this document overflows viewport causing
+  // out-of-reach issue. Currently it only takes horizontal overflow
+  // into consideration. This enum and the corresponding field is only
+  // set and read on a top level content document.
+  enum class ViewportOverflowType : uint8_t {
+    // Viewport doesn't have out-of-reach overflow content, either
+    // because the content doesn't overflow, or the viewport doesn't
+    // have "overflow: hidden".
+    NoOverflow,
+
+    // All following items indicates that the content overflows the
+    // scroll port which causing out-of-reach content.
+
+    // Meta viewport is disabled or the document is in desktop mode.
+    Desktop,
+    // The content does not overflow the minimum-scale size. When there
+    // is no minimum scale specified, the default value used by Blink,
+    // 0.25, is used for this matter.
+    ButNotMinScaleSize,
+    // The content overflows the minimum-scale size.
+    MinScaleSize,
+  };
+  ViewportOverflowType mViewportOverflowType;
+
   PLDHashTable* mSubDocuments;
 
   nsDocHeaderData* mHeaderData;
 
   RefPtr<PrincipalFlashClassifier> mPrincipalFlashClassifier;
   mozilla::dom::FlashClassification mFlashClassification;
   // Do not use this value directly. Call the |IsThirdParty()| method, which
   // caches its result here.
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -390,22 +390,22 @@ nsHTMLScrollFrame::TryLayout(ScrollReflo
     nsSize compositionSize = nsLayoutUtils::CalculateCompositionSizeForFrame(this, false);
     float resolution = presShell->GetResolution();
     compositionSize.width /= resolution;
     compositionSize.height /= resolution;
     visualScrollPortSize = nsSize(std::max(0, compositionSize.width - vScrollbarDesiredWidth),
                                   std::max(0, compositionSize.height - hScrollbarDesiredHeight));
   }
 
+  nsRect scrolledRect =
+    mHelper.GetUnsnappedScrolledRectInternal(aState->mContentsOverflowAreas.ScrollableOverflow(),
+                                             scrollPortSize);
+  nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1);
+
   if (!aForce) {
-    nsRect scrolledRect =
-      mHelper.GetUnsnappedScrolledRectInternal(aState->mContentsOverflowAreas.ScrollableOverflow(),
-                                               scrollPortSize);
-    nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1);
-
     // If the style is HIDDEN then we already know that aAssumeHScroll is false
     if (aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
       bool wantHScrollbar =
         aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL ||
         scrolledRect.XMost() >= visualScrollPortSize.width + oneDevPixel ||
         scrolledRect.x <= -oneDevPixel;
       if (scrollPortSize.width < hScrollbarMinSize.width)
         wantHScrollbar = false;
@@ -421,16 +421,41 @@ nsHTMLScrollFrame::TryLayout(ScrollReflo
         scrolledRect.y <= -oneDevPixel;
       if (scrollPortSize.height < vScrollbarMinSize.height)
         wantVScrollbar = false;
       if (wantVScrollbar != aAssumeVScroll)
         return false;
     }
   }
 
+  do {
+    if (!mHelper.mIsRoot) {
+      break;
+    }
+    // Check whether there is actually any overflow.
+    nscoord scrolledWidth = scrolledRect.width + oneDevPixel;
+    if (scrolledWidth <= scrollPortSize.width) {
+      break;
+    }
+    // Viewport scrollbar style is used below instead of aState->mStyles
+    // because the latter can be affected by various factors, while we
+    // only care about what the page itself specifies.
+    nsPresContext* pc = PresContext();
+    ScrollbarStyles styles = pc->GetViewportScrollbarStylesOverride();
+    if (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
+      break;
+    }
+    // Only top level content document is considered.
+    nsIDocument* doc = pc->Document();
+    if (!doc->IsTopLevelContentDocument()) {
+      break;
+    }
+    doc->UpdateViewportOverflowType(scrolledWidth, scrollPortSize.width);
+  } while (false);
+
   nscoord vScrollbarActualWidth = aState->mInsideBorderSize.width - scrollPortSize.width;
 
   aState->mShowHScrollbar = aAssumeHScroll;
   aState->mShowVScrollbar = aAssumeVScroll;
   nsPoint scrollPortOrigin(aState->mComputedBorder.left,
                            aState->mComputedBorder.top);
   if (!IsScrollbarOnRight()) {
     scrollPortOrigin.x += vScrollbarActualWidth;
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -13799,10 +13799,19 @@
     "record_in_processes": ["content"],
     "alert_emails": ["farre@mozilla.com"],
     "bug_numbers": [1441972],
     "expires_in_version": "67",
     "kind": "exponential",
     "high": 50,
     "n_buckets": 20,
     "description": "Total number of doc groups per tab group, including docgroups fully in bfcache. Collected at the point when the top level document of the tab group is unloaded."
+  },
+  "HIDDEN_VIEWPORT_OVERFLOW_TYPE": {
+    "record_in_processes": ["content"],
+    "alert_emails": ["xquan@mozilla.com", "botond@mozilla.com"],
+    "bug_numbers": [1423013, 1423017],
+    "expires_in_version": "65",
+    "kind": "categorical",
+    "labels": ["NoOverflow", "Desktop", "ButNotMinScaleSize", "MinScaleSize"],
+    "description": "How common are different types of out-of-reach viewport overflow?"
   }
 }