Commit 306fc194 authored by Lan Wei's avatar Lan Wei Committed by Commit Bot

Reland "Rewrite hr-timestamp/input-events.html to a browser test"

hr-timestamp/input-events.htmlEvent timestamp tests that the received
events on the page should be equal to the timestamp provided by the
eventSender's recorded last event. But we should deprecate eventSender in
the future, and GpuBenchmarking sends events based on the current system
time, which cannot be equal to the received events' timestamp.

Some Android bots such as android-pie-x86-rel have 1 ms difference, so
I add 1 ms error.

TBR=bokan@chromium.org,majidvp@chromium.org

Bug: 1047176
Change-Id: Ie2e52d0f097aa399823208cbb5ff2df24a23c56a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2314687Reviewed-by: default avatarLan Wei <lanwei@chromium.org>
Commit-Queue: Lan Wei <lanwei@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791466}
parent 37134953
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/input/synthetic_gesture_controller.h"
#include "content/browser/renderer_host/input/synthetic_gesture_target.h"
#include "content/browser/renderer_host/input/synthetic_pointer_driver.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/web_contents/web_contents_impl.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 "content/shell/common/shell_switches.h"
#include "third_party/blink/public/common/input/synthetic_web_input_event_builders.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "ui/events/blink/blink_features.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
namespace {
const std::string kEventListenerDataURL = R"HTML(
<!DOCTYPE html>
<meta name='viewport' content='width=device-width'/>
<style>
html, body {
margin: 0;
}
.spacer { height: 10000px; }
</style>
<div class=spacer></div>
<script type="text/javascript">
window.eventCounts =
{mousedown: 0, keydown: 0, touchstart: 0, click: 0, wheel: 0};
window.eventTimeStamp =
{mousedown: 0, keydown: 0, touchstart: 0, click: 0, wheel: 0};
function recordEvent(e) {
eventCounts[e.type]++;
if (eventCounts[e.type] == 1)
eventTimeStamp[e.type] = e.timeStamp;
}
for (var evt in eventCounts) {
document.addEventListener(evt, recordEvent);
}
document.title='ready';
</script>)HTML";
} // namespace
namespace content {
class InputEventBrowserTest : public ContentBrowserTest {
public:
InputEventBrowserTest() = default;
~InputEventBrowserTest() override = default;
RenderWidgetHostImpl* GetWidgetHost() {
return RenderWidgetHostImpl::From(
shell()->web_contents()->GetRenderViewHost()->GetWidget());
}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
ContentBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kExposeInternalsForTesting);
}
void LoadURL(const std::string& page_data) {
const GURL data_url("data:text/html," + page_data);
EXPECT_TRUE(NavigateToURL(shell(), data_url));
RenderWidgetHostImpl* host = GetWidgetHost();
frame_observer_ = std::make_unique<RenderFrameSubmissionObserver>(
host->render_frame_metadata_provider());
host->GetView()->SetSize(gfx::Size(400, 400));
base::string16 ready_title(base::ASCIIToUTF16("ready"));
TitleWatcher watcher(shell()->web_contents(), ready_title);
ignore_result(watcher.WaitAndGetTitle());
// We need to wait until hit test data is available. We use our own
// HitTestRegionObserver here because we have the RenderWidgetHostImpl
// available.
HitTestRegionObserver observer(host->GetFrameSinkId());
observer.WaitForHitTestData();
}
// ContentBrowserTest:
void PostRunTestOnMainThread() override {
// Delete this before the WebContents is destroyed.
frame_observer_.reset();
ContentBrowserTest::PostRunTestOnMainThread();
}
bool URLLoaded() {
base::string16 ready_title(base::ASCIIToUTF16("ready"));
TitleWatcher watcher(shell()->web_contents(), ready_title);
const base::string16 title = watcher.WaitAndGetTitle();
return title == ready_title;
}
int ExecuteScriptAndExtractInt(const std::string& script) {
int value = 0;
EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
shell(), "domAutomationController.send(" + script + ")", &value));
return value;
}
double ExecuteScriptAndExtractDouble(const std::string& script) {
double value = 0;
EXPECT_TRUE(content::ExecuteScriptAndExtractDouble(
shell(), "domAutomationController.send(" + script + ")", &value));
return value;
}
void SimulateSyntheticMousePressAt(base::TimeTicks event_time) {
DCHECK(URLLoaded());
std::unique_ptr<SyntheticPointerDriver> synthetic_pointer_driver =
SyntheticPointerDriver::Create(SyntheticGestureParams::MOUSE_INPUT);
RenderWidgetHostImpl* render_widget_host = GetWidgetHost();
auto* root_view = render_widget_host->GetView()->GetRootView();
std::unique_ptr<SyntheticGestureTarget> synthetic_gesture_target;
if (root_view)
synthetic_gesture_target = root_view->CreateSyntheticGestureTarget();
else
synthetic_gesture_target =
render_widget_host->GetView()->CreateSyntheticGestureTarget();
synthetic_pointer_driver->Press(50, 50, 0,
SyntheticPointerActionParams::Button::LEFT);
synthetic_pointer_driver->DispatchEvent(synthetic_gesture_target.get(),
event_time);
synthetic_pointer_driver->Release(
0, SyntheticPointerActionParams::Button::LEFT);
synthetic_pointer_driver->DispatchEvent(synthetic_gesture_target.get(),
event_time);
}
void SimulateSyntheticKeyDown(base::TimeTicks event_time) {
DCHECK(URLLoaded());
content::NativeWebKeyboardEvent event(
blink::WebKeyboardEvent::Type::kRawKeyDown,
blink::WebInputEvent::kNoModifiers, event_time);
event.windows_key_code = ui::VKEY_DOWN;
event.native_key_code =
ui::KeycodeConverter::DomCodeToNativeKeycode(ui::DomCode::ARROW_DOWN);
event.dom_code = static_cast<int>(ui::DomCode::ARROW_DOWN);
event.dom_key = ui::DomKey::ARROW_DOWN;
GetWidgetHost()->ForwardKeyboardEvent(event);
}
void SimulateSyntheticTouchTapAt(base::TimeTicks event_time) {
DCHECK(URLLoaded());
std::unique_ptr<SyntheticPointerDriver> synthetic_pointer_driver =
SyntheticPointerDriver::Create(SyntheticGestureParams::TOUCH_INPUT);
RenderWidgetHostImpl* render_widget_host = GetWidgetHost();
auto* root_view = render_widget_host->GetView()->GetRootView();
std::unique_ptr<SyntheticGestureTarget> synthetic_gesture_target;
if (root_view)
synthetic_gesture_target = root_view->CreateSyntheticGestureTarget();
else
synthetic_gesture_target =
render_widget_host->GetView()->CreateSyntheticGestureTarget();
synthetic_pointer_driver->Press(50, 50, 0,
SyntheticPointerActionParams::Button::LEFT);
synthetic_pointer_driver->DispatchEvent(synthetic_gesture_target.get(),
event_time);
synthetic_pointer_driver->Release(
0, SyntheticPointerActionParams::Button::LEFT);
synthetic_pointer_driver->DispatchEvent(synthetic_gesture_target.get(),
event_time);
}
void SimulateSyntheticWheelScroll(base::TimeTicks event_time) {
DCHECK(URLLoaded());
double x = 50;
double y = 50;
blink::WebMouseWheelEvent wheel_event =
blink::SyntheticWebMouseWheelEventBuilder::Build(
x, y, x, y, 20, 20, 0,
ui::ScrollGranularity::kScrollByPrecisePixel);
wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
wheel_event.SetTimeStamp(event_time);
GetWidgetHost()->ForwardWheelEvent(wheel_event);
}
private:
std::unique_ptr<RenderFrameSubmissionObserver> frame_observer_;
DISALLOW_COPY_AND_ASSIGN(InputEventBrowserTest);
};
#if defined(OS_ANDROID)
// Android does not support synthetic mouse events.
// TODO(lanwei): support dispatching WebMouseEvent in
// SyntheticGestureTargetAndroid.
#define MAYBE_MouseDownEventTimeStamp DISABLED_MouseDownEventTimeStamp
#else
#define MAYBE_MouseDownEventTimeStamp MouseDownEventTimeStamp
#endif
IN_PROC_BROWSER_TEST_F(InputEventBrowserTest, MAYBE_MouseDownEventTimeStamp) {
LoadURL(kEventListenerDataURL);
MainThreadFrameObserver frame_observer(
shell()->web_contents()->GetRenderViewHost()->GetWidget());
base::TimeTicks event_time = base::TimeTicks::Now();
int64_t event_time_ms = event_time.since_origin().InMilliseconds();
SimulateSyntheticMousePressAt(event_time);
while (ExecuteScriptAndExtractInt("eventCounts.mousedown") == 0)
frame_observer.Wait();
int64_t monotonic_time = ExecuteScriptAndExtractDouble(
"internals.zeroBasedDocumentTimeToMonotonicTime(eventTimeStamp."
"mousedown)");
EXPECT_EQ(1, ExecuteScriptAndExtractInt("eventCounts.mousedown"));
EXPECT_NEAR(event_time_ms, monotonic_time, 1);
}
IN_PROC_BROWSER_TEST_F(InputEventBrowserTest, KeyDownEventTimeStamp) {
LoadURL(kEventListenerDataURL);
MainThreadFrameObserver frame_observer(
shell()->web_contents()->GetRenderViewHost()->GetWidget());
base::TimeTicks event_time = base::TimeTicks::Now();
int64_t event_time_ms = event_time.since_origin().InMilliseconds();
SimulateSyntheticKeyDown(event_time);
while (ExecuteScriptAndExtractInt("eventCounts.keydown") == 0)
frame_observer.Wait();
int64_t monotonic_time = ExecuteScriptAndExtractDouble(
"internals.zeroBasedDocumentTimeToMonotonicTime(eventTimeStamp."
"keydown)");
EXPECT_EQ(1, ExecuteScriptAndExtractInt("eventCounts.keydown"));
EXPECT_NEAR(event_time_ms, monotonic_time, 1);
}
IN_PROC_BROWSER_TEST_F(InputEventBrowserTest, TouchStartEventTimeStamp) {
LoadURL(kEventListenerDataURL);
MainThreadFrameObserver frame_observer(
shell()->web_contents()->GetRenderViewHost()->GetWidget());
base::TimeTicks event_time = base::TimeTicks::Now();
int64_t event_time_ms = event_time.since_origin().InMilliseconds();
SimulateSyntheticTouchTapAt(event_time);
while (ExecuteScriptAndExtractInt("eventCounts.touchstart") == 0)
frame_observer.Wait();
int64_t monotonic_time = ExecuteScriptAndExtractDouble(
"internals.zeroBasedDocumentTimeToMonotonicTime(eventTimeStamp."
"touchstart)");
EXPECT_EQ(1, ExecuteScriptAndExtractInt("eventCounts.touchstart"));
EXPECT_NEAR(event_time_ms, monotonic_time, 1);
}
IN_PROC_BROWSER_TEST_F(InputEventBrowserTest, ClickEventTimeStamp) {
LoadURL(kEventListenerDataURL);
MainThreadFrameObserver frame_observer(
shell()->web_contents()->GetRenderViewHost()->GetWidget());
base::TimeTicks event_time = base::TimeTicks::Now();
int64_t event_time_ms = event_time.since_origin().InMilliseconds();
SimulateSyntheticTouchTapAt(event_time);
while (ExecuteScriptAndExtractInt("eventCounts.click") == 0)
frame_observer.Wait();
int64_t monotonic_time = ExecuteScriptAndExtractDouble(
"internals.zeroBasedDocumentTimeToMonotonicTime(eventTimeStamp."
"click)");
EXPECT_EQ(1, ExecuteScriptAndExtractInt("eventCounts.click"));
EXPECT_NEAR(event_time_ms, monotonic_time, 1);
}
IN_PROC_BROWSER_TEST_F(InputEventBrowserTest, WheelEventTimeStamp) {
LoadURL(kEventListenerDataURL);
MainThreadFrameObserver frame_observer(
shell()->web_contents()->GetRenderViewHost()->GetWidget());
base::TimeTicks event_time = base::TimeTicks::Now();
int64_t event_time_ms = event_time.since_origin().InMilliseconds();
SimulateSyntheticWheelScroll(event_time);
while (ExecuteScriptAndExtractInt("eventCounts.wheel") == 0)
frame_observer.Wait();
int64_t monotonic_time = ExecuteScriptAndExtractDouble(
"internals.zeroBasedDocumentTimeToMonotonicTime(eventTimeStamp."
"wheel)");
EXPECT_GE(ExecuteScriptAndExtractInt("eventCounts.wheel"), 1);
EXPECT_NEAR(event_time_ms, monotonic_time, 1);
}
} // namespace content
...@@ -121,7 +121,7 @@ void SyntheticGestureTargetAura::DispatchWebGestureEventToPlatform( ...@@ -121,7 +121,7 @@ void SyntheticGestureTargetAura::DispatchWebGestureEventToPlatform(
ui::GestureEvent pinch_event(web_gesture.PositionInWidget().x(), ui::GestureEvent pinch_event(web_gesture.PositionInWidget().x(),
web_gesture.PositionInWidget().y(), flags, web_gesture.PositionInWidget().y(), flags,
ui::EventTimeForNow(), pinch_details); web_gesture.TimeStamp(), pinch_details);
pinch_event.ConvertLocationToTarget(window, window->GetRootWindow()); pinch_event.ConvertLocationToTarget(window, window->GetRootWindow());
event_injector_.Inject(window->GetHost(), &pinch_event); event_injector_.Inject(window->GetHost(), &pinch_event);
...@@ -134,7 +134,7 @@ void SyntheticGestureTargetAura::DispatchWebGestureEventToPlatform( ...@@ -134,7 +134,7 @@ void SyntheticGestureTargetAura::DispatchWebGestureEventToPlatform(
: ui::EventMomentumPhase::END; : ui::EventMomentumPhase::END;
ui::ScrollEvent scroll_event(event_type, web_gesture.PositionInWidget(), ui::ScrollEvent scroll_event(event_type, web_gesture.PositionInWidget(),
web_gesture.PositionInWidget(), web_gesture.PositionInWidget(),
ui::EventTimeForNow(), flags, web_gesture.TimeStamp(), flags,
web_gesture.data.fling_start.velocity_x, web_gesture.data.fling_start.velocity_x,
web_gesture.data.fling_start.velocity_y, 0, 0, 2, web_gesture.data.fling_start.velocity_y, 0, 0, 2,
momentum_phase, ui::ScrollEventPhase::kNone); momentum_phase, ui::ScrollEventPhase::kNone);
...@@ -157,8 +157,8 @@ void SyntheticGestureTargetAura::DispatchWebMouseEventToPlatform( ...@@ -157,8 +157,8 @@ void SyntheticGestureTargetAura::DispatchWebMouseEventToPlatform(
} }
ui::MouseEvent mouse_event(event_type, web_mouse_event.PositionInWidget(), ui::MouseEvent mouse_event(event_type, web_mouse_event.PositionInWidget(),
web_mouse_event.PositionInWidget(), web_mouse_event.PositionInWidget(),
ui::EventTimeForNow(), flags, changed_button_flags, web_mouse_event.TimeStamp(), flags,
pointer_details); changed_button_flags, pointer_details);
aura::Window* window = GetWindow(); aura::Window* window = GetWindow();
mouse_event.ConvertLocationToTarget(window, window->GetRootWindow()); mouse_event.ConvertLocationToTarget(window, window->GetRootWindow());
......
...@@ -1036,6 +1036,7 @@ test("content_browsertests") { ...@@ -1036,6 +1036,7 @@ test("content_browsertests") {
"../browser/renderer_host/input/compositor_event_ack_browsertest.cc", "../browser/renderer_host/input/compositor_event_ack_browsertest.cc",
"../browser/renderer_host/input/event_latency_aura_browsertest.cc", "../browser/renderer_host/input/event_latency_aura_browsertest.cc",
"../browser/renderer_host/input/fling_browsertest.cc", "../browser/renderer_host/input/fling_browsertest.cc",
"../browser/renderer_host/input/input_event_browsertest.cc",
"../browser/renderer_host/input/interaction_mq_dynamic_browsertest.cc", "../browser/renderer_host/input/interaction_mq_dynamic_browsertest.cc",
"../browser/renderer_host/input/main_thread_event_queue_browsertest.cc", "../browser/renderer_host/input/main_thread_event_queue_browsertest.cc",
"../browser/renderer_host/input/mouse_latency_browsertest.cc", "../browser/renderer_host/input/mouse_latency_browsertest.cc",
......
...@@ -83,6 +83,16 @@ base::TimeDelta DocumentLoadTiming::MonotonicTimeToZeroBasedDocumentTime( ...@@ -83,6 +83,16 @@ base::TimeDelta DocumentLoadTiming::MonotonicTimeToZeroBasedDocumentTime(
return monotonic_time - reference_monotonic_time_; return monotonic_time - reference_monotonic_time_;
} }
int64_t DocumentLoadTiming::ZeroBasedDocumentTimeToMonotonicTime(
double dom_event_time) const {
if (reference_monotonic_time_.is_null())
return 0;
base::TimeTicks monotonic_time =
reference_monotonic_time_ +
base::TimeDelta::FromMillisecondsD(dom_event_time);
return monotonic_time.since_origin().InMilliseconds();
}
base::TimeDelta DocumentLoadTiming::MonotonicTimeToPseudoWallTime( base::TimeDelta DocumentLoadTiming::MonotonicTimeToPseudoWallTime(
base::TimeTicks monotonic_time) const { base::TimeTicks monotonic_time) const {
if (monotonic_time.is_null() || reference_monotonic_time_.is_null()) if (monotonic_time.is_null() || reference_monotonic_time_.is_null())
......
...@@ -49,6 +49,7 @@ class CORE_EXPORT DocumentLoadTiming final { ...@@ -49,6 +49,7 @@ class CORE_EXPORT DocumentLoadTiming final {
explicit DocumentLoadTiming(DocumentLoader&); explicit DocumentLoadTiming(DocumentLoader&);
base::TimeDelta MonotonicTimeToZeroBasedDocumentTime(base::TimeTicks) const; base::TimeDelta MonotonicTimeToZeroBasedDocumentTime(base::TimeTicks) const;
int64_t ZeroBasedDocumentTimeToMonotonicTime(double dom_event_time) const;
base::TimeDelta MonotonicTimeToPseudoWallTime(base::TimeTicks) const; base::TimeDelta MonotonicTimeToPseudoWallTime(base::TimeTicks) const;
void MarkNavigationStart(); void MarkNavigationStart();
......
...@@ -3317,6 +3317,11 @@ double Internals::monotonicTimeToZeroBasedDocumentTime( ...@@ -3317,6 +3317,11 @@ double Internals::monotonicTimeToZeroBasedDocumentTime(
.InSecondsF(); .InSecondsF();
} }
int64_t Internals::zeroBasedDocumentTimeToMonotonicTime(double dom_event_time) {
return document_->Loader()->GetTiming().ZeroBasedDocumentTimeToMonotonicTime(
dom_event_time);
}
int64_t Internals::currentTimeTicks() { int64_t Internals::currentTimeTicks() {
return base::TimeTicks::Now().since_origin().InMicroseconds(); return base::TimeTicks::Now().since_origin().InMicroseconds();
} }
......
...@@ -543,6 +543,10 @@ class Internals final : public ScriptWrappable { ...@@ -543,6 +543,10 @@ class Internals final : public ScriptWrappable {
// document time in seconds // document time in seconds
double monotonicTimeToZeroBasedDocumentTime(double, ExceptionState&); double monotonicTimeToZeroBasedDocumentTime(double, ExceptionState&);
// Translate an event's DOMHighResTimeStamp in seconds into a monotonic time
// in milliseconds.
int64_t zeroBasedDocumentTimeToMonotonicTime(double dom_event_time);
// Returns the current time ticks (in microseconds). // Returns the current time ticks (in microseconds).
int64_t currentTimeTicks(); int64_t currentTimeTicks();
......
...@@ -366,6 +366,11 @@ ...@@ -366,6 +366,11 @@
boolean setScrollbarVisibilityInScrollableArea(Node node, boolean visible); boolean setScrollbarVisibilityInScrollableArea(Node node, boolean visible);
[RaisesException] double monotonicTimeToZeroBasedDocumentTime(double platformTime); [RaisesException] double monotonicTimeToZeroBasedDocumentTime(double platformTime);
// Translate an event's DOMHighResTimeStamp in seconds into a monotonic time
// in milliseconds.
long long zeroBasedDocumentTimeToMonotonicTime(double domHighResTimeStamp);
long long currentTimeTicks(); long long currentTimeTicks();
DOMString getScrollAnimationState(Node node); DOMString getScrollAnimationState(Node node);
......
<!DOCTYPE html>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script type="text/javascript">
'use strict';
const testCases = {
'mousedown': () => eventSender.mouseDown(),
'keydown': () => eventSender.keyDown('x'),
'touchstart': () => {
eventSender.addTouchPoint(1, 1);
eventSender.touchStart();
},
'click': () => eventSender.gestureTap(1, 1),
'wheel': () => eventSender.mouseScrollBy(0, -50),
};
let receivedEvents = [];
for (let eventName in testCases)
createTest(eventName, testCases[eventName]);
function createTest(eventName, dispatchEventFn) {
async_test(function(t) {
document.addEventListener(eventName, t.step_func(function(e) {
receivedEvents.push(eventName);
// Prevent default to ensure contextmenu is not shown which can
// potentially hijack other events.
e.preventDefault();
const platformTimestamp = eventSender.lastEventTimestamp(); // in seconds
const expectedUnclampedTimestamp = internals.monotonicTimeToZeroBasedDocumentTime(platformTimestamp) * 1000; // in milliseconds
// Time clamping logic in Blink can introduce at most 2*100us of
// difference. Use 0.200001 instead of 0.2 to deal with floating
// point comparison issues.
assert_approx_equals(e.timeStamp, expectedUnclampedTimestamp, 0.200001);
t.done();
}));
dispatchEventFn();
t.step_timeout(function() {
assert_unreached("timeout with received events: " + receivedEvents.join(', '));
}, 5000);
}, "Event timestamp should be equal to the timestamp provided by the platform for " + eventName);
}
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment