blob: c7339bc4e49636468298b0dce96a5febe483606d [file] [log] [blame]
// Copyright 2018 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/feature_list.h"
#include "build/build_config.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/widget_messages.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/shell/browser/shell.h"
#include "ui/events/base_event_utils.h"
using blink::WebInputEvent;
namespace {
const std::string kAutoscrollDataURL = 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>
document.title='ready';
</script>)HTML";
} // namespace
namespace content {
// Waits for the ack of a gesture scroll event with the given type and returns
// the acked event.
class GestureScrollEventWatcher : public RenderWidgetHost::InputEventObserver {
public:
GestureScrollEventWatcher(RenderWidgetHost* rwh,
blink::WebInputEvent::Type event_type)
: rwh_(static_cast<RenderWidgetHostImpl*>(rwh)->GetWeakPtr()),
event_type_(event_type) {
rwh->AddInputEventObserver(this);
Reset();
}
~GestureScrollEventWatcher() override {
if (rwh_)
rwh_->RemoveInputEventObserver(this);
}
void OnInputEventAck(InputEventAckSource,
InputEventAckState,
const blink::WebInputEvent& event) override {
if (event.GetType() != event_type_)
return;
if (run_loop_)
run_loop_->Quit();
blink::WebGestureEvent acked_gesture_event =
*static_cast<const blink::WebGestureEvent*>(&event);
acked_gesture_event_ =
std::make_unique<blink::WebGestureEvent>(acked_gesture_event);
}
void Wait() {
if (acked_gesture_event_)
return;
DCHECK(run_loop_);
run_loop_->Run();
}
void Reset() {
acked_gesture_event_.reset();
run_loop_ = std::make_unique<base::RunLoop>();
}
const blink::WebGestureEvent* AckedGestureEvent() const {
return acked_gesture_event_.get();
}
private:
base::WeakPtr<RenderWidgetHostImpl> rwh_;
blink::WebInputEvent::Type event_type_;
std::unique_ptr<base::RunLoop> run_loop_;
std::unique_ptr<blink::WebGestureEvent> acked_gesture_event_;
};
class AutoscrollBrowserTest : public ContentBrowserTest {
public:
AutoscrollBrowserTest() {}
~AutoscrollBrowserTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII("--enable-blink-features",
"MiddleClickAutoscroll");
}
protected:
RenderWidgetHostImpl* GetWidgetHost() {
return RenderWidgetHostImpl::From(
shell()->web_contents()->GetRenderViewHost()->GetWidget());
}
void LoadURL(const std::string& page_data) {
const GURL data_url("data:text/html," + page_data);
NavigateToURL(shell(), data_url);
RenderWidgetHostImpl* host = GetWidgetHost();
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());
MainThreadFrameObserver main_thread_sync(host);
main_thread_sync.Wait();
}
// Force redraw and wait for a compositor commit for the given number of
// times.
void WaitForCommitFrames(int num_repeat) {
RenderFrameSubmissionObserver observer(
GetWidgetHost()->render_frame_metadata_provider());
for (int i = 0; i < num_repeat; i++) {
GetWidgetHost()->Send(
new WidgetMsg_ForceRedraw(GetWidgetHost()->GetRoutingID(), i));
observer.WaitForAnyFrameSubmission();
}
}
void SimulateMiddleClick(int x, int y, int modifiers) {
bool is_autoscroll_in_progress = GetWidgetHost()->IsAutoscrollInProgress();
// Simulate and send middle click mouse down.
blink::WebMouseEvent down_event = SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseDown, x, y, modifiers);
down_event.button = blink::WebMouseEvent::Button::kMiddle;
down_event.SetTimeStamp(ui::EventTimeForNow());
down_event.SetPositionInScreen(x, y);
GetWidgetHost()->ForwardMouseEvent(down_event);
// Simulate and send middle click mouse up.
blink::WebMouseEvent up_event = SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseUp, x, y, modifiers);
up_event.button = blink::WebMouseEvent::Button::kMiddle;
up_event.SetTimeStamp(ui::EventTimeForNow());
up_event.SetPositionInScreen(x, y);
GetWidgetHost()->ForwardMouseEvent(up_event);
// Wait till the IPC messages arrive and IsAutoscrollInProgress() toggles.
while (GetWidgetHost()->IsAutoscrollInProgress() ==
is_autoscroll_in_progress) {
WaitForCommitFrames(1);
}
}
void WaitForScroll(RenderFrameSubmissionObserver& observer) {
gfx::Vector2dF default_scroll_offset;
while (observer.LastRenderFrameMetadata()
.root_scroll_offset.value_or(default_scroll_offset)
.y() <= 0) {
observer.WaitForMetadataChange();
}
}
private:
DISALLOW_COPY_AND_ASSIGN(AutoscrollBrowserTest);
};
// We don't plan on supporting middle click autoscroll on Android.
// See https://crbug.com/686223
#if !defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_F(AutoscrollBrowserTest, AutoscrollFling) {
LoadURL(kAutoscrollDataURL);
// Start autoscroll with middle click.
auto scroll_begin_watcher = std::make_unique<GestureScrollEventWatcher>(
GetWidgetHost(), blink::WebInputEvent::kGestureScrollBegin);
SimulateMiddleClick(10, 10, blink::WebInputEvent::kNoModifiers);
// The page should start scrolling with mouse move.
RenderFrameSubmissionObserver observer(
GetWidgetHost()->render_frame_metadata_provider());
blink::WebMouseEvent move_event = SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseMove, 50, 50,
blink::WebInputEvent::kNoModifiers);
move_event.SetTimeStamp(ui::EventTimeForNow());
move_event.SetPositionInScreen(50, 50);
GetWidgetHost()->ForwardMouseEvent(move_event);
scroll_begin_watcher->Wait();
WaitForScroll(observer);
}
// Tests that the GSB sent in the beginning of a middle click autoscroll has
// none-zero delta hints.
IN_PROC_BROWSER_TEST_F(AutoscrollBrowserTest, AutoscrollFlingGSBDeltaHints) {
LoadURL(kAutoscrollDataURL);
// Start autoscroll with middle click.
auto scroll_begin_watcher = std::make_unique<GestureScrollEventWatcher>(
GetWidgetHost(), blink::WebInputEvent::kGestureScrollBegin);
SimulateMiddleClick(10, 10, blink::WebInputEvent::kNoModifiers);
// A GSB will be sent on first mouse move.
blink::WebMouseEvent move_event = SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseMove, 50, 50,
blink::WebInputEvent::kNoModifiers);
move_event.SetTimeStamp(ui::EventTimeForNow());
move_event.SetPositionInScreen(50, 50);
GetWidgetHost()->ForwardMouseEvent(move_event);
scroll_begin_watcher->Wait();
const blink::WebGestureEvent* acked_scroll_begin =
scroll_begin_watcher->AckedGestureEvent();
DCHECK(acked_scroll_begin);
DCHECK(acked_scroll_begin->data.scroll_begin.delta_x_hint ||
acked_scroll_begin->data.scroll_begin.delta_y_hint);
}
// Tests that the GSU and GSE events generated from the autoscroll fling have
// non-zero positions in widget.
// Disabled due to flakiness. See https://crbug.com/930011.
IN_PROC_BROWSER_TEST_F(AutoscrollBrowserTest,
DISABLED_GSUGSEValidPositionInWidget) {
LoadURL(kAutoscrollDataURL);
// Start autoscroll with middle click.
auto scroll_update_watcher = std::make_unique<GestureScrollEventWatcher>(
GetWidgetHost(), blink::WebInputEvent::kGestureScrollUpdate);
SimulateMiddleClick(10, 10, blink::WebInputEvent::kNoModifiers);
// Check that the generated GSU has non-zero position in widget.
blink::WebMouseEvent move_event = SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseMove, 50, 50,
blink::WebInputEvent::kNoModifiers);
move_event.SetTimeStamp(ui::EventTimeForNow());
move_event.SetPositionInScreen(50, 50);
GetWidgetHost()->ForwardMouseEvent(move_event);
scroll_update_watcher->Wait();
const blink::WebGestureEvent* acked_scroll_update =
scroll_update_watcher->AckedGestureEvent();
DCHECK(acked_scroll_update);
DCHECK(acked_scroll_update->PositionInWidget() != blink::WebFloatPoint());
// End autoscroll and check that the GSE generated from autoscroll fling
// cancelation has non-zero position in widget.
auto scroll_end_watcher = std::make_unique<GestureScrollEventWatcher>(
GetWidgetHost(), blink::WebInputEvent::kGestureScrollEnd);
SimulateMiddleClick(50, 50, blink::WebInputEvent::kNoModifiers);
scroll_end_watcher->Wait();
const blink::WebGestureEvent* acked_scroll_end =
scroll_end_watcher->AckedGestureEvent();
DCHECK(acked_scroll_end);
DCHECK(acked_scroll_end->PositionInWidget() != blink::WebFloatPoint());
}
// Checks that wheel scrolling works after autoscroll cancelation.
IN_PROC_BROWSER_TEST_F(AutoscrollBrowserTest,
WheelScrollingWorksAfterAutoscrollCancel) {
LoadURL(kAutoscrollDataURL);
// Start autoscroll with middle click.
SimulateMiddleClick(10, 10, blink::WebInputEvent::kNoModifiers);
// Without moving the mouse cancel the autoscroll fling with another click.
SimulateMiddleClick(10, 10, blink::WebInputEvent::kNoModifiers);
// The mouse wheel scrolling must work after autoscroll cancellation.
RenderFrameSubmissionObserver observer(
GetWidgetHost()->render_frame_metadata_provider());
blink::WebMouseWheelEvent wheel_event =
SyntheticWebMouseWheelEventBuilder::Build(10, 10, 0, -53, 0, true);
wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
GetWidgetHost()->ForwardWheelEvent(wheel_event);
WaitForScroll(observer);
}
// Checks that wheel scrolling does not work once the cursor has entered the
// autoscroll mode.
IN_PROC_BROWSER_TEST_F(AutoscrollBrowserTest,
WheelScrollingDoesNotWorkInAutoscrollMode) {
LoadURL(kAutoscrollDataURL);
// Start autoscroll with middle click.
SimulateMiddleClick(10, 10, blink::WebInputEvent::kNoModifiers);
// Without moving the mouse, start wheel scrolling.
RenderFrameSubmissionObserver observer(
GetWidgetHost()->render_frame_metadata_provider());
blink::WebMouseWheelEvent wheel_event =
SyntheticWebMouseWheelEventBuilder::Build(10, 10, 0, -53, 0, true);
wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
GetWidgetHost()->ForwardWheelEvent(wheel_event);
// Wait for 4 commits, then verify that the page has not scrolled.
WaitForCommitFrames(4);
gfx::Vector2dF default_scroll_offset;
DCHECK_EQ(observer.LastRenderFrameMetadata()
.root_scroll_offset.value_or(default_scroll_offset)
.y(),
0);
}
// Checks that autoscrolling still works after changing the scroll direction
// when the element is fully scrolled.
IN_PROC_BROWSER_TEST_F(AutoscrollBrowserTest,
AutoscrollDirectionChangeAfterFullyScrolled) {
LoadURL(kAutoscrollDataURL);
// Start autoscroll with middle click.
auto scroll_begin_watcher = std::make_unique<GestureScrollEventWatcher>(
GetWidgetHost(), blink::WebInputEvent::kGestureScrollBegin);
SimulateMiddleClick(100, 100, blink::WebInputEvent::kNoModifiers);
// Move the mouse up, no scrolling happens since the page is at its extent.
auto scroll_update_watcher = std::make_unique<InputMsgWatcher>(
GetWidgetHost(), blink::WebInputEvent::kGestureScrollUpdate);
blink::WebMouseEvent move_up = SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseMove, 20, 20,
blink::WebInputEvent::kNoModifiers);
move_up.SetTimeStamp(ui::EventTimeForNow());
move_up.SetPositionInScreen(20, 20);
GetWidgetHost()->ForwardMouseEvent(move_up);
scroll_begin_watcher->Wait();
EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
scroll_update_watcher->WaitForAck());
// Wait for 10 commits before changing the scroll direction.
WaitForCommitFrames(10);
// Now move the mouse down and wait for the page to scroll. The test will
// timeout if autoscrolling does not work after direction change.
RenderFrameSubmissionObserver observer(
GetWidgetHost()->render_frame_metadata_provider());
blink::WebMouseEvent move_down = SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseMove, 180, 180,
blink::WebInputEvent::kNoModifiers);
move_down.SetTimeStamp(ui::EventTimeForNow());
move_down.SetPositionInScreen(180, 180);
GetWidgetHost()->ForwardMouseEvent(move_down);
WaitForScroll(observer);
}
#endif // !defined(OS_ANDROID)
} // namespace content