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

#include "ash/metrics/ui_metrics_recorder.h"

#include <memory>
#include <string>
#include <string_view>
#include <utility>

#include "ash/test/ash_test_base.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "cc/metrics/event_metrics.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ime/ash/ime_bridge.h"
#include "ui/base/ime/ash/mock_ime_engine_handler.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/test/test_utils.h"
#include "ui/events/event.h"
#include "ui/events/test/test_event_handler.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/test_widget_builder.h"

namespace ash {
namespace {

using FpsBucket = UiMetricsRecorder::FpsBucket;
using CoreEventType = UiMetricsRecorder::CoreEventType;
constexpr int kMaxFpsBucketIndex = UiMetricsRecorder::kMaxFpsBucketIndex;
constexpr int kMaxCoreEventTypeIndex =
    UiMetricsRecorder::kMaxCoreEventTypeIndex;

std::string_view CoreEventTypeToString(CoreEventType type) {
  switch (type) {
    case CoreEventType::kKeyPressed:
      return "KeyPressed";
    case CoreEventType::kKeyReleased:
      return "KeyReleased";
    case CoreEventType::kMousePressed:
      return "MousePressed";
    case CoreEventType::kMouseReleased:
      return "MouseReleased";
    case CoreEventType::kMouseDragged:
      return "MouseDragged";
  }
  NOTREACHED();
}

std::string_view FpsBucketToString(FpsBucket bucket) {
  switch (bucket) {
    case FpsBucket::k30Fps:
      return "30Fps";
    case FpsBucket::k60Fps:
      return "60Fps";
    case FpsBucket::k120Fps:
      return "120Fps";
    case FpsBucket::kOtherFps:
      return "OtherFps";
    case FpsBucket::kUnset:
      return "UnsetFps";
  }
  NOTREACHED();
}

int GetHistogramIndex(CoreEventType core_event_type, FpsBucket fps_bucket) {
  return static_cast<int>(fps_bucket) * kMaxCoreEventTypeIndex +
         static_cast<int>(core_event_type);
}

// TestIMEEngineHandler invokes the callback synchronously for ProcessKeyEvent.
class TestIMEEngineHandler : public MockIMEEngineHandler {
 public:
  // MockIMEEngineHandler overrides:
  void ProcessKeyEvent(const ui::KeyEvent& key_event,
                       KeyEventDoneCallback callback) override {
    ++received_key_event_;
    MockIMEEngineHandler::ProcessKeyEvent(key_event, std::move(callback));
    last_passed_callback().Run(ui::ime::KeyEventHandledState::kNotHandled);
  }

  int GetReceivedKeyEvent() const { return received_key_event_; }

 private:
  int received_key_event_ = 0;
};

// WidgetDestroyHandler destroys a given widget synchronously on key events.
class WidgetDestroyHandler : public ui::test::TestEventHandler {
 public:
  explicit WidgetDestroyHandler(std::unique_ptr<views::Widget> widget)
      : widget_(std::move(widget)) {
    widget_->GetNativeWindow()->AddPreTargetHandler(this);
  }

  // ui::test::TestEventHandler:
  void OnKeyEvent(ui::KeyEvent* event) override {
    ++received_key_event_;
    event->SetHandled();
    widget_.reset();
  }

  int GetReceivedKeyEvent() const { return received_key_event_; }

 private:
  std::unique_ptr<views::Widget> widget_;
  int received_key_event_ = 0;
};

// FakeTestView to consume MouseEvent and trigger a force redraw.
// Derived directly from `View` so no additional frames would be generated.
class FakeTestView : public views::View {
 public:
  FakeTestView() {
    GetViewAccessibility().SetRole(ax::mojom::Role::kStaticText);
    GetViewAccessibility().SetName(u"FakeTestView");
  }
  ~FakeTestView() override = default;

  // views::View:
  bool OnMousePressed(const ui::MouseEvent& event) override {
    // Schedule a draw with no damaged rect to create a did-not-produce-frame
    // case.
    GetWidget()->GetCompositor()->ScheduleDraw();
    return true;
  }
};

// FakeTextField to consumer all events and trigger a paint. Derived
// from `Textfield` so that IME related tests dispatches key events to IME
// engine.
class FakeTextField : public views::Textfield {
 public:
  FakeTextField() { GetViewAccessibility().SetName(u"FakeTextField"); }
  ~FakeTextField() override = default;

