| // 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/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "cc/output/begin_frame_args.h" |
| #include "cc/output/compositor_frame.h" |
| #include "cc/output/compositor_frame_metadata.h" |
| #include "cc/output/copy_output_request.h" |
| #include "cc/surfaces/surface.h" |
| #include "cc/surfaces/surface_manager.h" |
| #include "cc/test/begin_frame_args_test.h" |
| #include "cc/test/fake_external_begin_frame_source.h" |
| #include "components/display_compositor/gl_helper.h" |
| #include "components/display_compositor/host_shared_bitmap_manager.h" |
| #include "content/browser/browser_thread_impl.h" |
| #include "content/browser/compositor/test/no_transport_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_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/render_widget_host_view_frame_subscriber.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/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/test_render_view_host.h" |
| #include "content/test/test_web_contents.h" |
| #include "ipc/ipc_message.h" |
| #include "ipc/ipc_test_sink.h" |
| #include "media/base/video_frame.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/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/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/events/blink/blink_event_util.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/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" |
| |
| 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; |
| |
| namespace content { |
| |
| void InstallDelegatedFrameHostClient( |
| RenderWidgetHostViewAura* render_widget_host_view, |
| std::unique_ptr<DelegatedFrameHostClient> delegated_frame_host_client); |
| |
| namespace { |
| |
| const cc::LocalSurfaceId kArbitraryLocalSurfaceId( |
| 1, |
| base::UnguessableToken::Deserialize(2, 3)); |
| |
| cc::LocalSurfaceId CreateLocalSurfaceId() { |
| return cc::LocalSurfaceId(1, base::UnguessableToken::Create()); |
| } |
| |
| class TestOverscrollDelegate : public OverscrollControllerDelegate { |
| public: |
| explicit TestOverscrollDelegate(RenderWidgetHostView* view) |
| : view_(view), |
| current_mode_(OVERSCROLL_NONE), |
| completed_mode_(OVERSCROLL_NONE), |
| delta_x_(0.f), |
| delta_y_(0.f) {} |
| |
| ~TestOverscrollDelegate() override {} |
| |
| OverscrollMode current_mode() const { return current_mode_; } |
| OverscrollMode completed_mode() const { return completed_mode_; } |
| float delta_x() const { return delta_x_; } |
| float delta_y() const { return delta_y_; } |
| |
| void Reset() { |
| current_mode_ = OVERSCROLL_NONE; |
| completed_mode_ = OVERSCROLL_NONE; |
| delta_x_ = delta_y_ = 0.f; |
| } |
| |
| private: |
| // Overridden from OverscrollControllerDelegate: |
| gfx::Rect GetVisibleBounds() const override { |
| return view_->IsShowing() ? view_->GetViewBounds() : gfx::Rect(); |
| } |
| |
| bool OnOverscrollUpdate(float delta_x, float delta_y) override { |
| delta_x_ = delta_x; |
| delta_y_ = delta_y; |
| return true; |
| } |
| |
| void OnOverscrollComplete(OverscrollMode overscroll_mode) override { |
| EXPECT_EQ(current_mode_, overscroll_mode); |
| completed_mode_ = overscroll_mode; |
| current_mode_ = OVERSCROLL_NONE; |
| } |
| |
| void OnOverscrollModeChange(OverscrollMode old_mode, |
| OverscrollMode new_mode, |
| OverscrollSource source) override { |
| EXPECT_EQ(current_mode_, old_mode); |
| current_mode_ = new_mode; |
| delta_x_ = delta_y_ = 0.f; |
| } |
| |
| RenderWidgetHostView* view_; |
| OverscrollMode current_mode_; |
| OverscrollMode completed_mode_; |
| float delta_x_; |
| float delta_y_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestOverscrollDelegate); |
| }; |
| |
| class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate { |
| public: |
| MockRenderWidgetHostDelegate() |
| : rwh_(nullptr), |
| is_fullscreen_(false), |
| focused_widget_(nullptr), |
| last_device_scale_factor_(0.0) {} |
| ~MockRenderWidgetHostDelegate() override {} |
| const NativeWebKeyboardEvent* last_event() const { return last_event_.get(); } |
| void set_widget_host(RenderWidgetHostImpl* rwh) { rwh_ = rwh; } |
| void set_is_fullscreen(bool is_fullscreen) { is_fullscreen_ = is_fullscreen; } |
| TextInputManager* GetTextInputManager() override { |
| return &text_input_manager_; |
| } |
| RenderWidgetHostImpl* GetFocusedRenderWidgetHost( |
| RenderWidgetHostImpl* widget_host) override { |
| return !!focused_widget_ ? focused_widget_ : widget_host; |
| } |
| void set_focused_widget(RenderWidgetHostImpl* focused_widget) { |
| focused_widget_ = focused_widget; |
| } |
| |
| double get_last_device_scale_factor() { return last_device_scale_factor_; } |
| void UpdateDeviceScaleFactor(double device_scale_factor) override { |
| last_device_scale_factor_ = device_scale_factor; |
| } |
| void set_pre_handle_keyboard_event_result( |
| KeyboardEventProcessingResult result) { |
| pre_handle_keyboard_event_result_ = result; |
| } |
| |
| protected: |
| // RenderWidgetHostDelegate: |
| KeyboardEventProcessingResult PreHandleKeyboardEvent( |
| const NativeWebKeyboardEvent& event) override { |
| last_event_.reset(new NativeWebKeyboardEvent(event)); |
| return pre_handle_keyboard_event_result_; |
| } |
| void Cut() override {} |
| void Copy() override {} |
| void Paste() override {} |
| void SelectAll() override {} |
| void SendScreenRects() override { |
| if (rwh_) |
| rwh_->SendScreenRects(); |
| } |
| bool IsFullscreenForCurrentTab() const override { return is_fullscreen_; } |
| |
| private: |
| std::unique_ptr<NativeWebKeyboardEvent> last_event_; |
| RenderWidgetHostImpl* rwh_; |
| bool is_fullscreen_; |
| TextInputManager text_input_manager_; |
| RenderWidgetHostImpl* focused_widget_; |
| double last_device_scale_factor_; |
| KeyboardEventProcessingResult pre_handle_keyboard_event_result_ = |
| KeyboardEventProcessingResult::HANDLED; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockRenderWidgetHostDelegate); |
| }; |
| |
| // 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 FakeSurfaceObserver : public cc::SurfaceObserver { |
| public: |
| void OnSurfaceCreated(const cc::SurfaceInfo& surface_info) override {} |
| |
| void OnSurfaceDamaged(const cc::SurfaceId& id, bool* changed) override { |
| *changed = true; |
| } |
| }; |
| |
| class FakeFrameSubscriber : public RenderWidgetHostViewFrameSubscriber { |
| public: |
| FakeFrameSubscriber(gfx::Size size, base::Callback<void(bool)> callback) |
| : size_(size), |
| callback_(callback), |
| should_capture_(true), |
| source_id_for_copy_request_(base::UnguessableToken::Create()) {} |
| |
| bool ShouldCaptureFrame(const gfx::Rect& damage_rect, |
| base::TimeTicks present_time, |
| scoped_refptr<media::VideoFrame>* storage, |
| DeliverFrameCallback* callback) override { |
| if (!should_capture_) |
| return false; |
| last_present_time_ = present_time; |
| *storage = media::VideoFrame::CreateFrame(media::PIXEL_FORMAT_YV12, size_, |
| gfx::Rect(size_), size_, |
| base::TimeDelta()); |
| *callback = base::Bind(&FakeFrameSubscriber::CallbackMethod, callback_); |
| return true; |
| } |
| |
| const base::UnguessableToken& GetSourceIdForCopyRequest() override { |
| return source_id_for_copy_request_; |
| } |
| |
| base::TimeTicks last_present_time() const { return last_present_time_; } |
| |
| void set_should_capture(bool should_capture) { |
| should_capture_ = should_capture; |
| } |
| |
| static void CallbackMethod(base::Callback<void(bool)> callback, |
| base::TimeTicks present_time, |
| const gfx::Rect& region_in_frame, |
| bool success) { |
| callback.Run(success); |
| } |
| |
| private: |
| gfx::Size size_; |
| base::Callback<void(bool)> callback_; |
| base::TimeTicks last_present_time_; |
| bool should_capture_; |
| base::UnguessableToken source_id_for_copy_request_; |
| }; |
| |
| class FakeWindowEventDispatcher : public aura::WindowEventDispatcher { |
| public: |
| FakeWindowEventDispatcher(aura::WindowTreeHost* host) |
| : WindowEventDispatcher(host), |
| processed_touch_event_count_(0) {} |
| |
| void ProcessedTouchEvent(uint32_t unique_event_id, |
| aura::Window* window, |
| ui::EventResult result) override { |
| WindowEventDispatcher::ProcessedTouchEvent(unique_event_id, window, result); |
| 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 ui::CompositorLockDelegate { |
| public: |
| explicit FakeDelegatedFrameHostClientAura( |
| RenderWidgetHostViewAura* render_widget_host_view) |
| : DelegatedFrameHostClientAura(render_widget_host_view), |
| weak_ptr_factory_(this) {} |
| ~FakeDelegatedFrameHostClientAura() override = default; |
| |
| void DisableResizeLock() { can_create_resize_lock_ = false; } |
| |
| bool resize_locked() const { return resize_locked_; } |
| bool compositor_locked() const { return compositor_locked_; } |
| |
| private: |
| // DelegatedFrameHostClientAura implementation. |
| bool DelegatedFrameCanCreateResizeLock() const override { |
| return can_create_resize_lock_; |
| } |
| |
| // CompositorResizeLockClient implemention. Overrides from |
| // DelegatedFrameHostClientAura, to prevent the lock from timing out. |
| std::unique_ptr<ui::CompositorLock> GetCompositorLock( |
| ui::CompositorLockClient* client) override { |
| resize_locked_ = compositor_locked_ = true; |
| return base::MakeUnique<ui::CompositorLock>(nullptr, |
| weak_ptr_factory_.GetWeakPtr()); |
| } |
| // CompositorResizeLockClient implemention. Overrides from |
| // // DelegatedFrameHostClientAura. |
| void CompositorResizeLockEnded() override { |
| resize_locked_ = false; |
| DelegatedFrameHostClientAura::CompositorResizeLockEnded(); |
| } |
| |
| // ui::CompositorLockDelegate implemention. |
| void RemoveCompositorLock(ui::CompositorLock*) override { |
| compositor_locked_ = false; |
| } |
| |
| bool can_create_resize_lock_ = true; |
| bool resize_locked_ = false; |
| bool compositor_locked_ = false; |
| base::WeakPtrFactory<FakeDelegatedFrameHostClientAura> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FakeDelegatedFrameHostClientAura); |
| }; |
| |
| class FakeRenderWidgetHostViewAura : public RenderWidgetHostViewAura { |
| public: |
| FakeRenderWidgetHostViewAura(RenderWidgetHost* widget, |
| bool is_guest_view_hack) |
| : RenderWidgetHostViewAura(widget, is_guest_view_hack), |
| delegated_frame_host_client_( |
| new FakeDelegatedFrameHostClientAura(this)) { |
| InstallDelegatedFrameHostClient( |
| this, base::WrapUnique(delegated_frame_host_client_)); |
| CreateNewRendererCompositorFrameSink(); |
| } |
| |
| ~FakeRenderWidgetHostViewAura() override {} |
| |
| void CreateNewRendererCompositorFrameSink() { |
| cc::mojom::MojoCompositorFrameSinkPtr sink; |
| cc::mojom::MojoCompositorFrameSinkRequest sink_request = |
| mojo::MakeRequest(&sink); |
| cc::mojom::MojoCompositorFrameSinkClientRequest client_request = |
| mojo::MakeRequest(&renderer_compositor_frame_sink_ptr_); |
| renderer_compositor_frame_sink_ = |
| base::MakeUnique<FakeRendererCompositorFrameSink>( |
| std::move(sink), std::move(client_request)); |
| DidCreateNewRendererCompositorFrameSink( |
| renderer_compositor_frame_sink_ptr_.get()); |
| } |
| |
| void DisableResizeLock() { |
| delegated_frame_host_client_->DisableResizeLock(); |
| } |
| |
| 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()); |
| } |
| |
| void InterceptCopyOfOutput(std::unique_ptr<cc::CopyOutputRequest> request) { |
| last_copy_request_ = std::move(request); |
| if (last_copy_request_->has_texture_mailbox()) { |
| // Give the resulting texture a size. |
| display_compositor::GLHelper* gl_helper = |
| ImageTransportFactory::GetInstance()->GetGLHelper(); |
| GLuint texture = gl_helper->ConsumeMailboxToTexture( |
| last_copy_request_->texture_mailbox().mailbox(), |
| last_copy_request_->texture_mailbox().sync_token()); |
| gl_helper->ResizeTexture(texture, window()->bounds().size()); |
| gl_helper->DeleteTexture(texture); |
| } |
| } |
| |
| cc::SurfaceId surface_id() const { |
| return GetDelegatedFrameHost()->SurfaceIdForTesting(); |
| } |
| |
| bool HasFrameData() const { |
| return GetDelegatedFrameHost()->HasFrameForTesting(); |
| } |
| |
| bool released_front_lock_active() const { |
| return GetDelegatedFrameHost()->ReleasedFrontLockActiveForTesting(); |
| } |
| |
| void ReclaimResources(const cc::ReturnedResourceArray& resources) { |
| GetDelegatedFrameHost()->ReclaimResources(resources); |
| } |
| |
| void ResetCompositor() { GetDelegatedFrameHost()->ResetCompositor(); } |
| |
| const ui::MotionEventAura& pointer_state() { |
| return event_handler()->pointer_state(); |
| } |
| |
| bool resize_locked() const { |
| return delegated_frame_host_client_->resize_locked(); |
| } |
| bool compositor_locked() const { |
| return delegated_frame_host_client_->compositor_locked(); |
| } |
| |
| gfx::Size last_frame_size_; |
| std::unique_ptr<cc::CopyOutputRequest> last_copy_request_; |
| FakeWindowEventDispatcher* dispatcher_; |
| std::unique_ptr<FakeRendererCompositorFrameSink> |
| renderer_compositor_frame_sink_; |
| |
| private: |
| FakeDelegatedFrameHostClientAura* delegated_frame_host_client_; |
| cc::mojom::MojoCompositorFrameSinkClientPtr |
| 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 MockWindowObserver : public aura::WindowObserver { |
| public: |
| MOCK_METHOD2(OnDelegatedFrameDamage, void(aura::Window*, const gfx::Rect&)); |
| }; |
| |
| class MockRenderWidgetHostImpl : public RenderWidgetHostImpl { |
| public: |
| MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate, |
| RenderProcessHost* process, |
| int32_t routing_id) |
| : RenderWidgetHostImpl(delegate, process, routing_id, false) { |
| set_renderer_initialized(true); |
| lastWheelOrTouchEventLatencyInfo = ui::LatencyInfo(); |
| } |
| |
| // 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); |
| } |
| |
| ui::LatencyInfo lastWheelOrTouchEventLatencyInfo; |
| }; |
| |
| const WebInputEvent* GetInputEventFromMessage(const IPC::Message& message) { |
| base::PickleIterator iter(message); |
| const char* data; |
| int data_length; |
| if (!iter.ReadData(&data, &data_length)) |
| return nullptr; |
| return reinterpret_cast<const WebInputEvent*>(data); |
| } |
| |
| } // namespace |
| |
| class RenderWidgetHostViewAuraTest : public testing::Test { |
| public: |
| RenderWidgetHostViewAuraTest() |
| : widget_host_uses_shutdown_to_destroy_(false), |
| is_guest_view_hack_(false) {} |
| |
| static void InstallDelegatedFrameHostClient( |
| RenderWidgetHostViewAura* render_widget_host_view, |
| std::unique_ptr<DelegatedFrameHostClient> delegated_frame_host_client) { |
| render_widget_host_view->delegated_frame_host_client_ = |
| std::move(delegated_frame_host_client); |
| } |
| |
| void SetUpEnvironment() { |
| ImageTransportFactory::InitializeForUnitTests( |
| std::unique_ptr<ImageTransportFactory>( |
| new NoTransportImageTransportFactory)); |
| 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_ = new RenderWidgetHostImpl(delegates_.back().get(), |
| process_host_, routing_id, false); |
| delegates_.back()->set_widget_host(parent_host_); |
| parent_view_ = |
| new RenderWidgetHostViewAura(parent_host_, is_guest_view_hack_); |
| parent_view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext(parent_view_->GetNativeView(), |
| aura_test_helper_->root_window(), |
| gfx::Rect()); |
| |
| routing_id = process_host_->GetNextRoutingID(); |
| delegates_.push_back(base::WrapUnique(new MockRenderWidgetHostDelegate)); |
| widget_host_ = new MockRenderWidgetHostImpl(delegates_.back().get(), |
| process_host_, routing_id); |
| delegates_.back()->set_widget_host(widget_host_); |
| widget_host_->Init(); |
| view_ = new FakeRenderWidgetHostViewAura(widget_host_, is_guest_view_hack_); |
| } |
| |
| void TearDownEnvironment() { |
| sink_ = nullptr; |
| process_host_ = nullptr; |
| if (view_) { |
| // For guest-views, |view_| is not the view used by |widget_host_|. |
| if (!is_guest_view_hack_) { |
| EXPECT_EQ(view_, widget_host_->GetView()); |
| } |
| view_->Destroy(); |
| if (!is_guest_view_hack_) { |
| EXPECT_EQ(nullptr, widget_host_->GetView()); |
| } |
| } |
| |
| if (widget_host_uses_shutdown_to_destroy_) |
| widget_host_->ShutdownAndDestroyWidget(true); |
| else |
| delete widget_host_; |
| |
| 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); |
| } |
| |
| void SendInputEventACK(WebInputEvent::Type type, |
| InputEventAckState ack_result) { |
| DCHECK(!WebInputEvent::IsTouchEventType(type)); |
| InputEventAck ack(InputEventAckSource::COMPOSITOR_THREAD, type, ack_result); |
| InputHostMsg_HandleInputEvent_ACK response(0, ack); |
| widget_host_->OnMessageReceived(response); |
| } |
| |
| void SendTouchEventACK(WebInputEvent::Type type, |
| InputEventAckState ack_result, |
| uint32_t event_id) { |
| DCHECK(WebInputEvent::IsTouchEventType(type)); |
| InputEventAck ack(InputEventAckSource::COMPOSITOR_THREAD, type, ack_result, |
| event_id); |
| InputHostMsg_HandleInputEvent_ACK response(0, ack); |
| widget_host_->OnMessageReceived(response); |
| } |
| |
| size_t GetSentMessageCountAndResetSink() { |
| size_t count = sink_->message_count(); |
| sink_->ClearMessages(); |
| return count; |
| } |
| |
| void AckLastSentInputEventIfNecessary(InputEventAckState ack_result) { |
| if (!sink_->message_count()) |
| return; |
| |
| InputMsg_HandleInputEvent::Param params; |
| if (!InputMsg_HandleInputEvent::Read( |
| sink_->GetMessageAt(sink_->message_count() - 1), ¶ms)) { |
| return; |
| } |
| |
| InputEventDispatchType dispatch_type = std::get<3>(params); |
| if (dispatch_type == InputEventDispatchType::DISPATCH_TYPE_NON_BLOCKING) |
| return; |
| |
| const blink::WebInputEvent* event = std::get<0>(params); |
| SendTouchEventACK(event->GetType(), ack_result, |
| WebInputEventTraits::GetUniqueTouchEventId(*event)); |
| } |
| |
| const ui::MotionEventAura& pointer_state() { return view_->pointer_state(); } |
| |
| void EnableRafAlignedTouchInput() { |
| feature_list_.InitFromCommandLine( |
| features::kRafAlignedTouchInputEvents.name, ""); |
| } |
| |
| void DisableRafAlignedTouchInput() { |
| feature_list_.InitFromCommandLine( |
| "", features::kRafAlignedTouchInputEvents.name); |
| } |
| |
| void SetFeatureList(bool raf_aligned_touch, bool wheel_scroll_latching) { |
| if (raf_aligned_touch && wheel_scroll_latching) { |
| feature_list_.InitWithFeatures( |
| {features::kRafAlignedTouchInputEvents, |
| features::kTouchpadAndWheelScrollLatching}, |
| {}); |
| } else if (raf_aligned_touch && !wheel_scroll_latching) { |
| feature_list_.InitWithFeatures( |
| {features::kRafAlignedTouchInputEvents}, |
| {features::kTouchpadAndWheelScrollLatching}); |
| } else if (!raf_aligned_touch && wheel_scroll_latching) { |
| feature_list_.InitWithFeatures( |
| {features::kTouchpadAndWheelScrollLatching}, |
| {features::kRafAlignedTouchInputEvents}); |
| } else { // !raf_aligned_touch && !wheel_scroll_latching |
| feature_list_.InitWithFeatures( |
| {}, {features::kRafAlignedTouchInputEvents, |
| features::kTouchpadAndWheelScrollLatching}); |
| } |
| } |
| |
| protected: |
| BrowserContext* browser_context() { return browser_context_.get(); } |
| |
| MockRenderWidgetHostDelegate* render_widget_host_delegate() const { |
| return delegates_.back().get(); |
| } |
| |
| // 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( |
| RenderWidgetHostImpl::From(view->GetRenderWidgetHost())); |
| } |
| |
| 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 feature_list_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraTest); |
| }; |
| |
| class RenderWidgetHostViewAuraRafAlignedTouchEnabledTest |
| : public RenderWidgetHostViewAuraTest { |
| public: |
| void SetUp() override { |
| EnableRafAlignedTouchInput(); |
| RenderWidgetHostViewAuraTest::SetUp(); |
| } |
| }; |
| |
| class RenderWidgetHostViewAuraRafAlignedTouchDisabledTest |
| : public RenderWidgetHostViewAuraTest { |
| public: |
| void SetUp() override { |
| DisableRafAlignedTouchInput(); |
| RenderWidgetHostViewAuraTest::SetUp(); |
| } |
| }; |
| |
| 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); |
| }; |
| |
| class RenderWidgetHostViewAuraOverscrollTest |
| : public RenderWidgetHostViewAuraTest { |
| public: |
| RenderWidgetHostViewAuraOverscrollTest( |
| bool wheel_scroll_latching_enabled = true) |
| : wheel_scroll_latching_enabled_(wheel_scroll_latching_enabled) {} |
| |
| // We explicitly invoke SetUp to allow gesture debounce customization. |
| void SetUp() override {} |
| |
| protected: |
| void SetUpOverscrollEnvironmentWithDebounce(int debounce_interval_in_ms) { |
| SetUpOverscrollEnvironmentImpl(debounce_interval_in_ms); |
| } |
| |
| void SetUpOverscrollEnvironment() { SetUpOverscrollEnvironmentImpl(0); } |
| |
| void SetUpOverscrollEnvironmentImpl(int debounce_interval_in_ms) { |
| SetFeatureList(true, wheel_scroll_latching_enabled_); |
| ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms( |
| debounce_interval_in_ms); |
| |
| RenderWidgetHostViewAuraTest::SetUp(); |
| |
| view_->SetOverscrollControllerEnabled(true); |
| overscroll_delegate_.reset(new TestOverscrollDelegate(view_)); |
| 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)); |
| } |
| |
| void SimulateMouseEventWithLatencyInfo(WebInputEvent::Type type, |
| const ui::LatencyInfo& ui_latency) { |
| widget_host_->ForwardMouseEventWithLatencyInfo( |
| SyntheticWebMouseEventBuilder::Build(type), ui_latency); |
| } |
| |
| void SimulateWheelEvent(float dX, float dY, int modifiers, bool precise) { |
| widget_host_->ForwardWheelEvent(SyntheticWebMouseWheelEventBuilder::Build( |
| 0, 0, dX, dY, modifiers, precise)); |
| } |
| |
| void SimulateWheelEventWithLatencyInfo(float dX, |
| float dY, |
| int modifiers, |
| bool precise, |
| const ui::LatencyInfo& ui_latency) { |
| widget_host_->ForwardWheelEventWithLatencyInfo( |
| SyntheticWebMouseWheelEventBuilder::Build(0, 0, dX, dY, modifiers, |
| precise), |
| ui_latency); |
| } |
| |
| 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); |
| } |
| |
| void SimulateWheelEventWithPhase(WebMouseWheelEvent::Phase phase) { |
| widget_host_->ForwardWheelEvent( |
| SyntheticWebMouseWheelEventBuilder::Build(phase)); |
| } |
| |
| // Inject provided synthetic WebGestureEvent instance. |
| void SimulateGestureEventCore(const WebGestureEvent& gesture_event) { |
| widget_host_->ForwardGestureEvent(gesture_event); |
| } |
| |
| void SimulateGestureEventCoreWithLatencyInfo( |
| const WebGestureEvent& gesture_event, |
| const ui::LatencyInfo& ui_latency) { |
| widget_host_->ForwardGestureEventWithLatencyInfo(gesture_event, ui_latency); |
| } |
| |
| // 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 ScrollStateIsContentScrolling() const { |
| return scroll_state() == OverscrollController::STATE_CONTENT_SCROLLING; |
| } |
| |
| bool ScrollStateIsOverscrolling() const { |
| return scroll_state() == OverscrollController::STATE_OVERSCROLLING; |
| } |
| |
| bool ScrollStateIsUnknown() const { |
| return scroll_state() == OverscrollController::STATE_UNKNOWN; |
| } |
| |
| OverscrollController::ScrollState scroll_state() const { |
| return view_->overscroll_controller()->scroll_state_; |
| } |
| |
| OverscrollMode overscroll_mode() const { |
| return view_->overscroll_controller()->overscroll_mode_; |
| } |
| |
| 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(); |
| 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 ExpectGestureScrollEndForWheelScrolling(bool is_last) { |
| if (wheel_scroll_latching_enabled_) { |
| if (!is_last) { |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| return; |
| } |
| // Let the ScrollEnd timer in mouseWheelEventQueue fire. This will cause |
| // the mouseWheelEventQueue to send a GestureScrollEnd event. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), |
| base::TimeDelta::FromMilliseconds( |
| kDefaultWheelScrollLatchingTransactionMs)); |
| base::RunLoop().Run(); |
| } |
| |
| EXPECT_EQ(1U, sink_->message_count()); |
| InputMsg_HandleInputEvent::Param params; |
| if (InputMsg_HandleInputEvent::Read(sink_->GetMessageAt(0), ¶ms)) { |
| const blink::WebInputEvent* event = std::get<0>(params); |
| EXPECT_EQ(WebInputEvent::kGestureScrollEnd, event->GetType()); |
| } |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| } |
| |
| void ExpectGestureScrollEventsAfterMouseWheelACK(bool is_first_ack, |
| bool enqueued_mouse_wheel) { |
| InputMsg_HandleInputEvent::Param params; |
| const blink::WebInputEvent* event; |
| size_t expected_message_count; |
| if (is_first_ack) { |
| expected_message_count = enqueued_mouse_wheel ? 3 : 2; |
| EXPECT_EQ(expected_message_count, sink_->message_count()); |
| |
| // The first message is GestureScrollBegin. |
| if (InputMsg_HandleInputEvent::Read(sink_->GetMessageAt(0), ¶ms)) { |
| event = std::get<0>(params); |
| EXPECT_EQ(WebInputEvent::kGestureScrollBegin, event->GetType()); |
| } |
| |
| // The second message is GestureScrollUpdate. |
| if (InputMsg_HandleInputEvent::Read(sink_->GetMessageAt(1), ¶ms)) { |
| event = std::get<0>(params); |
| EXPECT_EQ(WebInputEvent::kGestureScrollUpdate, event->GetType()); |
| } |
| |
| if (enqueued_mouse_wheel) { |
| // The last message is the queued MouseWheel. |
| if (InputMsg_HandleInputEvent::Read(sink_->GetMessageAt(2), ¶ms)) { |
| event = std::get<0>(params); |
| EXPECT_EQ(WebInputEvent::kMouseWheel, event->GetType()); |
| } |
| } |
| } else { // !is_first_ack |
| expected_message_count = enqueued_mouse_wheel ? 2 : 1; |
| size_t scroll_update_index = 0; |
| |
| if (!wheel_scroll_latching_enabled_) { |
| // The first message is GestureScrollBegin even in the middle of |
| // scrolling. |
| if (InputMsg_HandleInputEvent::Read(sink_->GetMessageAt(0), ¶ms)) { |
| event = std::get<0>(params); |
| EXPECT_EQ(WebInputEvent::kGestureScrollBegin, event->GetType()); |
| } |
| expected_message_count++; |
| scroll_update_index++; |
| } |
| |
| // Check the GestureScrollUpdate message. |
| if (InputMsg_HandleInputEvent::Read( |
| sink_->GetMessageAt(scroll_update_index), ¶ms)) { |
| event = std::get<0>(params); |
| EXPECT_EQ(WebInputEvent::kGestureScrollUpdate, event->GetType()); |
| } |
| |
| if (enqueued_mouse_wheel) { |
| // The last message is the queued MouseWheel. |
| if (InputMsg_HandleInputEvent::Read( |
| sink_->GetMessageAt(expected_message_count - 1), ¶ms)) { |
| event = std::get<0>(params); |
| EXPECT_EQ(WebInputEvent::kMouseWheel, event->GetType()); |
| } |
| } |
| } |
| EXPECT_EQ(expected_message_count, GetSentMessageCountAndResetSink()); |
| } |
| |
| void WheelNotPreciseScrollEvent(); |
| void WheelScrollOverscrollToggle(); |
| void OverscrollMouseMoveCompletion(); |
| void WheelScrollEventOverscrolls(); |
| void WheelScrollConsumedDoNotHorizOverscroll(); |
| void ScrollEventsOverscrollWithFling(); |
| void OverscrollDirectionChangeMouseWheel(); |
| void OverscrollStateResetsAfterScroll(); |
| void ScrollEventsOverscrollWithZeroFling(); |
| |
| SyntheticWebTouchEvent touch_event_; |
| |
| std::unique_ptr<TestOverscrollDelegate> overscroll_delegate_; |
| |
| bool wheel_scroll_latching_enabled_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraOverscrollTest); |
| }; |
| |
| class RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest |
| : public RenderWidgetHostViewAuraOverscrollTest { |
| public: |
| RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest() |
| : RenderWidgetHostViewAuraOverscrollTest(false) {} |
| }; |
| |
| 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_->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( |
| ViewHostMsg_UpdateScreenRects_ACK(widget_host_->GetRoutingID())); |
| widget_host_->OnMessageReceived( |
| ViewHostMsg_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(ViewMsg_UpdateScreenRects::ID, msg->type()); |
| ViewMsg_UpdateScreenRects::Param params; |
| ViewMsg_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( |
| ViewHostMsg_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(ViewMsg_UpdateScreenRects::ID, msg->type()); |
| ViewMsg_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( |
| ViewHostMsg_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; |
| } |
| |
| // 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_->InitAsPopup(parent_view_, gfx::Rect(10, 10, 100, 100)); |
| aura::Window* window = view_->GetNativeView(); |
| ASSERT_TRUE(window != nullptr); |
| |
| gfx::Point click_point; |
| 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_->InitAsPopup(parent_view_, gfx::Rect(10, 10, 100, 100)); |
| aura::Window* window = view_->GetNativeView(); |
| ASSERT_TRUE(window != nullptr); |
| |
| gfx::Point tap_point; |
| 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; |
| } |
| |
| #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_->SetPopupType(blink::kWebPopupTypePage); |
| 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_->SetPopupType(blink::kWebPopupTypePage); |
| 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.underlines.push_back( |
| ui::CompositionUnderline(0, 3, 0xff000000, true, 0x78563412)); |
| |
| // Non-focused segment, with different background color. |
| composition_text.underlines.push_back( |
| ui::CompositionUnderline(3, 4, 0xff000000, false, 0xefcdab90)); |
| |
| const ui::CompositionUnderlines& underlines = composition_text.underlines; |
| |
| // Caret is at the end. (This emulates Japanese MSIME 2007 and later) |
| composition_text.selection = gfx::Range(4); |
| |
| sink_->ClearMessages(); |
| view_->SetCompositionText(composition_text); |
| EXPECT_TRUE(view_->has_composition_text_); |
| { |
| const IPC::Message* msg = |
| sink_->GetFirstMessageMatching(InputMsg_ImeSetComposition::ID); |
| ASSERT_TRUE(msg != nullptr); |
| |
| InputMsg_ImeSetComposition::Param params; |
| InputMsg_ImeSetComposition::Read(msg, ¶ms); |
| // composition text |
| EXPECT_EQ(composition_text.text, std::get<0>(params)); |
| // underlines |
| ASSERT_EQ(underlines.size(), std::get<1>(params).size()); |
| for (size_t i = 0; i < underlines.size(); ++i) { |
| EXPECT_EQ(underlines[i].start_offset, |
| std::get<1>(params)[i].start_offset); |
| EXPECT_EQ(underlines[i].end_offset, std::get<1>(params)[i].end_offset); |
| EXPECT_EQ(underlines[i].color, std::get<1>(params)[i].color); |
| EXPECT_EQ(underlines[i].thick, std::get<1>(params)[i].thick); |
| EXPECT_EQ(underlines[i].background_color, |
| std::get<1>(params)[i].background_color); |
| } |
| EXPECT_EQ(gfx::Range::InvalidRange(), std::get<2>(params)); |
| // highlighted range |
| EXPECT_EQ(4, std::get<3>(params)) << "Should be the same to the caret pos"; |
| EXPECT_EQ(4, std::get<4>(params)) << "Should be the same to the caret pos"; |
| } |
| |
| view_->ImeCancelComposition(); |
| 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.underlines.push_back( |
| ui::CompositionUnderline(0, 3, 0xff000000, true, 0x78563412)); |
| |
| // Non-focused segment, with different background color. |
| composition_text.underlines.push_back( |
| ui::CompositionUnderline(3, 4, 0xff000000, false, 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_); |
| sink_->ClearMessages(); |
| |
| // 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); |
| |
| EXPECT_FALSE(view_->has_composition_text_); |
| |
| EXPECT_EQ(2U, sink_->message_count()); |
| |
| if (sink_->message_count() == 2) { |
| // Verify mouse event happens after the finish composing text event. |
| EXPECT_EQ(InputMsg_ImeFinishComposingText::ID, |
| sink_->GetMessageAt(0)->type()); |
| EXPECT_EQ(InputMsg_HandleInputEvent::ID, |
| sink_->GetMessageAt(1)->type()); |
| } |
| } |
| |
| // Checks that touch-event state is maintained correctly. |
| TEST_F(RenderWidgetHostViewAuraRafAlignedTouchDisabledTest, TouchEventState) { |
| view_->InitAsChild(nullptr); |
| view_->Show(); |
| GetSentMessageCountAndResetSink(); |
| |
| // Start with no touch-event handler in the renderer. |
| widget_host_->OnMessageReceived(ViewHostMsg_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); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(ui::MotionEvent::ACTION_DOWN, pointer_state().GetAction()); |
| |
| view_->OnTouchEvent(&move); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(ui::MotionEvent::ACTION_MOVE, pointer_state().GetAction()); |
| EXPECT_EQ(1U, pointer_state().GetPointerCount()); |
| |
| view_->OnTouchEvent(&release); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| 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(ViewHostMsg_HasTouchEventHandlers(0, true)); |
| |
| view_->OnTouchEvent(&press); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(ui::MotionEvent::ACTION_DOWN, pointer_state().GetAction()); |
| EXPECT_EQ(1U, pointer_state().GetPointerCount()); |
| |
| view_->OnTouchEvent(&move); |
| 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); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(ui::MotionEvent::ACTION_DOWN, pointer_state().GetAction()); |
| EXPECT_EQ(1U, pointer_state().GetPointerCount()); |
| |
| widget_host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, false)); |
| |
| // Ack'ing the outstanding event should flush the pending touch queue. |
| InputEventAck ack( |
| InputEventAckSource::COMPOSITOR_THREAD, blink::WebInputEvent::kTouchStart, |
| INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, press.unique_event_id()); |
| widget_host_->OnMessageReceived(InputHostMsg_HandleInputEvent_ACK(0, ack)); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| |
| 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); |
| 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); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(0U, pointer_state().GetPointerCount()); |
| } |
| |
| // Checks that touch-event state is maintained correctly. |
| TEST_F(RenderWidgetHostViewAuraRafAlignedTouchEnabledTest, TouchEventState) { |
| view_->InitAsChild(nullptr); |
| view_->Show(); |
| GetSentMessageCountAndResetSink(); |
| |
| // Start with no touch-event handler in the renderer. |
| widget_host_->OnMessageReceived(ViewHostMsg_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); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(ui::MotionEvent::ACTION_DOWN, pointer_state().GetAction()); |
| |
| view_->OnTouchEvent(&move); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(ui::MotionEvent::ACTION_MOVE, pointer_state().GetAction()); |
| EXPECT_EQ(1U, pointer_state().GetPointerCount()); |
| |
| view_->OnTouchEvent(&release); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| 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(ViewHostMsg_HasTouchEventHandlers(0, true)); |
| |
| view_->OnTouchEvent(&press); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(ui::MotionEvent::ACTION_DOWN, pointer_state().GetAction()); |
| EXPECT_EQ(1U, pointer_state().GetPointerCount()); |
| |
| view_->OnTouchEvent(&move); |
| 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); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(ui::MotionEvent::ACTION_DOWN, pointer_state().GetAction()); |
| EXPECT_EQ(1U, pointer_state().GetPointerCount()); |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| |
| widget_host_->OnMessageReceived(ViewHostMsg_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()); |
| widget_host_->OnMessageReceived(InputHostMsg_HandleInputEvent_ACK(0, ack)); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| |
| 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); |
| 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); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(0U, pointer_state().GetPointerCount()); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| } |
| |
| // Checks that touch-event state is maintained correctly for multiple touch |
| // points. |
| TEST_F(RenderWidgetHostViewAuraTest, MultiTouchPointsStates) { |
| view_->InitAsFullscreen(parent_view_); |
| view_->Show(); |
| view_->UseFakeDispatcher(); |
| GetSentMessageCountAndResetSink(); |
| |
| ui::TouchEvent press0( |
| ui::ET_TOUCH_PRESSED, gfx::Point(30, 30), ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0)); |
| |
| view_->OnTouchEvent(&press0); |
| SendTouchEventACK(blink::WebInputEvent::kTouchStart, |
| INPUT_EVENT_ACK_STATE_CONSUMED, press0.unique_event_id()); |
| 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); |
| SendTouchEventACK(blink::WebInputEvent::kTouchMove, |
| INPUT_EVENT_ACK_STATE_CONSUMED, move0.unique_event_id()); |
| 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); |
| SendTouchEventACK(blink::WebInputEvent::kTouchStart, |
| INPUT_EVENT_ACK_STATE_CONSUMED, press1.unique_event_id()); |
| 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); |
| SendTouchEventACK(blink::WebInputEvent::kTouchMove, |
| INPUT_EVENT_ACK_STATE_CONSUMED, move1.unique_event_id()); |
| 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); |
| SendTouchEventACK(blink::WebInputEvent::kTouchMove, |
| INPUT_EVENT_ACK_STATE_CONSUMED, move2.unique_event_id()); |
| 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); |
| 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); |
| EXPECT_EQ(1U, view_->dispatcher_->GetAndResetProcessedTouchEventCount()); |
| EXPECT_EQ(0U, pointer_state().GetPointerCount()); |
| } |
| |
| // Checks that touch-events are queued properly when there is a touch-event |
| // handler on the page. |
| TEST_F(RenderWidgetHostViewAuraTest, TouchEventSyncAsync) { |
| view_->InitAsChild(nullptr); |
| view_->Show(); |
| |
| widget_host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true)); |
| |
| 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)); |
| |
| view_->OnTouchEvent(&press); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(ui::MotionEvent::ACTION_DOWN, pointer_state().GetAction()); |
| EXPECT_EQ(1U, pointer_state().GetPointerCount()); |
| |
| view_->OnTouchEvent(&move); |
| EXPECT_TRUE(move.synchronous_handling_disabled()); |
| EXPECT_EQ(ui::MotionEvent::ACTION_MOVE, pointer_state().GetAction()); |
| EXPECT_EQ(1U, pointer_state().GetPointerCount()); |
| |
| // Send the same move event. Since the point hasn't moved, it won't affect the |
| // queue. However, the view should consume the event. |
| view_->OnTouchEvent(&move); |
| 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()); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraTest, PhysicalBackingSizeWithScale) { |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), |
| parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| sink_->ClearMessages(); |
| view_->SetSize(gfx::Size(100, 100)); |
| EXPECT_EQ("100x100", view_->GetPhysicalBackingSize().ToString()); |
| EXPECT_EQ(1u, sink_->message_count()); |
| EXPECT_EQ(ViewMsg_Resize::ID, sink_->GetMessageAt(0)->type()); |
| { |
| const IPC::Message* msg = sink_->GetMessageAt(0); |
| EXPECT_EQ(ViewMsg_Resize::ID, msg->type()); |
| ViewMsg_Resize::Param params; |
| ViewMsg_Resize::Read(msg, ¶ms); |
| EXPECT_EQ("100x100", std::get<0>(params).new_size.ToString()); // dip size |
| EXPECT_EQ( |
| "100x100", |
| std::get<0>(params).physical_backing_size.ToString()); // backing size |
| } |
| |
| widget_host_->ResetSizeAndRepaintPendingFlags(); |
| sink_->ClearMessages(); |
| |
| aura_test_helper_->test_screen()->SetDeviceScaleFactor(2.0f); |
| EXPECT_EQ("200x200", view_->GetPhysicalBackingSize().ToString()); |
| // Extra ScreenInfoChanged message for |parent_view_|. |
| // Changing the device scale factor triggers the |
| // RenderWidgetHostViewAura::OnDisplayMetricsChanged() observer callback, |
| // which sends a ViewMsg_Resize::ID message to the renderer. |
| EXPECT_EQ(1u, sink_->message_count()); |
| EXPECT_EQ(ViewMsg_Resize::ID, sink_->GetMessageAt(0)->type()); |
| auto* view_delegate = static_cast<MockRenderWidgetHostDelegate*>( |
| static_cast<RenderWidgetHostImpl*>(view_->GetRenderWidgetHost()) |
| ->delegate()); |
| EXPECT_EQ(2.0f, view_delegate->get_last_device_scale_factor()); |
| |
| widget_host_->ResetSizeAndRepaintPendingFlags(); |
| sink_->ClearMessages(); |
| |
| aura_test_helper_->test_screen()->SetDeviceScaleFactor(1.0f); |
| // Extra ScreenInfoChanged message for |parent_view_|. |
| EXPECT_EQ(1u, sink_->message_count()); |
| EXPECT_EQ(ViewMsg_Resize::ID, sink_->GetMessageAt(0)->type()); |
| EXPECT_EQ(1.0f, view_delegate->get_last_device_scale_factor()); |
| EXPECT_EQ("100x100", view_->GetPhysicalBackingSize().ToString()); |
| } |
| |
| // Checks that InputMsg_CursorVisibilityChange IPC messages are dispatched |
| // to the renderer at the correct times. |
| TEST_F(RenderWidgetHostViewAuraTest, CursorVisibilityChange) { |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), |
| parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| view_->SetSize(gfx::Size(100, 100)); |
| |
| aura::test::TestCursorClient cursor_client( |
| parent_view_->GetNativeView()->GetRootWindow()); |
| |
| cursor_client.AddObserver(view_); |
| |
| // Expect a message the first time the cursor is shown. |
| view_->Show(); |
| sink_->ClearMessages(); |
| cursor_client.ShowCursor(); |
| EXPECT_EQ(1u, sink_->message_count()); |
| EXPECT_TRUE(sink_->GetUniqueMessageMatching( |
| InputMsg_CursorVisibilityChange::ID)); |
| |
| // No message expected if the renderer already knows the cursor is visible. |
| sink_->ClearMessages(); |
| cursor_client.ShowCursor(); |
| EXPECT_EQ(0u, sink_->message_count()); |
| |
| // Hiding the cursor should send a message. |
| sink_->ClearMessages(); |
| cursor_client.HideCursor(); |
| EXPECT_EQ(1u, sink_->message_count()); |
| EXPECT_TRUE(sink_->GetUniqueMessageMatching( |
| InputMsg_CursorVisibilityChange::ID)); |
| |
| // No message expected if the renderer already knows the cursor is invisible. |
| sink_->ClearMessages(); |
| cursor_client.HideCursor(); |
| EXPECT_EQ(0u, sink_->message_count()); |
| |
| // No messages should be sent while the view is invisible. |
| view_->Hide(); |
| sink_->ClearMessages(); |
| cursor_client.ShowCursor(); |
| EXPECT_EQ(0u, sink_->message_count()); |
| cursor_client.HideCursor(); |
| EXPECT_EQ(0u, sink_->message_count()); |
| |
| // Show the view. Since the cursor was invisible when the view was hidden, |
| // no message should be sent. |
| sink_->ClearMessages(); |
| view_->Show(); |
| EXPECT_FALSE(sink_->GetUniqueMessageMatching( |
| InputMsg_CursorVisibilityChange::ID)); |
| |
| // No message expected if the renderer already knows the cursor is invisible. |
| sink_->ClearMessages(); |
| cursor_client.HideCursor(); |
| EXPECT_EQ(0u, sink_->message_count()); |
| |
| // Showing the cursor should send a message. |
| sink_->ClearMessages(); |
| cursor_client.ShowCursor(); |
| EXPECT_EQ(1u, sink_->message_count()); |
| EXPECT_TRUE(sink_->GetUniqueMessageMatching( |
| InputMsg_CursorVisibilityChange::ID)); |
| |
| // No messages should be sent while the view is invisible. |
| view_->Hide(); |
| sink_->ClearMessages(); |
| cursor_client.HideCursor(); |
| EXPECT_EQ(0u, sink_->message_count()); |
| |
| // Show the view. Since the cursor was visible when the view was hidden, |
| // a message is expected to be sent. |
| sink_->ClearMessages(); |
| view_->Show(); |
| EXPECT_TRUE(sink_->GetUniqueMessageMatching( |
| InputMsg_CursorVisibilityChange::ID)); |
| |
| cursor_client.RemoveObserver(view_); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraTest, UpdateCursorIfOverSelf) { |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), |
| parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| |
| // Note that all coordinates in this test are screen coordinates. |
| view_->SetBounds(gfx::Rect(60, 60, 100, 100)); |
| view_->Show(); |
| |
| aura::test::TestCursorClient cursor_client( |
| parent_view_->GetNativeView()->GetRootWindow()); |
| |
| // Cursor is in the middle of the window. |
| cursor_client.reset_calls_to_set_cursor(); |
| aura::Env::GetInstance()->set_last_mouse_location(gfx::Point(110, 110)); |
| view_->UpdateCursorIfOverSelf(); |
| EXPECT_EQ(1, cursor_client.calls_to_set_cursor()); |
| |
| // Cursor is near the top of the window. |
| cursor_client.reset_calls_to_set_cursor(); |
| aura::Env::GetInstance()->set_last_mouse_location(gfx::Point(80, 65)); |
| view_->UpdateCursorIfOverSelf(); |
| EXPECT_EQ(1, cursor_client.calls_to_set_cursor()); |
| |
| // Cursor is near the bottom of the window. |
| cursor_client.reset_calls_to_set_cursor(); |
| aura::Env::GetInstance()->set_last_mouse_location(gfx::Point(159, 159)); |
| view_->UpdateCursorIfOverSelf(); |
| EXPECT_EQ(1, cursor_client.calls_to_set_cursor()); |
| |
| // Cursor is above the window. |
| cursor_client.reset_calls_to_set_cursor(); |
| aura::Env::GetInstance()->set_last_mouse_location(gfx::Point(67, 59)); |
| view_->UpdateCursorIfOverSelf(); |
| EXPECT_EQ(0, cursor_client.calls_to_set_cursor()); |
| |
| // Cursor is below the window. |
| cursor_client.reset_calls_to_set_cursor(); |
| aura::Env::GetInstance()->set_last_mouse_location(gfx::Point(161, 161)); |
| view_->UpdateCursorIfOverSelf(); |
| EXPECT_EQ(0, cursor_client.calls_to_set_cursor()); |
| } |
| |
| cc::CompositorFrame MakeDelegatedFrame(float scale_factor, |
| gfx::Size size, |
| gfx::Rect damage) { |
| cc::CompositorFrame frame; |
| frame.metadata.device_scale_factor = scale_factor; |
| frame.metadata.begin_frame_ack = cc::BeginFrameAck(0, 1, 1, true); |
| |
| std::unique_ptr<cc::RenderPass> pass = cc::RenderPass::Create(); |
| pass->SetNew(1, gfx::Rect(size), damage, gfx::Transform()); |
| frame.render_pass_list.push_back(std::move(pass)); |
| if (!size.IsEmpty()) { |
| cc::TransferableResource resource; |
| resource.id = 1; |
| frame.resource_list.push_back(std::move(resource)); |
| } |
| return frame; |
| } |
| |
| // This test verifies that returned resources do not require a pending ack. |
| TEST_F(RenderWidgetHostViewAuraTest, ReturnedResources) { |
| gfx::Size view_size(100, 100); |
| gfx::Rect view_rect(view_size); |
| |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| view_->SetSize(view_size); |
| view_->Show(); |
| sink_->ClearMessages(); |
| |
| // Accumulate some returned resources. This should trigger an IPC. |
| cc::ReturnedResourceArray resources; |
| cc::ReturnedResource resource; |
| resource.id = 1; |
| resources.push_back(resource); |
| view_->renderer_compositor_frame_sink_->Reset(); |
| view_->ReclaimResources(resources); |
| view_->renderer_compositor_frame_sink_->Flush(); |
| EXPECT_FALSE(view_->renderer_compositor_frame_sink_->did_receive_ack()); |
| EXPECT_FALSE( |
| view_->renderer_compositor_frame_sink_->last_reclaimed_resources() |
| .empty()); |
| } |
| |
| // This test verifies that when the compositor_frame_sink_id changes, the old |
| // resources are not returned. |
| TEST_F(RenderWidgetHostViewAuraTest, TwoOutputSurfaces) { |
| FakeSurfaceObserver manager_observer; |
| ImageTransportFactory* factory = ImageTransportFactory::GetInstance(); |
| cc::SurfaceManager* manager = |
| factory->GetContextFactoryPrivate()->GetSurfaceManager(); |
| manager->AddObserver(&manager_observer); |
| |
| gfx::Size view_size(100, 100); |
| gfx::Rect view_rect(view_size); |
| |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| view_->SetSize(view_size); |
| view_->Show(); |
| sink_->ClearMessages(); |
| |
| // Submit a frame with resources. |
| cc::CompositorFrame frame = MakeDelegatedFrame(1.f, view_size, view_rect); |
| cc::TransferableResource resource; |
| resource.id = 1; |
| frame.resource_list.push_back(resource); |
| view_->SubmitCompositorFrame(kArbitraryLocalSurfaceId, std::move(frame)); |
| EXPECT_EQ(0u, sink_->message_count()); |
| |
| // Signal that a new RendererCompositorFrameSink was created by the renderer. |
| view_->CreateNewRendererCompositorFrameSink(); |
| |
| // Submit another frame. The resources for the previous frame belong to the |
| // old RendererCompositorFrameSink and should not be returned. |
| view_->SubmitCompositorFrame(CreateLocalSurfaceId(), |
| MakeDelegatedFrame(1.f, view_size, view_rect)); |
| EXPECT_EQ(0u, sink_->message_count()); |
| |
| // Report that the surface is drawn to trigger an ACK. |
| view_->renderer_compositor_frame_sink_->Reset(); |
| cc::Surface* surface = manager->GetSurfaceForId(view_->surface_id()); |
| EXPECT_TRUE(surface); |
| surface->RunDrawCallback(); |
| view_->renderer_compositor_frame_sink_->Flush(); |
| EXPECT_TRUE(view_->renderer_compositor_frame_sink_->did_receive_ack()); |
| |
| manager->RemoveObserver(&manager_observer); |
| } |
| |
| // Resizing in fullscreen mode should send the up-to-date screen info. |
| // http://crbug.com/324350 |
| TEST_F(RenderWidgetHostViewAuraTest, DISABLED_FullscreenResize) { |
| aura::Window* root_window = aura_test_helper_->root_window(); |
| root_window->SetLayoutManager(new FullscreenLayoutManager(root_window)); |
| view_->InitAsFullscreen(parent_view_); |
| view_->Show(); |
| widget_host_->ResetSizeAndRepaintPendingFlags(); |
| sink_->ClearMessages(); |
| |
| // Call WasResized to flush the old screen info. |
| view_->GetRenderWidgetHost()->WasResized(); |
| { |
| // 0 is CreatingNew message. |
| const IPC::Message* msg = sink_->GetMessageAt(0); |
| EXPECT_EQ(ViewMsg_Resize::ID, msg->type()); |
| ViewMsg_Resize::Param params; |
| ViewMsg_Resize::Read(msg, ¶ms); |
| EXPECT_EQ( |
| "0,0 800x600", |
| std::get<0>(params).screen_info.available_rect.ToString()); |
| EXPECT_EQ("800x600", std::get<0>(params).new_size.ToString()); |
| // Resizes are blocked until we swapped a frame of the correct size, and |
| // we've committed it. |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, std::get<0>(params).new_size, |
| gfx::Rect(std::get<0>(params).new_size))); |
| ui::DrawWaiterForTest::WaitForCommit( |
| root_window->GetHost()->compositor()); |
| } |
| |
| widget_host_->ResetSizeAndRepaintPendingFlags(); |
| sink_->ClearMessages(); |
| |
| // Make sure the corrent screen size is set along in the resize |
| // request when the screen size has changed. |
| aura_test_helper_->test_screen()->SetUIScale(0.5); |
| EXPECT_EQ(1u, sink_->message_count()); |
| { |
| const IPC::Message* msg = sink_->GetMessageAt(0); |
| EXPECT_EQ(ViewMsg_Resize::ID, msg->type()); |
| ViewMsg_Resize::Param params; |
| ViewMsg_Resize::Read(msg, ¶ms); |
| EXPECT_EQ( |
| "0,0 1600x1200", |
| std::get<0>(params).screen_info.available_rect.ToString()); |
| EXPECT_EQ("1600x1200", std::get<0>(params).new_size.ToString()); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, std::get<0>(params).new_size, |
| gfx::Rect(std::get<0>(params).new_size))); |
| ui::DrawWaiterForTest::WaitForCommit( |
| root_window->GetHost()->compositor()); |
| } |
| } |
| |
| // Swapping a frame should notify the window. |
| TEST_F(RenderWidgetHostViewAuraTest, SwapNotifiesWindow) { |
| gfx::Size view_size(100, 100); |
| gfx::Rect view_rect(view_size); |
| |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), |
| parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| view_->SetSize(view_size); |
| view_->Show(); |
| |
| MockWindowObserver observer; |
| view_->window_->AddObserver(&observer); |
| |
| // Delegated renderer path |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(view_->window_, view_rect)); |
| view_->SubmitCompositorFrame(kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, view_size, view_rect)); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(view_->window_, |
| gfx::Rect(5, 5, 5, 5))); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, view_size, gfx::Rect(5, 5, 5, 5))); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| |
| view_->window_->RemoveObserver(&observer); |
| } |
| |
| // Mirroring the layers for a window should cause Surface destruction to |
| // depend on both layers. |
| TEST_F(RenderWidgetHostViewAuraTest, MirrorLayers) { |
| gfx::Size view_size(100, 100); |
| gfx::Rect view_rect(view_size); |
| aura::Window* const root = parent_view_->GetNativeView()->GetRootWindow(); |
| |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), root, gfx::Rect()); |
| view_->SetSize(view_size); |
| view_->Show(); |
| |
| view_->SubmitCompositorFrame(kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, view_size, view_rect)); |
| std::unique_ptr<ui::LayerTreeOwner> mirror(wm::MirrorLayers( |
| view_->GetNativeView(), false /* sync_bounds */)); |
| |
| cc::SurfaceId id = view_->GetDelegatedFrameHost()->SurfaceIdForTesting(); |
| if (id.is_valid()) { |
| ImageTransportFactory* factory = ImageTransportFactory::GetInstance(); |
| cc::SurfaceManager* manager = |
| factory->GetContextFactoryPrivate()->GetSurfaceManager(); |
| cc::Surface* surface = manager->GetSurfaceForId(id); |
| EXPECT_TRUE(surface); |
| |
| // An orphaned mirror should not be a destruction dependency. |
| EXPECT_EQ(1u, surface->GetDestructionDependencyCount()); |
| |
| // Both layers should be destruction dependencies. |
| root->layer()->Add(mirror->root()); |
| EXPECT_EQ(2u, surface->GetDestructionDependencyCount()); |
| root->layer()->Remove(mirror->root()); |
| } |
| } |
| |
| // If the view size is larger than the compositor frame then extra layers |
| // should be created to fill the gap. |
| TEST_F(RenderWidgetHostViewAuraTest, DelegatedFrameGutter) { |
| gfx::Size large_size(100, 100); |
| gfx::Size small_size(40, 45); |
| gfx::Size medium_size(40, 95); |
| |
| // Prevent the DelegatedFrameHost from skipping frames. |
| // XXX |
| view_->DisableResizeLock(); |
| |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| view_->SetSize(large_size); |
| view_->Show(); |
| cc::CompositorFrame frame = |
| MakeDelegatedFrame(1.f, small_size, gfx::Rect(small_size)); |
| frame.metadata.root_background_color = SK_ColorRED; |
| view_->SubmitCompositorFrame(kArbitraryLocalSurfaceId, std::move(frame)); |
| |
| ui::Layer* parent_layer = view_->GetNativeView()->layer(); |
| |
| ASSERT_EQ(2u, parent_layer->children().size()); |
| EXPECT_EQ(gfx::Rect(40, 0, 60, 100), parent_layer->children()[0]->bounds()); |
| EXPECT_EQ(SK_ColorRED, parent_layer->children()[0]->background_color()); |
| EXPECT_EQ(gfx::Rect(0, 45, 40, 55), parent_layer->children()[1]->bounds()); |
| EXPECT_EQ(SK_ColorRED, parent_layer->children()[1]->background_color()); |
| |
| delegates_.back()->set_is_fullscreen(true); |
| view_->SetSize(medium_size); |
| |
| // Right gutter is unnecessary. |
| ASSERT_EQ(1u, parent_layer->children().size()); |
| EXPECT_EQ(gfx::Rect(0, 45, 40, 50), parent_layer->children()[0]->bounds()); |
| |
| // RWH is fullscreen, so gutters should be black. |
| EXPECT_EQ(SK_ColorBLACK, parent_layer->children()[0]->background_color()); |
| |
| frame = MakeDelegatedFrame(1.f, medium_size, gfx::Rect(medium_size)); |
| view_->SubmitCompositorFrame(kArbitraryLocalSurfaceId, std::move(frame)); |
| EXPECT_EQ(0u, parent_layer->children().size()); |
| |
| view_->SetSize(large_size); |
| ASSERT_EQ(2u, parent_layer->children().size()); |
| |
| // This should evict the frame and remove the gutter layers. |
| view_->Hide(); |
| view_->SetSize(small_size); |
| ASSERT_EQ(0u, parent_layer->children().size()); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraTest, Resize) { |
| gfx::Size size1(100, 100); |
| gfx::Size size2(200, 200); |
| gfx::Size size3(300, 300); |
| |
| aura::Window* root_window = parent_view_->GetNativeView()->GetRootWindow(); |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), root_window, gfx::Rect(size1)); |
| view_->Show(); |
| view_->SetSize(size1); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, size1, gfx::Rect(size1))); |
| ui::DrawWaiterForTest::WaitForCommit( |
| root_window->GetHost()->compositor()); |
| ViewHostMsg_UpdateRect_Params update_params; |
| update_params.view_size = size1; |
| update_params.flags = ViewHostMsg_UpdateRect_Flags::IS_RESIZE_ACK; |
| widget_host_->OnMessageReceived( |
| ViewHostMsg_UpdateRect(widget_host_->GetRoutingID(), update_params)); |
| sink_->ClearMessages(); |
| // Resize logic is idle (no pending resize, no pending commit). |
| EXPECT_EQ(size1.ToString(), view_->GetRequestedRendererSize().ToString()); |
| |
| // Resize renderer, should produce a Resize message |
| view_->SetSize(size2); |
| EXPECT_EQ(size2.ToString(), view_->GetRequestedRendererSize().ToString()); |
| EXPECT_EQ(1u, sink_->message_count()); |
| { |
| const IPC::Message* msg = sink_->GetMessageAt(0); |
| EXPECT_EQ(ViewMsg_Resize::ID, msg->type()); |
| ViewMsg_Resize::Param params; |
| ViewMsg_Resize::Read(msg, ¶ms); |
| EXPECT_EQ(size2.ToString(), std::get<0>(params).new_size.ToString()); |
| } |
| // Send resize ack to observe new Resize messages. |
| update_params.view_size = size2; |
| widget_host_->OnMessageReceived( |
| ViewHostMsg_UpdateRect(widget_host_->GetRoutingID(), update_params)); |
| sink_->ClearMessages(); |
| |
| // Resize renderer again, before receiving a frame. Should not produce a |
| // Resize message. |
| view_->SetSize(size3); |
| EXPECT_EQ(size2.ToString(), view_->GetRequestedRendererSize().ToString()); |
| EXPECT_EQ(0u, sink_->message_count()); |
| |
| // Receive a frame of the new size, should be skipped and not produce a Resize |
| // message. |
| view_->renderer_compositor_frame_sink_->Reset(); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, size3, gfx::Rect(size3))); |
| view_->renderer_compositor_frame_sink_->Flush(); |
| // Expect the frame ack; |
| EXPECT_TRUE(view_->renderer_compositor_frame_sink_->did_receive_ack()); |
| EXPECT_EQ(size2.ToString(), view_->GetRequestedRendererSize().ToString()); |
| |
| // Receive a frame of the correct size, should not be skipped and, and should |
| // produce a Resize message after the commit. |
| view_->renderer_compositor_frame_sink_->Reset(); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, size2, gfx::Rect(size2))); |
| view_->renderer_compositor_frame_sink_->Flush(); |
| cc::SurfaceId surface_id = view_->surface_id(); |
| if (!surface_id.is_valid()) { |
| // No frame ack yet. |
| EXPECT_FALSE(view_->renderer_compositor_frame_sink_->did_receive_ack()); |
| } else { |
| // Frame isn't desired size, so early ack. |
| EXPECT_TRUE(view_->renderer_compositor_frame_sink_->did_receive_ack()); |
| } |
| EXPECT_EQ(size2.ToString(), view_->GetRequestedRendererSize().ToString()); |
| |
| // Wait for commit, then we should unlock the compositor and send a Resize |
| // message (and a frame ack) |
| ui::DrawWaiterForTest::WaitForCommit( |
| root_window->GetHost()->compositor()); |
| |
| bool has_resize = false; |
| for (uint32_t i = 0; i < sink_->message_count(); ++i) { |
| const IPC::Message* msg = sink_->GetMessageAt(i); |
| switch (msg->type()) { |
| case InputMsg_HandleInputEvent::ID: { |
| // On some platforms, the call to view_->Show() causes a posted task to |
| // call |
| // ui::WindowEventDispatcher::SynthesizeMouseMoveAfterChangeToWindow, |
| // which the above WaitForCommit may cause to be picked up. Be robust |
| // to this extra IPC coming in. |
| InputMsg_HandleInputEvent::Param params; |
| InputMsg_HandleInputEvent::Read(msg, ¶ms); |
| const blink::WebInputEvent* event = std::get<0>(params); |
| EXPECT_EQ(blink::WebInputEvent::kMouseMove, event->GetType()); |
| break; |
| } |
| case ViewMsg_Resize::ID: { |
| EXPECT_FALSE(has_resize); |
| ViewMsg_Resize::Param params; |
| ViewMsg_Resize::Read(msg, ¶ms); |
| EXPECT_EQ(size3.ToString(), std::get<0>(params).new_size.ToString()); |
| has_resize = true; |
| break; |
| } |
| default: |
| ADD_FAILURE() << "Unexpected message " << msg->type(); |
| break; |
| } |
| } |
| EXPECT_TRUE(has_resize); |
| update_params.view_size = size3; |
| widget_host_->OnMessageReceived( |
| ViewHostMsg_UpdateRect(widget_host_->GetRoutingID(), update_params)); |
| sink_->ClearMessages(); |
| } |
| |
| // Skipped frames should not drop their damage. |
| TEST_F(RenderWidgetHostViewAuraTest, SkippedDelegatedFrames) { |
| gfx::Rect view_rect(100, 100); |
| gfx::Size frame_size = view_rect.size(); |
| |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), |
| parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| view_->SetSize(view_rect.size()); |
| |
| MockWindowObserver observer; |
| view_->window_->AddObserver(&observer); |
| |
| // A full frame of damage. |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(view_->window_, view_rect)); |
| view_->SubmitCompositorFrame(kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| view_->RunOnCompositingDidCommit(); |
| |
| // A partial damage frame. |
| gfx::Rect partial_view_rect(30, 30, 20, 20); |
| EXPECT_CALL(observer, |
| OnDelegatedFrameDamage(view_->window_, partial_view_rect)); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, partial_view_rect)); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| view_->RunOnCompositingDidCommit(); |
| |
| EXPECT_FALSE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| |
| // Lock the compositor. Now we should drop frames. |
| view_rect = gfx::Rect(150, 150); |
| view_->SetSize(view_rect.size()); |
| EXPECT_TRUE(view_->resize_locked()); |
| EXPECT_TRUE(view_->compositor_locked()); |
| |
| // This frame is dropped. |
| gfx::Rect dropped_damage_rect_1(10, 20, 30, 40); |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(_, _)).Times(0); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, dropped_damage_rect_1)); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| view_->RunOnCompositingDidCommit(); |
| |
| gfx::Rect dropped_damage_rect_2(40, 50, 10, 20); |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(_, _)).Times(0); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, dropped_damage_rect_2)); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| view_->RunOnCompositingDidCommit(); |
| |
| EXPECT_TRUE(view_->resize_locked()); |
| EXPECT_TRUE(view_->compositor_locked()); |
| |
| // Unlock the compositor. This frame should damage everything. |
| frame_size = view_rect.size(); |
| |
| gfx::Rect new_damage_rect(5, 6, 10, 10); |
| EXPECT_CALL(observer, |
| OnDelegatedFrameDamage(view_->window_, view_rect)); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, new_damage_rect)); |
| // The swap unlocks the compositor. |
| EXPECT_TRUE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| // The UI commit unlocks for further resize. |
| view_->RunOnCompositingDidCommit(); |
| EXPECT_FALSE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| |
| // A partial damage frame, this should not be dropped. |
| EXPECT_CALL(observer, |
| OnDelegatedFrameDamage(view_->window_, partial_view_rect)); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, partial_view_rect)); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| view_->RunOnCompositingDidCommit(); |
| EXPECT_FALSE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| |
| // Resize to something empty. This doesn't lock anything since it's not |
| // visible anymore anyways. |
| view_rect = gfx::Rect(100, 0); |
| view_->SetSize(view_rect.size()); |
| EXPECT_FALSE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| |
| // We're never expecting empty frames, resize to something non-empty. |
| view_rect = gfx::Rect(100, 100); |
| view_->SetSize(view_rect.size()); |
| EXPECT_TRUE(view_->resize_locked()); |
| EXPECT_TRUE(view_->compositor_locked()); |
| |
| // This frame should not be dropped. |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(view_->window_, view_rect)); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, view_rect.size(), view_rect)); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| EXPECT_TRUE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| view_->RunOnCompositingDidCommit(); |
| EXPECT_FALSE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| |
| view_->window_->RemoveObserver(&observer); |
| } |
| |
| // If resize races with a renderer frame, we should lock for the right size. |
| TEST_F(RenderWidgetHostViewAuraTest, ResizeAfterReceivingFrame) { |
| gfx::Rect view_rect(100, 100); |
| gfx::Size frame_size = view_rect.size(); |
| |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| view_->SetSize(view_rect.size()); |
| |
| MockWindowObserver observer; |
| view_->window_->AddObserver(&observer); |
| |
| // A frame of initial size. |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(view_->window_, view_rect)); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, gfx::Rect(frame_size))); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| view_->RunOnCompositingDidCommit(); |
| |
| // A frame of initial size arrives, but we don't commit in the UI yet. |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(view_->window_, _)); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, gfx::Rect(frame_size))); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| |
| EXPECT_FALSE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| |
| // Resize, and lock the compositor. Now we should drop frames of the old size. |
| view_rect = gfx::Rect(150, 150); |
| view_->SetSize(view_rect.size()); |
| EXPECT_TRUE(view_->resize_locked()); |
| EXPECT_TRUE(view_->compositor_locked()); |
| |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(_, _)).Times(0); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, gfx::Rect(frame_size))); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| |
| // If the CompositorLock times out in the meantime, a commit would happen. |
| // Verify that if a commit occurs, the lock remains and we reject frames |
| // of the wrong size still. |
| view_->RunOnCompositingDidCommit(); |
| |
| EXPECT_TRUE(view_->resize_locked()); |
| // In this case we lied about it and the CompositorLock is still active. |
| EXPECT_TRUE(view_->compositor_locked()); |
| |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(_, _)).Times(0); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, gfx::Rect(frame_size))); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| |
| // A frame arrives of the new size, which will be accepted. |
| frame_size = view_rect.size(); |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(view_->window_, _)); |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, gfx::Rect(frame_size))); |
| // Receiving the frame unlocks the compositor so it can commit. |
| EXPECT_TRUE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| |
| // When the frame of the correct size is committed, the CompositorResizeLock |
| // is released. |
| view_->RunOnCompositingDidCommit(); |
| EXPECT_FALSE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| |
| view_->window_->RemoveObserver(&observer); |
| } |
| |
| // When the DelegatedFrameHost does not have a frame from the renderer, it has |
| // no reason to lock the compositor as there can't be guttering around a |
| // renderer frame that doesn't exist. |
| TEST_F(RenderWidgetHostViewAuraTest, MissingFramesDontLock) { |
| gfx::Rect view_rect(100, 100); |
| gfx::Size frame_size = view_rect.size(); |
| |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| |
| // The view is resized before the first frame, which should not lock the |
| // compositor as it's never received a frame to show yet. |
| view_->SetSize(view_rect.size()); |
| |
| EXPECT_FALSE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| |
| // Submit a frame of initial size to make a frame present in |
| // DelegatedFrameHost, at which point locking becomes feasible if resized. |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, gfx::Rect(frame_size))); |
| view_->RunOnCompositingDidCommit(); |
| |
| EXPECT_FALSE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| |
| // The view is resized and has its frame evicted, before a new frame arrives. |
| // The resize will lock the compositor, but when evicted, it should no longer |
| // be locked. |
| view_rect.SetRect(0, 0, 150, 150); |
| view_->SetSize(view_rect.size()); |
| EXPECT_TRUE(view_->resize_locked()); |
| EXPECT_TRUE(view_->compositor_locked()); |
| |
| view_->ClearCompositorFrame(); |
| EXPECT_FALSE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| |
| // And future resizes after eviction should not lock the compositor since |
| // there is no frame present. |
| view_rect.SetRect(0, 0, 120, 120); |
| view_->SetSize(view_rect.size()); |
| EXPECT_FALSE(view_->resize_locked()); |
| EXPECT_FALSE(view_->compositor_locked()); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraTest, OutputSurfaceIdChange) { |
| gfx::Rect view_rect(100, 100); |
| gfx::Size frame_size = view_rect.size(); |
| |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), |
| parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| view_->SetSize(view_rect.size()); |
| |
| MockWindowObserver observer; |
| view_->window_->AddObserver(&observer); |
| |
| // Swap a frame. |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(view_->window_, view_rect)); |
| view_->SubmitCompositorFrame(kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| view_->RunOnCompositingDidCommit(); |
| |
| // Signal that a new RendererCompositorFrameSink was created. |
| view_->CreateNewRendererCompositorFrameSink(); |
| |
| // Submit a frame from the new RendererCompositorFrameSink. |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(view_->window_, view_rect)); |
| view_->SubmitCompositorFrame(CreateLocalSurfaceId(), |
| MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| view_->RunOnCompositingDidCommit(); |
| |
| // Signal that a new RendererCompositorFrameSink was created. |
| view_->CreateNewRendererCompositorFrameSink(); |
| |
| // Submit a frame from the new RendererCompositorFrameSink. |
| view_->SubmitCompositorFrame( |
| CreateLocalSurfaceId(), |
| MakeDelegatedFrame(1.f, gfx::Size(), gfx::Rect())); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| view_->RunOnCompositingDidCommit(); |
| |
| // Signal that a new RendererCompositorFrameSink was created. |
| view_->CreateNewRendererCompositorFrameSink(); |
| |
| // Swap another frame, with a different surface id. |
| EXPECT_CALL(observer, OnDelegatedFrameDamage(view_->window_, view_rect)); |
| view_->SubmitCompositorFrame(CreateLocalSurfaceId(), |
| MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| view_->RunOnCompositingDidCommit(); |
| |
| view_->window_->RemoveObserver(&observer); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraTest, DiscardDelegatedFrames) { |
| view_->InitAsChild(nullptr); |
| |
| size_t max_renderer_frames = |
| FrameEvictionManager::GetInstance()->GetMaxNumberOfSavedFrames(); |
| ASSERT_LE(2u, max_renderer_frames); |
| size_t renderer_count = max_renderer_frames + 1; |
| gfx::Rect view_rect(100, 100); |
| gfx::Size frame_size = view_rect.size(); |
| DCHECK_EQ(0u, display_compositor::HostSharedBitmapManager::current() |
| ->AllocatedBitmapCount()); |
| |
| std::unique_ptr<RenderWidgetHostImpl* []> hosts( |
| new RenderWidgetHostImpl*[renderer_count]); |
| std::unique_ptr<FakeRenderWidgetHostViewAura* []> views( |
| new FakeRenderWidgetHostViewAura*[renderer_count]); |
| |
| // Create a bunch of renderers. |
| for (size_t i = 0; i < renderer_count; ++i) { |
| int32_t routing_id = process_host_->GetNextRoutingID(); |
| delegates_.push_back(base::WrapUnique(new MockRenderWidgetHostDelegate)); |
| hosts[i] = new RenderWidgetHostImpl(delegates_.back().get(), process_host_, |
| routing_id, false); |
| delegates_.back()->set_widget_host(hosts[i]); |
| hosts[i]->Init(); |
| views[i] = new FakeRenderWidgetHostViewAura(hosts[i], false); |
| // Prevent frames from being skipped due to resize, this test does not |
| // run a UI compositor so the DelegatedFrameHost doesn't get the chance |
| // to release its resize lock once it receives a frame of the expected |
| // size. |
| views[i]->DisableResizeLock(); |
| views[i]->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| views[i]->GetNativeView(), |
| parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| views[i]->SetSize(view_rect.size()); |
| } |
| |
| // Make each renderer visible, and swap a frame on it, then make it invisible. |
| for (size_t i = 0; i < renderer_count; ++i) { |
| views[i]->Show(); |
| views[i]->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| EXPECT_TRUE(views[i]->HasFrameData()); |
| views[i]->Hide(); |
| } |
| |
| // There should be max_renderer_frames with a frame in it, and one without it. |
| // Since the logic is LRU eviction, the first one should be without. |
| EXPECT_FALSE(views[0]->HasFrameData()); |
| for (size_t i = 1; i < renderer_count; ++i) |
| EXPECT_TRUE(views[i]->HasFrameData()); |
| |
| // LRU renderer is [0], make it visible, it shouldn't evict anything yet. |
| views[0]->Show(); |
| EXPECT_FALSE(views[0]->HasFrameData()); |
| EXPECT_TRUE(views[1]->HasFrameData()); |
| // Since [0] doesn't have a frame, it should be waiting for the renderer to |
| // give it one. |
| EXPECT_TRUE(views[0]->released_front_lock_active()); |
| |
| // Swap a frame on it, it should evict the next LRU [1]. |
| views[0]->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| EXPECT_TRUE(views[0]->HasFrameData()); |
| EXPECT_FALSE(views[1]->HasFrameData()); |
| // Now that [0] got a frame, it shouldn't be waiting any more. |
| EXPECT_FALSE(views[0]->released_front_lock_active()); |
| views[0]->Hide(); |
| |
| // LRU renderer is [1], still hidden. Swap a frame on it, it should evict |
| // the next LRU [2]. |
| views[1]->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| EXPECT_TRUE(views[0]->HasFrameData()); |
| EXPECT_TRUE(views[1]->HasFrameData()); |
| EXPECT_FALSE(views[2]->HasFrameData()); |
| for (size_t i = 3; i < renderer_count; ++i) |
| EXPECT_TRUE(views[i]->HasFrameData()); |
| |
| // Make all renderers but [0] visible and swap a frame on them, keep [0] |
| // hidden, it becomes the LRU. |
| for (size_t i = 1; i < renderer_count; ++i) { |
| views[i]->Show(); |
| // The renderers who don't have a frame should be waiting. The ones that |
| // have a frame should not. |
| // In practice, [1] has a frame, but anything after has its frame evicted. |
| EXPECT_EQ(!views[i]->HasFrameData(), |
| views[i]->released_front_lock_active()); |
| views[i]->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| // Now everyone has a frame. |
| EXPECT_FALSE(views[i]->released_front_lock_active()); |
| EXPECT_TRUE(views[i]->HasFrameData()); |
| } |
| EXPECT_FALSE(views[0]->HasFrameData()); |
| |
| // Swap a frame on [0], it should be evicted immediately. |
| views[0]->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| EXPECT_FALSE(views[0]->HasFrameData()); |
| |
| // Make [0] visible, and swap a frame on it. Nothing should be evicted |
| // although we're above the limit. |
| views[0]->Show(); |
| // We don't have a frame, wait. |
| EXPECT_TRUE(views[0]->released_front_lock_active()); |
| views[0]->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| EXPECT_FALSE(views[0]->released_front_lock_active()); |
| for (size_t i = 0; i < renderer_count; ++i) |
| EXPECT_TRUE(views[i]->HasFrameData()); |
| |
| // Make [0] hidden, it should evict its frame. |
| views[0]->Hide(); |
| EXPECT_FALSE(views[0]->HasFrameData()); |
| |
| // Make [0] visible, don't give it a frame, it should be waiting. |
| views[0]->Show(); |
| EXPECT_TRUE(views[0]->released_front_lock_active()); |
| // Make [0] hidden, it should stop waiting. |
| views[0]->Hide(); |
| EXPECT_FALSE(views[0]->released_front_lock_active()); |
| |
| // Make [1] hidden, resize it. It should drop its frame. |
| views[1]->Hide(); |
| EXPECT_TRUE(views[1]->HasFrameData()); |
| gfx::Size size2(200, 200); |
| views[1]->SetSize(size2); |
| EXPECT_FALSE(views[1]->HasFrameData()); |
| // Show it, it should block until we give it a frame. |
| views[1]->Show(); |
| EXPECT_TRUE(views[1]->released_front_lock_active()); |
| views[1]->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, size2, gfx::Rect(size2))); |
| EXPECT_FALSE(views[1]->released_front_lock_active()); |
| |
| for (size_t i = 0; i < renderer_count - 1; ++i) |
| views[i]->Hide(); |
| |
| // Allocate enough bitmaps so that two frames (proportionally) would be |
| // enough hit the handle limit. |
| int handles_per_frame = 5; |
| FrameEvictionManager::GetInstance()->set_max_handles(handles_per_frame * 2); |
| |
| display_compositor::HostSharedBitmapManagerClient bitmap_client( |
| display_compositor::HostSharedBitmapManager::current()); |
| |
| for (size_t i = 0; i < (renderer_count - 1) * handles_per_frame; i++) { |
| bitmap_client.ChildAllocatedSharedBitmap(1, base::SharedMemoryHandle(), |
| cc::SharedBitmap::GenerateId()); |
| } |
| |
| // Hiding this last bitmap should evict all but two frames. |
| views[renderer_count - 1]->Hide(); |
| for (size_t i = 0; i < renderer_count; ++i) { |
| if (i + 2 < renderer_count) |
| EXPECT_FALSE(views[i]->HasFrameData()); |
| else |
| EXPECT_TRUE(views[i]->HasFrameData()); |
| } |
| FrameEvictionManager::GetInstance()->set_max_handles( |
| base::SharedMemory::GetHandleLimit()); |
| |
| for (size_t i = 0; i < renderer_count; ++i) { |
| views[i]->Destroy(); |
| delete hosts[i]; |
| } |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraTest, DiscardDelegatedFramesWithLocking) { |
| view_->InitAsChild(nullptr); |
| |
| size_t max_renderer_frames = |
| FrameEvictionManager::GetInstance()->GetMaxNumberOfSavedFrames(); |
| ASSERT_LE(2u, max_renderer_frames); |
| size_t renderer_count = max_renderer_frames + 1; |
| gfx::Rect view_rect(100, 100); |
| gfx::Size frame_size = view_rect.size(); |
| DCHECK_EQ(0u, display_compositor::HostSharedBitmapManager::current() |
| ->AllocatedBitmapCount()); |
| |
| std::unique_ptr<RenderWidgetHostImpl* []> hosts( |
| new RenderWidgetHostImpl*[renderer_count]); |
| std::unique_ptr<FakeRenderWidgetHostViewAura* []> views( |
| new FakeRenderWidgetHostViewAura*[renderer_count]); |
| |
| // Create a bunch of renderers. |
| for (size_t i = 0; i < renderer_count; ++i) { |
| int32_t routing_id = process_host_->GetNextRoutingID(); |
| delegates_.push_back(base::WrapUnique(new MockRenderWidgetHostDelegate)); |
| hosts[i] = new RenderWidgetHostImpl(delegates_.back().get(), process_host_, |
| routing_id, false); |
| delegates_.back()->set_widget_host(hosts[i]); |
| hosts[i]->Init(); |
| views[i] = new FakeRenderWidgetHostViewAura(hosts[i], false); |
| views[i]->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| views[i]->GetNativeView(), |
| parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| views[i]->SetSize(view_rect.size()); |
| } |
| |
| // Make each renderer visible and swap a frame on it. No eviction should |
| // occur because all frames are visible. |
| for (size_t i = 0; i < renderer_count; ++i) { |
| views[i]->Show(); |
| views[i]->SubmitCompositorFrame( |
| i ? CreateLocalSurfaceId() : kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| EXPECT_TRUE(views[i]->HasFrameData()); |
| } |
| |
| // If we hide [0], then [0] should be evicted. |
| views[0]->Hide(); |
| EXPECT_FALSE(views[0]->HasFrameData()); |
| |
| // If we lock [0] before hiding it, then [0] should not be evicted. |
| views[0]->Show(); |
| views[0]->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| EXPECT_TRUE(views[0]->HasFrameData()); |
| views[0]->GetDelegatedFrameHost()->LockResources(); |
| views[0]->Hide(); |
| EXPECT_TRUE(views[0]->HasFrameData()); |
| |
| // If we unlock [0] now, then [0] should be evicted. |
| views[0]->GetDelegatedFrameHost()->UnlockResources(); |
| EXPECT_FALSE(views[0]->HasFrameData()); |
| |
| for (size_t i = 0; i < renderer_count; ++i) { |
| views[i]->Destroy(); |
| delete hosts[i]; |
| } |
| } |
| |
| // Test that changing the memory pressure should delete saved frames. This test |
| // only applies to ChromeOS. |
| TEST_F(RenderWidgetHostViewAuraTest, DiscardDelegatedFramesWithMemoryPressure) { |
| view_->InitAsChild(nullptr); |
| |
| // The test logic below relies on having max_renderer_frames > 2. By default, |
| // this value is calculated from total physical memory and causes the test to |
| // fail when run on hardware with < 256MB of RAM. |
| const size_t kMaxRendererFrames = 5; |
| FrameEvictionManager::GetInstance()->set_max_number_of_saved_frames( |
| kMaxRendererFrames); |
| |
| size_t renderer_count = kMaxRendererFrames; |
| gfx::Rect view_rect(100, 100); |
| gfx::Size frame_size = view_rect.size(); |
| DCHECK_EQ(0u, display_compositor::HostSharedBitmapManager::current() |
| ->AllocatedBitmapCount()); |
| |
| std::unique_ptr<RenderWidgetHostImpl* []> hosts( |
| new RenderWidgetHostImpl*[renderer_count]); |
| std::unique_ptr<FakeRenderWidgetHostViewAura* []> views( |
| new FakeRenderWidgetHostViewAura*[renderer_count]); |
| |
| // Create a bunch of renderers. |
| for (size_t i = 0; i < renderer_count; ++i) { |
| int32_t routing_id = process_host_->GetNextRoutingID(); |
| delegates_.push_back(base::WrapUnique(new MockRenderWidgetHostDelegate)); |
| hosts[i] = new RenderWidgetHostImpl(delegates_.back().get(), process_host_, |
| routing_id, false); |
| delegates_.back()->set_widget_host(hosts[i]); |
| hosts[i]->Init(); |
| views[i] = new FakeRenderWidgetHostViewAura(hosts[i], false); |
| views[i]->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| views[i]->GetNativeView(), |
| parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| views[i]->SetSize(view_rect.size()); |
| } |
| |
| // Make each renderer visible and swap a frame on it. No eviction should |
| // occur because all frames are visible. |
| for (size_t i = 0; i < renderer_count; ++i) { |
| views[i]->Show(); |
| views[i]->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, frame_size, view_rect)); |
| EXPECT_TRUE(views[i]->HasFrameData()); |
| } |
| |
| // If we hide one, it should not get evicted. |
| views[0]->Hide(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(views[0]->HasFrameData()); |
| // Using a lesser memory pressure event however, should evict. |
| SimulateMemoryPressure( |
| base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(views[0]->HasFrameData()); |
| |
| // Check the same for a higher pressure event. |
| views[1]->Hide(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(views[1]->HasFrameData()); |
| SimulateMemoryPressure( |
| base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(views[1]->HasFrameData()); |
| |
| for (size_t i = 0; i < renderer_count; ++i) { |
| views[i]->Destroy(); |
| delete hosts[i]; |
| } |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraTest, SourceEventTypeExistsInLatencyInfo) { |
| // WHEEL source exists. |
| ui::ScrollEvent scroll(ui::ET_SCROLL, gfx::Point(2, 2), ui::EventTimeForNow(), |
| 0, 0, 0, 0, 0, 2); |
| view_->OnScrollEvent(&scroll); |
| EXPECT_EQ(widget_host_->lastWheelOrTouchEventLatencyInfo.source_event_type(), |
| ui::SourceEventType::WHEEL); |
| |
| // TOUCH source exists. |
| 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)); |
| view_->OnTouchEvent(&press); |
| view_->OnTouchEvent(&move); |
| EXPECT_EQ(widget_host_->lastWheelOrTouchEventLatencyInfo.source_event_type(), |
| ui::SourceEventType::TOUCH); |
| view_->OnTouchEvent(&release); |
| } |
| |
| namespace { |
| class LastObserverTracker : public cc::FakeExternalBeginFrameSource::Client { |
| public: |
| void OnAddObserver(cc::BeginFrameObserver* obs) override { |
| last_observer_ = obs; |
| } |
| void OnRemoveObserver(cc::BeginFrameObserver* obs) override {} |
| |
| cc::BeginFrameObserver* last_observer_ = nullptr; |
| }; |
| } // namespace |
| |
| // Tests that BeginFrameAcks are forwarded correctly from the |
| // SwapCompositorFrame and OnBeginFrameDidNotSwap IPCs through |
| // DelegatedFrameHost and its CompositorFrameSinkSupport. |
| TEST_F(RenderWidgetHostViewAuraTest, ForwardsBeginFrameAcks) { |
| gfx::Rect view_rect(100, 100); |
| gfx::Size frame_size = view_rect.size(); |
| |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| view_->SetSize(view_rect.size()); |
| |
| // Replace BeginFrameSource so that we can observe acknowledgments. Since the |
| // DelegatedFrameHost doesn't directly observe our BeginFrameSource, |
| // |observer_tracker| grabs a pointer to the observer (the |
| // DelegatedFrameHost's CompositorFrameSinkSupport). |
| LastObserverTracker observer_tracker; |
| cc::FakeExternalBeginFrameSource source(0.f, false); |
| uint32_t source_id = source.source_id(); |
| source.SetClient(&observer_tracker); |
| cc::FrameSinkId frame_sink_id = |
| view_->GetDelegatedFrameHost()->GetFrameSinkId(); |
| ImageTransportFactory* factory = ImageTransportFactory::GetInstance(); |
| cc::SurfaceManager* surface_manager = |
| factory->GetContextFactoryPrivate()->GetSurfaceManager(); |
| surface_manager->RegisterBeginFrameSource(&source, frame_sink_id); |
| view_->SetNeedsBeginFrames(true); |
| EXPECT_TRUE(observer_tracker.last_observer_); |
| |
| { |
| cc::BeginFrameArgs args = |
| cc::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, source_id, 5u); |
| source.TestOnBeginFrame(args); |
| |
| // Ack from CompositorFrame is forwarded. |
| cc::BeginFrameAck ack(source_id, 5, 4, true); |
| cc::CompositorFrame frame = MakeDelegatedFrame(1.f, frame_size, view_rect); |
| frame.metadata.begin_frame_ack = ack; |
| view_->SubmitCompositorFrame(kArbitraryLocalSurfaceId, std::move(frame)); |
| view_->RunOnCompositingDidCommit(); |
| EXPECT_EQ(ack, source.LastAckForObserver(observer_tracker.last_observer_)); |
| } |
| |
| { |
| cc::BeginFrameArgs args = |
| cc::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, source_id, 6u); |
| source.TestOnBeginFrame(args); |
| |
| // Explicit ack through OnBeginFrameDidNotSwap is forwarded. |
| cc::BeginFrameAck ack(source_id, 6, 4, false); |
| view_->OnBeginFrameDidNotSwap(ack); |
| EXPECT_EQ(ack, source.LastAckForObserver(observer_tracker.last_observer_)); |
| } |
| |
| // Lock the compositor. Now we should drop frames and, thus, |
| // latest_confirmed_sequence_number should not change. |
| view_rect = gfx::Rect(150, 150); |
| view_->SetSize(view_rect.size()); |
| |
| { |
| cc::BeginFrameArgs args = |
| cc::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, source_id, 7u); |
| source.TestOnBeginFrame(args); |
| |
| // Ack from CompositorFrame is forwarded with old |
| // latest_confirmed_sequence_number and without damage. |
| cc::BeginFrameAck ack(source_id, 7, 7, true); |
| gfx::Rect dropped_damage_rect(10, 20, 30, 40); |
| cc::CompositorFrame frame = |
| MakeDelegatedFrame(1.f, frame_size, dropped_damage_rect); |
| frame.metadata.begin_frame_ack = ack; |
| view_->SubmitCompositorFrame(kArbitraryLocalSurfaceId, std::move(frame)); |
| view_->RunOnCompositingDidCommit(); |
| ack.latest_confirmed_sequence_number = 4; |
| ack.has_damage = false; |
| EXPECT_EQ(ack, source.LastAckForObserver(observer_tracker.last_observer_)); |
| } |
| |
| // Change source_id known to the view. This should reset the |
| // latest_confirmed_sequence_number tracked by the view. |
| source_id = cc::BeginFrameArgs::kManualSourceId; |
| |
| { |
| cc::BeginFrameArgs args = cc::CreateBeginFrameArgsForTesting( |
| BEGINFRAME_FROM_HERE, source_id, 10u); |
| source.TestOnBeginFrame(args); |
| |
| // Ack from CompositorFrame is forwarded with invalid |
| // latest_confirmed_sequence_number and without damage. |
| cc::BeginFrameAck ack(source_id, 10, 10, true); |
| gfx::Rect dropped_damage_rect(10, 20, 30, 40); |
| cc::CompositorFrame frame = |
| MakeDelegatedFrame(1.f, frame_size, dropped_damage_rect); |
| frame.metadata.begin_frame_ack = ack; |
| view_->SubmitCompositorFrame(kArbitraryLocalSurfaceId, std::move(frame)); |
| view_->RunOnCompositingDidCommit(); |
| ack.latest_confirmed_sequence_number = |
| cc::BeginFrameArgs::kInvalidFrameNumber; |
| ack.has_damage = false; |
| EXPECT_EQ(ack, source.LastAckForObserver(observer_tracker.last_observer_)); |
| } |
| |
| { |
| cc::BeginFrameArgs args = cc::CreateBeginFrameArgsForTesting( |
| BEGINFRAME_FROM_HERE, source_id, 11u); |
| source.TestOnBeginFrame(args); |
| |
| // Explicit ack through OnBeginFrameDidNotSwap is forwarded with invalid |
| // latest_confirmed_sequence_number. |
| cc::BeginFrameAck ack(source_id, 11, 11, false); |
| view_->OnBeginFrameDidNotSwap(ack); |
| ack.latest_confirmed_sequence_number = |
| cc::BeginFrameArgs::kInvalidFrameNumber; |
| EXPECT_EQ(ack, source.LastAckForObserver(observer_tracker.last_observer_)); |
| } |
| |
| // Unlock the compositor again with a new CompositorFrame of correct size. |
| frame_size = view_rect.size(); |
| |
| { |
| cc::BeginFrameArgs args = cc::CreateBeginFrameArgsForTesting( |
| BEGINFRAME_FROM_HERE, source_id, 12u); |
| source.TestOnBeginFrame(args); |
| |
| // Ack from CompositorFrame is forwarded. |
| cc::BeginFrameAck ack(source_id, 12, 12, true); |
| cc::CompositorFrame frame = MakeDelegatedFrame(1.f, frame_size, view_rect); |
| frame.metadata.begin_frame_ack = ack; |
| view_->SubmitCompositorFrame(kArbitraryLocalSurfaceId, std::move(frame)); |
| view_->RunOnCompositingDidCommit(); |
| EXPECT_EQ(ack, source.LastAckForObserver(observer_tracker.last_observer_)); |
| } |
| |
| { |
| cc::BeginFrameArgs args = cc::CreateBeginFrameArgsForTesting( |
| BEGINFRAME_FROM_HERE, source_id, 13u); |
| source.TestOnBeginFrame(args); |
| |
| // Explicit ack through OnBeginFrameDidNotSwap is forwarded. |
| cc::BeginFrameAck ack(source_id, 13, 13, false); |
| view_->OnBeginFrameDidNotSwap(ack); |
| EXPECT_EQ(ack, source.LastAckForObserver(observer_tracker.last_observer_)); |
| } |
| |
| surface_manager->UnregisterBeginFrameSource(&source); |
| } |
| |
| class RenderWidgetHostViewAuraCopyRequestTest |
| : public RenderWidgetHostViewAuraShutdownTest { |
| public: |
| RenderWidgetHostViewAuraCopyRequestTest() |
| : callback_count_(0), |
| result_(false), |
| frame_subscriber_(nullptr), |
| tick_clock_(nullptr), |
| view_rect_(100, 100) {} |
| |
| void CallbackMethod(bool result) { |
| result_ = result; |
| callback_count_++; |
| quit_closure_.Run(); |
| } |
| |
| void RunLoopUntilCallback() { |
| base::RunLoop run_loop; |
| quit_closure_ = run_loop.QuitClosure(); |
| // Temporarily ignore real draw requests. |
| frame_subscriber_->set_should_capture(false); |
| run_loop.Run(); |
| frame_subscriber_->set_should_capture(true); |
| } |
| |
| void InitializeView() { |
| view_->InitAsChild(nullptr); |
| view_->GetDelegatedFrameHost()->SetRequestCopyOfOutputCallbackForTesting( |
| base::Bind(&FakeRenderWidgetHostViewAura::InterceptCopyOfOutput, |
| base::Unretained(view_))); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| view_->SetSize(view_rect_.size()); |
| view_->Show(); |
| |
| frame_subscriber_ = new FakeFrameSubscriber( |
| view_rect_.size(), |
| base::Bind(&RenderWidgetHostViewAuraCopyRequestTest::CallbackMethod, |
| base::Unretained(this))); |
| view_->BeginFrameSubscription(base::WrapUnique(frame_subscriber_)); |
| ASSERT_EQ(0, callback_count_); |
| ASSERT_FALSE(view_->last_copy_request_); |
| } |
| |
| void InstallFakeTickClock() { |
| // Create a fake tick clock and transfer ownership to the frame host. |
| tick_clock_ = new base::SimpleTestTickClock(); |
| view_->GetDelegatedFrameHost()->tick_clock_ = base::WrapUnique(tick_clock_); |
| } |
| |
| void SubmitCompositorFrame() { |
| view_->SubmitCompositorFrame( |
| kArbitraryLocalSurfaceId, |
| MakeDelegatedFrame(1.f, view_rect_.size(), view_rect_)); |
| cc::SurfaceId surface_id = |
| view_->GetDelegatedFrameHost()->SurfaceIdForTesting(); |
| if (surface_id.is_valid()) |
| view_->GetDelegatedFrameHost()->WillDrawSurface( |
| surface_id.local_surface_id(), view_rect_); |
| ASSERT_TRUE(view_->last_copy_request_); |
| } |
| |
| void ReleaseSwappedFrame() { |
| std::unique_ptr<cc::CopyOutputRequest> request = |
| std::move(view_->last_copy_request_); |
| request->SendTextureResult(view_rect_.size(), request->texture_mailbox(), |
| std::unique_ptr<cc::SingleReleaseCallback>()); |
| RunLoopUntilCallback(); |
| } |
| |
| void SubmitCompositorFrameAndRelease() { |
| SubmitCompositorFrame(); |
| ReleaseSwappedFrame(); |
| } |
| |
| void RunOnCompositingDidCommitAndReleaseFrame() { |
| view_->RunOnCompositingDidCommit(); |
| ReleaseSwappedFrame(); |
| } |
| |
| void OnUpdateVSyncParameters(base::TimeTicks timebase, |
| base::TimeDelta interval) { |
| view_->GetDelegatedFrameHost()->OnUpdateVSyncParameters(timebase, interval); |
| } |
| |
| base::TimeTicks vsync_timebase() { |
| return view_->GetDelegatedFrameHost()->vsync_timebase_; |
| } |
| |
| base::TimeDelta vsync_interval() { |
| return view_->GetDelegatedFrameHost()->vsync_interval_; |
| } |
| |
| int callback_count_; |
| bool result_; |
| FakeFrameSubscriber* frame_subscriber_; // Owned by |view_|. |
| base::SimpleTestTickClock* tick_clock_; // Owned by DelegatedFrameHost. |
| const gfx::Rect view_rect_; |
| |
| private: |
| base::Closure quit_closure_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraCopyRequestTest); |
| }; |
| |
| // Tests that only one copy/readback request will be executed per one browser |
| // composite operation, even when multiple render frame swaps occur in between |
| // browser composites, and even if the frame subscriber desires more frames than |
| // the number of browser composites. |
| TEST_F(RenderWidgetHostViewAuraCopyRequestTest, DedupeFrameSubscriberRequests) { |
| InitializeView(); |
| int expected_callback_count = 0; |
| |
| // Normal case: A browser composite executes for each render frame swap. |
| for (int i = 0; i < 3; ++i) { |
| // Renderer provides another frame and the Browser composites with the |
| // frame, executing the copy request, and then the result is delivered. |
| SubmitCompositorFrame(); |
| RunOnCompositingDidCommitAndReleaseFrame(); |
| |
| // The callback should be run with success status. |
| ++expected_callback_count; |
| ASSERT_EQ(expected_callback_count, callback_count_); |
| EXPECT_TRUE(result_); |
| } |
| |
| // De-duping case: One browser composite executes per varied number of render |
| // frame swaps. |
| for (int i = 0; i < 3; ++i) { |
| const int num_swaps = 1 + i % 3; |
| |
| // The renderer provides |num_swaps| frames. |
| for (int j = 0; j < num_swaps; ++j) { |
| SubmitCompositorFrame(); |
| if (j > 0) { |
| ++expected_callback_count; |
| ASSERT_EQ(expected_callback_count, callback_count_); |
| EXPECT_FALSE(result_); // The prior copy request was aborted. |
| } |
| } |
| |
| // Browser composites with the frame, executing the last copy request that |
| // was made, and then the result is delivered. |
| RunOnCompositingDidCommitAndReleaseFrame(); |
| |
| // The final callback should be run with success status. |
| ++expected_callback_count; |
| ASSERT_EQ(expected_callback_count, callback_count_); |
| EXPECT_TRUE(result_); |
| } |
| |
| // Destroy the RenderWidgetHostViewAura and ImageTransportFactory. |
| TearDownEnvironment(); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraCopyRequestTest, DestroyedAfterCopyRequest) { |
| InitializeView(); |
| |
| SubmitCompositorFrame(); |
| EXPECT_EQ(0, callback_count_); |
| EXPECT_TRUE(view_->last_copy_request_); |
| EXPECT_TRUE(view_->last_copy_request_->has_texture_mailbox()); |
| |
| // Notify DelegatedFrameHost that the copy requests were moved to the |
| // compositor thread by calling OnCompositingDidCommit(). |
| // |
| // Send back the mailbox included in the request. There's no release callback |
| // since the mailbox came from the RWHVA originally. |
| RunOnCompositingDidCommitAndReleaseFrame(); |
| |
| // The callback should succeed. |
| EXPECT_EQ(1, callback_count_); |
| EXPECT_TRUE(result_); |
| |
| SubmitCompositorFrame(); |
| EXPECT_EQ(1, callback_count_); |
| std::unique_ptr<cc::CopyOutputRequest> request = |
| std::move(view_->last_copy_request_); |
| |
| // Destroy the RenderWidgetHostViewAura and ImageTransportFactory. |
| TearDownEnvironment(); |
| |
| // Send the result after-the-fact. It goes nowhere since DelegatedFrameHost |
| // has been destroyed. |
| request->SendTextureResult(view_rect_.size(), request->texture_mailbox(), |
| std::unique_ptr<cc::SingleReleaseCallback>()); |
| |
| // Because the copy request callback may be holding state within it, that |
| // state must handle the RWHVA and ImageTransportFactory going away before the |
| // callback is called. This test passes if it does not crash as a result of |
| // these things being destroyed. |
| EXPECT_EQ(2, callback_count_); |
| EXPECT_FALSE(result_); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraCopyRequestTest, PresentTime) { |
| InitializeView(); |
| InstallFakeTickClock(); |
| |
| // Verify our initial state. |
| EXPECT_EQ(base::TimeTicks(), frame_subscriber_->last_present_time()); |
| EXPECT_EQ(base::TimeTicks(), tick_clock_->NowTicks()); |
| |
| // Start our fake clock from a non-zero, but not an even multiple of the |
| // interval, value to differentiate it from our initialization state. |
| const base::TimeDelta kDefaultInterval = |
| cc::BeginFrameArgs::DefaultInterval(); |
| tick_clock_->Advance(kDefaultInterval / 3); |
| |
| // Swap the first frame without any vsync information. |
| ASSERT_EQ(base::TimeTicks(), vsync_timebase()); |
| ASSERT_EQ(base::TimeDelta(), vsync_interval()); |
| |
| // During this first call, there is no known vsync information, so while the |
| // callback should succeed the present time is effectively just current time. |
| SubmitCompositorFrameAndRelease(); |
| EXPECT_EQ(tick_clock_->NowTicks(), frame_subscriber_->last_present_time()); |
| |
| // Now initialize the vsync parameters with a null timebase, but a known vsync |
| // interval; which should give us slightly better frame time estimates. |
| OnUpdateVSyncParameters(base::TimeTicks(), kDefaultInterval); |
| ASSERT_EQ(base::TimeTicks(), vsync_timebase()); |
| ASSERT_EQ(kDefaultInterval, vsync_interval()); |
| |
| // Now that we have a vsync interval, the presentation time estimate should be |
| // the nearest presentation interval, which is just kDefaultInterval since our |
| // tick clock is initialized to a time before that. |
| SubmitCompositorFrameAndRelease(); |
| EXPECT_EQ(base::TimeTicks() + kDefaultInterval, |
| frame_subscriber_->last_present_time()); |
| |
| // Now initialize the vsync parameters with a valid timebase and a known vsync |
| // interval; which should give us the best frame time estimates. |
| const base::TimeTicks kBaseTime = tick_clock_->NowTicks(); |
| OnUpdateVSyncParameters(kBaseTime, kDefaultInterval); |
| ASSERT_EQ(kBaseTime, vsync_timebase()); |
| ASSERT_EQ(kDefaultInterval, vsync_interval()); |
| |
| // Now that we have a vsync interval and a timebase, the presentation time |
| // should be based on the number of vsync intervals which have elapsed since |
| // the vsync timebase. Advance time by a non integer number of intervals to |
| // verify. |
| const double kElapsedIntervals = 2.5; |
| tick_clock_->Advance(kDefaultInterval * kElapsedIntervals); |
| SubmitCompositorFrameAndRelease(); |
| EXPECT_EQ(kBaseTime + kDefaultInterval * std::ceil(kElapsedIntervals), |
| frame_subscriber_->last_present_time()); |
| |
| // Destroy the RenderWidgetHostViewAura and ImageTransportFactory. |
| TearDownEnvironment(); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraTest, VisibleViewportTest) { |
| gfx::Rect view_rect(100, 100); |
| |
| view_->InitAsChild(nullptr); |
| aura::client::ParentWindowWithContext( |
| view_->GetNativeView(), |
| parent_view_->GetNativeView()->GetRootWindow(), |
| gfx::Rect()); |
| view_->SetSize(view_rect.size()); |
| view_->Show(); |
| |
| // Defaults to full height of the view. |
| EXPECT_EQ(100, view_->GetVisibleViewportSize().height()); |
| |
| widget_host_->ResetSizeAndRepaintPendingFlags(); |
| sink_->ClearMessages(); |
| view_->SetInsets(gfx::Insets(0, 0, 40, 0)); |
| |
| EXPECT_EQ(60, view_->GetVisibleViewportSize().height()); |
| |
| const IPC::Message *message = sink_->GetFirstMessageMatching( |
| ViewMsg_Resize::ID); |
| ASSERT_TRUE(message != nullptr); |
| |
| ViewMsg_Resize::Param params; |
| ViewMsg_Resize::Read(message, ¶ms); |
| EXPECT_EQ(60, std::get<0>(params).visible_viewport_size.height()); |
| } |
| |
| // Ensures that touch event positions are never truncated to integers. |
| TEST_F(RenderWidgetHostViewAuraTest, TouchEventPositionsArentRounded) { |
| const float kX = 30.58f; |
| const float kY = 50.23f; |
| |
| view_->InitAsChild(nullptr); |
| view_->Show(); |
| |
| ui::TouchEvent press( |
| ui::ET_TOUCH_PRESSED, gfx::Point(), ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0)); |
| press.set_location_f(gfx::PointF(kX, kY)); |
| press.set_root_location_f(gfx::PointF(kX, kY)); |
| |
| view_->OnTouchEvent(&press); |
| EXPECT_EQ(ui::MotionEvent::ACTION_DOWN, pointer_state().GetAction()); |
| EXPECT_EQ(1U, pointer_state().GetPointerCount()); |
| EXPECT_EQ(kX, pointer_state().GetX(0)); |
| EXPECT_EQ(kY, pointer_state().GetY(0)); |
| } |
| |
| void RenderWidgetHostViewAuraOverscrollTest::WheelNotPreciseScrollEvent() { |
| SetUpOverscrollEnvironment(); |
| |
| // Simulate wheel events. |
| SimulateWheelEvent(-5, 0, 0, false); // sent directly |
| SimulateWheelEvent(-60, 1, 0, false); // enqueued |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Receive ACK the first wheel event as not processed. |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(true, true); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(true); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, WheelNotPreciseScrollEvent) { |
| WheelNotPreciseScrollEvent(); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest, |
| WheelNotPreciseScrollEvent) { |
| WheelNotPreciseScrollEvent(); |
| } |
| |
| void RenderWidgetHostViewAuraOverscrollTest::WheelScrollEventOverscrolls() { |
| SetUpOverscrollEnvironment(); |
| |
| // Simulate wheel events. |
| SimulateWheelEvent(-5, 0, 0, true); // sent directly |
| SimulateWheelEvent(-1, 1, 0, true); // enqueued |
| SimulateWheelEvent(-10, -3, 0, true); // coalesced into previous event |
| SimulateWheelEvent(-15, -1, 0, true); // coalesced into previous event |
| SimulateWheelEvent(-30, -3, 0, true); // coalesced into previous event |
| SimulateWheelEvent(-20, 6, 1, true); // enqueued, different modifiers |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Receive ACK the first wheel event as not processed. |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(true, true); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Receive ACK for the second (coalesced) event as not processed. This will |
| // start a back navigation. However, this will also cause the queued next |
| // event to be sent to the renderer. But since overscroll navigation has |
| // started, that event will also be included in the overscroll computation |
| // instead of being sent to the renderer. So the result will be an overscroll |
| // back navigation, and no ScrollUpdate event will be sent to the renderer. |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, true); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| if (wheel_scroll_latching_enabled_) { |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| } else { |
| // ScrollBegin and ScrollEnd will be queued events. |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| } |
| |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(-81.f, overscroll_delta_x()); |
| EXPECT_EQ(-31.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| EXPECT_EQ(0U, sink_->message_count()); |
| |
| // Send a mouse-move event. This should cancel the overscroll navigation. |
| SimulateMouseMove(5, 10, 0); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(1U, sink_->message_count()); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, WheelScrollEventOverscrolls) { |
| WheelScrollEventOverscrolls(); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest, |
| WheelScrollEventOverscrolls) { |
| WheelScrollEventOverscrolls(); |
| } |
| |
| // Tests that if some scroll events are consumed towards the start, then |
| // subsequent scrolls do not horizontal overscroll. |
| void RenderWidgetHostViewAuraOverscrollTest:: |
| WheelScrollConsumedDoNotHorizOverscroll() { |
| SetUpOverscrollEnvironment(); |
| |
| // Simulate wheel events. |
| SimulateWheelEvent(-5, 0, 0, true); // sent directly |
| SimulateWheelEvent(-1, -1, 0, true); // enqueued |
| SimulateWheelEvent(-10, -3, 0, true); // coalesced into previous event |
| SimulateWheelEvent(-15, -1, 0, true); // coalesced into previous event |
| SimulateWheelEvent(-30, -3, 0, true); // coalesced into previous event |
| SimulateWheelEvent(-20, 6, 1, true); // enqueued, different modifiers |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Receive ACK the first wheel event as processed. |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(true, true); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Receive ACK for the second (coalesced) event as not processed. This should |
| // not initiate overscroll, since the beginning of the scroll has been |
| // consumed. The queued event with different modifiers should be sent to the |
| // renderer. |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, true); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(true); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, |
| WheelScrollConsumedDoNotHorizOverscroll) { |
| WheelScrollConsumedDoNotHorizOverscroll(); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest, |
| WheelScrollConsumedDoNotHorizOverscroll) { |
| WheelScrollConsumedDoNotHorizOverscroll(); |
| } |
| |
| // Tests that wheel-scrolling correctly turns overscroll on and off. |
| void RenderWidgetHostViewAuraOverscrollTest::WheelScrollOverscrollToggle() { |
| SetUpOverscrollEnvironment(); |
| |
| // Send a wheel event. ACK the event as not processed. This should not |
| // initiate an overscroll gesture since it doesn't cross the threshold yet. |
| SimulateWheelEvent(10, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(true, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Scroll some more so as to not overscroll. |
| SimulateWheelEvent(10, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Scroll some more to initiate an overscroll. |
| SimulateWheelEvent(40, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(60.f, overscroll_delta_x()); |
| EXPECT_EQ(10.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| |
| // Scroll in the reverse direction enough to abort the overscroll. |
| SimulateWheelEvent(-20, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| if (wheel_scroll_latching_enabled_) { |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| } else { |
| // ScrollBegin and ScrollEnd will be queued events. |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| } |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Continue to scroll in the reverse direction. |
| SimulateWheelEvent(-20, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Continue to scroll in the reverse direction enough to initiate overscroll |
| // in that direction. |
| SimulateWheelEvent(-55, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(true); |
| |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(-75.f, overscroll_delta_x()); |
| EXPECT_EQ(-25.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, WheelScrollOverscrollToggle) { |
| WheelScrollOverscrollToggle(); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest, |
| WheelScrollOverscrollToggle) { |
| WheelScrollOverscrollToggle(); |
| } |
| |
| void RenderWidgetHostViewAuraOverscrollTest::ScrollEventsOverscrollWithFling() { |
| SetUpOverscrollEnvironment(); |
| |
| // Send a wheel event. ACK the event as not processed. This should not |
| // initiate an overscroll gesture since it doesn't cross the threshold yet. |
| SimulateWheelEvent(10, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(true, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Scroll some more so as to not overscroll. |
| SimulateWheelEvent(20, 0, 0, true); |
| EXPECT_EQ(1U, sink_->message_count()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| sink_->ClearMessages(); |
| |
| // Scroll some more to initiate an overscroll. |
| SimulateWheelEvent(30, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| |
| EXPECT_EQ(60.f, overscroll_delta_x()); |
| EXPECT_EQ(10.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| |
| // Send a fling start, but with a small velocity, so that the overscroll is |
| // aborted. The fling should proceed to the renderer, through the gesture |
| // event filter. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureFlingStartEvent(0.f, 0.1f, blink::kWebGestureDeviceTouchpad); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| |
| if (!wheel_scroll_latching_enabled_) { |
| // ScrollBegin and FlingStart will be queued events. |
| EXPECT_EQ(2U, sink_->message_count()); |
| } else { |
| // ScrollEnd, ScrollBegin, and FlingStart will be queued events. |
| EXPECT_EQ(3U, sink_->message_count()); |
| } |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, |
| ScrollEventsOverscrollWithFling) { |
| ScrollEventsOverscrollWithFling(); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest, |
| ScrollEventsOverscrollWithFling) { |
| ScrollEventsOverscrollWithFling(); |
| } |
| |
| // Same as ScrollEventsOverscrollWithFling, but with zero velocity. Checks that |
| // the zero-velocity fling does not reach the renderer. |
| void RenderWidgetHostViewAuraOverscrollTest:: |
| ScrollEventsOverscrollWithZeroFling() { |
| SetUpOverscrollEnvironment(); |
| |
| // Send a wheel event. ACK the event as not processed. This should not |
| // initiate an overscroll gesture since it doesn't cross the threshold yet. |
| SimulateWheelEvent(10, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(true, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Scroll some more so as to not overscroll. |
| SimulateWheelEvent(20, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Scroll some more to initiate an overscroll. |
| SimulateWheelEvent(30, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| |
| EXPECT_EQ(60.f, overscroll_delta_x()); |
| EXPECT_EQ(10.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| |
| // Send a fling start, but with a small velocity, so that the overscroll is |
| // aborted. The fling should proceed to the renderer, through the gesture |
| // event filter. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureFlingStartEvent(10.f, 0.f, blink::kWebGestureDeviceTouchpad); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| |
| if (!wheel_scroll_latching_enabled_) { |
| // ScrollBegin and FlingStart will be queued events. |
| EXPECT_EQ(2U, sink_->message_count()); |
| } else { |
| // ScrollEnd, ScrollBegin, and FlingStart will be queued events. |
| EXPECT_EQ(3U, sink_->message_count()); |
| } |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, |
| ScrollEventsOverscrollWithZeroFling) { |
| ScrollEventsOverscrollWithZeroFling(); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest, |
| ScrollEventsOverscrollWithZeroFling) { |
| ScrollEventsOverscrollWithZeroFling(); |
| } |
| |
| // Tests that a fling in the opposite direction of the overscroll cancels the |
| // overscroll nav instead of completing it. |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, ReverseFlingCancelsOverscroll) { |
| SetUpOverscrollEnvironment(); |
| |
| { |
| // Start and end a gesture in the same direction without processing the |
| // gesture events in the renderer. This should initiate and complete an |
| // overscroll navigation. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureScrollUpdateEvent(300, -5, 0); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| sink_->ClearMessages(); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->completed_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(1U, sink_->message_count()); |
| } |
| |
| { |
| // Start over, except instead of ending the gesture with ScrollEnd, end it |
| // with a FlingStart, with velocity in the reverse direction. This should |
| // initiate an overscroll navigation, but it should be cancelled because of |
| // the fling in the opposite direction. |
| overscroll_delegate()->Reset(); |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureScrollUpdateEvent(-300, -5, 0); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_delegate()->current_mode()); |
| sink_->ClearMessages(); |
| |
| SimulateGestureFlingStartEvent(100, 0, blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->completed_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(1U, sink_->message_count()); |
| } |
| } |
| |
| // Tests that touch-scroll events are handled correctly by the overscroll |
| // controller. This also tests that the overscroll controller and the |
| // gesture-event filter play nice with each other. |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, GestureScrollOverscrolls) { |
| SetUpOverscrollEnvironment(); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Send another gesture event and ACK as not being processed. This should |
| // initiate the navigation gesture. |
| SimulateGestureScrollUpdateEvent(55, -5, 0); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(55.f, overscroll_delta_x()); |
| EXPECT_EQ(-5.f, overscroll_delta_y()); |
| EXPECT_EQ(5.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(-5.f, overscroll_delegate()->delta_y()); |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| |
| // Send another gesture update event. This event should be consumed by the |
| // controller, and not be forwarded to the renderer. The gesture-event filter |
| // should not also receive this event. |
| SimulateGestureScrollUpdateEvent(10, -5, 0); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(65.f, overscroll_delta_x()); |
| EXPECT_EQ(-10.f, overscroll_delta_y()); |
| EXPECT_EQ(15.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(-10.f, overscroll_delegate()->delta_y()); |
| EXPECT_EQ(0U, sink_->message_count()); |
| |
| // Now send a scroll end. This should cancel the overscroll gesture, and send |
| // the event to the renderer. The gesture-event filter should receive this |
| // event. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(1U, sink_->message_count()); |
| } |
| |
| // Tests that if the page is scrolled because of a scroll-gesture, then that |
| // particular scroll sequence never generates overscroll if the scroll direction |
| // is horizontal. |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, |
| GestureScrollConsumedHorizontal) { |
| SetUpOverscrollEnvironment(); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureScrollUpdateEvent(10, 0, 0); |
| |
| // Start scrolling on content. ACK both events as being processed. |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| sink_->ClearMessages(); |
| |
| // Send another gesture event and ACK as not being processed. This should |
| // not initiate overscroll because the beginning of the scroll event did |
| // scroll some content on the page. Since there was no overscroll, the event |
| // should reach the renderer. |
| SimulateGestureScrollUpdateEvent(55, 0, 0); |
| EXPECT_EQ(1U, sink_->message_count()); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| } |
| |
| // Tests that the overscroll controller plays nice with touch-scrolls and the |
| // gesture event filter with debounce filtering turned on. |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, |
| GestureScrollDebounceOverscrolls) { |
| SetUpOverscrollEnvironmentWithDebounce(100); |
| |
| // Start scrolling. Receive ACK as it being processed. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Send update events. |
| SimulateGestureScrollUpdateEvent(25, 0, 0); |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| |
| // Quickly end and restart the scroll gesture. These two events should get |
| // discarded. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(0U, sink_->message_count()); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(0U, sink_->message_count()); |
| |
| // Send another update event. This should be sent right away. |
| SimulateGestureScrollUpdateEvent(30, 0, 0); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Receive an ACK for the first scroll-update event as not being processed. |
| // This will contribute to the overscroll gesture, but not enough for the |
| // overscroll controller to start consuming gesture events. This also cause |
| // the queued gesture event to be forwarded to the renderer. |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Send another update event. This should get into the queue. |
| SimulateGestureScrollUpdateEvent(10, 0, 0); |
| EXPECT_EQ(0U, sink_->message_count()); |
| |
| // Receive an ACK for the second scroll-update event as not being processed. |
| // This will now initiate an overscroll. This will also cause the queued |
| // gesture event to be released. But instead of going to the renderer, it will |
| // be consumed by the overscroll controller. |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(65.f, overscroll_delta_x()); |
| EXPECT_EQ(15.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| EXPECT_EQ(0U, sink_->message_count()); |
| } |
| |
| // Tests that the gesture debounce timer plays nice with the overscroll |
| // controller. |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, |
| GestureScrollDebounceTimerOverscroll) { |
| SetUpOverscrollEnvironmentWithDebounce(10); |
| |
| // Start scrolling. Receive ACK as it being processed. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Send update events. |
| SimulateGestureScrollUpdateEvent(55, 0, 0); |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| |
| // Send an end event. This should get in the debounce queue. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(0U, sink_->message_count()); |
| |
| // Receive ACK for the scroll-update event. |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(55.f, overscroll_delta_x()); |
| EXPECT_EQ(5.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| EXPECT_EQ(0U, sink_->message_count()); |
| |
| // Let the timer for the debounce queue fire. That should release the queued |
| // scroll-end event. Since overscroll has started, but there hasn't been |
| // enough overscroll to complete the gesture, the overscroll controller |
| // will reset the state. The scroll-end should therefore be dispatched to the |
| // renderer, and the gesture-event-filter should await an ACK for it. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), |
| base::TimeDelta::FromMilliseconds(15)); |
| base::RunLoop().Run(); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(1U, sink_->message_count()); |
| } |
| |
| // Tests that when touch-events are dispatched to the renderer, the overscroll |
| // gesture deals with them correctly. |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, OverscrollWithTouchEvents) { |
| SetUpOverscrollEnvironmentWithDebounce(10); |
| widget_host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true)); |
| sink_->ClearMessages(); |
| |
| // The test sends an intermingled sequence of touch and gesture events. |
| PressTouchPoint(0, 1); |
| uint32_t touch_press_event_id1 = SendTouchEvent(); |
| SendTouchEventACK(WebInputEvent::kTouchStart, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED, touch_press_event_id1); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| MoveTouchPoint(0, 20, 5); |
| uint32_t touch_move_event_id1 = SendTouchEvent(); |
| SendTouchEventACK(WebInputEvent::kTouchMove, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED, touch_move_event_id1); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SimulateGestureScrollUpdateEvent(20, 0, 0); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Another touch move event should reach the renderer since overscroll hasn't |
| // started yet. Note that touch events sent during the scroll period may |
| // not require an ack (having been marked uncancelable). |
| MoveTouchPoint(0, 65, 10); |
| SendTouchEvent(); |
| AckLastSentInputEventIfNecessary(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| SimulateGestureScrollUpdateEvent(45, 0, 0); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(65.f, overscroll_delta_x()); |
| EXPECT_EQ(15.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Send another touch event. The page should get the touch-move event, even |
| // though overscroll has started. |
| MoveTouchPoint(0, 55, 5); |
| SendTouchEvent(); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(65.f, overscroll_delta_x()); |
| EXPECT_EQ(15.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| AckLastSentInputEventIfNecessary(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| SimulateGestureScrollUpdateEvent(-10, 0, 0); |
| EXPECT_EQ(0U, sink_->message_count()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(55.f, overscroll_delta_x()); |
| EXPECT_EQ(5.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| |
| PressTouchPoint(255, 5); |
| SendTouchEvent(); |
| AckLastSentInputEventIfNecessary(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| SimulateGestureScrollUpdateEvent(200, 0, 0); |
| EXPECT_EQ(0U, sink_->message_count()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(255.f, overscroll_delta_x()); |
| EXPECT_EQ(205.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| |
| // The touch-end/cancel event should always reach the renderer if the page has |
| // touch handlers. |
| ReleaseTouchPoint(1); |
| SendTouchEvent(); |
| AckLastSentInputEventIfNecessary(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| ReleaseTouchPoint(0); |
| SendTouchEvent(); |
| AckLastSentInputEventIfNecessary(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| SimulateGestureEvent(blink::WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), |
| base::TimeDelta::FromMilliseconds(10)); |
| base::RunLoop().Run(); |
| EXPECT_EQ(1U, sink_->message_count()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->completed_mode()); |
| } |
| |
| // Tests that touch-gesture end is dispatched to the renderer at the end of a |
| // touch-gesture initiated overscroll. |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, |
| TouchGestureEndDispatchedAfterOverscrollComplete) { |
| SetUpOverscrollEnvironmentWithDebounce(10); |
| widget_host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true)); |
| sink_->ClearMessages(); |
| |
| // Start scrolling. Receive ACK as it being processed. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| // The scroll begin event will have received a synthetic ack from the input |
| // router. |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Send update events. |
| SimulateGestureScrollUpdateEvent(55, -5, 0); |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(0U, sink_->message_count()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(55.f, overscroll_delta_x()); |
| EXPECT_EQ(5.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(-5.f, overscroll_delegate()->delta_y()); |
| |
| // Send end event. |
| SimulateGestureEvent(blink::WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(0U, sink_->message_count()); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), |
| base::TimeDelta::FromMilliseconds(10)); |
| base::RunLoop().Run(); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->completed_mode()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Start scrolling. Receive ACK as it being processed. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Send update events. |
| SimulateGestureScrollUpdateEvent(235, -5, 0); |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(0U, sink_->message_count()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(235.f, overscroll_delta_x()); |
| EXPECT_EQ(185.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(-5.f, overscroll_delegate()->delta_y()); |
| |
| // Send end event. |
| SimulateGestureEvent(blink::WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(0U, sink_->message_count()); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), |
| base::TimeDelta::FromMilliseconds(10)); |
| base::RunLoop().Run(); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->completed_mode()); |
| EXPECT_EQ(1U, sink_->message_count()); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, OverscrollDirectionChange) { |
| SetUpOverscrollEnvironmentWithDebounce(100); |
| |
| // Start scrolling. Receive ACK as it being processed. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Send update events and receive ack as not consumed. |
| SimulateGestureScrollUpdateEvent(125, -5, 0); |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(0U, sink_->message_count()); |
| |
| // Send another update event, but in the reverse direction. The overscroll |
| // controller will not consume the event, because it is not triggering |
| // gesture-nav. |
| SimulateGestureScrollUpdateEvent(-260, 0, 0); |
| EXPECT_EQ(1U, sink_->message_count()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| |
| // Since the overscroll mode has been reset, the next scroll update events |
| // should reach the renderer. |
| SimulateGestureScrollUpdateEvent(-20, 0, 0); |
| EXPECT_EQ(1U, sink_->message_count()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| } |
| |
| void RenderWidgetHostViewAuraOverscrollTest:: |
| OverscrollDirectionChangeMouseWheel() { |
| SetUpOverscrollEnvironment(); |
| |
| // Send wheel event and receive ack as not consumed. |
| SimulateWheelEvent(125, -5, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(true, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| |
| // Send another wheel event, but in the reverse direction. The overscroll |
| // controller will not consume the event, because it is not triggering |
| // gesture-nav. |
| |
| SimulateWheelEvent(-260, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| // Since it was unhandled; the overscroll should now be west |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_delegate()->current_mode()); |
| |
| SimulateWheelEvent(-20, 0, 0, true); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| // wheel event ack generates gesture scroll update; which gets consumed |
| // solely by the overflow controller. |
| if (!wheel_scroll_latching_enabled_) { |
| // No ScrollUpdates, only ScrollBegin and ScrollEnd will be queued events. |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| } else { |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| } |
| |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_delegate()->current_mode()); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, |
| OverscrollDirectionChangeMouseWheel) { |
| OverscrollDirectionChangeMouseWheel(); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest, |
| OverscrollDirectionChangeMouseWheel) { |
| OverscrollDirectionChangeMouseWheel(); |
| } |
| |
| void RenderWidgetHostViewAuraOverscrollTest::OverscrollMouseMoveCompletion() { |
| SetUpOverscrollEnvironment(); |
| |
| SimulateWheelEvent(5, 0, 0, true); // sent directly |
| SimulateWheelEvent(-1, 0, 0, true); // enqueued |
| SimulateWheelEvent(-10, -3, 0, true); // coalesced into previous event |
| SimulateWheelEvent(-15, -1, 0, true); // coalesced into previous event |
| SimulateWheelEvent(-30, -3, 0, true); // coalesced into previous event |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Receive ACK the first wheel event as not processed. |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(true, true); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| |
| // Receive ACK for the second (coalesced) event as not processed. This will |
| // start an overcroll gesture. |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_delegate()->current_mode()); |
| |
| // Send a mouse-move event. This should cancel the overscroll navigation |
| // (since the amount overscrolled is not above the threshold), and so the |
| // mouse-move should reach the renderer. |
| SimulateMouseMove(5, 10, 0); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->completed_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| SendInputEventACK(WebInputEvent::kMouseMove, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| // Moving the mouse more should continue to send the events to the renderer. |
| SimulateMouseMove(5, 10, 0); |
| SendInputEventACK(WebInputEvent::kMouseMove, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Now try with gestures. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureScrollUpdateEvent(300, -5, 0); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| sink_->ClearMessages(); |
| |
| // Overscroll gesture is in progress. Send a mouse-move now. This should |
| // complete the gesture (because the amount overscrolled is above the |
| // threshold). |
| SimulateMouseMove(5, 10, 0); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->completed_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| SendInputEventACK(WebInputEvent::kMouseMove, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Move mouse some more. The mouse-move events should reach the renderer. |
| SimulateMouseMove(5, 10, 0); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| SendInputEventACK(WebInputEvent::kMouseMove, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, OverscrollMouseMoveCompletion) { |
| OverscrollMouseMoveCompletion(); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest, |
| OverscrollMouseMoveCompletion) { |
| OverscrollMouseMoveCompletion(); |
| } |
| |
| // Tests that if a page scrolled, then the overscroll controller's states are |
| // reset after the end of the scroll. |
| void RenderWidgetHostViewAuraOverscrollTest:: |
| OverscrollStateResetsAfterScroll() { |
| SetUpOverscrollEnvironment(); |
| |
| SimulateWheelEvent(0, 5, 0, true); // sent directly |
| SimulateWheelEvent(0, 30, 0, true); // enqueued |
| SimulateWheelEvent(0, 40, 0, true); // coalesced into previous event |
| SimulateWheelEvent(0, 10, 0, true); // coalesced into previous event |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // The first wheel event is consumed. Dispatches the queued wheel event. |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(true, true); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| EXPECT_TRUE(ScrollStateIsContentScrolling()); |
| |
| // The second wheel event is consumed. |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(true); |
| EXPECT_TRUE(ScrollStateIsContentScrolling()); |
| |
| // Touchpad scroll can end with a zero-velocity fling. But it is not |
| // dispatched, but it should still reset the overscroll controller state. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureFlingStartEvent(0.f, 0.f, blink::kWebGestureDeviceTouchpad); |
| EXPECT_TRUE(ScrollStateIsUnknown()); |
| // ScrollBegin will be queued events. |
| EXPECT_EQ(1U, sink_->message_count()); |
| |
| // Dropped flings should neither propagate *nor* indicate that they were |
| // consumed and have triggered a fling animation (as tracked by the router). |
| EXPECT_FALSE(parent_host_->input_router()->HasPendingEvents()); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| // ScrollBegin, and ScrollEnd will be queued events. |
| EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); |
| |
| SimulateWheelEvent(-5, 0, 0, true); // sent directly |
| SimulateWheelEvent(-60, 0, 0, true); // enqueued |
| SimulateWheelEvent(-100, 0, 0, true); // coalesced into previous event |
| EXPECT_TRUE(ScrollStateIsUnknown()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // The first wheel scroll did not scroll content. Overscroll should not start |
| // yet, since enough hasn't been scrolled. |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(true, true); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(false); |
| EXPECT_TRUE(ScrollStateIsUnknown()); |
| |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEventsAfterMouseWheelACK(false, false); |
| |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| ExpectGestureScrollEndForWheelScrolling(true); |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode()); |
| EXPECT_TRUE(ScrollStateIsOverscrolling()); |
| |
| // The GestureScrollBegin will reset the delegate's mode, so check it here. |
| EXPECT_EQ(OVERSCROLL_WEST, overscroll_delegate()->current_mode()); |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureFlingStartEvent(0.f, 0.f, blink::kWebGestureDeviceTouchpad); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_TRUE(ScrollStateIsUnknown()); |
| // ScrollBegin will be the queued event. |
| EXPECT_EQ(1U, sink_->message_count()); |
| EXPECT_FALSE(parent_host_->input_router()->HasPendingEvents()); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, |
| OverscrollStateResetsAfterScroll) { |
| OverscrollStateResetsAfterScroll(); |
| } |
| TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest, |
| OverscrollStateResetsAfterScroll) { |
| OverscrollStateResetsAfterScroll(); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, OverscrollResetsOnBlur) { |
| SetUpOverscrollEnvironment(); |
| |
| // Start an overscroll with gesture scroll. In the middle of the scroll, blur |
| // the host. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureScrollUpdateEvent(300, -5, 0); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(3U, GetSentMessageCountAndResetSink()); |
| |
| view_->OnWindowFocused(nullptr, view_->GetNativeView()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->completed_mode()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_x()); |
| EXPECT_EQ(0.f, overscroll_delegate()->delta_y()); |
| sink_->ClearMessages(); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| |
| // Start a scroll gesture again. This should correctly start the overscroll |
| // after the threshold. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureScrollUpdateEvent(300, -5, 0); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->completed_mode()); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); |
| EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->completed_mode()); |
| EXPECT_EQ(4U, sink_->message_count()); |
| } |
| |
| // Tests that when view initiated shutdown happens (i.e. RWHView is deleted |
| // before RWH), we clean up properly and don't leak the RWHVGuest. |
| TEST_F(RenderWidgetHostViewGuestAuraTest, GuestViewDoesNotLeak) { |
| view_->InitAsChild(nullptr); |
| TearDownEnvironment(); |
| ASSERT_FALSE(guest_view_weak_.get()); |
| } |
| |
| // Tests that invalid touch events are consumed and handled |
| // synchronously. |
| TEST_F(RenderWidgetHostViewAuraTest, |
| InvalidEventsHaveSyncHandlingDisabled) { |
| view_->InitAsChild(nullptr); |
| view_->Show(); |
| GetSentMessageCountAndResetSink(); |
| |
| widget_host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true)); |
| |
| ui::TouchEvent press( |
| ui::ET_TOUCH_PRESSED, gfx::Point(30, 30), ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0)); |
| |
| // Construct a move with a touch id which doesn't exist. |
| ui::TouchEvent invalid_move( |
| ui::ET_TOUCH_MOVED, gfx::Point(30, 30), ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1)); |
| |
| // Valid press is handled asynchronously. |
| view_->OnTouchEvent(&press); |
| EXPECT_TRUE(press.synchronous_handling_disabled()); |
| EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); |
| AckLastSentInputEventIfNecessary(INPUT_EVENT_ACK_STATE_CONSUMED); |
| |
| // Invalid move is handled synchronously, but is consumed. It should not |
| // be forwarded to the renderer. |
| view_->OnTouchEvent(&invalid_move); |
| EXPECT_EQ(0U, GetSentMessageCountAndResetSink()); |
| EXPECT_FALSE(invalid_move.synchronous_handling_disabled()); |
| EXPECT_TRUE(invalid_move.stopped_propagation()); |
| } |
| |
| // Checks key event codes. |
| TEST_F(RenderWidgetHostViewAuraTest, KeyEvent) { |
| view_->InitAsChild(nullptr); |
| view_->Show(); |
| |
| ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::DomCode::US_A, |
| ui::EF_NONE); |
| view_->OnKeyEvent(&key_event); |
| |
| const NativeWebKeyboardEvent* event = delegates_.back()->last_event(); |
| ASSERT_TRUE(event); |
| EXPECT_EQ(key_event.key_code(), event->windows_key_code); |
| EXPECT_EQ(ui::KeycodeConverter::DomCodeToNativeKeycode(key_event.code()), |
| event->native_key_code); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraTest, KeyEventsHandled) { |
| view_->InitAsChild(nullptr); |
| view_->Show(); |
| |
| ui::KeyEvent key_event1(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE); |
| view_->OnKeyEvent(&key_event1); |
| // Normally event should be handled. |
| EXPECT_TRUE(key_event1.handled()); |
| |
| ASSERT_FALSE(delegates_.empty()); |
| // Make the delegate mark the event as not-handled. |
| delegates_.back()->set_pre_handle_keyboard_event_result( |
| KeyboardEventProcessingResult::HANDLED_DONT_UPDATE_EVENT); |
| ui::KeyEvent key_event2(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE); |
| view_->OnKeyEvent(&key_event2); |
| EXPECT_FALSE(key_event2.handled()); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraTest, SetCanScrollForWebMouseWheelEvent) { |
| view_->InitAsChild(nullptr); |
| view_->Show(); |
| |
| sink_->ClearMessages(); |
| |
| // Simulates the mouse wheel event with ctrl modifier applied. |
| ui::MouseWheelEvent event(gfx::Vector2d(1, 1), gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), ui::EF_CONTROL_DOWN, 0); |
| view_->OnMouseEvent(&event); |
| |
| const WebInputEvent* input_event = |
| GetInputEventFromMessage(*sink_->GetMessageAt(0)); |
| const WebMouseWheelEvent* wheel_event = |
| static_cast<const WebMouseWheelEvent*>(input_event); |
| // Check if scroll is caused when ctrl-scroll is generated from |
| // mouse wheel event. |
| EXPECT_FALSE(WebInputEventTraits::CanCauseScroll(*wheel_event)); |
| sink_->ClearMessages(); |
| |
| // Ack'ing the outstanding event should flush the pending event queue. |
| SendInputEventACK(blink::WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_CONSUMED); |
| |
| // Simulates the mouse wheel event with no modifier applied. |
| event = ui::MouseWheelEvent(gfx::Vector2d(1, 1), gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), ui::EF_NONE, 0); |
| |
| view_->OnMouseEvent(&event); |
| |
| input_event = GetInputEventFromMessage(*sink_->GetMessageAt(0)); |
| wheel_event = static_cast<const WebMouseWheelEvent*>(input_event); |
| // Check if scroll is caused when no modifier is applied to the |
| // mouse wheel event. |
| EXPECT_TRUE(WebInputEventTraits::CanCauseScroll(*wheel_event)); |
| sink_->ClearMessages(); |
| |
| SendInputEventACK(blink::WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_CONSUMED); |
| |
| // Simulates the scroll event with ctrl modifier applied. |
| ui::ScrollEvent scroll(ui::ET_SCROLL, gfx::Point(2, 2), ui::EventTimeForNow(), |
| ui::EF_CONTROL_DOWN, 0, 5, 0, 5, 2); |
| view_->OnScrollEvent(&scroll); |
| |
| input_event = GetInputEventFromMessage(*sink_->GetMessageAt(0)); |
| wheel_event = static_cast<const WebMouseWheelEvent*>(input_event); |
| // Check if scroll is caused when ctrl-touchpad-scroll is generated |
| // from scroll event. |
| EXPECT_TRUE(WebInputEventTraits::CanCauseScroll(*wheel_event)); |
| } |
| |
| // Ensures that the mapping from ui::TouchEvent to blink::WebTouchEvent doesn't |
| // lose track of the number of acks required. |
| TEST_F(RenderWidgetHostViewAuraTest, CorrectNumberOfAcksAreDispatched) { |
| view_->InitAsFullscreen(parent_view_); |
| view_->Show(); |
| view_->UseFakeDispatcher(); |
| |
| ui::TouchEvent press1( |
| ui::ET_TOUCH_PRESSED, gfx::Point(30, 30), ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0)); |
| |
| view_->OnTouchEvent(&press1); |
| SendTouchEventACK(blink::WebInputEvent::kTouchStart, |
| INPUT_EVENT_ACK_STATE_CONSUMED, press1.unique_event_id()); |
| |
| ui::TouchEvent press2( |
| ui::ET_TOUCH_PRESSED, gfx::Point(20, 20), ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1)); |
| view_->OnTouchEvent(&press2); |
| SendTouchEventACK(blink::WebInputEvent::kTouchStart, |
| INPUT_EVENT_ACK_STATE_CONSUMED, press2.unique_event_id()); |
| |
| EXPECT_EQ(2U, view_->dispatcher_->GetAndResetProcessedTouchEventCount()); |
| } |
| |
| // Tests that the scroll deltas stored within the overscroll controller get |
| // reset at the end of the overscroll gesture even if the overscroll threshold |
| // isn't surpassed and the overscroll mode stays OVERSCROLL_NONE. |
| TEST_F(RenderWidgetHostViewAuraOverscrollTest, ScrollDeltasResetOnEnd) { |
| SetUpOverscrollEnvironment(); |
| // Wheel event scroll ending with mouse move. |
| SimulateWheelEvent(-30, -10, 0, true); // sent directly |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(-30.f, overscroll_delta_x()); |
| EXPECT_EQ(-10.f, overscroll_delta_y()); |
| SimulateMouseMove(5, 10, 0); |
| EXPECT_EQ(0.f, overscroll_delta_x()); |
| EXPECT_EQ(0.f, overscroll_delta_y()); |
| |
| // Scroll gesture. |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureScrollUpdateEvent(-30, -5, 0); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(-30.f, overscroll_delta_x()); |
| EXPECT_EQ(-5.f, overscroll_delta_y()); |
| SimulateGestureEvent(WebInputEvent::kGestureScrollEnd, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(0.f, overscroll_delta_x()); |
| EXPECT_EQ(0.f, overscroll_delta_y()); |
| |
| // Wheel event scroll ending with a fling. |
| SimulateWheelEvent(5, 0, 0, true); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| SimulateWheelEvent(10, -5, 0, true); |
| SendInputEventACK(WebInputEvent::kMouseWheel, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| SendInputEventACK(WebInputEvent::kGestureScrollUpdate, |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); |
| EXPECT_EQ(15.f, overscroll_delta_x()); |
| EXPECT_EQ(-5.f, overscroll_delta_y()); |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| SimulateGestureFlingStartEvent(0.f, 0.1f, blink::kWebGestureDeviceTouchpad); |
| EXPECT_EQ(0.f, overscroll_delta_x()); |
| EXPECT_EQ(0.f, overscroll_delta_y()); |
| } |
| |
| TEST_F(RenderWidgetHostViewAuraTest, ForwardMouseEvent) { |
| aura::Window* root = parent_view_->GetNativeView()->GetRootWindow(); |
| |
| // Set up test delegate and window hierarchy. |
| aura::test::EventCountDelegate delegate; |
| std::unique_ptr<aura::Window> parent(new aura::Window(&delegate)); |
| parent->Init(ui::LAYER_TEXTURED); |
| root->AddChild(parent.get()); |
| view_->InitAsChild(parent.get()); |
| |
| // Simulate mouse events, ensure they are forwarded to delegate. |
| ui::MouseEvent mouse_event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| 0); |
| view_->OnMouseEvent(&mouse_event); |
| EXPECT_EQ("1 0", delegate.GetMouseButtonCountsAndReset()); |
| |
| // Simulate mouse events, ensure they are forwarded to delegate. |
| mouse_event = ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(1, 1), |
| gfx::Point(), ui::EventTimeForNow(), 0, 0); |
| view_->OnMouseEvent(&mouse_event); |
| EXPECT_EQ("0 1 0", delegate.GetMouseMotionCountsAndReset()); |
| |
| // Lock the mouse, simulate, and ensure they are forwarded. |
| view_->LockMouse(); |
| |
| mouse_event = |
| ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0); |
| view_->OnMouseEvent(&mouse_event); |
| EXPECT_EQ("1 0", delegate.GetMouseButtonCountsAndReset()); |
| |
| mouse_event = ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), 0, 0); |
| view_->OnMouseEvent(&mouse_event); |
| EXPECT_EQ("0 1 0", delegate.GetMouseMotionCountsAndReset()); |
| |
| view_->UnlockMouse(); |
| |
| // view_ will be destroyed when parent is destroyed. |
| view_ = nullptr; |
| } |
| |
| // This class provides functionality to test a RenderWidgetHostViewAura |
| // instance which has been hooked up to a test RenderViewHost instance and |
| // a WebContents instance. |
| class RenderWidgetHostViewAuraWithViewHarnessTest |
| : public RenderViewHostImplTestHarness { |
| public: |
| RenderWidgetHostViewAuraWithViewHarnessTest() |
| : view_(nullptr) {} |
| ~RenderWidgetHostViewAuraWithViewHarnessTest() override {} |
| |
| protected: |
| void SetUp() override { |
| RenderViewHostImplTestHarness::SetUp(); |
| // Delete the current RenderWidgetHostView instance before setting |
| // the RWHVA as the view. |
| delete contents()->GetRenderViewHost()->GetWidget()->GetView(); |
| // This instance is destroyed in the TearDown method below. |
| view_ = new RenderWidgetHostViewAura( |
| contents()->GetRenderViewHost()->GetWidget(), false); |
| } |
| |
| void TearDown() override { |
| view_->Destroy(); |
| RenderViewHostImplTestHarness::TearDown(); |
| } |
| |
| RenderWidgetHostViewAura* view() { |
| return view_; |
| } |
| |
| private: |
| RenderWidgetHostViewAura* view_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraWithViewHarnessTest); |
| }; |
| |
| // Provides a mock implementation of the WebContentsViewDelegate class. |
| // Currently provides functionality to validate the ShowContextMenu |
| // callback. |
| class MockWebContentsViewDelegate : public WebContentsViewDelegate { |
| public: |
| MockWebContentsViewDelegate() |
| : context_menu_request_received_(false) {} |
| |
| ~MockWebContentsViewDelegate() override {} |
| |
| bool context_menu_request_received() const { |
| return context_menu_request_received_; |
| } |
| |
| ui::MenuSourceType context_menu_source_type() const { |
| return context_menu_params_.source_type; |
| } |
| |
| // WebContentsViewDelegate overrides. |
| void ShowContextMenu(RenderFrameHost* render_frame_host, |
| const ContextMenuParams& params) override { |
| context_menu_request_received_ = true; |
| context_menu_params_ = params; |
| } |
| |
| void ClearState() { |
| context_menu_request_received_ = false; |
| context_menu_params_.source_type = ui::MENU_SOURCE_NONE; |
| } |
| |
| private: |
| bool context_menu_request_received_; |
| ContextMenuParams context_menu_params_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockWebContentsViewDelegate); |
| }; |
| |
| // On Windows we don't want the context menu to be displayed in the context of |
| // a long press gesture. It should be displayed when the touch is released. |
| // On other platforms we should display the context menu in the long press |
| // gesture. |
| // This test validates this behavior. |
| TEST_F(RenderWidgetHostViewAuraWithViewHarnessTest, |
| ContextMenuTest) { |
| // This instance will be destroyed when the WebContents instance is |
| // destroyed. |
| MockWebContentsViewDelegate* delegate = new MockWebContentsViewDelegate; |
| static_cast<WebContentsViewAura*>( |
| contents()->GetView())->SetDelegateForTesting(delegate); |
| |
| RenderViewHostFactory::set_is_real_render_view_host(true); |
| |
| // A context menu request with the MENU_SOURCE_MOUSE source type should |
| // result in the MockWebContentsViewDelegate::ShowContextMenu method |
| // getting called. This means that the request worked correctly. |
| ContextMenuParams context_menu_params; |
| context_menu_params.source_type = ui::MENU_SOURCE_MOUSE; |
| contents()->ShowContextMenu(contents()->GetRenderViewHost()->GetMainFrame(), |
| context_menu_params); |
| EXPECT_TRUE(delegate->context_menu_request_received()); |
| EXPECT_EQ(delegate->context_menu_source_type(), ui::MENU_SOURCE_MOUSE); |
| |
| // A context menu request with the MENU_SOURCE_TOUCH source type should |
| // result in the MockWebContentsViewDelegate::ShowContextMenu method |
| // getting called on all platforms. This means that the request worked |
| // correctly. |
| delegate->ClearState(); |
| context_menu_params.source_type = ui::MENU_SOURCE_TOUCH; |
| contents()->ShowContextMenu(contents()->GetRenderViewHost()->GetMainFrame(), |
| context_menu_params); |
| EXPECT_TRUE(delegate->context_menu_request_received()); |
| |
| // A context menu request with the MENU_SOURCE_LONG_TAP source type should |
| // result in the MockWebContentsViewDelegate::ShowContextMenu method |
| // getting called on all platforms. This means that the request worked |
| // correctly. |
| delegate->ClearState(); |
| context_menu_params.source_type = ui::MENU_SOURCE_LONG_TAP; |
| contents()->ShowContextMenu(contents()->GetRenderViewHost()->GetMainFrame(), |
| context_menu_params); |
| EXPECT_TRUE(delegate->context_menu_request_received()); |
| |
| // A context menu request with the MENU_SOURCE_LONG_PRESS source type should |
| // result in the MockWebContentsViewDelegate::ShowContextMenu method |
| // getting called on non Windows platforms. This means that the request |
| // worked correctly. On Windows this should be blocked. |
| delegate->ClearState(); |
| context_menu_params.source_type = ui::MENU_SOURCE_LONG_PRESS; |
| contents()->ShowContextMenu(contents()->GetRenderViewHost()->GetMainFrame(), |
| context_menu_params); |
| #if defined(OS_WIN) |
| EXPECT_FALSE(delegate->context_menu_request_received()); |
| #else |
| EXPECT_TRUE(delegate->context_menu_request_received()); |
| #endif |
| |
| #if defined(OS_WIN) |
| // On Windows the context menu request blocked above should be received when |
| // the ET_GESTURE_LONG_TAP gesture is sent to the RenderWidgetHostViewAura |
| // instance. This means that the touch was released. |
| delegate->ClearState(); |
| |
| ui::GestureEventDetails event_details(ui::ET_GESTURE_LONG_TAP); |
| ui::GestureEvent gesture_event( |
| 100, 100, 0, ui::EventTimeForNow(), event_details); |
| view()->OnGestureEvent(&gesture_event); |
| |
| EXPECT_TRUE(delegate->context_menu_request_received()); |
| EXPECT_EQ(delegate->context_menu_source_type(), ui::MENU_SOURCE_TOUCH); |
| #endif |
| |
| RenderViewHostFactory::set_is_real_render_view_host(false); |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // TextInputManager and IME-Related Tests |
| |
| // The test class for OOPIF IME related unit tests in RenderWidgetHostViewAura. |
| // In each test, 3 views are created where one is in process with main frame and |
| // the other two are in distinct processes (this makes a total of 4 RWHVs). |
| class InputMethodAuraTestBase : public RenderWidgetHostViewAuraTest { |
| public: |
| InputMethodAuraTestBase() {} |
| ~InputMethodAuraTestBase() override {} |
| |
| void SetUp() override { |
| RenderWidgetHostViewAuraTest::SetUp(); |
| InitializeAura(); |
| |
| view_for_first_process_ = CreateViewForProcess(tab_process()); |
| |
| second_process_host_ = CreateNewProcessHost(); |
| view_for_second_process_ = CreateViewForProcess(second_process_host_); |
| |
| third_process_host_ = CreateNewProcessHost(); |
| view_for_third_process_ = CreateViewForProcess(third_process_host_); |
| |
| views_.insert(views_.begin(), |
| {tab_view(), view_for_first_process_, |
| view_for_second_process_, view_for_third_process_}); |
| processes_.insert(processes_.begin(), |
| {tab_process(), tab_process(), second_process_host_, |
| third_process_host_}); |
| active_view_sequence_.insert(active_view_sequence_.begin(), |
| {0, 1, 2, 1, 1, 3, 0, 3, 1}); |
| } |
| |
| void TearDown() override { |
| RenderWidgetHost* widget_for_first_process = |
| view_for_first_process_->GetRenderWidgetHost(); |
| view_for_first_process_->Destroy(); |
| delete widget_for_first_process; |
| |
| RenderWidgetHost* widget_for_second_process = |
| view_for_second_process_->GetRenderWidgetHost(); |
| view_for_second_process_->Destroy(); |
| delete widget_for_second_process; |
| |
| RenderWidgetHost* widget_for_third_process = |
| view_for_third_process_->GetRenderWidgetHost(); |
| view_for_third_process_->Destroy(); |
| delete widget_for_third_process; |
| |
| RenderWidgetHostViewAuraTest::TearDown(); |
| } |
| |
| protected: |
| ui::TextInputClient* text_input_client() const { return view_; } |
| |
| bool has_composition_text() const { |
| return tab_view()->has_composition_text_; |
| } |
| |
| MockRenderProcessHost* CreateNewProcessHost() { |
| MockRenderProcessHost* process_host = |
| new MockRenderProcessHost(browser_context()); |
| return process_host; |
| } |
| |
| RenderWidgetHostImpl* CreateRenderWidgetHostForProcess( |
| MockRenderProcessHost* process_host) { |
| return new RenderWidgetHostImpl(render_widget_host_delegate(), process_host, |
| process_host->GetNextRoutingID(), false); |
| } |
| |
| TestRenderWidgetHostView* CreateViewForProcess( |
| MockRenderProcessHost* process_host) { |
| RenderWidgetHostImpl* host = CreateRenderWidgetHostForProcess(process_host); |
| TestRenderWidgetHostView* view = new TestRenderWidgetHostView(host); |
| host->SetView(view); |
| return view; |
| } |
| |
| void SetHasCompositionTextToTrue() { |
| ui::CompositionText composition_text; |
| composition_text.text = base::ASCIIToUTF16("text"); |
| tab_view()->SetCompositionText(composition_text); |
| EXPECT_TRUE(has_composition_text()); |
| } |
| |
| MockRenderProcessHost* tab_process() const { return process_host_; } |
| |
| RenderWidgetHostViewAura* tab_view() const { return view_; } |
| |
| std::vector<RenderWidgetHostViewBase*> views_; |
| std::vector<MockRenderProcessHost*> processes_; |
| // A sequence of indices in [0, 3] which determines the index of a RWHV in |
| // |views_|. This sequence is used in the tests to sequentially make a RWHV |
| // active for a subsequent IME result method call. |
| std::vector<size_t> active_view_sequence_; |
| |
| private: |
| // This will initialize |window_| in RenderWidgetHostViewAura. It is needed |
| // for RenderWidgetHostViewAura::GetInputMethod() to work. |
| void InitializeAura() { |
| view_->InitAsChild(nullptr); |
| view_->Show(); |
| } |
| |
| TestRenderWidgetHostView* view_for_first_process_; |
| MockRenderProcessHost* second_process_host_; |
| TestRenderWidgetHostView* view_for_second_process_; |
| MockRenderProcessHost* third_process_host_; |
| TestRenderWidgetHostView* view_for_third_process_; |
| |
| DISALLOW_COPY_AND_ASSIGN(InputMethodAuraTestBase); |
| }; |
| |
| // A group of tests which verify that the IME method results are routed to the |
| // right RenderWidget when there are multiple RenderWidgetHostViews on tab. Each |
| // test will verify the correctness of routing for one of the IME result |
| // methods. The method is called on ui::TextInputClient (i.e., RWHV for the tab |
| // in aura) and then the test verifies that the IPC is routed to the |
| // RenderWidget corresponding to the active view (i.e., the RenderWidget |
| // with focused <input>). |
| class InputMethodResultAuraTest : public InputMethodAuraTestBase { |
| public: |
| InputMethodResultAuraTest() {} |
| ~InputMethodResultAuraTest() override {} |
| |
| protected: |
| const IPC::Message* RunAndReturnIPCSent(const base::Closure closure, |
| MockRenderProcessHost* process, |
| int32_t message_id) { |
| process->sink().ClearMessages(); |
| closure.Run(); |
| return process->sink().GetFirstMessageMatching(message_id); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(InputMethodResultAuraTest); |
| }; |
| |
| // This test verifies ui::TextInputClient::SetCompositionText. |
| TEST_F(InputMethodResultAuraTest, SetCompositionText) { |
| base::Closure ime_call = |
| base::Bind(&ui::TextInputClient::SetCompositionText, |
| base::Unretained(text_input_client()), ui::CompositionText()); |
| for (auto index : active_view_sequence_) { |
| ActivateViewForTextInputManager(views_[index], ui::TEXT_INPUT_TYPE_TEXT); |
| EXPECT_TRUE(!!RunAndReturnIPCSent(ime_call, processes_[index], |
| InputMsg_ImeSetComposition::ID)); |
| } |
| } |
| |
| // This test is for ui::TextInputClient::ConfirmCompositionText. |
| TEST_F(InputMethodResultAuraTest, ConfirmCompositionText) { |
| base::Closure ime_call = |
| base::Bind(&ui::TextInputClient::ConfirmCompositionText, |
| base::Unretained(text_input_client())); |
| for (auto index : active_view_sequence_) { |
| ActivateViewForTextInputManager(views_[index], ui::TEXT_INPUT_TYPE_TEXT); |
| SetHasCompositionTextToTrue(); |
| EXPECT_TRUE(!!RunAndReturnIPCSent(ime_call, processes_[index], |
| InputMsg_ImeFinishComposingText::ID)); |
| } |
| } |
| |
| // This test is for ui::TextInputClient::ClearCompositionText. |
| TEST_F(InputMethodResultAuraTest, ClearCompositionText) { |
| base::Closure ime_call = |
| base::Bind(&ui::TextInputClient::ClearCompositionText, |
| base::Unretained(text_input_client())); |
| for (auto index : active_view_sequence_) { |
| ActivateViewForTextInputManager(views_[index], ui::TEXT_INPUT_TYPE_TEXT); |
| SetHasCompositionTextToTrue(); |
| EXPECT_TRUE(!!RunAndReturnIPCSent(ime_call, processes_[index], |
| InputMsg_ImeSetComposition::ID)); |
| } |
| } |
| |
| // This test is for ui::TextInputClient::InsertText with empty text. |
| TEST_F(InputMethodResultAuraTest, FinishComposingText) { |
| base::Closure ime_call = |
| base::Bind(&ui::TextInputClient::InsertText, |
| base::Unretained(text_input_client()), base::string16()); |
| for (auto index : active_view_sequence_) { |
| ActivateViewForTextInputManager(views_[index], ui::TEXT_INPUT_TYPE_TEXT); |
| SetHasCompositionTextToTrue(); |
| EXPECT_TRUE(!!RunAndReturnIPCSent(ime_call, processes_[index], |
| InputMsg_ImeFinishComposingText::ID)); |
| } |
| } |
| |
| // This test is for ui::TextInputClient::InsertText with non-empty text. |
| TEST_F(InputMethodResultAuraTest, CommitText) { |
| base::Closure ime_call = base::Bind(&ui::TextInputClient::InsertText, |
| base::Unretained(text_input_client()), |
| base::UTF8ToUTF16("hello")); |
| for (auto index : active_view_sequence_) { |
| ActivateViewForTextInputManager(views_[index], ui::TEXT_INPUT_TYPE_TEXT); |
| EXPECT_TRUE(!!RunAndReturnIPCSent(ime_call, processes_[index], |
| InputMsg_ImeCommitText::ID)); |
| } |
| } |
| |
| // This test is for RenderWidgetHostViewAura::FinishImeCompositionSession which |
| // is in response to a mouse click during an ongoing composition. |
| TEST_F(InputMethodResultAuraTest, FinishImeCompositionSession) { |
| base::Closure ime_finish_session_call = |
| base::Bind(&RenderWidgetHostViewEventHandler::FinishImeCompositionSession, |
| base::Unretained(tab_view()->event_handler())); |
| for (auto index : active_view_sequence_) { |
| ActivateViewForTextInputManager(views_[index], ui::TEXT_INPUT_TYPE_TEXT); |
| SetHasCompositionTextToTrue(); |
| EXPECT_TRUE(!!RunAndReturnIPCSent(ime_finish_session_call, |
| processes_[index], |
| InputMsg_ImeFinishComposingText::ID)); |
| } |
| } |
| |
| // This test is for ui::TextInputClient::ChangeTextDirectionAndLayoutAlignment. |
| TEST_F(InputMethodResultAuraTest, ChangeTextDirectionAndLayoutAlignment) { |
| base::Closure ime_finish_session_call = base::Bind( |
| base::IgnoreResult( |
| &RenderWidgetHostViewAura::ChangeTextDirectionAndLayoutAlignment), |
| base::Unretained(tab_view()), base::i18n::LEFT_TO_RIGHT); |
| for (auto index : active_view_sequence_) { |
| ActivateViewForTextInputManager(views_[index], ui::TEXT_INPUT_TYPE_TEXT); |
| EXPECT_TRUE(!!RunAndReturnIPCSent(ime_finish_session_call, |
| processes_[index], |
| ViewMsg_SetTextDirection::ID)); |
| } |
| } |
| |
| // A class of tests which verify the correctness of some tracked IME related |
| // state at the browser side. Each test verifies the correctness tracking for |
| // one specific state. To do so, the views are activated in a predetermined |
| // sequence and each time, the IPC call for the corresponding state is simulated |
| // through calling the method on the view. Then the test verifies that the value |
| // returned by the view or ui::TextInputClient is the expected value from IPC. |
| class InputMethodStateAuraTest : public InputMethodAuraTestBase { |
| public: |
| InputMethodStateAuraTest() {} |
| ~InputMethodStateAuraTest() override {} |
| |
| protected: |
| gfx::SelectionBound GetSelectionBoundFromRect(const gfx::Rect& rect) { |
| gfx::SelectionBound bound; |
| bound.SetEdge(gfx::PointF(rect.origin()), gfx::PointF(rect.bottom_left())); |
| return bound; |
| } |
| |
| gfx::Rect TransformRectToViewsRootCoordSpace(const gfx::Rect rect, |
| RenderWidgetHostView* view) { |
| return gfx::Rect(view->TransformPointToRootCoordSpace(rect.origin()), |
| rect.size()); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(InputMethodStateAuraTest); |
| }; |
| |
| // This test is for caret bounds which are calculated based on the tracked value |
| // for selection bounds. |
| TEST_F(InputMethodStateAuraTest, GetCaretBounds) { |
| ViewHostMsg_SelectionBounds_Params params; |
| params.is_anchor_first = true; |
| params.anchor_dir = blink::kWebTextDirectionLeftToRight; |
| params.focus_dir = blink::kWebTextDirectionLeftToRight; |
| params.anchor_rect = gfx::Rect(0, 0, 10, 10); |
| for (auto index : active_view_sequence_) { |
| ActivateViewForTextInputManager(views_[index], ui::TEXT_INPUT_TYPE_TEXT); |
| params.focus_rect = gfx::Rect(10 + index, 10 + index, 10, 10); |
| views_[index]->SelectionBoundsChanged(params); |
| |
| // Calculate the bounds. |
| gfx::SelectionBound anchor_bound = GetSelectionBoundFromRect( |
| TransformRectToViewsRootCoordSpace(params.anchor_rect, views_[index])); |
| gfx::SelectionBound focus_bound = GetSelectionBoundFromRect( |
| TransformRectToViewsRootCoordSpace(params.focus_rect, views_[index])); |
| anchor_bound.set_type(gfx::SelectionBound::LEFT); |
| focus_bound.set_type(gfx::SelectionBound::RIGHT); |
| gfx::Rect measured_rect = |
| gfx::RectBetweenSelectionBounds(anchor_bound, focus_bound); |
| |
| EXPECT_EQ(measured_rect, text_input_client()->GetCaretBounds()); |
| } |
| } |
| |
| // This test is for composition character bounds. |
| TEST_F(InputMethodStateAuraTest, GetCompositionCharacterBounds) { |
| gfx::Rect bound; |
| // Initially, there should be no bounds. |
| EXPECT_FALSE(text_input_client()->GetCompositionCharacterBounds(0, &bound)); |
| for (auto index : active_view_sequence_) { |
| ActivateViewForTextInputManager(views_[index], ui::TEXT_INPUT_TYPE_TEXT); |
| // Simulate an IPC to set character bounds for the view. |
| views_[index]->ImeCompositionRangeChanged(gfx::Range(), |
| {gfx::Rect(1, 2, 3, 4 + index)}); |
| |
| // No bounds at index 1. |
| EXPECT_FALSE(text_input_client()->GetCompositionCharacterBounds(1, &bound)); |
| |
| // Valid bound at index 0. |
| EXPECT_TRUE(text_input_client()->GetCompositionCharacterBounds(0, &bound)); |
| EXPECT_EQ(4 + (int)index, bound.height()); |
| } |
| } |
| |
| // This test is for selected text. |
| TEST_F(InputMethodStateAuraTest, GetSelectedText) { |
| base::string16 text = base::ASCIIToUTF16("some text of length 22"); |
| size_t offset = 0U; |
| gfx::Range selection_range(20, 21); |
| |
| for (auto index : active_view_sequence_) { |
| render_widget_host_delegate()->set_focused_widget( |
| RenderWidgetHostImpl::From(views_[index]->GetRenderWidgetHost())); |
| views_[index]->SelectionChanged(text, offset, selection_range); |
| base::string16 expected_text = text.substr( |
| selection_range.GetMin() - offset, selection_range.length()); |
| |
| EXPECT_EQ(expected_text, views_[index]->GetSelectedText()); |
| |
| // Changing offset to make sure that the next view has a different text |
| // selection. |
| offset++; |
| } |
| } |
| |
| // This test is for text range. |
| TEST_F(InputMethodStateAuraTest, GetTextRange) { |
| base::string16 text = base::ASCIIToUTF16("some text of length 22"); |
| size_t offset = 0U; |
| gfx::Range selection_range; |
| |
| for (auto index : active_view_sequence_) { |
| render_widget_host_delegate()->set_focused_widget( |
| RenderWidgetHostImpl::From(views_[index]->GetRenderWidgetHost())); |
| gfx::Range expected_range(offset, offset + text.length()); |
| views_[index]->SelectionChanged(text, offset, selection_range); |
| gfx::Range range_from_client; |
| |
| // For aura this always returns true. |
| EXPECT_TRUE(text_input_client()->GetTextRange(&range_from_client)); |
| EXPECT_EQ(expected_range, range_from_client); |
| |
| // Changing offset to make sure that the next view has a different text |
| // selection. |
| offset++; |
| } |
| } |
| |
| // This test is for selection range. |
| TEST_F(InputMethodStateAuraTest, GetSelectionRange) { |
| base::string16 text; |
| gfx::Range expected_range(0U, 1U); |
| |
| for (auto index : active_view_sequence_) { |
| render_widget_host_delegate()->set_focused_widget( |
| RenderWidgetHostImpl::From(views_[index]->GetRenderWidgetHost())); |
| views_[index]->SelectionChanged(text, 0U, expected_range); |
| gfx::Range range_from_client; |
| |
| // This method always returns true. |
| EXPECT_TRUE(text_input_client()->GetSelectionRange(&range_from_client)); |
| EXPECT_EQ(expected_range, range_from_client); |
| |
| // Changing range to make sure that the next view has a different text |
| // selection. |
| expected_range.set_end(expected_range.end() + 1U); |
| } |
| } |
| |
| #if defined(USE_X11) && !defined(OS_CHROMEOS) |
| // This test will verify that after selection, the selected text is written to |
| // the clipboard from the focused widget. |
| TEST_F(InputMethodStateAuraTest, SelectedTextCopiedToClipboard) { |
| ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); |
| EXPECT_TRUE(!!clipboard); |
| std::vector<std::string> texts = {"text0", "text1", "text2", "text3"}; |
| for (auto index : active_view_sequence_) { |
| clipboard->Clear(ui::CLIPBOARD_TYPE_SELECTION); |
| |
| // Focus the corresponding widget. |
| render_widget_host_delegate()->set_focused_widget( |
| RenderWidgetHostImpl::From(views_[index]->GetRenderWidgetHost())); |
| |
| // Change the selection of the currently focused widget. It suffices to just |
| // call the method on the view. |
| base::string16 expected_text = base::ASCIIToUTF16(texts[index]); |
| views_[index]->SelectionChanged(expected_text, 0U, gfx::Range(0, 5)); |
| |
| // Retrieve the selected text from clipboard and verify it is as expected. |
| base::string16 result_text; |
| clipboard->ReadText(ui::CLIPBOARD_TYPE_SELECTION, &result_text); |
| EXPECT_EQ(expected_text, result_text); |
| } |
| } |
| #endif |
| |
| // This test verifies that when any view on the page cancels an ongoing |
| // composition, the RenderWidgetHostViewAura will receive the notification and |
| // the current composition is canceled. |
| TEST_F(InputMethodStateAuraTest, ImeCancelCompositionForAllViews) { |
| for (auto* view : views_) { |
| ActivateViewForTextInputManager(view, ui::TEXT_INPUT_TYPE_TEXT); |
| // There is no composition in the beginning. |
| EXPECT_FALSE(has_composition_text()); |
| SetHasCompositionTextToTrue(); |
| view->ImeCancelComposition(); |
| // The composition must have been canceled. |
| EXPECT_FALSE(has_composition_text()); |
| } |
| } |
| |
| } // namespace content |