910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_trace_processor.h"
#include "build/build_config.h"
#include "cc/base/switches.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/input/synthetic_gesture.h"
#include "content/common/input/synthetic_gesture_controller.h"
#include "content/common/input/synthetic_gesture_params.h"
#include "content/common/input/synthetic_gesture_target.h"
#include "content/common/input/synthetic_smooth_scroll_gesture.h"
#include "content/common/input/synthetic_smooth_scroll_gesture_params.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/hit_test_region_observer.h"
#include "content/shell/browser/shell.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/input/synthetic_web_input_event_builders.h"
#include "third_party/blink/public/common/switches.h"
#include "ui/base/ui_base_features.h"
#include "ui/native_theme/features/native_theme_features.h"

#if BUILDFLAG(IS_MAC)
#include "ui/base/test/scoped_preferred_scroller_style_mac.h"
#endif

namespace {

const char kDataURL[] =
    "data:text/html;charset=utf-8,"
    "<!DOCTYPE html>"
    "<html>"
    "<head>"
    "<title>Scroll latency histograms browsertests.</title>"
    "<style>"
    "body {"
    "  height:9000px;"
    "  overscroll-behavior:none;"
    "}"
    "</style>"
    "</head>"
    "<body>"
    "<div id='spinner'>Spinning</div>"
    "</body>"
    "<script>"
    "var degree = 0;"
    "function spin() {"
    "degree = degree + 3;"
    "if (degree >= 360)"
    "degree -= 360;"
    "document.getElementById('spinner').style['transform'] = "
    "'rotate(' + degree + 'deg)';"
    "requestAnimationFrame(spin);"
    "}"
    "spin();"
    "</script>"
    "</html>";
}  // namespace

namespace content {

class ScrollLatencyBrowserTest : public ContentBrowserTest {
 public:
  ScrollLatencyBrowserTest() {}

  ScrollLatencyBrowserTest(const ScrollLatencyBrowserTest&) = delete;
  ScrollLatencyBrowserTest& operator=(const ScrollLatencyBrowserTest&) = delete;

  ~ScrollLatencyBrowserTest() override {}

  RenderWidgetHostImpl* GetWidgetHost() {
    return RenderWidgetHostImpl::From(shell()
                                          ->web_contents()
                                          ->GetPrimaryMainFrame()
                                          ->GetRenderViewHost()
                                          ->GetWidget());
  }

  // TODO(tdresser): Find a way to avoid sleeping like this. See
  // crbug.com/405282 for details.
  void GiveItSomeTime(int delay_ms = 10) {
    base::RunLoop run_loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(delay_ms));
    run_loop.Run();
  }