  // views::View:
  void OnEvent(ui::Event* event) override {
    if (!do_nothing_in_event_handling_) {
      views::View::OnEvent(event);
      SchedulePaint();
    }

    event->SetHandled();
  }

  void OnKeyEvent(ui::KeyEvent* event) override { ++received_key_event_; }

  int GetReceivedKeyEvent() const { return received_key_event_; }

  void set_do_nothing_in_event_handling(bool do_nothing) {
    do_nothing_in_event_handling_ = do_nothing;
  }

 protected:
  int received_key_event_ = 0;
  bool do_nothing_in_event_handling_ = false;
};

class UiMetricsRecorderTest : public AshTestBase {
 public:
  UiMetricsRecorderTest() = default;
  UiMetricsRecorderTest(const UiMetricsRecorderTest&) = delete;
  UiMetricsRecorderTest& operator=(const UiMetricsRecorderTest&) = delete;
  ~UiMetricsRecorderTest() override = default;

  std::unique_ptr<views::Widget> CreateTestWindowWidget() {
    return views::test::TestWidgetBuilder()
        .SetDelegate(nullptr)
        .SetBounds(gfx::Rect(0, 0, 100, 100))
        .SetShow(true)
        .SetWidgetType(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS)
        .BuildOwnsNativeWidget();
  }
};

// Ash.EventLatency metrics should be recorded when key events generating
// UI changes.
TEST_F(UiMetricsRecorderTest, KeyEvent) {
  std::unique_ptr<views::Widget> widget = CreateTestWindowWidget();
  FakeTextField* view =
      widget->SetContentsView(std::make_unique<FakeTextField>());
  widget->GetFocusManager()->SetFocusedView(view);

  base::HistogramTester histogram_tester;

  EXPECT_EQ(view->GetReceivedKeyEvent(), 0);
  PressAndReleaseKey(ui::VKEY_A);
  // Expect to receive two key events: KeyPressed and KeyRelease.
  EXPECT_EQ(view->GetReceivedKeyEvent(), 2);
  EXPECT_TRUE(ui::WaitForNextFrameToBePresented(widget->GetCompositor()));

  histogram_tester.ExpectTotalCount("Ash.EventLatency.KeyPressed.TotalLatency",
                                    1);
  histogram_tester.ExpectTotalCount("Ash.EventLatency.KeyReleased.TotalLatency",
                                    1);
  histogram_tester.ExpectTotalCount("Ash.EventLatency.TotalLatency", 2);
}

TEST_F(UiMetricsRecorderTest, Gestures) {
  std::unique_ptr<views::Widget> widget = CreateTestWindowWidget();
  FakeTextField* view =
      widget->SetContentsView(std::make_unique<FakeTextField>());
  const gfx::Rect bounds = view->GetBoundsInScreen();

  {
    // Tap.
    base::HistogramTester histogram_tester;

    GestureTapOn(view);
    EXPECT_TRUE(ui::WaitForNextFrameToBePresented(widget->GetCompositor()));

    histogram_tester.ExpectTotalCount(
        "Ash.EventLatency.GestureTapDown.TotalLatency", 1);
    histogram_tester.ExpectTotalCount(
        "Ash.EventLatency.GestureShowPress.TotalLatency", 1);
    histogram_tester.ExpectTotalCount(
        "Ash.EventLatency.GestureTap.TotalLatency", 1);
  }

  {
    // Scroll.
    base::HistogramTester histogram_tester;

    constexpr int kNumOfTouches = 5;
    GetEventGenerator()->GestureScrollSequence(
        bounds.top_center(), bounds.bottom_center(), base::Milliseconds(100),
        kNumOfTouches);
    EXPECT_TRUE(ui::WaitForNextFrameToBePresented(widget->GetCompositor()));

    histogram_tester.ExpectTotalCount(
        "Ash.EventLatency.GestureTapDown.TotalLatency", 1);
    histogram_tester.ExpectTotalCount(
        "Ash.EventLatency.GestureTapCancel.TotalLatency", 1);
    histogram_tester.ExpectTotalCount(
        "Ash.EventLatency.GestureScrollBegin.TotalLatency", 1);
    histogram_tester.GetBucketCount(
        "Ash.EventLatency.GestureScrollUpdate.TotalLatency", kNumOfTouches);
    histogram_tester.ExpectTotalCount(
        "Ash.EventLatency.GestureScrollEnd.TotalLatency", 1);
  }

  {
    // Pinch.
    base::HistogramTester histogram_tester;

    GetEventGenerator()->PressTouchId(1, bounds.origin());
    GetEventGenerator()->PressTouchId(2, bounds.bottom_right());

    GetEventGenerator()->MoveTouchId(bounds.CenterPoint(), 1);
    GetEventGenerator()->MoveTouchId(bounds.CenterPoint(), 2);

    GetEventGenerator()->ReleaseTouchId(1);
    GetEventGenerator()->ReleaseTouchId(2);

    EXPECT_TRUE(ui::WaitForNextFrameToBePresented(widget->GetCompositor()));

    histogram_tester.ExpectTotalCount(
        "Ash.EventLatency.GesturePinchBegin.TotalLatency", 1);
    histogram_tester.ExpectTotalCount(
        "Ash.EventLatency.GesturePinchUpdate.TotalLatency", 1);
    histogram_tester.ExpectTotalCount(
        "Ash.EventLatency.GesturePinchEnd.TotalLatency", 1);
  }
}

// Verifies no crashes when `EventTarget` is destroyed through a synchronous IME
// `TextInputMethod::ProcessKeyEvent` call. See http://crbug.com/1392491.
TEST_F(UiMetricsRecorderTest, TargetDestroyedWithSyncIME) {
  // Setup.
  auto ime_engine = std::make_unique<TestIMEEngineHandler>();
  IMEBridge::Get()->SetCurrentEngineHandler(ime_engine.get());

  std::unique_ptr<views::Widget> widget = CreateTestWindowWidget();
  FakeTextField* view =
      widget->SetContentsView(std::make_unique<FakeTextField>());
  widget->GetFocusManager()->SetFocusedView(view);

  // Create an event handler on the test widget to close it synchronously.
  WidgetDestroyHandler destroyer(std::move(widget));

  // Press a key and no crash should happen.
  PressAndReleaseKey(ui::VKEY_A);

  // IME engine and `destroyer` should get the key event.
  EXPECT_EQ(ime_engine->GetReceivedKeyEvent(), 1);
  EXPECT_EQ(destroyer.GetReceivedKeyEvent(), 1);

  // Teardown.
  IMEBridge::Get()->SetCurrentEngineHandler(nullptr);
}

// Verifies that event latency is not recorded if UI handling does not cause
// screen updates.
TEST_F(UiMetricsRecorderTest, NoScreenUpdateNoLatency) {
  std::unique_ptr<views::Widget> widget = CreateTestWindowWidget();
  FakeTextField* view =
      widget->SetContentsView(std::make_unique<FakeTextField>());

  base::HistogramTester histogram_tester;

  // No screen update is created during event handling.
  view->set_do_nothing_in_event_handling(/*do_nothing=*/true);
  LeftClickOn(view);

  // Force one frame out side event handling to ensure no latency is reported.
  auto* compositor = widget->GetCompositor();
  compositor->ScheduleFullRedraw();
  EXPECT_TRUE(ui::WaitForNextFrameToBePresented(compositor));

  histogram_tester.ExpectTotalCount("Ash.EventLatency.TotalLatency", 0);
}

// Verifies that the event latency is not recorded when its frame has no damage.
TEST_F(UiMetricsRecorderTest, NoDamageNoLatency) {
  std::unique_ptr<views::Widget> widget = CreateTestWindowWidget();
  FakeTestView* view =
      widget->SetContentsView(std::make_unique<FakeTestView>());

  base::HistogramTester histogram_tester;
  auto* compositor = widget->GetCompositor();

  // Force one frame to ensure that the screen is updated.
  compositor->ScheduleFullRedraw();
  EXPECT_TRUE(ui::WaitForNextFrameToBePresented(compositor));

  // Simulate an event that triggers commit but there is no damage.
  LeftClickOn(view);

  // Wait for the event metrics to be picked up.
  ASSERT_EQ(compositor->saved_events_metrics_count_for_testing(), 1u);
  while (compositor->saved_events_metrics_count_for_testing() != 0) {
    base::RunLoop run_loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(100));
    run_loop.Run();
  }

