// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/test/metrics/histogram_tester.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.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 "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/test/event_generator.h"

namespace content {

class EventLatencyBrowserTest : public ContentBrowserTest {
 public:
  EventLatencyBrowserTest() = default;
  ~EventLatencyBrowserTest() override = default;

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

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

  // Starts the test server and navigates to the test page. Returns after the
  // navigation is complete.
  void LoadTestPage() {
    ASSERT_TRUE(embedded_test_server()->Start());

    // Navigate to the test page which has a rAF animation and a main thread
    // animation running.
    GURL test_url =
        embedded_test_server()->GetURL("/event-latency-animation.html");
    EXPECT_TRUE(NavigateToURL(shell(), test_url));

    aura::Window* content = shell()->web_contents()->GetContentNativeView();
    content->GetHost()->SetBoundsInPixels(gfx::Rect(800, 600));

    RenderWidgetHostImpl* host = GetWidgetHost();
    HitTestRegionObserver observer(host->GetFrameSinkId());

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

  void FocusButton() const { ASSERT_TRUE(ExecJs(shell(), "focusButton()")); }

  void FocusInput() const { ASSERT_TRUE(ExecJs(shell(), "focusInput()")); }

  void StartAnimations() const {
    ASSERT_TRUE(ExecJs(shell(), "startAnimations()"));
  }
};

// Tests that if a key-press on a page causes a visual update, appropriate event
// latency metrics are reported.
// TODO(crbug.com/40132021): flaky test.
IN_PROC_BROWSER_TEST_F(EventLatencyBrowserTest, DISABLED_KeyPressOnButton) {
  base::HistogramTester histogram_tester;

  ASSERT_NO_FATAL_FAILURE(LoadTestPage());
  FocusButton();

  ui::test::EventGenerator generator(shell()
                                         ->web_contents()
                                         ->GetRenderWidgetHostView()
                                         ->GetNativeView()
                                         ->GetRootWindow());

  // Press and release the space key. Since the button on the test page is
  // focused, this should change the visuals of the button and generate
  // compositor frames with appropriate event latency metrics.
  generator.PressKey(ui::VKEY_SPACE, 0);
  generator.ReleaseKey(ui::VKEY_SPACE, 0);
  RunUntilInputProcessed(GetWidgetHost());

  FetchHistogramsFromChildProcesses();

  base::HistogramTester::CountsMap expected_counts = {
      {"EventLatency.KeyPressed.BrowserToRendererCompositor", 1},
      {"EventLatency.KeyPressed.BeginImplFrameToSendBeginMainFrame", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.HandleInputEvents",
       1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Animate", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.StyleUpdate", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.LayoutUpdate", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Prepaint", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Composite", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Paint", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit."
       "ScrollingCoordinator",
       1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.CompositeCommit", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.UpdateLayers", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit."
       "BeginMainSentToStarted",
       1},
      {"EventLatency.KeyPressed.Commit", 1},
      {"EventLatency.KeyPressed.EndCommitToActivation", 1},
      {"EventLatency.KeyPressed.Activation", 1},
      {"EventLatency.KeyPressed.EndActivateToSubmitCompositorFrame", 1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame",
       1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "SubmitToReceiveCompositorFrame",
       1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "ReceivedCompositorFrameToStartDraw",
       1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "StartDrawToSwapStart",
       1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame.SwapStartToSwapEnd",
       1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "SwapEndToPresentationCompositorFrame",
       1},
      {"EventLatency.KeyPressed.TotalLatency", 1},
      {"EventLatency.KeyReleased.BrowserToRendererCompositor", 1},
      {"EventLatency.KeyReleased.BeginImplFrameToSendBeginMainFrame", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.HandleInputEvents",
       1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.Animate", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.StyleUpdate", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.LayoutUpdate", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.Prepaint", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.Composite", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.Paint", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit."
       "ScrollingCoordinator",
       1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.CompositeCommit",
       1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.UpdateLayers", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit."
       "BeginMainSentToStarted",
       1},
      {"EventLatency.KeyReleased.Commit", 1},
      {"EventLatency.KeyReleased.EndCommitToActivation", 1},
      {"EventLatency.KeyReleased.Activation", 1},
      {"EventLatency.KeyReleased.EndActivateToSubmitCompositorFrame", 1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame",
       1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "SubmitToReceiveCompositorFrame",
       1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "ReceivedCompositorFrameToStartDraw",
       1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "StartDrawToSwapStart",
       1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame.SwapStartToSwapEnd",
       1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "SwapEndToPresentationCompositorFrame",
       1},
      {"EventLatency.KeyReleased.TotalLatency", 1},
      {"EventLatency.TotalLatency", 2},
  };
  EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix("EventLatency."),
              testing::ContainerEq(expected_counts));
}

// Tests that if a key-press on a page with an animation causes a visual update,
// appropriate event latency metrics are reported.
// TODO(crbug.com/40128555): Test is flaky.
IN_PROC_BROWSER_TEST_F(EventLatencyBrowserTest,
                       DISABLED_KeyPressOnButtonWithAnimation) {
  base::HistogramTester histogram_tester;

  ASSERT_NO_FATAL_FAILURE(LoadTestPage());
  StartAnimations();
  FocusButton();

  ui::test::EventGenerator generator(shell()
                                         ->web_contents()
                                         ->GetRenderWidgetHostView()
                                         ->GetNativeView()
                                         ->GetRootWindow());

  // Press and release the space key. Since the button on the test page is
  // focused, this should change the visuals of the button and generate
  // compositor frames with appropriate event latency metrics.
  generator.PressKey(ui::VKEY_SPACE, 0);
  generator.ReleaseKey(ui::VKEY_SPACE, 0);
  RunUntilInputProcessed(GetWidgetHost());

  FetchHistogramsFromChildProcesses();

  base::HistogramTester::CountsMap expected_counts = {
      {"EventLatency.KeyPressed.BrowserToRendererCompositor", 1},
      {"EventLatency.KeyPressed.BeginImplFrameToSendBeginMainFrame", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.HandleInputEvents",
       1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Animate", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.StyleUpdate", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.LayoutUpdate", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Prepaint", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Composite", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Paint", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit."
       "ScrollingCoordinator",
       1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.CompositeCommit", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.UpdateLayers", 1},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit."
       "BeginMainSentToStarted",
       1},
      {"EventLatency.KeyPressed.Commit", 1},
      {"EventLatency.KeyPressed.EndCommitToActivation", 1},
      {"EventLatency.KeyPressed.Activation", 1},
      {"EventLatency.KeyPressed.EndActivateToSubmitCompositorFrame", 1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame",
       1},
      {"EventLatency.KeyPressed.TotalLatency", 1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "SubmitToReceiveCompositorFrame",
       1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "ReceivedCompositorFrameToStartDraw",
       1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "StartDrawToSwapStart",
       1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame.SwapStartToSwapEnd",
       1},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "SwapEndToPresentationCompositorFrame",
       1},
      {"EventLatency.KeyReleased.BrowserToRendererCompositor", 1},
      {"EventLatency.KeyReleased.BeginImplFrameToSendBeginMainFrame", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.HandleInputEvents",
       1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.Animate", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.StyleUpdate", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.LayoutUpdate", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.Prepaint", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.Composite", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.Paint", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit."
       "ScrollingCoordinator",
       1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.CompositeCommit",
       1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit.UpdateLayers", 1},
      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit."
       "BeginMainSentToStarted",
       1},
      {"EventLatency.KeyReleased.Commit", 1},
      {"EventLatency.KeyReleased.EndCommitToActivation", 1},
      {"EventLatency.KeyReleased.Activation", 1},
      {"EventLatency.KeyReleased.EndActivateToSubmitCompositorFrame", 1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame",
       1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "SubmitToReceiveCompositorFrame",
       1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "ReceivedCompositorFrameToStartDraw",
       1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "StartDrawToSwapStart",
       1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame.SwapStartToSwapEnd",
       1},
      {"EventLatency.KeyReleased."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "SwapEndToPresentationCompositorFrame",
       1},
      {"EventLatency.KeyReleased.TotalLatency", 1},
      {"EventLatency.TotalLatency", 2},
  };
  EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix("EventLatency."),
              testing::ContainerEq(expected_counts));
}

