Measure content outside viewport via ScrollableArea

This commit changes the algorithm from examining the offsets and sizes
of text and images nodes to looking at their scrolling container.  This
algorithm is less expensive and more accurately considers overflow-x:
hidden.

Change-Id: Ia60d41a14f008b22c5139d7d91989d9345f6e3c9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3041015
Commit-Queue: Andrew Gaul <gaul@google.com>
Reviewed-by: Xianzhu Wang <wangxianzhu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#907438}
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
index 421815d..2c27821 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
@@ -8,6 +8,7 @@
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/page_scale_constraints_set.h"
+#include "third_party/blink/renderer/core/frame/root_frame_viewport.h"
 #include "third_party/blink/renderer/core/frame/visual_viewport.h"
 #include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
 #include "third_party/blink/renderer/core/html/html_anchor_element.h"
@@ -333,12 +334,8 @@
   if (font_size_check_enabled_)
     mobile_friendliness_.small_text_ratio = text_area_sizes_.SmallTextRatio();
 
-  // As long as evaluated as MF, TextOutsideViewportPercentage UKM must not be
-  // -1 (means unknown). Even if there is no call of
-  // ComputeTextContentOutsideViewport(), as far as there are FCP notification
-  // and unload event, that value is not -1 anymore and to be 0.
-  mobile_friendliness_.text_content_outside_viewport_percentage = std::max(
-      0, mobile_friendliness_.text_content_outside_viewport_percentage);
+  mobile_friendliness_.text_content_outside_viewport_percentage =
+      ComputeContentOutsideViewport();
 
   frame_view_->DidChangeMobileFriendliness(mobile_friendliness_);
 }
@@ -387,8 +384,6 @@
 
 void MobileFriendlinessChecker::NotifyInvalidatePaint(
     const LayoutObject& object) {
-  ComputeTextContentOutsideViewport(object);
-
   if (font_size_check_enabled_)
     ComputeSmallTextRatio(object);
 }
@@ -418,63 +413,29 @@
   }
 }
 
