blob: e02e16f51513ef36458ec54100181caf8adb1eb9 [file] [log] [blame]
// Copyright (c) 2012 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 "content/browser/renderer_host/render_widget_host_view_aura.h"
#include <stddef.h>
#include <stdint.h>
#include <tuple>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/shared_memory.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "base/test/null_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "cc/trees/render_frame_metadata.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/compositor_frame_metadata.h"
#include "components/viz/common/surfaces/child_local_surface_id_allocator.h"
#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
#include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/service/hit_test/hit_test_manager.h"
#include "components/viz/service/surfaces/surface.h"
#include "components/viz/test/begin_frame_args_test.h"
#include "components/viz/test/compositor_frame_helpers.h"
#include "components/viz/test/fake_external_begin_frame_source.h"
#include "components/viz/test/fake_surface_observer.h"
#include "components/viz/test/test_latest_local_surface_id_lookup_delegate.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/compositor/test/test_image_transport_factory.h"
#include "content/browser/frame_host/render_widget_host_view_guest.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/renderer_host/delegated_frame_host.h"
#include "content/browser/renderer_host/delegated_frame_host_client_aura.h"
#include "content/browser/renderer_host/input/input_router.h"
#include "content/browser/renderer_host/input/mouse_wheel_event_queue.h"
#include "content/browser/renderer_host/overscroll_controller.h"
#include "content/browser/renderer_host/overscroll_controller_delegate.h"
#include "content/browser/renderer_host/render_frame_metadata_provider_impl.h"
#include "content/browser/renderer_host/render_view_host_factory.h"
#include "content/browser/renderer_host/render_widget_host_delegate.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_event_handler.h"
#include "content/browser/renderer_host/text_input_manager.h"
#include "content/browser/web_contents/web_contents_view_aura.h"
#include "content/common/input/synthetic_web_input_event_builders.h"
#include "content/common/input_messages.h"
#include "content/common/text_input_state.h"
#include "content/common/view_messages.h"
#include "content/common/widget_messages.h"
#include "content/public/browser/keyboard_event_processing_result.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents_view_delegate.h"
#include "content/public/common/content_features.h"
#include "content/public/common/context_menu_params.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "content/test/fake_renderer_compositor_frame_sink.h"
#include "content/test/mock_render_widget_host_delegate.h"
#include "content/test/mock_widget_impl.h"
#include "content/test/test_overscroll_delegate.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_test_sink.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/client/window_parenting_client.h"
#include "ui/aura/env.h"
#include "ui/aura/layout_manager.h"
#include "ui/aura/scoped_keyboard_hook.h"
#include "ui/aura/test/aura_test_helper.h"
#include "ui/aura/test/aura_test_utils.h"
#include "ui/aura/test/test_cursor_client.h"
#include "ui/aura/test/test_screen.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_observer.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/ime/init/input_method_factory.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/input_method_keyboard_controller.h"
#include "ui/base/ime/mock_input_method.h"
#include "ui/base/ui_base_switches.h"
#include "ui/base/ui_base_types.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/test/draw_waiter_for_test.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/blink/blink_event_util.h"
#include "ui/events/blink/blink_features.h"
#include "ui/events/blink/web_input_event_traits.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#include "ui/events/gestures/motion_event_aura.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/selection_bound.h"
#include "ui/wm/core/default_activation_client.h"
#include "ui/wm/core/default_screen_position_client.h"
#include "ui/wm/core/window_util.h"
#if defined(OS_CHROMEOS)
#include "ui/base/ime/input_method.h"
#endif
using testing::_;
using blink::WebGestureEvent;
using blink::WebInputEvent;
using blink::WebMouseEvent;
using blink::WebMouseWheelEvent;
using blink::WebTouchEvent;
using blink::WebTouchPoint;
using ui::WebInputEventTraits;
using viz::FrameEvictionManager;
#define EXPECT_EVICTED(view) \
{ \
EXPECT_FALSE((view)->HasPrimarySurface()); \
EXPECT_FALSE((view)->HasSavedFrame()); \
}
#define EXPECT_HAS_FRAME(view) \
{ \
EXPECT_TRUE((view)->HasPrimarySurface()); \
EXPECT_TRUE((view)->HasSavedFrame()); \
}
namespace content {
void InstallDelegatedFrameHostClient(
RenderWidgetHostViewAura* render_widget_host_view,
std::unique_ptr<DelegatedFrameHostClient> delegated_frame_host_client);
const viz::LocalSurfaceId kArbitraryLocalSurfaceId(
1,
base::UnguessableToken::Deserialize(2, 3));
std::string GetMessageNames(
const MockWidgetInputHandler::MessageVector& events) {
std::vector<std::string> result;
for (auto& event : events)
result.push_back(event->name());
return base::JoinString(result, " ");
}
uint64_t FrameIndexForView(RenderWidgetHostViewAura* view) {
return ImageTransportFactory::GetInstance()
->GetContextFactoryPrivate()
->GetFrameSinkManager()
->surface_manager()
->GetSurfaceForId(view->GetCurrentSurfaceId())
->GetActiveFrameIndex();
}
const gfx::Rect& DamageRectForView(RenderWidgetHostViewAura* view) {
return ImageTransportFactory::GetInstance()
->GetContextFactoryPrivate()
->GetFrameSinkManager()
->surface_manager()
->GetSurfaceForId(view->GetCurrentSurfaceId())
->GetActiveFrame()
.render_pass_list.back()
->damage_rect;
}
// Simple observer that keeps track of changes to a window for tests.
class TestWindowObserver : public aura::WindowObserver {
public:
explicit TestWindowObserver(aura::Window* window_to_observe)
: window_(window_to_observe) {
window_->AddObserver(this);
}
~TestWindowObserver() override {
if (window_)
window_->RemoveObserver(this);
}
bool destroyed() const { return destroyed_; }
// aura::WindowObserver overrides:
void OnWindowDestroyed(aura::Window* window) override {
CHECK_EQ(window, window_);
destroyed_ = true;
window_ = nullptr;
}
private:
// Window that we're observing, or nullptr if it's been destroyed.
aura::Window* window_;
// Was |window_| destroyed?
bool destroyed_;
DISALLOW_COPY_AND_ASSIGN(TestWindowObserver);
};
class FakeWindowEventDispatcher : public aura::WindowEventDispatcher {
public:
FakeWindowEventDispatcher(aura::WindowTreeHost* host)
: WindowEventDispatcher(host, true), processed_touch_event_count_(0) {}
void ProcessedTouchEvent(
uint32_t unique_event_id,
aura::Window* window,
ui::EventResult result,
bool is_source_touch_event_set_non_blocking) override {
WindowEventDispatcher::ProcessedTouchEvent(
unique_event_id, window, result,
is_source_touch_event_set_non_blocking);
processed_touch_event_count_++;
}
size_t GetAndResetProcessedTouchEventCount() {
size_t count = processed_touch_event_count_;
processed_touch_event_count_ = 0;
return count;
}
private:
size_t processed_touch_event_count_;
};
class FakeDelegatedFrameHostClientAura : public DelegatedFrameHostClientAura {
public:
explicit FakeDelegatedFrameHostClientAura(
RenderWidgetHostViewAura* render_widget_host_view)
: DelegatedFrameHostClientAura(render_widget_host_view) {}
~FakeDelegatedFrameHostClientAura() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(FakeDelegatedFrameHostClientAura);
};
class FakeRenderWidgetHostViewAura : public RenderWidgetHostViewAura {
public:
FakeRenderWidgetHostViewAura(RenderWidgetHost* widget,
bool is_guest_view_hack)
: RenderWidgetHostViewAura(widget, is_guest_view_hack),
is_guest_view_hack_(is_guest_view_hack),
delegated_frame_host_client_(
new FakeDelegatedFrameHostClientAura(this)) {
InstallDelegatedFrameHostClient(
this, base::WrapUnique(delegated_frame_host_client_));
CreateNewRendererCompositorFrameSink();
}
~FakeRenderWidgetHostViewAura() override {}
void CreateNewRendererCompositorFrameSink() {
viz::mojom::CompositorFrameSinkPtr sink;
viz::mojom::CompositorFrameSinkRequest sink_request =
mojo::MakeRequest(&sink);
viz::mojom::CompositorFrameSinkClientRequest client_request =
mojo::MakeRequest(&renderer_compositor_frame_sink_ptr_);
renderer_compositor_frame_sink_ =
std::make_unique<FakeRendererCompositorFrameSink>(
std::move(sink), std::move(client_request));
DidCreateNewRendererCompositorFrameSink(
renderer_compositor_frame_sink_ptr_.get());
}
void UseFakeDispatcher() {
dispatcher_ = new FakeWindowEventDispatcher(window()->GetHost());
std::unique_ptr<aura::WindowEventDispatcher> dispatcher(dispatcher_);
aura::test::SetHostDispatcher(window()->GetHost(), std::move(dispatcher));
}
void RunOnCompositingDidCommit() {
GetDelegatedFrameHost()->OnCompositingDidCommitForTesting(
window()->GetHost()->compositor());
}
viz::SurfaceId surface_id() const {
return GetDelegatedFrameHost()->GetCurrentSurfaceId();
}
bool HasPrimarySurface() const {
return GetDelegatedFrameHost()->HasPrimarySurface();
}
bool HasFallbackSurface() const override {
return GetDelegatedFrameHost()->HasFallbackSurface();
}
bool HasSavedFrame() const {
return GetDelegatedFrameHost()->HasSavedFrame();
}
void ReclaimResources(const std::vector<viz::ReturnedResource>& resources) {
GetDelegatedFrameHost()->ReclaimResources(resources);
}
const ui::MotionEventAura& pointer_state() {
return event_handler()->pointer_state();
}
void SetRenderFrameMetadata(cc::RenderFrameMetadata metadata) {
host()->render_frame_metadata_provider()->SetLastRenderFrameMetadataForTest(
metadata);
}
bool is_guest_view_hack() { return is_guest_view_hack_; }
gfx::Size last_frame_size_;
FakeWindowEventDispatcher* dispatcher_;
std::unique_ptr<FakeRendererCompositorFrameSink>
renderer_compositor_frame_sink_;
private:
bool is_guest_view_hack_;
FakeDelegatedFrameHostClientAura* delegated_frame_host_client_;
viz::mojom::CompositorFrameSinkClientPtr renderer_compositor_frame_sink_ptr_;
DISALLOW_COPY_AND_ASSIGN(FakeRenderWidgetHostViewAura);
};
// A layout manager that always resizes a child to the root window size.
class FullscreenLayoutManager : public aura::LayoutManager {
public:
explicit FullscreenLayoutManager(aura::Window* owner) : owner_(owner) {}
~FullscreenLayoutManager() override {}
// Overridden from aura::LayoutManager:
void OnWindowResized() override {
aura::Window::Windows::const_iterator i;
for (i = owner_->children().begin(); i != owner_->children().end(); ++i) {
(*i)->SetBounds(gfx::Rect());
}
}
void OnWindowAddedToLayout(aura::Window* child) override {
child->SetBounds(gfx::Rect());
}
void OnWillRemoveWindowFromLayout(aura::Window* child) override {}
void OnWindowRemovedFromLayout(aura::Window* child) override {}
void OnChildWindowVisibilityChanged(aura::Window* child,
bool visible) override {}
void SetChildBounds(aura::Window* child,
const gfx::Rect& requested_bounds) override {
SetChildBoundsDirect(child, gfx::Rect(owner_->bounds().size()));
}
private:
aura::Window* owner_;
DISALLOW_COPY_AND_ASSIGN(FullscreenLayoutManager);
};
class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
public:
~MockRenderWidgetHostImpl() override {}
// Extracts |latency_info| for wheel event, and stores it in
// |lastWheelOrTouchEventLatencyInfo|.
void ForwardWheelEventWithLatencyInfo(
const blink::WebMouseWheelEvent& wheel_event,
const ui::LatencyInfo& ui_latency) override {
RenderWidgetHostImpl::ForwardWheelEventWithLatencyInfo(wheel_event,
ui_latency);
lastWheelOrTouchEventLatencyInfo = ui::LatencyInfo(ui_latency);
}
// Extracts |latency_info| for touch event, and stores it in
// |lastWheelOrTouchEventLatencyInfo|.
void ForwardTouchEventWithLatencyInfo(
const blink::WebTouchEvent& touch_event,
const ui::LatencyInfo& ui_latency) override {
RenderWidgetHostImpl::ForwardTouchEventWithLatencyInfo(touch_event,
ui_latency);
lastWheelOrTouchEventLatencyInfo = ui::LatencyInfo(ui_latency);
}
void ForwardGestureEventWithLatencyInfo(
const blink::WebGestureEvent& gesture_event,
const ui::LatencyInfo& ui_latency) override {
RenderWidgetHostImpl::ForwardGestureEventWithLatencyInfo(gesture_event,
ui_latency);
last_forwarded_gesture_event_ = gesture_event;
}
base::Optional<WebGestureEvent> GetAndResetLastForwardedGestureEvent() {
base::Optional<WebGestureEvent> ret;
last_forwarded_gesture_event_.swap(ret);
return ret;
}
static MockRenderWidgetHostImpl* Create(RenderWidgetHostDelegate* delegate,
RenderProcessHost* process,
int32_t routing_id) {
mojom::WidgetPtr widget;
std::unique_ptr<MockWidgetImpl> widget_impl =
std::make_unique<MockWidgetImpl>(mojo::MakeRequest(&widget));
return new MockRenderWidgetHostImpl(delegate, process, routing_id,
std::move(widget_impl),
std::move(widget));
}
ui::LatencyInfo lastWheelOrTouchEventLatencyInfo;
MockWidgetInputHandler* input_handler() {
return widget_impl_->input_handler();
}
void reset_new_content_rendering_timeout_fired() {
new_content_rendering_timeout_fired_ = false;
}
bool new_content_rendering_timeout_fired() const {
return new_content_rendering_timeout_fired_;
}
private:
MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
RenderProcessHost* process,
int32_t routing_id,
std::unique_ptr<MockWidgetImpl> widget_impl,
mojom::WidgetPtr widget)
: RenderWidgetHostImpl(delegate,
process,
routing_id,
std::move(widget),
false),
widget_impl_(std::move(widget_impl)) {
lastWheelOrTouchEventLatencyInfo = ui::LatencyInfo();
}
void NotifyNewContentRenderingTimeoutForTesting() override {
new_content_rendering_timeout_fired_ = true;
}
bool new_content_rendering_timeout_fired_ = false;
std::unique_ptr<MockWidgetImpl> widget_impl_;
base::Optional<WebGestureEvent> last_forwarded_gesture_event_;
};
class TestScopedKeyboardHook : public aura::ScopedKeyboardHook {
public:
TestScopedKeyboardHook();
~TestScopedKeyboardHook() override;
// aura::ScopedKeyboardHook override.
bool IsKeyLocked(ui::DomCode dom_code) override;
// Set up the keys being locked for testing. One of these methods must be
// called before using an instance.
void LockAllKeys();
void LockSpecificKey(ui::DomCode dom_code);
private:
bool keyboard_lock_active_ = false;
base::Optional<ui::DomCode> locked_key_;
DISALLOW_COPY_AND_ASSIGN(TestScopedKeyboardHook);
};
TestScopedKeyboardHook::TestScopedKeyboardHook() = default;
TestScopedKeyboardHook::~TestScopedKeyboardHook() = default;
bool TestScopedKeyboardHook::IsKeyLocked(ui::DomCode dom_code) {
DCHECK(keyboard_lock_active_) << "Did you forget to reserve keys to lock?";
return !locked_key_ || (locked_key_.value() == dom_code);
}
void TestScopedKeyboardHook::LockAllKeys() {
keyboard_lock_active_ = true;
locked_key_.reset();
}
void TestScopedKeyboardHook::LockSpecificKey(ui::DomCode dom_code) {
keyboard_lock_active_ = true;
locked_key_ = dom_code;
}
class RenderWidgetHostViewAuraTest : public testing::Test {
public:
RenderWidgetHostViewAuraTest()
: widget_host_uses_shutdown_to_destroy_(false),
is_guest_view_hack_(false) {
ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms(
0);
}
static void InstallDelegatedFrameHostClient(
RenderWidgetHostViewAura* view,
std::unique_ptr<DelegatedFrameHostClient> delegated_frame_host_client) {
// Follow RWHVAura code that does not create DelegateFrameHost when there is
// no valid frame sink id.
if (!view->frame_sink_id_.is_valid())
return;
view->delegated_frame_host_client_ = std::move(delegated_frame_host_client);
view->delegated_frame_host_ = nullptr;
view->delegated_frame_host_ = std::make_unique<DelegatedFrameHost>(
view->frame_sink_id_, view->delegated_frame_host_client_.get(),
false /* should_register_frame_sink_id */);
}
FakeRenderWidgetHostViewAura* CreateView(bool is_guest_view_hack) {
int32_t routing_id = process_host_->GetNextRoutingID();
delegates_.push_back(base::WrapUnique(new MockRenderWidgetHostDelegate));
auto* widget_host = MockRenderWidgetHostImpl::Create(
delegates_.back().get(), process_host_, routing_id);
delegates_.back()->set_widget_host(widget_host);
widget_host->Init();
return new FakeRenderWidgetHostViewAura(widget_host, is_guest_view_hack);
}
void DestroyView(FakeRenderWidgetHostViewAura* view) {
// For guest-views, |view_| is not the view used by |widget_host_|.
bool is_guest_view_hack = view->is_guest_view_hack();
RenderWidgetHostImpl* host = view->host();
if (!is_guest_view_hack)
EXPECT_EQ(view, host->GetView());
view->Destroy();
if (!is_guest_view_hack)
EXPECT_EQ(nullptr, host->GetView());
if (widget_host_uses_shutdown_to_destroy_)
host->ShutdownAndDestroyWidget(true);
else
delete host;
}
void SetUpEnvironment() {
ImageTransportFactory::SetFactory(
std::make_unique<TestImageTransportFactory>());
aura_test_helper_.reset(new aura::test::AuraTestHelper());
aura_test_helper_->SetUp(
ImageTransportFactory::GetInstance()->GetContextFactory(),
ImageTransportFactory::GetInstance()->GetContextFactoryPrivate());
new wm::DefaultActivationClient(aura_test_helper_->root_window());
browser_context_.reset(new TestBrowserContext);
process_host_ = new MockRenderProcessHost(browser_context_.get());
process_host_->Init();
sink_ = &process_host_->sink();
int32_t routing_id = process_host_->GetNextRoutingID();
delegates_.push_back(base::WrapUnique(new MockRenderWidgetHostDelegate));
parent_host_ = MockRenderWidgetHostImpl::Create(delegates_.back().get(),
process_host_, routing_id);
delegates_.back()->set_widget_host(parent_host_);
parent_view_ =
new RenderWidgetHostViewAura(parent_host_, is_guest_view_hack_);
parent_view_->InitAsChild(nullptr);
aura::client::ParentWindowWithContext(parent_view_->GetNativeView(),
aura_test_helper_->root_window(),
gfx::Rect());
view_ = CreateView(is_guest_view_hack_);
widget_host_ = static_cast<MockRenderWidgetHostImpl*>(view_->host());
// Set the mouse_wheel_phase_handler_ timer timeout to 100ms.
view_->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout(
base::TimeDelta::FromMilliseconds(100));
base::RunLoop().RunUntilIdle();
}
void TearDownEnvironment() {
sink_ = nullptr;
process_host_ = nullptr;
if (view_)
DestroyView(view_);
parent_view_->Destroy();
delete parent_host_;
browser_context_.reset();
aura_test_helper_->TearDown();
base::RunLoop().RunUntilIdle();
ImageTransportFactory::Terminate();
}
void SetUp() override {
SetUpEnvironment();
}
void TearDown() override { TearDownEnvironment(); }
void set_widget_host_uses_shutdown_to_destroy(bool use) {
widget_host_uses_shutdown_to_destroy_ = use;
}
void SimulateMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel level) {
// Here should be base::MemoryPressureListener::NotifyMemoryPressure, but
// since the FrameEvictionManager is installing a MemoryPressureListener
// which uses base::ObserverListThreadSafe, which furthermore remembers the
// message loop for the thread it was created in. Between tests, the
// FrameEvictionManager singleton survives and and the MessageLoop gets
// destroyed. The correct fix would be to have base::ObserverListThreadSafe
// look
// up the proper message loop every time (see crbug.com/443824.)
FrameEvictionManager::GetInstance()->OnMemoryPressure(level);
}
MockWidgetInputHandler::MessageVector GetAndResetDispatchedMessages() {
return widget_host_->input_handler()->GetAndResetDispatchedMessages();
}
void SendNotConsumedAcks(MockWidgetInputHandler::MessageVector& events) {
events.clear();
}
const ui::MotionEventAura& pointer_state() { return view_->pointer_state(); }
protected:
BrowserContext* browser_context() { return browser_context_.get(); }
MockRenderWidgetHostDelegate* render_widget_host_delegate() const {
return delegates_.back().get();
}
MouseWheelPhaseHandler* GetMouseWheelPhaseHandler() const {
return view_->GetMouseWheelPhaseHandler();
}
// Sets the |view| active in TextInputManager with the given |type|. |type|
// cannot be ui::TEXT_INPUT_TYPE_NONE.
// Must not be called in the destruction path of |view|.
void ActivateViewForTextInputManager(RenderWidgetHostViewBase* view,
ui::TextInputType type) {
DCHECK_NE(ui::TEXT_INPUT_TYPE_NONE, type);
// First mock-focus the widget if not already.
if (render_widget_host_delegate()->GetFocusedRenderWidgetHost(
widget_host_) != view->GetRenderWidgetHost()) {
render_widget_host_delegate()->set_focused_widget(view->host());
}
TextInputManager* manager =
static_cast<RenderWidgetHostImpl*>(view->GetRenderWidgetHost())
->delegate()
->GetTextInputManager();
if (manager->GetActiveWidget()) {
manager->active_view_for_testing()->TextInputStateChanged(
TextInputState());
}
if (!view)
return;
TextInputState state_with_type_text;
state_with_type_text.type = type;
view->TextInputStateChanged(state_with_type_text);
}
// If true, then calls RWH::Shutdown() instead of deleting RWH.
bool widget_host_uses_shutdown_to_destroy_;
bool is_guest_view_hack_;
TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<aura::test::AuraTestHelper> aura_test_helper_;
std::unique_ptr<BrowserContext> browser_context_;
std::vector<std::unique_ptr<MockRenderWidgetHostDelegate>> delegates_;
MockRenderProcessHost* process_host_;
// Tests should set these to nullptr if they've already triggered their
// destruction.
RenderWidgetHostImpl* parent_host_;
RenderWidgetHostViewAura* parent_view_;
// Tests should set these to nullptr if they've already triggered their
// destruction.
MockRenderWidgetHostImpl* widget_host_;
FakeRenderWidgetHostViewAura* view_;
IPC::TestSink* sink_;
base::test::ScopedFeatureList mojo_feature_list_;
base::test::ScopedFeatureList feature_list_;
viz::ParentLocalSurfaceIdAllocator parent_local_surface_id_allocator_;
private:
DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraTest);
};
class RenderWidgetHostViewAuraSurfaceSynchronizationTest
: public RenderWidgetHostViewAuraTest {
void SetUp() override {
surface_synchronization_feature_list_.InitAndEnableFeature(
features::kEnableSurfaceSynchronization);
SetUpEnvironment();
}
private:
base::test::ScopedFeatureList surface_synchronization_feature_list_;
};
void InstallDelegatedFrameHostClient(
RenderWidgetHostViewAura* render_widget_host_view,
std::unique_ptr<DelegatedFrameHostClient> delegated_frame_host_client) {
RenderWidgetHostViewAuraTest::InstallDelegatedFrameHostClient(
render_widget_host_view, std::move(delegated_frame_host_client));
}
// Helper class to instantiate RenderWidgetHostViewGuest which is backed
// by an aura platform view.
class RenderWidgetHostViewGuestAuraTest : public RenderWidgetHostViewAuraTest {
public:
RenderWidgetHostViewGuestAuraTest() {
// Use RWH::Shutdown to destroy RWH, instead of deleting.
// This will ensure that the RenderWidgetHostViewGuest is not leaked and
// is deleted properly upon RWH going away.
set_widget_host_uses_shutdown_to_destroy(true);
}
// We explicitly invoke SetUp to allow gesture debounce customization.
void SetUp() override {
is_guest_view_hack_ = true;
RenderWidgetHostViewAuraTest::SetUp();
guest_view_weak_ = (RenderWidgetHostViewGuest::Create(widget_host_, nullptr,
view_->GetWeakPtr()))
->GetWeakPtr();
}
void TearDown() override {
// Internal override to do nothing, we clean up ourselves in the test body.
// This helps us test that |guest_view_weak_| does not leak.
}
protected:
base::WeakPtr<RenderWidgetHostViewBase> guest_view_weak_;
private:
DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewGuestAuraTest);
};
// TODO(mohsen): Consider moving these tests to OverscrollControllerTest if
// appropriate.
class RenderWidgetHostViewAuraOverscrollTest
: public RenderWidgetHostViewAuraTest {
public:
RenderWidgetHostViewAuraOverscrollTest() : RenderWidgetHostViewAuraTest() {}
// We explicitly invoke SetUp to allow gesture debounce customization.
void SetUp() override {}
void SendScrollUpdateAck(MockWidgetInputHandler::MessageVector& messages,
InputEventAckState ack_result) {
for (size_t i = 0; i < messages.size(); ++i) {
MockWidgetInputHandler::DispatchedEventMessage* event =
messages[i]->ToEvent();
if (event &&
event->Event()->web_event->GetType() ==
WebInputEvent::kGestureScrollUpdate &&
event->HasCallback()) {
event->CallCallback(ack_result);
return;
}
}
EXPECT_TRUE(false);
}
void SendScrollBeginAckIfNeeded(
MockWidgetInputHandler::MessageVector& messages,
InputEventAckState ack_result) {
for (size_t i = 0; i < messages.size(); ++i) {
MockWidgetInputHandler::DispatchedEventMessage* event =
messages[i]->ToEvent();
// GSB events are blocking, send the ack.
if (event && event->Event()->web_event->GetType() ==
WebInputEvent::kGestureScrollBegin) {
event->CallCallback(ack_result);
return;
}
}
}
void SendScrollBeginAckIfNeeded(InputEventAckState ack_result) {
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
SendScrollBeginAckIfNeeded(events, ack_result);
}
protected:
void SetUpOverscrollEnvironmentWithDebounce(int debounce_interval_in_ms) {
SetUpOverscrollEnvironmentImpl(debounce_interval_in_ms);
}
void SetUpOverscrollEnvironment() { SetUpOverscrollEnvironmentImpl(0); }
void SetUpOverscrollEnvironmentImpl(int debounce_interval_in_ms) {
scoped_feature_list_.InitAndEnableFeature(
features::kTouchpadOverscrollHistoryNavigation);
ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms(
debounce_interval_in_ms);
RenderWidgetHostViewAuraTest::SetUpEnvironment();
view_->SetOverscrollControllerEnabled(true);
gfx::Size display_size = display::Screen::GetScreen()
->GetDisplayNearestView(view_->GetNativeView())
.size();
overscroll_delegate_.reset(new TestOverscrollDelegate(display_size));
view_->overscroll_controller()->set_delegate(overscroll_delegate_.get());
view_->InitAsChild(nullptr);
view_->SetBounds(gfx::Rect(0, 0, 400, 200));
view_->Show();
sink_->ClearMessages();
}
// TODO(jdduke): Simulate ui::Events, injecting through the view.
void SimulateMouseEvent(WebInputEvent::Type type) {
widget_host_->ForwardMouseEvent(SyntheticWebMouseEventBuilder::Build(type));
base::RunLoop().RunUntilIdle();
}
void SimulateMouseEventWithLatencyInfo(WebInputEvent::Type type,
const ui::LatencyInfo& ui_latency) {
widget_host_->ForwardMouseEventWithLatencyInfo(
SyntheticWebMouseEventBuilder::Build(type), ui_latency);
base::RunLoop().RunUntilIdle();
}
void SimulateWheelEvent(float dX,
float dY,
int modifiers,
bool precise,
WebMouseWheelEvent::Phase phase) {
WebMouseWheelEvent wheel_event = SyntheticWebMouseWheelEventBuilder::Build(
0, 0, dX, dY, modifiers, precise);
wheel_event.phase = phase;
widget_host_->ForwardWheelEvent(wheel_event);
base::RunLoop().RunUntilIdle();
}
void SimulateMouseMove(int x, int y, int modifiers) {
SimulateMouseEvent(WebInputEvent::kMouseMove, x, y, modifiers, false);
}
void SimulateMouseEvent(WebInputEvent::Type type,
int x,
int y,
int modifiers,
bool pressed) {
WebMouseEvent event =
SyntheticWebMouseEventBuilder::Build(type, x, y, modifiers);
if (pressed)
event.button = WebMouseEvent::Button::kLeft;
widget_host_->ForwardMouseEvent(event);
base::RunLoop().RunUntilIdle();
}
// Inject provided synthetic WebGestureEvent instance.
void SimulateGestureEventCore(const WebGestureEvent& gesture_event) {
widget_host_->ForwardGestureEvent(gesture_event);
base::RunLoop().RunUntilIdle();
}
void SimulateGestureEventCoreWithLatencyInfo(
const WebGestureEvent& gesture_event,
const ui::LatencyInfo& ui_latency) {
widget_host_->ForwardGestureEventWithLatencyInfo(gesture_event, ui_latency);
base::RunLoop().RunUntilIdle();
}
// Inject simple synthetic WebGestureEvent instances.
void SimulateGestureEvent(WebInputEvent::Type type,
blink::WebGestureDevice sourceDevice) {
SimulateGestureEventCore(
SyntheticWebGestureEventBuilder::Build(type, sourceDevice));
}
void SimulateGestureEventWithLatencyInfo(WebInputEvent::Type type,
blink::WebGestureDevice sourceDevice,
const ui::LatencyInfo& ui_latency) {
SimulateGestureEventCoreWithLatencyInfo(
SyntheticWebGestureEventBuilder::Build(type, sourceDevice), ui_latency);
}
void SimulateGestureScrollUpdateEvent(float dX, float dY, int modifiers) {
SimulateGestureEventCore(SyntheticWebGestureEventBuilder::BuildScrollUpdate(
dX, dY, modifiers, blink::WebGestureDevice::kTouchscreen));
}
void SimulateGesturePinchUpdateEvent(float scale,
float anchorX,
float anchorY,
int modifiers) {
SimulateGestureEventCore(SyntheticWebGestureEventBuilder::BuildPinchUpdate(
scale, anchorX, anchorY, modifiers,
blink::WebGestureDevice::kTouchscreen));
}
// Inject synthetic GestureFlingStart events.
void SimulateGestureFlingStartEvent(float velocityX,
float velocityY,
blink::WebGestureDevice sourceDevice) {
SimulateGestureEventCore(SyntheticWebGestureEventBuilder::BuildFling(
velocityX, velocityY, sourceDevice));
}
bool ScrollStateIsContentConsuming() const {
return scroll_state() ==
OverscrollController::ScrollState::CONTENT_CONSUMING;
}
bool ScrollStateIsOverscrolling() const {
return scroll_state() == OverscrollController::ScrollState::OVERSCROLLING;
}
bool ScrollStateIsUnknown() const {
return scroll_state() == OverscrollController::ScrollState::NONE;
}
OverscrollController::ScrollState scroll_state() const {
return view_->overscroll_controller()->scroll_state_;
}
OverscrollMode overscroll_mode() const {
return view_->overscroll_controller()->overscroll_mode_;
}
OverscrollSource overscroll_source() const {
return view_->overscroll_controller()->overscroll_source_;
}
float overscroll_delta_x() const {
return view_->overscroll_controller()->overscroll_delta_x_;
}
float overscroll_delta_y() const {
return view_->overscroll_controller()->overscroll_delta_y_;
}
TestOverscrollDelegate* overscroll_delegate() {
return overscroll_delegate_.get();
}
uint32_t SendTouchEvent() {
uint32_t touch_event_id = touch_event_.unique_touch_event_id;
widget_host_->ForwardTouchEventWithLatencyInfo(touch_event_,
ui::LatencyInfo());
touch_event_.ResetPoints();
base::RunLoop().RunUntilIdle();
return touch_event_id;
}
void PressTouchPoint(int x, int y) {
touch_event_.PressPoint(x, y);
}
void MoveTouchPoint(int index, int x, int y) {
touch_event_.MovePoint(index, x, y);
}
void ReleaseTouchPoint(int index) {
touch_event_.ReleasePoint(index);
}
void PressAndSetTouchActionAuto() {
PressTouchPoint(0, 1);
SendTouchEvent();
widget_host_->input_router()->OnSetTouchAction(cc::kTouchActionAuto);
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
EXPECT_EQ("TouchStart", GetMessageNames(events));
}
void ReleaseAndResetDispatchedMessages() {
ReleaseTouchPoint(0);
SendTouchEvent();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
}
MockWidgetInputHandler::MessageVector ExpectGestureScrollEndForWheelScrolling(
bool is_last) {
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
if (is_last) {
// Scroll latching will have one GestureScrollEnd at the end.
EXPECT_EQ("GestureScrollEnd", GetMessageNames(events));
return events;
}
// No GestureScrollEnd during the scroll.
EXPECT_EQ(0U, events.size());
return events;
}
MockWidgetInputHandler::MessageVector
ExpectGestureScrollEventsAfterMouseWheelACK(
bool is_first_ack,
size_t enqueued_wheel_event_count) {
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
std::string expected_events;
// If the ack for the first sent event is not consumed,
// MouseWheelEventQueue(MWEQ) sends the rest of the wheel events in the
// current scrolling sequence as non-blocking events. Since MWEQ
// receives the ack for non-blocking events asynchronously, it sends the
// next queued wheel event immediately and this continues till the queue
// is empty.
// Expecting a GSB+GSU for ACKing the first MouseWheel, plus an additional
// MouseWheel+GSU per enqueued wheel event. Note that GestureEventQueue
// allows multiple in-flight events.
if (is_first_ack)
expected_events += "GestureScrollBegin GestureScrollUpdate ";
for (size_t i = 0; i < enqueued_wheel_event_count; ++i)
expected_events += "MouseWheel GestureScrollUpdate ";
EXPECT_EQ(base::TrimWhitespaceASCII(expected_events, base::TRIM_TRAILING),
GetMessageNames(events));
return events;
}
MockWidgetInputHandler::MessageVector
ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(
bool wheel_was_queued) {
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
size_t gesture_scroll_update_index;
if (wheel_was_queued) {
// The queued wheel event is already sent.
gesture_scroll_update_index = 0;
} else {
// The first sent must be the wheel event and the second one must be
// GestureScrollUpdate since the ack for the wheel event is non-blocking.
EXPECT_TRUE(events[0]->ToEvent());
EXPECT_EQ(WebInputEvent::kMouseWheel,
events[0]->ToEvent()->Event()->web_event->GetType());
gesture_scroll_update_index = 1;
}
EXPECT_EQ(gesture_scroll_update_index + 1, events.size());
EXPECT_TRUE(events[gesture_scroll_update_index]->ToEvent());
EXPECT_EQ(WebInputEvent::kGestureScrollUpdate,
events[gesture_scroll_update_index]
->ToEvent()
->Event()
->web_event->GetType());
return events;
}
SyntheticWebTouchEvent touch_event_;
std::unique_ptr<TestOverscrollDelegate> overscroll_delegate_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraOverscrollTest);
};
class RenderWidgetHostViewAuraShutdownTest
: public RenderWidgetHostViewAuraTest {
public:
RenderWidgetHostViewAuraShutdownTest() {}
void TearDown() override {
// No TearDownEnvironment here, we do this explicitly during the test.
}
private:
DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraShutdownTest);
};
// Checks that RenderWidgetHostViewAura can be destroyed before it is properly
// initialized.
TEST_F(RenderWidgetHostViewAuraTest, DestructionBeforeProperInitialization) {
// Terminate the test without initializing |view_|.
}
// Checks that a fullscreen view has the correct show-state and receives the
// focus.
TEST_F(RenderWidgetHostViewAuraTest, FocusFullscreen) {
view_->InitAsFullscreen(parent_view_);
aura::Window* window = view_->GetNativeView();
ASSERT_TRUE(window != nullptr);
EXPECT_EQ(ui::SHOW_STATE_FULLSCREEN,
window->GetProperty(aura::client::kShowStateKey));
// Check that we requested and received the focus.
EXPECT_TRUE(window->HasFocus());
// Check that we'll also say it's okay to activate the window when there's an
// ActivationClient defined.
EXPECT_TRUE(view_->ShouldActivate());
}
// Checks that a popup is positioned correctly relative to its parent using
// screen coordinates.
TEST_F(RenderWidgetHostViewAuraTest, PositionChildPopup) {
wm::DefaultScreenPositionClient screen_position_client;
aura::Window* window = parent_view_->GetNativeView();
aura::Window* root = window->GetRootWindow();
aura::client::SetScreenPositionClient(root, &screen_position_client);
parent_view_->SetBounds(gfx::Rect(10, 10, 800, 600));
gfx::Rect bounds_in_screen = parent_view_->GetViewBounds();
int horiz = bounds_in_screen.width() / 4;
int vert = bounds_in_screen.height() / 4;
bounds_in_screen.Inset(horiz, vert);
// Verify that when the popup is initialized for the first time, it correctly
// treats the input bounds as screen coordinates.
view_->SetWidgetType(WidgetType::kPopup);
view_->InitAsPopup(parent_view_, bounds_in_screen);
gfx::Rect final_bounds_in_screen = view_->GetViewBounds();
EXPECT_EQ(final_bounds_in_screen.ToString(), bounds_in_screen.ToString());
// Verify that directly setting the bounds via SetBounds() treats the input
// as screen coordinates.
bounds_in_screen = gfx::Rect(60, 60, 100, 100);
view_->SetBounds(bounds_in_screen);
final_bounds_in_screen = view_->GetViewBounds();
EXPECT_EQ(final_bounds_in_screen.ToString(), bounds_in_screen.ToString());
// Verify that setting the size does not alter the origin.
gfx::Point original_origin = window->bounds().origin();
view_->SetSize(gfx::Size(120, 120));
gfx::Point new_origin = window->bounds().origin();
EXPECT_EQ(original_origin.ToString(), new_origin.ToString());
aura::client::SetScreenPositionClient(root, nullptr);
}
// Checks that moving parent sends new screen bounds.
TEST_F(RenderWidgetHostViewAuraTest, ParentMovementUpdatesScreenRect) {
view_->InitAsChild(nullptr);
aura::Window* root = parent_view_->GetNativeView()->GetRootWindow();
aura::test::TestWindowDelegate delegate1, delegate2;
std::unique_ptr<aura::Window> parent1(new aura::Window(&delegate1));
parent1->Init(ui::LAYER_TEXTURED);
parent1->Show();
std::unique_ptr<aura::Window> parent2(new aura::Window(&delegate2));
parent2->Init(ui::LAYER_TEXTURED);
parent2->Show();
root->AddChild(parent1.get());
parent1->AddChild(parent2.get());
parent2->AddChild(view_->GetNativeView());
root->SetBounds(gfx::Rect(0, 0, 800, 600));
parent1->SetBounds(gfx::Rect(1, 1, 300, 300));
parent2->SetBounds(gfx::Rect(2, 2, 200, 200));
view_->SetBounds(gfx::Rect(3, 3, 100, 100));
// view_ will be destroyed when parent is destroyed.
view_ = nullptr;
// Flush the state after initial setup is done.
widget_host_->OnMessageReceived(
WidgetHostMsg_UpdateScreenRects_ACK(widget_host_->GetRoutingID()));
widget_host_->OnMessageReceived(
WidgetHostMsg_UpdateScreenRects_ACK(widget_host_->GetRoutingID()));
sink_->ClearMessages();
// Move parents.
parent2->SetBounds(gfx::Rect(20, 20, 200, 200));
ASSERT_EQ(1U, sink_->message_count());
const IPC::Message* msg = sink_->GetMessageAt(0);
ASSERT_EQ(static_cast<uint32_t>(WidgetMsg_UpdateScreenRects::ID),
msg->type());
WidgetMsg_UpdateScreenRects::Param params;
WidgetMsg_UpdateScreenRects::Read(msg, &params);
EXPECT_EQ(gfx::Rect(24, 24, 100, 100), std::get<0>(params));
EXPECT_EQ(gfx::Rect(1, 1, 300, 300), std::get<1>(params));
sink_->ClearMessages();
widget_host_->OnMessageReceived(
WidgetHostMsg_UpdateScreenRects_ACK(widget_host_->GetRoutingID()));
// There should not be any pending update.
EXPECT_EQ(0U, sink_->message_count());
parent1->SetBounds(gfx::Rect(10, 10, 300, 300));
ASSERT_EQ(1U, sink_->message_count());
msg = sink_->GetMessageAt(0);
ASSERT_EQ(static_cast<uint32_t>(WidgetMsg_UpdateScreenRects::ID),
msg->type());
WidgetMsg_UpdateScreenRects::Read(msg, &params);
EXPECT_EQ(gfx::Rect(33, 33, 100, 100), std::get<0>(params));
EXPECT_EQ(gfx::Rect(10, 10, 300, 300), std::get<1>(params));
sink_->ClearMessages();
widget_host_->OnMessageReceived(
WidgetHostMsg_UpdateScreenRects_ACK(widget_host_->GetRoutingID()));
// There should not be any pending update.
EXPECT_EQ(0U, sink_->message_count());
}
// Checks that a fullscreen view is destroyed when it loses the focus.
TEST_F(RenderWidgetHostViewAuraTest, DestroyFullscreenOnBlur) {
view_->InitAsFullscreen(parent_view_);
aura::Window* window = view_->GetNativeView();
ASSERT_TRUE(window != nullptr);
ASSERT_TRUE(window->HasFocus());
// After we create and focus another window, the RWHVA's window should be
// destroyed.
TestWindowObserver observer(window);
aura::test::TestWindowDelegate delegate;
std::unique_ptr<aura::Window> sibling(new aura::Window(&delegate));
sibling->Init(ui::LAYER_TEXTURED);
sibling->Show();
window->parent()->AddChild(sibling.get());
sibling->Focus();
ASSERT_TRUE(sibling->HasFocus());
ASSERT_TRUE(observer.destroyed());
widget_host_ = nullptr;
view_ = nullptr;
}
#if defined(OS_CHROMEOS)
// Checks that a popup view is destroyed when a user clicks outside of the popup
// view and focus does not change. This is the case when the user clicks on the
// desktop background on Chrome OS.
TEST_F(RenderWidgetHostViewAuraTest, DestroyPopupClickOutsidePopup) {
parent_view_->SetBounds(gfx::Rect(10, 10, 400, 400));
parent_view_->Focus();
EXPECT_TRUE(parent_view_->HasFocus());
view_->SetWidgetType(WidgetType::kPopup);
view_->InitAsPopup(parent_view_, gfx::Rect(10, 10, 100, 100));
aura::Window* window = view_->GetNativeView();
ASSERT_TRUE(window != nullptr);
gfx::Point click_point(0, 0);
EXPECT_FALSE(window->GetBoundsInRootWindow().Contains(click_point));
aura::Window* parent_window = parent_view_->GetNativeView();
EXPECT_FALSE(parent_window->GetBoundsInRootWindow().Contains(click_point));
TestWindowObserver observer(window);
ui::test::EventGenerator generator(window->GetRootWindow(), click_point);
generator.ClickLeftButton();
ASSERT_TRUE(parent_view_->HasFocus());
ASSERT_TRUE(observer.destroyed());
widget_host_ = nullptr;
view_ = nullptr;
}
// Checks that a popup view is destroyed when a user taps outside of the popup
// view and focus does not change. This is the case when the user taps the
// desktop background on Chrome OS.
TEST_F(RenderWidgetHostViewAuraTest, DestroyPopupTapOutsidePopup) {
parent_view_->SetBounds(gfx::Rect(10, 10, 400, 400));
parent_view_->Focus();
EXPECT_TRUE(parent_view_->HasFocus());
view_->SetWidgetType(WidgetType::kPopup);
view_->InitAsPopup(parent_view_, gfx::Rect(10, 10, 100, 100));
aura::Window* window = view_->GetNativeView();
ASSERT_TRUE(window != nullptr);
gfx::Point tap_point(0, 0);
EXPECT_FALSE(window->GetBoundsInRootWindow().Contains(tap_point));
aura::Window* parent_window = parent_view_->GetNativeView();
EXPECT_FALSE(parent_window->GetBoundsInRootWindow().Contains(tap_point));
TestWindowObserver observer(window);
ui::test::EventGenerator generator(window->GetRootWindow(), tap_point);
generator.GestureTapAt(tap_point);
ASSERT_TRUE(parent_view_->HasFocus());
ASSERT_TRUE(observer.destroyed());
widget_host_ = nullptr;
view_ = nullptr;
}
#endif
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
// On Desktop Linux, select boxes need mouse capture in order to work. Test that
// when a select box is opened via a mouse press that it retains mouse capture
// after the mouse is released.
TEST_F(RenderWidgetHostViewAuraTest, PopupRetainsCaptureAfterMouseRelease) {
parent_view_->SetBounds(gfx::Rect(10, 10, 400, 400));
parent_view_->Focus();
EXPECT_TRUE(parent_view_->HasFocus());
ui::test::EventGenerator generator(
parent_view_->GetNativeView()->GetRootWindow(), gfx::Point(300, 300));
generator.PressLeftButton();
view_->SetWidgetType(WidgetType::kPopup);
view_->InitAsPopup(parent_view_, gfx::Rect(10, 10, 100, 100));
ASSERT_TRUE(view_->NeedsMouseCapture());
aura::Window* window = view_->GetNativeView();
EXPECT_TRUE(window->HasCapture());
generator.ReleaseLeftButton();
EXPECT_TRUE(window->HasCapture());
}
#endif
// Test that select boxes close when their parent window loses focus (e.g. due
// to an alert or system modal dialog).
TEST_F(RenderWidgetHostViewAuraTest, PopupClosesWhenParentLosesFocus) {
parent_view_->SetBounds(gfx::Rect(10, 10, 400, 400));
parent_view_->Focus();
EXPECT_TRUE(parent_view_->HasFocus());
view_->SetWidgetType(WidgetType::kPopup);
view_->InitAsPopup(parent_view_, gfx::Rect(10, 10, 100, 100));
aura::Window* popup_window = view_->GetNativeView();
TestWindowObserver observer(popup_window);
aura::test::TestWindowDelegate delegate;
std::unique_ptr<aura::Window> dialog_window(new aura::Window(&delegate));
dialog_window->Init(ui::LAYER_TEXTURED);
aura::client::ParentWindowWithContext(
dialog_window.get(), popup_window, gfx::Rect());
dialog_window->Show();
wm::ActivateWindow(dialog_window.get());
dialog_window->Focus();
ASSERT_TRUE(wm::IsActiveWindow(dialog_window.get()));
EXPECT_TRUE(observer.destroyed());
widget_host_ = nullptr;
view_ = nullptr;
}
// Checks that IME-composition-event state is maintained correctly.
TEST_F(RenderWidgetHostViewAuraTest, SetCompositionText) {
view_->InitAsChild(nullptr);
view_->Show();
ActivateViewForTextInputManager(view_, ui::TEXT_INPUT_TYPE_TEXT);
ui::CompositionText composition_text;
composition_text.text = base::ASCIIToUTF16("|a|b");
// Focused segment
composition_text.ime_text_spans.push_back(
ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, 3,
ui::ImeTextSpan::Thickness::kThick, 0x78563412));
// Non-focused segment, with different background color.
composition_text.ime_text_spans.push_back(
ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 3, 4,
ui::ImeTextSpan::Thickness::kThin, 0xefcdab90));
const ui::ImeTextSpans& ime_text_spans = composition_text.ime_text_spans;
// Caret is at the end. (This emulates Japanese MSIME 2007 and later)
composition_text.selection = gfx::Range(4);
view_->SetCompositionText(composition_text);
EXPECT_TRUE(view_->has_composition_text_);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
EXPECT_EQ("SetComposition", GetMessageNames(events));
MockWidgetInputHandler::DispatchedIMEMessage* ime_message =
events[0]->ToIME();
EXPECT_TRUE(ime_message);
EXPECT_TRUE(ime_message->Matches(composition_text.text, ime_text_spans,
gfx::Range::InvalidRange(), 4, 4));
view_->ImeCancelComposition();
EXPECT_FALSE(view_->has_composition_text_);
}
// Checks that we reset has_composition_text_ to false upon when the focused
// node is changed.
TEST_F(RenderWidgetHostViewAuraTest, FocusedNodeChanged) {
view_->InitAsChild(nullptr);
view_->Show();
ActivateViewForTextInputManager(view_, ui::TEXT_INPUT_TYPE_TEXT);
ui::CompositionText composition_text;
composition_text.text = base::ASCIIToUTF16("hello");
view_->SetCompositionText(composition_text);
EXPECT_TRUE(view_->has_composition_text_);
view_->FocusedNodeChanged(true, gfx::Rect());
EXPECT_FALSE(view_->has_composition_text_);
}
// Checks that sequence of IME-composition-event and mouse-event when mouse
// clicking to cancel the composition.
TEST_F(RenderWidgetHostViewAuraTest, FinishCompositionByMouse) {
view_->InitAsChild(nullptr);
view_->Show();
ActivateViewForTextInputManager(view_, ui::TEXT_INPUT_TYPE_TEXT);
ui::CompositionText composition_text;
composition_text.text = base::ASCIIToUTF16("|a|b");
// Focused segment
composition_text.ime_text_spans.push_back(
ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, 3,
ui::ImeTextSpan::Thickness::kThick, 0x78563412));
// Non-focused segment, with different background color.
composition_text.ime_text_spans.push_back(
ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 3, 4,
ui::ImeTextSpan::Thickness::kThin, 0xefcdab90));
// Caret is at the end. (This emulates Japanese MSIME 2007 and later)
composition_text.selection = gfx::Range(4);
view_->SetCompositionText(composition_text);
EXPECT_TRUE(view_->has_composition_text_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ("SetComposition", GetMessageNames(GetAndResetDispatchedMessages()));
// Simulates the mouse press.
ui::MouseEvent mouse_event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
0);
view_->OnMouseEvent(&mouse_event);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(view_->has_composition_text_);
EXPECT_EQ("FinishComposingText MouseDown",
GetMessageNames(GetAndResetDispatchedMessages()));
}
// Checks that WasOcculded/WasUnOccluded notifies RenderWidgetHostImpl.
TEST_F(RenderWidgetHostViewAuraTest, WasOccluded) {
view_->InitAsChild(nullptr);
view_->Show();
EXPECT_FALSE(widget_host_->is_hidden());
// Verifies WasOccluded sets RenderWidgetHostImpl as hidden and WasUnOccluded
// resets the state.
view_->WasOccluded();
EXPECT_TRUE(widget_host_->is_hidden());
view_->WasUnOccluded();
EXPECT_FALSE(widget_host_->is_hidden());
// Verifies WasOccluded sets RenderWidgetHostImpl as hidden and Show resets
// the state.
view_->WasOccluded();
EXPECT_TRUE(widget_host_->is_hidden());
view_->Show();
EXPECT_FALSE(widget_host_->is_hidden());
// WasOccluded and WasUnOccluded are not in pairs. The last one dictates
// the final state.
for (int i = 0; i < 2; ++i) {
view_->WasOccluded();
EXPECT_TRUE(widget_host_->is_hidden());
}
view_->WasUnOccluded();
EXPECT_FALSE(widget_host_->is_hidden());
for (int i = 0; i < 4; ++i) {
view_->WasUnOccluded();
EXPECT_FALSE(widget_host_->is_hidden());
}
view_->WasOccluded();
EXPECT_TRUE(widget_host_->is_hidden());
}
// Checks that touch-event state is maintained correctly.
TEST_F(RenderWidgetHostViewAuraTest, TouchEventState) {
view_->InitAsChild(nullptr);
view_->Show();
// Start with no touch-event handler in the renderer.
widget_host_->OnMessageReceived(
WidgetHostMsg_HasTouchEventHandlers(0, false));
ui::TouchEvent press(
ui::ET_TOUCH_PRESSED, gfx::Point(30, 30), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
ui::TouchEvent move(
ui::ET_TOUCH_MOVED, gfx::Point(20, 20), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
ui::TouchEvent release(
ui::ET_TOUCH_RELEASED, gfx::Point(20, 20), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
// The touch events should get forwarded from the view, but they should not
// reach the renderer.
view_->OnTouchEvent(&press);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
EXPECT_EQ(0U, events.size());
EXPECT_TRUE(press.synchronous_handling_disabled());
EXPECT_EQ(ui::MotionEvent::Action::DOWN, pointer_state().GetAction());
view_->OnTouchEvent(&move);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ(0U, events.size());
EXPECT_TRUE(press.synchronous_handling_disabled());
EXPECT_EQ(ui::MotionEvent::Action::MOVE, pointer_state().GetAction());
EXPECT_EQ(1U, pointer_state().GetPointerCount());
view_->OnTouchEvent(&release);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ(0U, events.size());
EXPECT_TRUE(press.synchronous_handling_disabled());
EXPECT_EQ(0U, pointer_state().GetPointerCount());
// Now install some touch-event handlers and do the same steps. The touch
// events should now be consumed. However, the touch-event state should be
// updated as before.
widget_host_->OnMessageReceived(WidgetHostMsg_HasTouchEventHandlers(0, true));
view_->OnTouchEvent(&press);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ(1U, events.size());
EXPECT_TRUE(press.synchronous_handling_disabled());
EXPECT_EQ(ui::MotionEvent::Action::DOWN, pointer_state().GetAction());
EXPECT_EQ(1U, pointer_state().GetPointerCount());
widget_host_->input_router()->OnSetTouchAction(cc::kTouchActionAuto);
view_->OnTouchEvent(&move);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(move.synchronous_handling_disabled());
EXPECT_EQ(ui::MotionEvent::Action::MOVE, pointer_state().GetAction());
EXPECT_EQ(1U, pointer_state().GetPointerCount());
view_->OnTouchEvent(&release);
EXPECT_TRUE(release.synchronous_handling_disabled());
EXPECT_EQ(0U, pointer_state().GetPointerCount());
// Now start a touch event, and remove the event-handlers before the release.
view_->OnTouchEvent(&press);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(press.synchronous_handling_disabled());
EXPECT_EQ(ui::MotionEvent::Action::DOWN, pointer_state().GetAction());
EXPECT_EQ(1U, pointer_state().GetPointerCount());
events = GetAndResetDispatchedMessages();
EXPECT_EQ(3U, events.size());
widget_host_->OnMessageReceived(
WidgetHostMsg_HasTouchEventHandlers(0, false));
// All outstanding events should have already been sent but no new events
// should get sent.
InputEventAck ack(
InputEventAckSource::COMPOSITOR_THREAD, blink::WebInputEvent::kTouchStart,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, press.unique_event_id());
events = GetAndResetDispatchedMessages();
EXPECT_EQ(0U, events.size());
ui::TouchEvent move2(
ui::ET_TOUCH_MOVED, gfx::Point(20, 20), base::TimeTicks::Now(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
view_->OnTouchEvent(&move2);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(press.synchronous_handling_disabled());
EXPECT_EQ(ui::MotionEvent::Action::MOVE, pointer_state().GetAction());
EXPECT_EQ(1U, pointer_state().GetPointerCount());
ui::TouchEvent release2(
ui::ET_TOUCH_RELEASED, gfx::Point(20, 20), base::TimeTicks::Now(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
view_->OnTouchEvent(&release2);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(press.synchronous_handling_disabled());
EXPECT_EQ(0U, pointer_state().GetPointerCount());
events = GetAndResetDispatchedMessages();
EXPECT_EQ(0U, events.size());
}
// The DOM KeyCode map for Fuchsia maps all DomCodes to 0. This means that
// tests which expect different behaviors for different keys will not work.
// Tracked via crbug.com/829551
#if defined(OS_FUCHSIA)
#define MAYBE_KeyEventRoutingWithKeyboardLockActiveForOneKey \
DISABLED_KeyEventRoutingWithKeyboardLockActiveForOneKey
#else
#define MAYBE_KeyEventRoutingWithKeyboardLockActiveForOneKey \
KeyEventRoutingWithKeyboardLockActiveForOneKey
#endif
TEST_F(RenderWidgetHostViewAuraTest,
MAYBE_KeyEventRoutingWithKeyboardLockActiveForOneKey) {
view_->InitAsChild(nullptr);
view_->Show();
auto test_hook = std::make_unique<TestScopedKeyboardHook>();
test_hook->LockSpecificKey(ui::DomCode::US_A);
view_->event_handler()->scoped_keyboard_hook_ = std::move(test_hook);
// This locked key will skip the prehandler and be sent to the input handler.
ui::KeyEvent key_event1(ui::ET_KEY_PRESSED,
ui::DomCodeToUsLayoutKeyboardCode(ui::DomCode::US_A),
ui::DomCode::US_A, ui::EF_NONE);
view_->OnKeyEvent(&key_event1);
const NativeWebKeyboardEvent* event1 =
render_widget_host_delegate()->last_event();
ASSERT_FALSE(event1);
// Run the runloop to ensure input messages are dispatched. Otherwise the
// result of GetAndResetDispatchedMessages() will not be valid.
base::RunLoop().RunUntilIdle();
auto events = GetAndResetDispatchedMessages();
ASSERT_FALSE(events.empty());
const NativeWebKeyboardEvent* native_key_event1 =
static_cast<const NativeWebKeyboardEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
ASSERT_TRUE(native_key_event1);
ASSERT_EQ(key_event1.key_code(), native_key_event1->windows_key_code);
ASSERT_EQ(ui::KeycodeConverter::DomCodeToNativeKeycode(key_event1.code()),
native_key_event1->native_key_code);
// These keys will pass through the prehandler since they aren't locked.
std::vector<ui::DomCode> dom_codes = {
ui::DomCode::US_B, ui::DomCode::US_Z, ui::DomCode::TAB,
ui::DomCode::ALT_LEFT, ui::DomCode::ENTER, ui::DomCode::ESCAPE};
for (ui::DomCode dom_code : dom_codes) {
ui::KeyEvent key_event(ui::ET_KEY_PRESSED,
ui::DomCodeToUsLayoutKeyboardCode(dom_code),
dom_code, ui::EF_NONE);
view_->OnKeyEvent(&key_event);
const NativeWebKeyboardEvent* event =
render_widget_host_delegate()->last_event();
ASSERT_TRUE(event) << "Failed for DomCode: "
<< ui::KeycodeConverter::DomCodeToCodeString(dom_code);
ASSERT_EQ(key_event.key_code(), event->windows_key_code);
ASSERT_EQ(ui::KeycodeConverter::DomCodeToNativeKeycode(key_event.code()),
event->native_key_code);
}
}
// The DOM KeyCode map for Fuchsia maps all DomCodes to 0. This means that
// tests which expect different behaviors for different keys will not work.
// Tracked via crbug.com/829551
#if defined(OS_FUCHSIA)
#define MAYBE_KeyEventRoutingWithKeyboardLockActiveForEscKey \
DISABLED_KeyEventRoutingWithKeyboardLockActiveForEscKey
#else
#define MAYBE_KeyEventRoutingWithKeyboardLockActiveForEscKey \
KeyEventRoutingWithKeyboardLockActiveForEscKey
#endif
TEST_F(RenderWidgetHostViewAuraTest,
MAYBE_KeyEventRoutingWithKeyboardLockActiveForEscKey) {
view_->InitAsChild(nullptr);
view_->Show();
auto test_hook = std::make_unique<TestScopedKeyboardHook>();
test_hook->LockSpecificKey(ui::DomCode::ESCAPE);
view_->event_handler()->scoped_keyboard_hook_ = std::move(test_hook);
// Although this key was locked, it will still pass through the prehandler as
// we do not want to prevent ESC from being used to exit fullscreen.
ui::KeyEvent key_event1(
ui::ET_KEY_PRESSED,
ui::DomCodeToUsLayoutKeyboardCode(ui::DomCode::ESCAPE),
ui::DomCode::ESCAPE, ui::EF_NONE);
view_->OnKeyEvent(&key_event1);
const NativeWebKeyboardEvent* event1 =
render_widget_host_delegate()->last_event();
ASSERT_TRUE(event1);
ASSERT_EQ(key_event1.key_code(), event1->windows_key_code);
ASSERT_EQ(ui::KeycodeConverter::DomCodeToNativeKeycode(key_event1.code()),
event1->native_key_code);
// This event will pass through the prehandler since it isn't locked.
ui::KeyEvent key_event2(ui::ET_KEY_PRESSED,
ui::DomCodeToUsLayoutKeyboardCode(ui::DomCode::US_B),
ui::DomCode::US_B, ui::EF_NONE);
view_->OnKeyEvent(&key_event2);
const NativeWebKeyboardEvent* event2 =
render_widget_host_delegate()->last_event();
ASSERT_TRUE(event2);
ASSERT_EQ(key_event2.key_code(), event2->windows_key_code);
ASSERT_EQ(ui::KeycodeConverter::DomCodeToNativeKeycode(key_event2.code()),
event2->native_key_code);
}
TEST_F(RenderWidgetHostViewAuraTest,
KeyEventRoutingWithKeyboardLockActiveForAllKeys) {
view_->InitAsChild(nullptr);
view_->Show();
auto test_hook = std::make_unique<TestScopedKeyboardHook>();
test_hook->LockAllKeys();
view_->event_handler()->scoped_keyboard_hook_ = std::move(test_hook);
// These keys will skip the prehandler and be sent to the input handler.
std::vector<ui::DomCode> dom_codes = {ui::DomCode::US_A, ui::DomCode::US_B,
ui::DomCode::TAB, ui::DomCode::ALT_LEFT,
ui::DomCode::ENTER};
for (ui::DomCode dom_code : dom_codes) {
ui::KeyEvent key_event(ui::ET_KEY_PRESSED,
ui::DomCodeToUsLayoutKeyboardCode(dom_code),
dom_code, ui::EF_NONE);
view_->OnKeyEvent(&key_event);
const NativeWebKeyboardEvent* event =
render_widget_host_delegate()->last_event();
ASSERT_FALSE(event) << "Failed for DomCode: "
<< ui::KeycodeConverter::DomCodeToCodeString(dom_code);
// Run the runloop to ensure input messages are dispatched. Otherwise the
// result of GetAndResetDispatchedMessages() will not be valid.
base::RunLoop().RunUntilIdle();
auto events = GetAndResetDispatchedMessages();
ASSERT_FALSE(events.empty())
<< "Failed for DomCode: "
<< ui::KeycodeConverter::DomCodeToCodeString(dom_code);
const NativeWebKeyboardEvent* native_key_event =
static_cast<const NativeWebKeyboardEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
ASSERT_TRUE(native_key_event)
<< "Failed for DomCode: "
<< ui::KeycodeConverter::DomCodeToCodeString(dom_code);
ASSERT_EQ(key_event.key_code(), native_key_event->windows_key_code);
ASSERT_EQ(ui::KeycodeConverter::DomCodeToNativeKeycode(key_event.code()),
native_key_event->native_key_code);
}
// Although this key was locked, it will still pass through the prehandler as
// we do not want to prevent ESC from being used to exit fullscreen.
ui::KeyEvent esc_key_event(
ui::ET_KEY_PRESSED,
ui::DomCodeToUsLayoutKeyboardCode(ui::DomCode::ESCAPE),
ui::DomCode::ESCAPE, ui::EF_NONE);
view_->OnKeyEvent(&esc_key_event);
const NativeWebKeyboardEvent* esc_event =
render_widget_host_delegate()->last_event();
ASSERT_TRUE(esc_event);
ASSERT_EQ(esc_key_event.key_code(), esc_event->windows_key_code);
ASSERT_EQ(ui::KeycodeConverter::DomCodeToNativeKeycode(esc_key_event.code()),
esc_event->native_key_code);
}
TEST_F(RenderWidgetHostViewAuraTest,
KeyEventRoutingKeyboardLockAndChildPopupWithInputGrab) {
parent_view_->SetBounds(gfx::Rect(10, 10, 400, 400));
parent_view_->Focus();
ASSERT_TRUE(parent_view_->HasFocus());
view_->SetWidgetType(WidgetType::kPopup);
view_->InitAsPopup(parent_view_, gfx::Rect(10, 10, 100, 100));
ASSERT_NE(nullptr, view_->GetNativeView());
view_->Show();
MockRenderWidgetHostImpl* parent_host =
static_cast<MockRenderWidgetHostImpl*>(parent_host_);
// Run the runloop to ensure input messages are dispatched. Otherwise the
// result of GetAndResetDispatchedMessages() will not be valid.
base::RunLoop().RunUntilIdle();
// A MouseCapture lost message is posted when the child gains focus, clear
// that message out so we can reliably test the number of messages
// dispatched later on in the test.
parent_host->input_handler()->GetAndResetDispatchedMessages();
// The parent view owns the KeyboardLock for this test.
auto test_hook = std::make_unique<TestScopedKeyboardHook>();
test_hook->LockAllKeys();
parent_view_->event_handler()->scoped_keyboard_hook_ = std::move(test_hook);
// These keys will not be processed by the parent view but will be handled in
// the child (popup) view.
std::vector<ui::DomCode> dom_codes = {
ui::DomCode::US_A, ui::DomCode::ENTER, ui::DomCode::TAB,
ui::DomCode::ALT_LEFT, ui::DomCode::US_Z, ui::DomCode::ESCAPE};
for (ui::DomCode dom_code : dom_codes) {
ui::KeyEvent key_event(ui::ET_KEY_PRESSED,
ui::DomCodeToUsLayoutKeyboardCode(dom_code),
dom_code, ui::EF_NONE);
parent_view_->OnKeyEvent(&key_event);
const NativeWebKeyboardEvent* parent_event = delegates_[0]->last_event();
ASSERT_FALSE(parent_event)
<< "Failed for DomCode: "
<< ui::KeycodeConverter::DomCodeToCodeString(dom_code);
const NativeWebKeyboardEvent* child_event =
render_widget_host_delegate()->last_event();
ASSERT_TRUE(child_event)
<< "Failed for DomCode: "
<< ui::KeycodeConverter::DomCodeToCodeString(dom_code);
ASSERT_EQ(key_event.key_code(), child_event->windows_key_code);
ASSERT_EQ(ui::KeycodeConverter::DomCodeToNativeKeycode(key_event.code()),
child_event->native_key_code);
// Run the runloop to ensure input messages are dispatched. Otherwise the
// result of GetAndResetDispatchedMessages() will not be valid.
base::RunLoop().RunUntilIdle();
auto parent_events =
parent_host->input_handler()->GetAndResetDispatchedMessages();
ASSERT_TRUE(parent_events.empty())
<< "Failed for DomCode: "
<< ui::KeycodeConverter::DomCodeToCodeString(dom_code);
auto child_events = GetAndResetDispatchedMessages();
ASSERT_FALSE(child_events.empty())
<< "Failed for DomCode: "
<< ui::KeycodeConverter::DomCodeToCodeString(dom_code);
}
}
TEST_F(RenderWidgetHostViewAuraTest, TimerBasedWheelEventPhaseInfo) {
view_->InitAsChild(nullptr);
view_->Show();
sink_->ClearMessages();
ui::MouseWheelEvent event(gfx::Vector2d(0, 5), gfx::Point(2, 2),
gfx::Point(2, 2), ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&event);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
EXPECT_TRUE(events[0]->ToEvent());
const WebMouseWheelEvent* wheel_event =
static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
events = GetAndResetDispatchedMessages();
const WebGestureEvent* gesture_event = static_cast<const WebGestureEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebInputEvent::kGestureScrollBegin, gesture_event->GetType());
EXPECT_TRUE(gesture_event->data.scroll_begin.synthetic);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
gesture_event = static_cast<const WebGestureEvent*>(
events[1]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebInputEvent::kGestureScrollUpdate, gesture_event->GetType());
EXPECT_EQ(0U, gesture_event->data.scroll_update.delta_x);
EXPECT_EQ(5U, gesture_event->data.scroll_update.delta_y);
events[1]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
// Send a ui::ScrollEvent instead of ui::MouseWheel event, the timer based
// phase info doesn't diffrentiate between the two types of events.
ui::ScrollEvent scroll1(ui::ET_SCROLL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 2, 0, 2, 2);
view_->OnScrollEvent(&scroll1);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
base::TimeTicks wheel_event_timestamp = wheel_event->TimeStamp();
EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged, wheel_event->phase);
gesture_event = static_cast<const WebGestureEvent*>(
events[1]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebInputEvent::kGestureScrollUpdate, gesture_event->GetType());
EXPECT_EQ(0U, gesture_event->data.scroll_update.delta_x);
EXPECT_EQ(2U, gesture_event->data.scroll_update.delta_y);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
// Let the MouseWheelPhaseHandler::mouse_wheel_end_dispatch_timer_ fire. A
// synthetic wheel event with zero deltas and kPhaseEnded will be sent.
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
const WebMouseWheelEvent* wheel_end_event =
static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseEnded, wheel_end_event->phase);
EXPECT_EQ(0U, wheel_end_event->delta_x);
EXPECT_EQ(0U, wheel_end_event->delta_y);
EXPECT_EQ(0U, wheel_end_event->wheel_ticks_x);
EXPECT_EQ(0U, wheel_end_event->wheel_ticks_y);
EXPECT_GT(wheel_end_event->TimeStamp(), wheel_event_timestamp);
gesture_event = static_cast<const WebGestureEvent*>(
events[1]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebInputEvent::kGestureScrollEnd, gesture_event->GetType());
EXPECT_TRUE(gesture_event->data.scroll_end.synthetic);
}
// Tests that latching breaks when the difference between location of the first
// wheel event in the sequence and the location of the current wheel event is
// larger than some maximum threshold.
TEST_F(RenderWidgetHostViewAuraTest, TimerBasedLatchingBreaksWithMouseMove) {
// Set the mouse_wheel_phase_handler_ timer timeout to a large value to make
// sure that the timer is still running when the wheel event with different
// location is sent.
view_->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout(
TestTimeouts::action_max_timeout());
view_->InitAsChild(nullptr);
view_->Show();
sink_->ClearMessages();
ui::MouseWheelEvent event(gfx::Vector2d(0, 5), gfx::Point(2, 2),
gfx::Point(2, 2), ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&event);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
EXPECT_TRUE(events[0]->ToEvent());
const WebMouseWheelEvent* wheel_event =
static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
events = GetAndResetDispatchedMessages();
// Send the second wheel event with a location within the slop region. The
// second wheel event will still be part of the current scrolling sequence
// since the location difference is less than the allowed threshold.
ui::MouseWheelEvent event2(gfx::Vector2d(0, 5),
gfx::Point(2 + kWheelLatchingSlopRegion / 2, 2),
gfx::Point(2 + kWheelLatchingSlopRegion / 2, 2),
ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&event2);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel GestureScrollUpdate", GetMessageNames(events));
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged, wheel_event->phase);
events = GetAndResetDispatchedMessages();
// Send the third wheel event with a location outside of the slop region. The
// third wheel event will break the latching since the location difference is
// larger than the allowed threshold.
ui::MouseWheelEvent event3(
gfx::Vector2d(0, 5), gfx::Point(2 + kWheelLatchingSlopRegion, 2),
gfx::Point(2 + kWheelLatchingSlopRegion, 2), ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&event3);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel GestureScrollEnd MouseWheel", GetMessageNames(events));
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseEnded, wheel_event->phase);
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[2]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
}
// Tests that latching breaks when the current wheel event has different
// modifiers.
TEST_F(RenderWidgetHostViewAuraTest,
TimerBasedLatchingBreaksWithModifiersChange) {
// Set the mouse_wheel_phase_handler_ timer timeout to a large value to make
// sure that the timer is still running when the wheel event with different
// modifiers is sent.
view_->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout(
TestTimeouts::action_max_timeout());
view_->InitAsChild(nullptr);
view_->Show();
sink_->ClearMessages();
ui::MouseWheelEvent event(gfx::Vector2d(0, 5), gfx::Point(2, 2),
gfx::Point(2, 2), ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&event);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
EXPECT_TRUE(events[0]->ToEvent());
const WebMouseWheelEvent* wheel_event =
static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
events = GetAndResetDispatchedMessages();
// Send the second wheel event with the same modifiers. The second wheel event
// will still be part of the current scrolling sequence.
ui::MouseWheelEvent event2(gfx::Vector2d(0, 5), gfx::Point(2, 2),
gfx::Point(2, 2), ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&event2);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel GestureScrollUpdate", GetMessageNames(events));
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged, wheel_event->phase);
events = GetAndResetDispatchedMessages();
// Send the third wheel event with a ctrl key down. The third wheel event will
// break the latching since the event modifiers have changed.
ui::MouseWheelEvent event3(gfx::Vector2d(0, 5), gfx::Point(2, 2),
gfx::Point(2, 2), ui::EventTimeForNow(),
ui::EF_CONTROL_DOWN, 0);
view_->OnMouseEvent(&event3);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel GestureScrollEnd MouseWheel", GetMessageNames(events));
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseEnded, wheel_event->phase);
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[2]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
}
// Tests that latching breaks when the new wheel event goes a different
// direction from previous wheel events and the previous GSU events are not
// consumed.
TEST_F(RenderWidgetHostViewAuraTest,
TimerBasedLatchingBreaksWithDirectionChange) {
// Set the mouse_wheel_phase_handler_ timer timeout to a large value to make
// sure that the timer is still running when the wheel event with different
// modifiers is sent.
view_->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout(
TestTimeouts::action_max_timeout());
view_->InitAsChild(nullptr);
view_->Show();
sink_->ClearMessages();
ui::MouseWheelEvent event(gfx::Vector2d(0, 5), gfx::Point(2, 2),
gfx::Point(2, 2), ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&event);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
EXPECT_TRUE(events[0]->ToEvent());
const WebMouseWheelEvent* wheel_event =
static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
// ACK the GSU as NOT_CONSUMED.
events = GetAndResetDispatchedMessages();
EXPECT_EQ("GestureScrollBegin GestureScrollUpdate", GetMessageNames(events));
EXPECT_TRUE(events[0]->ToEvent());
EXPECT_TRUE(events[1]->ToEvent());
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
events[1]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
// Send the second wheel event with different directions. This wheel event
// will break the latching since the last GSU was NOT_CONSUMED and the
// scrolling direction has changed.
ui::MouseWheelEvent event2(gfx::Vector2d(-5, 0), gfx::Point(2, 2),
gfx::Point(2, 2), ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&event2);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel GestureScrollEnd MouseWheel", GetMessageNames(events));
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseEnded, wheel_event->phase);
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[2]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
}
TEST_F(RenderWidgetHostViewAuraTest,
TimerBasedLatchingBreaksWithAutoscrollStart) {
// Set the mouse_wheel_phase_handler_ timer timeout to a large value to make
// sure that the timer is still running when the Autoscroll starts.
view_->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout(
TestTimeouts::action_max_timeout());
view_->InitAsChild(nullptr);
view_->Show();
sink_->ClearMessages();
ui::MouseWheelEvent event(gfx::Vector2d(0, 5), gfx::Point(2, 2),
gfx::Point(2, 2), ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&event);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel", GetMessageNames(events));
EXPECT_TRUE(events[0]->ToEvent());
const WebMouseWheelEvent* wheel_event =
static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
EXPECT_TRUE(GetMouseWheelPhaseHandler()->HasPendingWheelEndEvent());
events = GetAndResetDispatchedMessages();
// Autoscroll start breaks wheel scroll latching sequence by sending the
// pending wheel end event, the non-blocking wheel end event will be acked
// immediately and a GSE will be sent. The next wheel event will start a new
// scrolling sequence.
view_->OnAutoscrollStart();
EXPECT_FALSE(GetMouseWheelPhaseHandler()->HasPendingWheelEndEvent());
ui::MouseWheelEvent event2(gfx::Vector2d(0, 5), gfx::Point(2, 2),
gfx::Point(2, 2), ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&event2);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel GestureScrollEnd MouseWheel", GetMessageNames(events));
EXPECT_TRUE(events[0]->ToEvent());
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseEnded, wheel_event->phase);
EXPECT_TRUE(events[2]->ToEvent());
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[2]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
}
// Tests that a gesture fling start with touchpad source resets wheel phase
// state.
TEST_F(RenderWidgetHostViewAuraTest, TouchpadFlingStartResetsWheelPhaseState) {
// Set the mouse_wheel_phase_handler_ timer timeout to a large value to make
// sure that the timer is still running when the touchpad fling start is sent.
view_->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout(
TestTimeouts::action_max_timeout());
// When the user puts their fingers down a GFC is receieved.
ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 0, 0, 0, 2);
view_->OnScrollEvent(&fling_cancel);
// Scrolling starts.
ui::ScrollEvent scroll0(ui::ET_SCROLL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 5, 0, 5, 2);
view_->OnScrollEvent(&scroll0);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
const WebMouseWheelEvent* wheel_event =
static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ("MouseWheel", GetMessageNames(events));
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
events = GetAndResetDispatchedMessages();
EXPECT_EQ("GestureScrollBegin GestureScrollUpdate", GetMessageNames(events));
const WebGestureEvent* gesture_event = static_cast<const WebGestureEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebInputEvent::kGestureScrollBegin, gesture_event->GetType());
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
gesture_event = static_cast<const WebGestureEvent*>(
events[1]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebInputEvent::kGestureScrollUpdate, gesture_event->GetType());
EXPECT_EQ(0U, gesture_event->data.scroll_update.delta_x);
EXPECT_EQ(5U, gesture_event->data.scroll_update.delta_y);
events[1]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
// Wait for some time and resume scrolling. The second scroll will latch since
// the user hasn't lifted their fingers, yet.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(),
base::TimeDelta::FromMilliseconds(200));
run_loop.Run();
ui::ScrollEvent scroll1(ui::ET_SCROLL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 15, 0, 15, 2);
view_->OnScrollEvent(&scroll1);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ(2U, events.size());
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged, wheel_event->phase);
EXPECT_EQ("MouseWheel GestureScrollUpdate", GetMessageNames(events));
gesture_event = static_cast<const WebGestureEvent*>(
events[1]->ToEvent()->Event()->web_event.get());
events[1]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
EXPECT_EQ(WebInputEvent::kGestureScrollUpdate, gesture_event->GetType());
EXPECT_EQ(0U, gesture_event->data.scroll_update.delta_x);
EXPECT_EQ(15U, gesture_event->data.scroll_update.delta_y);
// A GFS is received showing that the user has lifted their fingers. This will
// reset the scroll state of the wheel phase handler. The velocity should be
// big enough to make sure that fling is still active while sending the scroll
// event.
ui::ScrollEvent fling_start(ui::ET_SCROLL_FLING_START, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 1000, 0, 1000, 2);
view_->OnScrollEvent(&fling_start);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
// A GFS with touchpad source won't get dispatched to the renderer. However,
// since progressFling is called right away after processing the GFS, it is
// possible that a progress event is sent if the time delta between GFS
// timestamp and the time that it gets processed is large enough.
bool progress_event_sent = events.size();
if (progress_event_sent)
EXPECT_EQ("MouseWheel GestureScrollUpdate", GetMessageNames(events));
// Handling the next ui::ET_SCROLL event will generate a GFC which resets the
// phase state. The fling controller processes GFC and generates a wheel event
// with momentum_phase == kPhaseEnded. The mouse wheel created from scroll2
// will have phase == kPhaseBegan.
ui::ScrollEvent scroll2(ui::ET_SCROLL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 15, 0, 15, 2);
view_->OnScrollEvent(&scroll2);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(widget_host_->FlingCancellationIsDeferred());
events = GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel", GetMessageNames(events));
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
}
// Tests that the touchpad scroll state in mouse wheel phase handler gets reset
// when a mouse wheel event from an external mouse arrives.
TEST_F(RenderWidgetHostViewAuraTest, MouseWheelScrollingAfterGFCWithoutGFS) {
// Set the mouse_wheel_phase_handler_ timer timeout to a large value to make
// sure that the timer is still running when we are checking for the pending
// wheel end event after sending ui::MouseWheelEvent.
view_->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout(
TestTimeouts::action_max_timeout());
view_->InitAsChild(nullptr);
view_->Show();
sink_->ClearMessages();
// When the user puts their fingers down a GFC is received. This will change
// the touchpad scroll state in mouse wheel phase handler to may_begin.
EXPECT_EQ(
content::TOUCHPAD_SCROLL_STATE_UNKNOWN,
GetMouseWheelPhaseHandler()->touchpad_scroll_phase_state_for_test());
ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 0, 0, 0, 2);
view_->OnScrollEvent(&fling_cancel);
GetAndResetDispatchedMessages();
EXPECT_EQ(
content::TOUCHPAD_SCROLL_MAY_BEGIN,
GetMouseWheelPhaseHandler()->touchpad_scroll_phase_state_for_test());
EXPECT_FALSE(GetMouseWheelPhaseHandler()->HasPendingWheelEndEvent());
// The user lifts their fingers without doing any touchpad scroll
// (ui::ScrollEevent), the touchpad scroll state must still be may_begin since
// without touchpad scrolling no GFS is recieved to reset the state.
EXPECT_EQ(
content::TOUCHPAD_SCROLL_MAY_BEGIN,
GetMouseWheelPhaseHandler()->touchpad_scroll_phase_state_for_test());
// The user starts scrolling by external mouse device.
ui::MouseWheelEvent wheel(gfx::Vector2d(0, 5), gfx::Point(2, 2),
gfx::Point(2, 2), ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&wheel);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
const WebMouseWheelEvent* wheel_event =
static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ("MouseWheel", GetMessageNames(events));
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
// After arrival of the mouse wheel event, the touchpad scroll state must get
// reset and the timer based wheel scroll latching must be active.
EXPECT_EQ(
content::TOUCHPAD_SCROLL_STATE_UNKNOWN,
GetMouseWheelPhaseHandler()->touchpad_scroll_phase_state_for_test());
EXPECT_TRUE(GetMouseWheelPhaseHandler()->HasPendingWheelEndEvent());
}
TEST_F(RenderWidgetHostViewAuraTest,
ScrollingWithExternalMouseBreaksTouchpadScrollLatching) {
// Set the mouse_wheel_phase_handler_ timer timeout to a large value to make
// sure that the timer is still running when we are checking for the pending
// wheel end event after sending ui::MouseWheelEvent.
view_->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout(
TestTimeouts::action_max_timeout());
view_->InitAsChild(nullptr);
view_->Show();
sink_->ClearMessages();
// When the user puts their fingers down a GFC is receieved.
ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 0, 0, 0, 2);
view_->OnScrollEvent(&fling_cancel);
// Start touchpad scrolling by sending a ui::ET_SCROLL event.
ui::ScrollEvent scroll0(ui::ET_SCROLL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 5, 0, 5, 2);
view_->OnScrollEvent(&scroll0);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
const WebMouseWheelEvent* wheel_event =
static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ("MouseWheel", GetMessageNames(events));
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
// The mouse_wheel_phase_handler's timer won't be running during touchpad
// scroll.
EXPECT_FALSE(GetMouseWheelPhaseHandler()->HasPendingWheelEndEvent());
// ACK the GSB and GSU events generated from the first touchpad wheel event.
events = GetAndResetDispatchedMessages();
EXPECT_EQ("GestureScrollBegin GestureScrollUpdate", GetMessageNames(events));
const WebGestureEvent* gesture_event = static_cast<const WebGestureEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebInputEvent::kGestureScrollBegin, gesture_event->GetType());
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
gesture_event = static_cast<const WebGestureEvent*>(
events[1]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebInputEvent::kGestureScrollUpdate, gesture_event->GetType());
events[1]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
// Start mouse wheel scrolling by sending a ui::ET_MOUSEWHEEL event. This
// should end the touchpad scrolling sequence and start a new timer-based
// wheel scrolling sequence.
ui::MouseWheelEvent wheel(gfx::Vector2d(0, 5), gfx::Point(2, 2),
gfx::Point(2, 2), ui::EventTimeForNow(), 0, 0);
view_->OnMouseEvent(&wheel);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel GestureScrollEnd MouseWheel", GetMessageNames(events));
EXPECT_TRUE(events[0]->ToEvent());
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseEnded, wheel_event->phase);
EXPECT_TRUE(events[2]->ToEvent());
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[2]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
// The mouse_wheel_phase_handler's timer will be running during mouse wheel
// scroll.
EXPECT_TRUE(GetMouseWheelPhaseHandler()->HasPendingWheelEndEvent());
}
TEST_F(RenderWidgetHostViewAuraTest,
GSBWithTouchSourceStopsWheelScrollSequence) {
// Set the mouse_wheel_phase_handler_ timer timeout to a large value to make
// sure that the timer is still running when the GSB event with touch source
// is sent.
view_->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout(
TestTimeouts::action_max_timeout());
ui::ScrollEvent scroll0(ui::ET_SCROLL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 5, 0, 5, 2);
view_->OnScrollEvent(&scroll0);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel", GetMessageNames(events));
const WebMouseWheelEvent* wheel_event =
static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
events = GetAndResetDispatchedMessages();
EXPECT_EQ("GestureScrollBegin GestureScrollUpdate", GetMessageNames(events));
const WebGestureEvent* gesture_event = static_cast<const WebGestureEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
gesture_event = static_cast<const WebGestureEvent*>(
events[1]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(0U, gesture_event->data.scroll_update.delta_x);
EXPECT_EQ(5U, gesture_event->data.scroll_update.delta_y);
events[1]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
ui::GestureEventDetails gesture_tap_down_details(ui::ET_GESTURE_TAP_DOWN);
gesture_tap_down_details.set_is_source_touch_event_set_non_blocking(true);
gesture_tap_down_details.set_device_type(
ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
ui::GestureEvent gesture_tap_down(2, 2, 0, ui::EventTimeForNow(),
gesture_tap_down_details);
view_->OnGestureEvent(&gesture_tap_down);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
ui::GestureEventDetails event_details(ui::ET_GESTURE_SCROLL_BEGIN);
event_details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
ui::GestureEvent scroll_begin(2, 2, 0, ui::EventTimeForNow(), event_details);
view_->OnGestureEvent(&scroll_begin);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel GestureScrollEnd GestureScrollBegin",
GetMessageNames(events));
EXPECT_EQ(3U, events.size());
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseEnded, wheel_event->phase);
EXPECT_EQ(0U, wheel_event->delta_x);
EXPECT_EQ(0U, wheel_event->delta_y);
gesture_event = static_cast<const WebGestureEvent*>(
events[1]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebInputEvent::kGestureScrollEnd, gesture_event->GetType());
EXPECT_EQ(blink::WebGestureDevice::kTouchpad, gesture_event->SourceDevice());
gesture_event = static_cast<const WebGestureEvent*>(
events[2]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebInputEvent::kGestureScrollBegin, gesture_event->GetType());
EXPECT_EQ(blink::WebGestureDevice::kTouchscreen,
gesture_event->SourceDevice());
}
TEST_F(RenderWidgetHostViewAuraTest,
SyntheticFlingCancelAtTouchpadScrollBegin) {
ui::ScrollEvent scroll_event(ui::ET_SCROLL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 5, 0, 5, 2);
// Send the beginning scroll event. This should generate a synthetic fling
// cancel to cancel any ongoing flings before the start of this scroll.
view_->OnScrollEvent(&scroll_event);
base::RunLoop().RunUntilIdle();
base::Optional<WebGestureEvent> last_gesture =
widget_host_->GetAndResetLastForwardedGestureEvent();
ASSERT_TRUE(last_gesture);
EXPECT_EQ(WebInputEvent::kGestureFlingCancel, last_gesture->GetType());
// Consume the wheel to prevent gesture scrolls from interfering with the
// rest of the test.
MockWidgetInputHandler::MessageVector dispatched_events =
GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel", GetMessageNames(dispatched_events));
dispatched_events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
dispatched_events = GetAndResetDispatchedMessages();
EXPECT_EQ(0U, dispatched_events.size());
// Send a scroll update. A synthetic fling cancel has already been sent for
// this sequence, so we should not generate another.
view_->OnScrollEvent(&scroll_event);
base::RunLoop().RunUntilIdle();
last_gesture = widget_host_->GetAndResetLastForwardedGestureEvent();
EXPECT_FALSE(last_gesture);
dispatched_events = GetAndResetDispatchedMessages();
EXPECT_EQ("MouseWheel", GetMessageNames(dispatched_events));
dispatched_events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
dispatched_events = GetAndResetDispatchedMessages();
EXPECT_EQ(0U, dispatched_events.size());
}
// Checks that touch-event state is maintained correctly for multiple touch
// points.
TEST_F(RenderWidgetHostViewAuraTest, MultiTouchPointsStates) {
view_->InitAsFullscreen(parent_view_);
view_->Show();
view_->UseFakeDispatcher();
ui::TouchEvent press0(
ui::ET_TOUCH_PRESSED, gfx::Point(30, 30), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
view_->OnTouchEvent(&press0);
view_->GetFocusedWidget()->input_router()->OnSetTouchAction(
cc::kTouchActionAuto);
base::RunLoop().RunUntilIdle();
MockWidgetInputHandler::MessageVector events =
GetAndResetDispatchedMessages();
EXPECT_EQ("SetFocus TouchStart", GetMessageNames(events));
events[1]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
EXPECT_EQ(ui::MotionEvent::Action::DOWN, pointer_state().GetAction());
EXPECT_EQ(1U, pointer_state().GetPointerCount());
EXPECT_EQ(1U, view_->dispatcher_->GetAndResetProcessedTouchEventCount());
ui::TouchEvent move0(
ui::ET_TOUCH_MOVED, gfx::Point(20, 20), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
view_->OnTouchEvent(&move0);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("TouchMove", GetMessageNames(events));
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
EXPECT_EQ(ui::MotionEvent::Action::MOVE, pointer_state().GetAction());
EXPECT_EQ(1U, pointer_state().GetPointerCount());
EXPECT_EQ(1U, view_->dispatcher_->GetAndResetProcessedTouchEventCount());
// For the second touchstart, only the state of the second touch point is
// StatePressed, the state of the first touch point is StateStationary.
ui::TouchEvent press1(
ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1));
view_->OnTouchEvent(&press1);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("TouchStart", GetMessageNames(events));
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
EXPECT_EQ(ui::MotionEvent::Action::POINTER_DOWN, pointer_state().GetAction());
EXPECT_EQ(1, pointer_state().GetActionIndex());
EXPECT_EQ(2U, pointer_state().GetPointerCount());
EXPECT_EQ(1U, view_->dispatcher_->GetAndResetProcessedTouchEventCount());
// For the touchmove of second point, the state of the second touch point is
// StateMoved, the state of the first touch point is StateStationary.
ui::TouchEvent move1(
ui::ET_TOUCH_MOVED, gfx::Point(30, 30), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1));
view_->OnTouchEvent(&move1);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("TouchMove", GetMessageNames(events));
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
EXPECT_EQ(ui::MotionEvent::Action::MOVE, pointer_state().GetAction());
EXPECT_EQ(2U, pointer_state().GetPointerCount());
EXPECT_EQ(1U, view_->dispatcher_->GetAndResetProcessedTouchEventCount());
// For the touchmove of first point, the state of the first touch point is
// StateMoved, the state of the second touch point is StateStationary.
ui::TouchEvent move2(
ui::ET_TOUCH_MOVED, gfx::Point(10, 10), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
view_->OnTouchEvent(&move2);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("TouchMove", GetMessageNames(events));
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
EXPECT_EQ(ui::MotionEvent::Action::MOVE, pointer_state().GetAction());
EXPECT_EQ(2U, pointer_state().GetPointerCount());
EXPECT_EQ(1U, view_->dispatcher_->GetAndResetProcessedTouchEventCount());
ui::TouchEvent cancel0(
ui::ET_TOUCH_CANCELLED, gfx::Point(10, 10), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
// For the touchcancel, only the state of the current touch point is
// StateCancelled, the state of the other touch point is StateStationary.
view_->OnTouchEvent(&cancel0);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("TouchCancel", GetMessageNames(events));
EXPECT_EQ(1U, pointer_state().GetPointerCount());
EXPECT_EQ(1U, view_->dispatcher_->GetAndResetProcessedTouchEventCount());
ui::TouchEvent cancel1(
ui::ET_TOUCH_CANCELLED, gfx::Point(30, 30), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1));
view_->OnTouchEvent(&cancel1);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ("TouchCancel", GetMessageNames(events));
EXPECT_EQ(1U, view_->dispatcher_->GetAndResetProcessedTouchEventCount());
EXPECT_EQ(0U, pointer_state().GetPointerCount());