// Tests that entering a character in a textbox leads to appropriate event
// latency metrics being reported even though the page has an animation running.
//
// Disabled due to flakiness on several platforms. See crbug.com/1072340
IN_PROC_BROWSER_TEST_F(EventLatencyBrowserTest,
                       DISABLED_KeyPressInInputWithAnimation) {
  base::HistogramTester histogram_tester;

  ASSERT_NO_FATAL_FAILURE(LoadTestPage());
  StartAnimations();
  FocusInput();

  ui::test::EventGenerator generator(shell()
                                         ->web_contents()
                                         ->GetRenderWidgetHostView()
                                         ->GetNativeView()
                                         ->GetRootWindow());

  // Enter a character into the focused textbox. This should generate compositor
  // frames with appropriate event latency metrics.
  generator.PressKey(ui::VKEY_A, 0);
  generator.ReleaseKey(ui::VKEY_A, 0);
  RunUntilInputProcessed(GetWidgetHost());

  FetchHistogramsFromChildProcesses();

  // TODO(crbug.com/40126863): Since this is this first key-press after the
  // textbox is focused, there would be two reports, one for the RawKeyDown that
  // causes some style changes (due to :focus-visible behavior) and one for the
  // Char that inserts the actual character. These should be reported
  // separately.
  base::HistogramTester::CountsMap expected_counts = {
      {"EventLatency.KeyPressed.BrowserToRendererCompositor", 2},
      {"EventLatency.KeyPressed.BeginImplFrameToSendBeginMainFrame", 2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit", 2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.HandleInputEvents",
       2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Animate", 2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.StyleUpdate", 2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.LayoutUpdate", 2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Prepaint", 2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Composite", 2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.Paint", 2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit."
       "ScrollingCoordinator",
       2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.CompositeCommit", 2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit.UpdateLayers", 2},
      {"EventLatency.KeyPressed.SendBeginMainFrameToCommit."
       "BeginMainSentToStarted",
       2},
      {"EventLatency.KeyPressed.Commit", 2},
      {"EventLatency.KeyPressed.EndCommitToActivation", 2},
      {"EventLatency.KeyPressed.Activation", 2},
      {"EventLatency.KeyPressed.EndActivateToSubmitCompositorFrame", 2},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame",
       2},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "SubmitToReceiveCompositorFrame",
       2},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "ReceivedCompositorFrameToStartDraw",
       2},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "StartDrawToSwapStart",
       2},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame.SwapStartToSwapEnd",
       2},
      {"EventLatency.KeyPressed."
       "SubmitCompositorFrameToPresentationCompositorFrame."
       "SwapEndToPresentationCompositorFrame",
       2},
      {"EventLatency.KeyPressed.TotalLatency", 2},
      {"EventLatency.TotalLatency", 2},
  };
  EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix("EventLatency."),
              testing::ContainerEq(expected_counts));
}

}  // namespace content