-constexpr int kMaxAncestorCount = 5;
-bool CheckParentHasOverflowXHidden(const LayoutObject* obj) {
-  int ancestor_count = kMaxAncestorCount;
-  while (obj && ancestor_count > 0) {
-    const ComputedStyle* style = obj->Style();
-    if (style->OverflowX() == EOverflow::kHidden)
-      return true;
-    obj = obj->Parent();
-    --ancestor_count;
-  }
-  return false;
-}
-
-void MobileFriendlinessChecker::ComputeTextContentOutsideViewport(
-    const LayoutObject& object) {
+int MobileFriendlinessChecker::ComputeContentOutsideViewport() {
   int frame_width = frame_view_->GetPage()->GetVisualViewport().Size().Width();
   if (frame_width == 0) {
-    return;
+    return 0;
   }
 
-  int total_text_width;
-  if (const auto* text = DynamicTo<LayoutText>(object)) {
-    const ComputedStyle* style = text->Style();
-    if (style->Visibility() != EVisibility::kVisible ||
-        style->ContentVisibility() != EContentVisibility::kVisible ||
-        style->Opacity() == 0.0 || CheckParentHasOverflowXHidden(&object))
-      return;
-    total_text_width = text->PhysicalRightOffset().ToInt();
-  } else if (const auto* image = DynamicTo<LayoutImage>(object)) {
-    const ComputedStyle* style = image->Style();
-    if (style->Visibility() != EVisibility::kVisible ||
-        style->ContentVisibility() != EContentVisibility::kVisible ||
-        style->Opacity() == 0.0 || CheckParentHasOverflowXHidden(&object))
-      return;
-    total_text_width = image->FrameRect().MaxX().ToInt();
-  } else {
-    return;
+  const auto* root_frame_viewport = frame_view_->GetRootFrameViewport();
+  if (root_frame_viewport == nullptr) {
+    return 0;
   }
 
   double initial_scale = frame_view_->GetPage()
                              ->GetPageScaleConstraintsSet()
                              .FinalConstraints()
                              .initial_scale;
-  if (initial_scale > 0)
-    total_text_width *= initial_scale;
+  int content_width =
+      root_frame_viewport->LayoutViewport().ContentsSize().Width() *
+      initial_scale;
+  int max_scroll_offset = content_width - frame_width;
 
-  int text_content_outside_viewport_percentage = 0;
-  if (total_text_width > frame_width) {
-    // We use ceil function here because we want to treat 100.1% as 101 which
-    // requires a scroll bar.
-    text_content_outside_viewport_percentage =
-        std::ceil((total_text_width - frame_width) * 100.0 / frame_width);
-  }
-
-  mobile_friendliness_.text_content_outside_viewport_percentage =
-      std::max(mobile_friendliness_.text_content_outside_viewport_percentage,
-               text_content_outside_viewport_percentage);
+  // We use ceil function here because we want to treat 100.1% as 101 which
+  // requires a scroll bar.
+  return std::ceil(max_scroll_offset * 100.0 / frame_width);
 }
 
 void MobileFriendlinessChecker::Trace(Visitor* visitor) const {
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h
index 45322373..5b4e6ae 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h
@@ -44,7 +44,7 @@
 
  private:
   void ComputeSmallTextRatio(const LayoutObject& object);
-  void ComputeTextContentOutsideViewport(const LayoutObject& object);
+  int ComputeContentOutsideViewport();
   void ComputeBadTapTargetsRatio();
 
  private:
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
index c3f538e..c143841 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
@@ -339,26 +339,24 @@
   EXPECT_NE(actual_mf.text_content_outside_viewport_percentage, 0);
 }
 
-TEST_F(MobileFriendlinessCheckerTest, TextTooWideOpacityZero) {
+TEST_F(MobileFriendlinessCheckerTest, TextTooWideAbsolutePositioning) {
   MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(
       R"(
 <html>
   <body>
-    <pre style="opacity:0">)" +
-      std::string(10000, 'a') +
-      R"(</pre>
+    <pre style="position:absolute; left:2000px">a</pre>
   </body>
 </html>
 )");
-  EXPECT_EQ(actual_mf.text_content_outside_viewport_percentage, 0);
+  EXPECT_EQ(actual_mf.text_content_outside_viewport_percentage, 317);
 }
 
-TEST_F(MobileFriendlinessCheckerTest, TextTooWideVisibilityHidden) {
+TEST_F(MobileFriendlinessCheckerTest, TextTooWideOverflowXHidden) {
   MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(
       R"(
 <html>
   <body>
-    <pre style="visibility:hidden">)" +
+    <pre style="overflow-x:hidden">)" +
       std::string(10000, 'a') + R"(</pre>
   </body>
 </html>
@@ -435,10 +433,22 @@
   </body>
 </html>
 )");
-  EXPECT_EQ(actual_mf.text_content_outside_viewport_percentage, 317);
+  EXPECT_EQ(actual_mf.text_content_outside_viewport_percentage, 319);
 }
 
-TEST_F(MobileFriendlinessCheckerTest, ImageAbsolutePosition) {
+TEST_F(MobileFriendlinessCheckerTest, ImageTooWideTwoImages) {
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
+<html>
+  <body style="width:4000px">
+    <img style="width:2000px; height:50px">
+    <img style="width:2000px; height:50px">
+  </body>
+</html>
+)");
+  EXPECT_EQ(actual_mf.text_content_outside_viewport_percentage, 735);
+}
+
+TEST_F(MobileFriendlinessCheckerTest, ImageTooWideAbsolutePosition) {
   MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <body>
@@ -484,6 +494,44 @@
   EXPECT_GE(actual_mf.text_content_outside_viewport_percentage, 100.0);
 }
 
+TEST_F(MobileFriendlinessCheckerTest, ScrollerOutsideViewport) {
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
+<html>
+  <head>
+    <style>
+      div.scrollmenu {
+        background-color: #333;
+        overflow: auto;
+        white-space: nowrap;
+      }
+      div.scrollmenu a {
+        display: inline-block;
+        color: white;
+        padding: 14px;
+      }
+    </style>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0 minimum-scale=1.0">
+  </head>
+  <body style="font-size: 18px">
+  <div class="scrollmenu">
+    <a href="#1">First text</a>
+    <a href="#2">Second text</a>
+    <a href="#3">Third text</a>
+    <a href="#4">Fourth text</a>
+    <a href="#5">Fifth text</a>
+    <a href="#6">Sixth text</a>
+    <a href="#7">Seventh text</a>
+    <a href="#8">Eighth text</a>
+    <a href="#9">Ninth text</a>
+    <a href="#10">Tenth text</a>
+  </div>
+  </body>
+</html>
+)");
+  // the viewport
+  EXPECT_EQ(actual_mf.text_content_outside_viewport_percentage, 0.0);
+}
+
 TEST_F(MobileFriendlinessCheckerTest, SingleTapTarget) {
   MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
   <head>