| // 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/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/input_method.h" |
| #include "ui/base/ime/input_method_factory.h" |
| #include "ui/base/ime/input_method_keyboard_controller.h" |
| #include "ui/base/ime/mock_input_method.h" |
| #include "ui/base/ui_base_features.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, |
| false /* is_mus_browser_plugin_guest */), |
| 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_); |
| const bool is_mus_browser_plugin_guest = false; |
| parent_view_ = new RenderWidgetHostViewAura( |
| parent_host_, is_guest_view_hack_, is_mus_browser_plugin_guest); |
| 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::kWebGestureDeviceTouchscreen)); |
| } |
| |
| void SimulateGesturePinchUpdateEvent(float scale, |
| float anchorX, |
| float anchorY, |
| int modifiers) { |
| SimulateGestureEventCore(SyntheticWebGestureEventBuilder::BuildPinchUpdate( |
| scale, anchorX, anchorY, modifiers, |
| blink::kWebGestureDeviceTouchscreen)); |
| } |
| |
| // 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, ¶ms); |
| 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, ¶ms); |
| 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::kWebGestureDeviceTouchpad, 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::kWebGestureDeviceTouchscreen, 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(<
|