  void OnSyntheticGestureCompleted(SyntheticGesture::Result result) {
    EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result);
    run_loop_->Quit();
  }

 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Set the scroll animation duration to a large number so that
    // we ensure secondary GestureScrollUpdates update the animation
    // instead of starting a new one.
    command_line->AppendSwitchASCII(
        switches::kCCScrollAnimationDurationForTesting, "10000000");
  }

  void LoadURL() {
    const GURL data_url(kDataURL);
    EXPECT_TRUE(NavigateToURL(shell(), data_url));

    RenderWidgetHostImpl* host = GetWidgetHost();

    HitTestRegionObserver observer(host->GetFrameSinkId());

    // Wait for the hit test data to be ready
    observer.WaitForHitTestData();
  }

  // Generate the gestures associated with kNumWheelScrolls ticks,
  // scrolling by |distance|. This will perform a smooth scroll on platforms
  // which support it.
  void DoSmoothWheelScroll(const gfx::Vector2d& distance) {
    std::vector<gfx::Vector2d> scroll_events = {
        gfx::Vector2d(distance.x(), -distance.y()),
        gfx::Vector2d(distance.x(), -distance.y())};
    DoScroll(distance, scroll_events);
  }

  void DoScroll(gfx::Vector2d begin_position,
                std::vector<gfx::Vector2d> scroll_events,
                int delay_ms = 10) {
    blink::WebGestureEvent event =
        blink::SyntheticWebGestureEventBuilder::BuildScrollBegin(
            begin_position.x(), -begin_position.y(),
            blink::WebGestureDevice::kTouchpad, 1);
    event.data.scroll_begin.delta_hint_units =
        ui::ScrollGranularity::kScrollByPixel;
    GetWidgetHost()->ForwardGestureEvent(event);

    for (uint32_t i = 0; i < scroll_events.size(); i++) {
      // Install a VisualStateCallback and wait for the callback in response
      // to each GestureScrollUpdate before sending the next GSU. This will
      // ensure the events are not coalesced (resulting in fewer end-to-end
      // latency histograms being logged).
      // We must install a callback for each gesture since they are one-shot
      // callbacks.
      shell()->web_contents()->GetPrimaryMainFrame()->InsertVisualStateCallback(
          base::BindOnce(&ScrollLatencyBrowserTest::InvokeVisualStateCallback,
                         base::Unretained(this)));

      auto distance_to_scroll = scroll_events[i];
      blink::WebGestureEvent event2 =
          blink::SyntheticWebGestureEventBuilder::BuildScrollUpdate(
              distance_to_scroll.x(), distance_to_scroll.y(), 0,
              blink::WebGestureDevice::kTouchpad);
      event2.data.scroll_update.delta_units =
          ui::ScrollGranularity::kScrollByPixel;
      GetWidgetHost()->ForwardGestureEvent(event2);

      while (visual_state_callback_count_ <= i) {
        // TODO: There's currently no way to block until a GPU swap
        // completes. Until then we need to spin and wait. See
        // crbug.com/897520 for more details.
        GiveItSomeTime(delay_ms);
      }
    }
  }

  void InvokeVisualStateCallback(bool result) {
    EXPECT_TRUE(result);
    visual_state_callback_count_++;
  }

  // Returns true if the given histogram has recorded the expected number of
  // samples.
  [[nodiscard]] bool VerifyRecordedSamplesForHistogram(
      const size_t num_samples,
      const std::string& histogram_name) const {
    return num_samples == GetSampleCountForHistogram(histogram_name);
  }

  size_t GetSampleCountForHistogram(const std::string& histogram_name) const {
    return histogram_tester_.GetAllSamples(histogram_name).size();
  }

  std::string GetScaledDeltaTraceValue(int offset) {
    return base::NumberToString(
        static_cast<double>(GetWidgetHost()->GetDeviceScaleFactor() * offset));
  }

  std::unique_ptr<base::RunLoop> run_loop_;

 protected:
  base::HistogramTester histogram_tester_;
  uint32_t visual_state_callback_count_ = 0;
};

// Do an upward touch scroll, and verify that no scroll metrics is recorded when
// the scroll event is ignored.
IN_PROC_BROWSER_TEST_F(ScrollLatencyBrowserTest,
                       ScrollLatencyNotRecordedIfGSUIgnored) {
  LoadURL();
  auto scroll_update_watcher = std::make_unique<InputMsgWatcher>(
      GetWidgetHost(), blink::WebInputEvent::Type::kGestureScrollUpdate);

  // Try to scroll upward, the GSU(s) will get ignored since the scroller is at
  // its extent.
  SyntheticSmoothScrollGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.anchor = gfx::PointF(10, 10);
  params.distances.push_back(gfx::Vector2d(0, 60));

  run_loop_ = std::make_unique<base::RunLoop>();

  std::unique_ptr<SyntheticSmoothScrollGesture> gesture(
      new SyntheticSmoothScrollGesture(params));
  GetWidgetHost()->QueueSyntheticGesture(
      std::move(gesture),
      base::BindOnce(&ScrollLatencyBrowserTest::OnSyntheticGestureCompleted,
                     base::Unretained(this)));

  // Runs until we get the OnSyntheticGestureCompleted callback and verify that
  // the first GSU event is ignored.
  run_loop_->Run();
  EXPECT_EQ(blink::mojom::InputEventResultState::kNoConsumerExists,
            scroll_update_watcher->GetAckStateWaitIfNecessary());

  // Wait for one frame and then verify that the scroll metrics are not
  // recorded.
  std::unique_ptr<RenderFrameSubmissionObserver> frame_observer =
      std::make_unique<RenderFrameSubmissionObserver>(
          GetWidgetHost()->render_frame_metadata_provider());
  frame_observer->WaitForAnyFrameSubmission();

  EXPECT_TRUE(VerifyRecordedSamplesForHistogram(
      0, "EventLatency.GestureScrollUpdate.TotalLatency2"));
}

// TODO(crbug.com/370658912) heap-use-after-free on Win ASAN.
#if BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER)
#define MAYBE_ScrollingEventLatencyTrace DISABLED_ScrollingEventLatencyTrace
#else
#define MAYBE_ScrollingEventLatencyTrace ScrollingEventLatencyTrace
#endif  // BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER)

