blob: 2b5a683eb0cbf34ef1c4e2ff0da35d6038d2e14c [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 <vector>
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/render_pass.h"
#include "components/viz/common/surfaces/local_surface_id.h"
#include "content/browser/bad_message.h"
#include "content/browser/renderer_host/input/touch_emulator.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_input_event_router.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/input/synthetic_web_input_event_builders.h"
#include "content/public/browser/web_contents.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 "content/test/content_browser_test_utils_internal.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_input_event.h"
#include "third_party/blink/public/platform/web_mouse_event.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/latency/latency_info.h"
namespace content {
class RenderWidgetHostBrowserTest : public ContentBrowserTest {};
IN_PROC_BROWSER_TEST_F(RenderWidgetHostBrowserTest,
ProhibitsCopyRequestsFromRenderer) {
NavigateToURL(shell(), GURL("data:text/html,<!doctype html>"
"<body style='background-color: red;'></body>"));
// Wait for the view's surface to become available.
auto* const view = static_cast<RenderWidgetHostViewBase*>(
shell()->web_contents()->GetRenderWidgetHostView());
while (!view->IsSurfaceAvailableForCopy()) {
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(),
base::TimeDelta::FromMilliseconds(250));
run_loop.Run();
}
// The browser process should be allowed to make a CopyOutputRequest.
bool did_receive_copy_result = false;
base::RunLoop run_loop;
view->CopyFromSurface(gfx::Rect(), gfx::Size(),
base::BindOnce(
[](bool* success, base::OnceClosure quit_closure,
const SkBitmap& bitmap) {
*success = !bitmap.drawsNothing();
std::move(quit_closure).Run();
},
&did_receive_copy_result, run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(did_receive_copy_result);
// Create a simulated-from-renderer CompositorFrame with a CopyOutputRequest.
viz::CompositorFrame frame;
std::unique_ptr<viz::RenderPass> pass = viz::RenderPass::Create();
const gfx::Rect output_rect =
gfx::Rect(view->GetCompositorViewportPixelSize());
pass->SetNew(1 /* render pass id */, output_rect, output_rect,
gfx::Transform());
bool did_receive_aborted_copy_result = false;
pass->copy_requests.push_back(std::make_unique<viz::CopyOutputRequest>(
viz::CopyOutputRequest::ResultFormat::RGBA_BITMAP,
base::BindOnce(
[](bool* got_nothing, std::unique_ptr<viz::CopyOutputResult> result) {
*got_nothing = result->IsEmpty();
},
&did_receive_aborted_copy_result)));
frame.render_pass_list.push_back(std::move(pass));
// Submit the frame and expect the renderer to be instantly killed.
auto* const host = RenderWidgetHostImpl::From(view->GetRenderWidgetHost());
RenderProcessHostKillWaiter waiter(host->GetProcess());
host->SubmitCompositorFrame(viz::LocalSurfaceId(), std::move(frame),
base::nullopt, 0);
base::Optional<bad_message::BadMessageReason> result = waiter.Wait();
ASSERT_TRUE(result.has_value());
EXPECT_EQ(bad_message::RWH_COPY_REQUEST_ATTEMPT, *result);
// Check that the copy request result callback received an empty result. In a
// normal browser, the requestor (in the render process) might never see a
// response to the copy request before the process is killed. Nevertheless,
// ensure the result is empty, just in case there is a race.
EXPECT_TRUE(did_receive_aborted_copy_result);
}
class TestInputEventObserver : public RenderWidgetHost::InputEventObserver {
public:
using EventTypeVector = std::vector<blink::WebInputEvent::Type>;
~TestInputEventObserver() override {}
void OnInputEvent(const blink::WebInputEvent& event) override {
dispatched_events_.push_back(event.GetType());
}
void OnInputEventAck(InputEventAckSource source,
InputEventAckState state,
const blink::WebInputEvent& event) override {
if (blink::WebInputEvent::IsTouchEventType(event.GetType()))
acked_touch_event_type_ = event.GetType();
}
EventTypeVector GetAndResetDispatchedEventTypes() {
EventTypeVector new_event_types;
std::swap(new_event_types, dispatched_events_);
return new_event_types;
}
blink::WebInputEvent::Type acked_touch_event_type() const {
return acked_touch_event_type_;
}
private:
EventTypeVector dispatched_events_;
blink::WebInputEvent::Type acked_touch_event_type_;
};
class RenderWidgetHostTouchEmulatorBrowserTest
: public RenderWidgetHostBrowserTest {
public:
RenderWidgetHostTouchEmulatorBrowserTest()
: view_(nullptr),
host_(nullptr),
router_(nullptr),
last_simulated_event_time_(ui::EventTimeForNow()),
simulated_event_time_delta_(base::TimeDelta::FromMilliseconds(100)) {}
void SetUpOnMainThread() override {
RenderWidgetHostBrowserTest::SetUpOnMainThread();
NavigateToURL(shell(),
GURL("data:text/html,<!doctype html>"
"<body style='background-color: red;'></body>"));
view_ = static_cast<RenderWidgetHostViewBase*>(
shell()->web_contents()->GetRenderWidgetHostView());
host_ = static_cast<RenderWidgetHostImpl*>(view_->GetRenderWidgetHost());
router_ = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
ASSERT_TRUE(router_);
}
base::TimeTicks GetNextSimulatedEventTime() {
last_simulated_event_time_ += simulated_event_time_delta_;
return last_simulated_event_time_;
}
void SimulateRoutedMouseEvent(blink::WebInputEvent::Type type,
int x,
int y,
int modifiers,
bool pressed) {
blink::WebMouseEvent event =
SyntheticWebMouseEventBuilder::Build(type, x, y, modifiers);
if (pressed)
event.button = blink::WebMouseEvent::Button::kLeft;
event.SetTimeStamp(GetNextSimulatedEventTime());
router_->RouteMouseEvent(view_, &event, ui::LatencyInfo());
}
RenderWidgetHostImpl* host() { return host_; }
private:
RenderWidgetHostViewBase* view_;
RenderWidgetHostImpl* host_;
RenderWidgetHostInputEventRouter* router_;
base::TimeTicks last_simulated_event_time_;
base::TimeDelta simulated_event_time_delta_;
};
IN_PROC_BROWSER_TEST_F(RenderWidgetHostTouchEmulatorBrowserTest,
TouchEmulator) {
// All touches will be immediately acked instead of sending them to the
// renderer since the test page does not have a touch handler.
host()->GetTouchEmulator()->Enable(
TouchEmulator::Mode::kEmulatingTouchFromMouse,
ui::GestureProviderConfigType::GENERIC_MOBILE);
TestInputEventObserver observer;
host()->AddInputEventObserver(&observer);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 10, 0, false);
TestInputEventObserver::EventTypeVector dispatched_events =
observer.GetAndResetDispatchedEventTypes();
EXPECT_EQ(0u, dispatched_events.size());
// Mouse press becomes touch start which in turn becomes tap.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseDown, 10, 10, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchStart,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchStart, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, dispatched_events[1]);
// Mouse drag generates touch move, cancels tap and starts scroll.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 30, 0, true);
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(4u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapCancel, dispatched_events[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, dispatched_events[2]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[3]);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size());
// Mouse drag with shift becomes pinch.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 35,
blink::WebInputEvent::kShiftKey, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin, dispatched_events[1]);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 50,
blink::WebInputEvent::kShiftKey, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchUpdate, dispatched_events[1]);
// Mouse drag without shift becomes scroll again.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 60, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(3u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd, dispatched_events[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[2]);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 70, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[1]);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseUp, 10, 70, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchEnd, observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchEnd, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, dispatched_events[1]);
// Mouse move does nothing.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 80, 0, false);
dispatched_events = observer.GetAndResetDispatchedEventTypes();
EXPECT_EQ(0u, dispatched_events.size());
// Another mouse down continues scroll.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseDown, 10, 80, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchStart,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchStart, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, dispatched_events[1]);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 100, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(4u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapCancel, dispatched_events[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, dispatched_events[2]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[3]);
EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size());
// Another pinch.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 110,
blink::WebInputEvent::kShiftKey, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
EXPECT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin, dispatched_events[1]);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 120,
blink::WebInputEvent::kShiftKey, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
EXPECT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchUpdate, dispatched_events[1]);
// Turn off emulation during a pinch.
host()->GetTouchEmulator()->Disable();
EXPECT_EQ(blink::WebInputEvent::kTouchCancel,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(3u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchCancel, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd, dispatched_events[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, dispatched_events[2]);
// Mouse event should pass untouched.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 10,
blink::WebInputEvent::kShiftKey, true);
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(1u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kMouseMove, dispatched_events[0]);
// Turn on emulation.
host()->GetTouchEmulator()->Enable(
TouchEmulator::Mode::kEmulatingTouchFromMouse,
ui::GestureProviderConfigType::GENERIC_MOBILE);
// Another touch.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseDown, 10, 10, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchStart,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchStart, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, dispatched_events[1]);
// Scroll.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 30, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(4u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapCancel, dispatched_events[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, dispatched_events[2]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[3]);
EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size());
// Turn off emulation during a scroll.
host()->GetTouchEmulator()->Disable();
EXPECT_EQ(blink::WebInputEvent::kTouchCancel,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchCancel, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, dispatched_events[1]);
host()->RemoveInputEventObserver(&observer);
}
} // namespace content