  // Force one frame out side event handling to ensure no latency is reported.
  compositor->ScheduleFullRedraw();
  EXPECT_TRUE(ui::WaitForNextFrameToBePresented(compositor));

  histogram_tester.ExpectTotalCount("Ash.EventLatency.TotalLatency", 0);
}

// Verifies that the fixed table of event latency histogram names matches the
// cc::EventMetrics::EventType enum.
TEST_F(UiMetricsRecorderTest, EventLatencyHistogramNameTable) {
  auto histogram_names =
      UiMetricsRecorder::GetEventLatencyHistogramNamesForTest();
  ASSERT_EQ(histogram_names.size(),
            static_cast<size_t>(cc::EventMetrics::EventType::kMaxValue) + 1);
  for (size_t i = 0; i < histogram_names.size(); ++i) {
    auto event_type = static_cast<cc::EventMetrics::EventType>(i);
    const char* type_name = cc::EventMetrics::GetTypeName(event_type);
    std::string expected_name =
        base::StrCat({"Ash.EventLatency.", type_name, ".TotalLatency"});
    EXPECT_EQ(expected_name, histogram_names[i]);
  }
}

// Verifies that the fixed table of core event latency + fps histogram names
// matches the CoreEventType and FpsBucket enums.
TEST_F(UiMetricsRecorderTest, CoreEventLatencyFpsHistogramNameTable) {
  auto histogram_names =
      UiMetricsRecorder::GetCoreEventLatencyHistogramNamesForTest();
  ASSERT_EQ(histogram_names.size(),
            static_cast<size_t>(kMaxCoreEventTypeIndex * kMaxFpsBucketIndex));

  for (int i = 0; i <= static_cast<int>(FpsBucket::kMaxValue); ++i) {
    for (int j = 0; j <= static_cast<int>(CoreEventType::kMaxValue); ++j) {
      const auto fps_bucket = static_cast<FpsBucket>(i);
      const auto core_event_type = static_cast<CoreEventType>(j);
      const int index = GetHistogramIndex(core_event_type, fps_bucket);

      std::string expected_name = base::StrCat(
          {"Ash.EventLatency.Core.", CoreEventTypeToString(core_event_type),
           ".", FpsBucketToString(fps_bucket), ".TotalLatency"});
      EXPECT_EQ(expected_name, histogram_names[index]);
    }
  }
}

class UiMetricsRecorderFpsBucketTest : public UiMetricsRecorderTest,
                                       public testing::WithParamInterface<int> {
 protected:
  void SetUp() override {
    UiMetricsRecorderTest::SetUp();
    GetContextFactory()->SetRefreshRateForTests(GetParam());
  }
};

INSTANTIATE_TEST_SUITE_P(,
                         UiMetricsRecorderFpsBucketTest,
                         testing::Values(200, 120, 60, 30, 20),
                         [](const testing::TestParamInfo<int>& params) {
                           return base::StringPrintf("%dFps", params.param);
                         });

TEST_P(UiMetricsRecorderFpsBucketTest, KeyEvent) {
  const int refresh_rate = GetParam();
  std::string expected_fps_bucket;
  if (refresh_rate == 200 || refresh_rate == 20) {
    // 200 and 20fps are not assigned a bucket.
    expected_fps_bucket = "OtherFps";
  } else {
    expected_fps_bucket = base::StringPrintf("%dFps", refresh_rate);
  }

  std::unique_ptr<views::Widget> widget = CreateTestWindowWidget();
  FakeTextField* view =
      widget->SetContentsView(std::make_unique<FakeTextField>());
  widget->GetFocusManager()->SetFocusedView(view);

  base::HistogramTester histogram_tester;

  EXPECT_EQ(view->GetReceivedKeyEvent(), 0);
  PressAndReleaseKey(ui::VKEY_A);
  // Expect to receive two key events: KeyPressed and KeyRelease.
  EXPECT_EQ(view->GetReceivedKeyEvent(), 2);
  EXPECT_TRUE(ui::WaitForNextFrameToBePresented(widget->GetCompositor()));

  histogram_tester.ExpectTotalCount("Ash.EventLatency.KeyPressed.TotalLatency",
                                    1);
  histogram_tester.ExpectTotalCount(
      base::StrCat({"Ash.EventLatency.Core.KeyPressed.", expected_fps_bucket,
                    ".TotalLatency"}),
      1);

  histogram_tester.ExpectTotalCount("Ash.EventLatency.KeyReleased.TotalLatency",
                                    1);
  histogram_tester.ExpectTotalCount(
      base::StrCat({"Ash.EventLatency.Core.KeyReleased.", expected_fps_bucket,
                    ".TotalLatency"}),
      1);

  histogram_tester.ExpectTotalCount("Ash.EventLatency.TotalLatency", 2);
  histogram_tester.ExpectTotalCount("Ash.EventLatency.Core.TotalLatency", 2);
}

}  // namespace
}  // namespace ash