// A basic smoke test verifying that key scroll-related events are recorded
// during scrolling. This test performs a simple scroll and expects to see three
// EventLatency events with the correct types.
IN_PROC_BROWSER_TEST_F(ScrollLatencyBrowserTest,
                       MAYBE_ScrollingEventLatencyTrace) {
  LoadURL();
  base::test::TestTraceProcessor ttp;
  ttp.StartTrace("input.scrolling");
  DoSmoothWheelScroll(gfx::Vector2d(0, 100));
  while (!VerifyRecordedSamplesForHistogram(
      1, "EventLatency.GestureScrollUpdate.TotalLatency2")) {
    GiveItSomeTime();
    FetchHistogramsFromChildProcesses();
  }
  absl::Status status = ttp.StopAndParseTrace();
  ASSERT_TRUE(status.ok()) << status.message();
  std::string query =
      R"(
      SELECT EXTRACT_ARG(arg_set_id, 'event_latency.event_type') AS type
      FROM slice
      WHERE name = 'EventLatency'
      )";
  auto result = ttp.RunQuery(query);
  ASSERT_TRUE(result.has_value()) << result.error();
  EXPECT_THAT(result.value(),
              ::testing::ElementsAre(
                  std::vector<std::string>{"type"},
                  std::vector<std::string>{"GESTURE_SCROLL_BEGIN"},
                  std::vector<std::string>{"FIRST_GESTURE_SCROLL_UPDATE"},
                  std::vector<std::string>{"GESTURE_SCROLL_UPDATE"}));
}

// Basic smoke test for predictor jank metrics.
#if BUILDFLAG(IS_ANDROID)
// TODO(b/345225978): the android-x86-rel bot occasionally flakes due to
// missing events after Swap.
#if !defined(ARCH_CPU_X86_FAMILY)
IN_PROC_BROWSER_TEST_F(ScrollLatencyBrowserTest, ScrollingPredictor) {
  LoadURL();
  base::test::TestTraceProcessor ttp;
  ttp.StartTrace("input.scrolling");

  const gfx::Vector2d scroll_1(0, -5);
  const gfx::Vector2d scroll_2(0, 1);   // Previous update and subsequent update
                                        // will produce jank where
                                        // min(prev, next)/curr > threshold.
  const gfx::Vector2d scroll_3(0, -5);  // Previous update and subsequent update
                                        // will not produce jank.
  const gfx::Vector2d scroll_4(0, -10);  // Previous update and subsequent
                                         // update will produce jank where
                                         // curr/max(prev, next) > threshold.
  const gfx::Vector2d scroll_5(0, 5);

  DoScroll(gfx::Vector2d(0, 25),
           {scroll_1, scroll_2, scroll_3, scroll_4, scroll_5},
           16 /*delay_ms - ensure there is enough time for vsync*/
  );

  // Allow enough time for the inputs to be processed and the trace events
  // to be recorded.
  GiveItSomeTime(100);

  absl::Status status = ttp.StopAndParseTrace();
  ASSERT_TRUE(status.ok()) << status.message();
  std::string query =
      R"(
      INCLUDE PERFETTO MODULE chrome.scroll_jank.predictor_error;

      SELECT
        prev_delta,
        delta_y,
        next_delta,
        predictor_jank,
        delta_threshold
      FROM chrome_predictor_error
        ORDER BY present_ts
      ;
      )";
  auto result = ttp.RunQuery(query);
  ASSERT_TRUE(result.has_value()) << result.error();
  EXPECT_THAT(
      result.value(),
      ::testing::ElementsAre(
          std::vector<std::string>{"prev_delta", "delta_y", "next_delta",
                                   "predictor_jank", "delta_threshold"},
          std::vector<std::string>{GetScaledDeltaTraceValue(scroll_1.y()),
                                   GetScaledDeltaTraceValue(scroll_2.y()),
                                   GetScaledDeltaTraceValue(scroll_3.y()),
                                   "3.8", "1.2"},
          std::vector<std::string>{GetScaledDeltaTraceValue(scroll_2.y()),
                                   GetScaledDeltaTraceValue(scroll_3.y()),
                                   GetScaledDeltaTraceValue(scroll_4.y()), "0",
                                   "1.2"},
          std::vector<std::string>{GetScaledDeltaTraceValue(scroll_3.y()),
                                   GetScaledDeltaTraceValue(scroll_4.y()),
                                   GetScaledDeltaTraceValue(scroll_5.y()),
                                   "0.8", "1.2"}));
}
#endif  // !defined(ARCH_CPU_X86_FAMILY)
#endif  // BUILDFLAG(IS_ANDROID)

}  // namespace content