diff options
Diffstat (limited to 'chromium/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc')
-rw-r--r-- | chromium/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc | 1899 |
1 files changed, 1735 insertions, 164 deletions
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/chromium/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc index 120956067df..d05bd44da8b 100644 --- a/chromium/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc +++ b/chromium/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc @@ -5,20 +5,28 @@ #include "content/browser/renderer_host/render_widget_host_view_aura.h" #include "base/basictypes.h" +#include "base/command_line.h" #include "base/memory/shared_memory.h" #include "base/message_loop/message_loop.h" +#include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "cc/output/compositor_frame.h" #include "cc/output/compositor_frame_metadata.h" -#include "cc/output/gl_frame_data.h" -#include "content/browser/aura/resize_lock.h" +#include "cc/output/copy_output_request.h" #include "content/browser/browser_thread_impl.h" +#include "content/browser/compositor/resize_lock.h" +#include "content/browser/renderer_host/overscroll_controller.h" +#include "content/browser/renderer_host/overscroll_controller_delegate.h" #include "content/browser/renderer_host/render_widget_host_delegate.h" #include "content/browser/renderer_host/render_widget_host_impl.h" +#include "content/common/gpu/client/gl_helper.h" #include "content/common/gpu/gpu_messages.h" +#include "content/common/host_shared_bitmap_manager.h" +#include "content/common/input/synthetic_web_input_event_builders.h" #include "content/common/input_messages.h" #include "content/common/view_messages.h" #include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/render_widget_host_view_frame_subscriber.h" #include "content/public/test/mock_render_process_host.h" #include "content/public/test/test_browser_context.h" #include "ipc/ipc_test_sink.h" @@ -29,21 +37,32 @@ #include "ui/aura/client/window_tree_client.h" #include "ui/aura/env.h" #include "ui/aura/layout_manager.h" -#include "ui/aura/root_window.h" #include "ui/aura/test/aura_test_helper.h" +#include "ui/aura/test/event_generator.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/ui_base_types.h" #include "ui/compositor/compositor.h" -#include "ui/compositor/test/test_context_factory.h" +#include "ui/compositor/test/draw_waiter_for_test.h" +#include "ui/compositor/test/in_process_context_factory.h" #include "ui/events/event.h" #include "ui/events/event_utils.h" +#include "ui/events/gestures/gesture_configuration.h" +#include "ui/wm/core/default_activation_client.h" using testing::_; +using blink::WebGestureEvent; +using blink::WebInputEvent; +using blink::WebMouseEvent; +using blink::WebMouseWheelEvent; +using blink::WebTouchEvent; +using blink::WebTouchPoint; + namespace content { namespace { @@ -76,6 +95,61 @@ class TestScreenPositionClient } }; +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) {} + + virtual ~TestOverscrollDelegate() {} + + 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: + virtual gfx::Rect GetVisibleBounds() const OVERRIDE { + return view_->IsShowing() ? view_->GetViewBounds() : gfx::Rect(); + } + + virtual void OnOverscrollUpdate(float delta_x, float delta_y) OVERRIDE { + delta_x_ = delta_x; + delta_y_ = delta_y; + } + + virtual void OnOverscrollComplete(OverscrollMode overscroll_mode) OVERRIDE { + EXPECT_EQ(current_mode_, overscroll_mode); + completed_mode_ = overscroll_mode; + current_mode_ = OVERSCROLL_NONE; + } + + virtual void OnOverscrollModeChange(OverscrollMode old_mode, + OverscrollMode new_mode) 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() {} @@ -113,6 +187,34 @@ class TestWindowObserver : public aura::WindowObserver { DISALLOW_COPY_AND_ASSIGN(TestWindowObserver); }; +class FakeFrameSubscriber : public RenderWidgetHostViewFrameSubscriber { + public: + FakeFrameSubscriber(gfx::Size size, base::Callback<void(bool)> callback) + : size_(size), callback_(callback) {} + + virtual bool ShouldCaptureFrame(base::TimeTicks present_time, + scoped_refptr<media::VideoFrame>* storage, + DeliverFrameCallback* callback) OVERRIDE { + *storage = media::VideoFrame::CreateFrame(media::VideoFrame::YV12, + size_, + gfx::Rect(size_), + size_, + base::TimeDelta()); + *callback = base::Bind(&FakeFrameSubscriber::CallbackMethod, callback_); + return true; + } + + static void CallbackMethod(base::Callback<void(bool)> callback, + base::TimeTicks timestamp, + bool success) { + callback.Run(success); + } + + private: + gfx::Size size_; + base::Callback<void(bool)> callback_; +}; + class FakeRenderWidgetHostViewAura : public RenderWidgetHostViewAura { public: FakeRenderWidgetHostViewAura(RenderWidgetHost* widget) @@ -120,20 +222,38 @@ class FakeRenderWidgetHostViewAura : public RenderWidgetHostViewAura { virtual ~FakeRenderWidgetHostViewAura() {} - virtual bool ShouldCreateResizeLock() OVERRIDE { - gfx::Size desired_size = window()->bounds().size(); - return desired_size != current_frame_size(); - } - - virtual scoped_ptr<ResizeLock> CreateResizeLock(bool defer_compositor_lock) - OVERRIDE { + virtual scoped_ptr<ResizeLock> CreateResizeLock( + bool defer_compositor_lock) OVERRIDE { gfx::Size desired_size = window()->bounds().size(); return scoped_ptr<ResizeLock>( new FakeResizeLock(desired_size, defer_compositor_lock)); } void RunOnCompositingDidCommit() { - OnCompositingDidCommit(window()->GetDispatcher()->compositor()); + GetDelegatedFrameHost()->OnCompositingDidCommitForTesting( + window()->GetHost()->compositor()); + } + + virtual bool ShouldCreateResizeLock() OVERRIDE { + return GetDelegatedFrameHost()->ShouldCreateResizeLockForTesting(); + } + + virtual void RequestCopyOfOutput(scoped_ptr<cc::CopyOutputRequest> request) + OVERRIDE { + last_copy_request_ = request.Pass(); + if (last_copy_request_->has_texture_mailbox()) { + // Give the resulting texture a size. + GLHelper* gl_helper = ImageTransportFactory::GetInstance()->GetGLHelper(); + GLuint texture = gl_helper->ConsumeMailboxToTexture( + last_copy_request_->texture_mailbox().mailbox(), + last_copy_request_->texture_mailbox().sync_point()); + gl_helper->ResizeTexture(texture, window()->bounds().size()); + gl_helper->DeleteTexture(texture); + } + } + + cc::DelegatedFrameProvider* frame_provider() const { + return GetDelegatedFrameHost()->FrameProviderForTesting(); } // A lock that doesn't actually do anything to the compositor, and does not @@ -146,18 +266,58 @@ class FakeRenderWidgetHostViewAura : public RenderWidgetHostViewAura { bool has_resize_lock_; gfx::Size last_frame_size_; + scoped_ptr<cc::CopyOutputRequest> last_copy_request_; +}; + +// 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) {} + virtual ~FullscreenLayoutManager() {} + + // Overridden from aura::LayoutManager: + virtual void OnWindowResized() OVERRIDE { + aura::Window::Windows::const_iterator i; + for (i = owner_->children().begin(); i != owner_->children().end(); ++i) { + (*i)->SetBounds(gfx::Rect()); + } + } + virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE { + child->SetBounds(gfx::Rect()); + } + virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE {} + virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE {} + virtual void OnChildWindowVisibilityChanged(aura::Window* child, + bool visible) OVERRIDE {} + virtual 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(OnWindowPaintScheduled, void(aura::Window*, const gfx::Rect&)); }; +} // namespace + class RenderWidgetHostViewAuraTest : public testing::Test { public: RenderWidgetHostViewAuraTest() : browser_thread_for_ui_(BrowserThread::UI, &message_loop_) {} - virtual void SetUp() { + void SetUpEnvironment() { + ui::ContextFactory* context_factory = new ui::InProcessContextFactory; ImageTransportFactory::InitializeForUnitTests( - scoped_ptr<ui::ContextFactory>(new ui::TestContextFactory)); + scoped_ptr<ui::ContextFactory>(context_factory)); aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_)); - aura_test_helper_->SetUp(); + aura_test_helper_->SetUp(context_factory); + new wm::DefaultActivationClient(aura_test_helper_->root_window()); browser_context_.reset(new TestBrowserContext); process_host_ = new MockRenderProcessHost(browser_context_.get()); @@ -166,8 +326,7 @@ class RenderWidgetHostViewAuraTest : public testing::Test { parent_host_ = new RenderWidgetHostImpl( &delegate_, process_host_, MSG_ROUTING_NONE, false); - parent_view_ = static_cast<RenderWidgetHostViewAura*>( - RenderWidgetHostView::CreateViewForWidget(parent_host_)); + parent_view_ = new RenderWidgetHostViewAura(parent_host_); parent_view_->InitAsChild(NULL); aura::client::ParentWindowWithContext(parent_view_->GetNativeView(), aura_test_helper_->root_window(), @@ -176,12 +335,10 @@ class RenderWidgetHostViewAuraTest : public testing::Test { widget_host_ = new RenderWidgetHostImpl( &delegate_, process_host_, MSG_ROUTING_NONE, false); widget_host_->Init(); - widget_host_->OnMessageReceived( - ViewHostMsg_DidActivateAcceleratedCompositing(0, true)); view_ = new FakeRenderWidgetHostViewAura(widget_host_); } - virtual void TearDown() { + void TearDownEnvironment() { sink_ = NULL; process_host_ = NULL; if (view_) @@ -199,6 +356,10 @@ class RenderWidgetHostViewAuraTest : public testing::Test { ImageTransportFactory::Terminate(); } + virtual void SetUp() { SetUpEnvironment(); } + + virtual void TearDown() { TearDownEnvironment(); } + protected: base::MessageLoopForUI message_loop_; BrowserThreadImpl browser_thread_for_ui_; @@ -223,46 +384,239 @@ class RenderWidgetHostViewAuraTest : public testing::Test { DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraTest); }; -// A layout manager that always resizes a child to the root window size. -class FullscreenLayoutManager : public aura::LayoutManager { +class RenderWidgetHostViewAuraOverscrollTest + : public RenderWidgetHostViewAuraTest { public: - explicit FullscreenLayoutManager(aura::Window* owner) - : owner_(owner) {} - virtual ~FullscreenLayoutManager() {} + RenderWidgetHostViewAuraOverscrollTest() {} - // Overridden from aura::LayoutManager: - virtual void OnWindowResized() OVERRIDE { - aura::Window::Windows::const_iterator i; - for (i = owner_->children().begin(); i != owner_->children().end(); ++i) { - (*i)->SetBounds(gfx::Rect()); - } + // We explicitly invoke SetUp to allow gesture debounce customization. + virtual void SetUp() {} + + protected: + void SetUpOverscrollEnvironmentWithDebounce(int debounce_interval_in_ms) { + SetUpOverscrollEnvironmentImpl(debounce_interval_in_ms); } - virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE { - child->SetBounds(gfx::Rect()); + + void SetUpOverscrollEnvironment() { SetUpOverscrollEnvironmentImpl(0); } + + void SetUpOverscrollEnvironmentImpl(int debounce_interval_in_ms) { + ui::GestureConfiguration::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(NULL); + view_->SetBounds(gfx::Rect(0, 0, 400, 200)); + view_->Show(); + + sink_->ClearMessages(); } - virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE { + + // TODO(jdduke): Simulate ui::Events, injecting through the view. + void SimulateMouseEvent(WebInputEvent::Type type) { + widget_host_->ForwardMouseEvent(SyntheticWebMouseEventBuilder::Build(type)); } - virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE { + + void SimulateMouseEventWithLatencyInfo(WebInputEvent::Type type, + const ui::LatencyInfo& ui_latency) { + widget_host_->ForwardMouseEventWithLatencyInfo( + SyntheticWebMouseEventBuilder::Build(type), ui_latency); } - virtual void OnChildWindowVisibilityChanged(aura::Window* child, - bool visible) OVERRIDE { + + void SimulateWheelEvent(float dX, float dY, int modifiers, bool precise) { + widget_host_->ForwardWheelEvent( + SyntheticWebMouseWheelEventBuilder::Build(dX, dY, modifiers, precise)); } - virtual void SetChildBounds(aura::Window* child, - const gfx::Rect& requested_bounds) OVERRIDE { - SetChildBoundsDirect(child, gfx::Rect(owner_->bounds().size())); + + void SimulateWheelEventWithLatencyInfo(float dX, + float dY, + int modifiers, + bool precise, + const ui::LatencyInfo& ui_latency) { + widget_host_->ForwardWheelEventWithLatencyInfo( + SyntheticWebMouseWheelEventBuilder::Build(dX, dY, modifiers, precise), + ui_latency); + } + + void SimulateMouseMove(int x, int y, int modifiers) { + SimulateMouseEvent(WebInputEvent::MouseMove, 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::ButtonLeft; + 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)); + } + + void SimulateGesturePinchUpdateEvent(float scale, + float anchorX, + float anchorY, + int modifiers) { + SimulateGestureEventCore(SyntheticWebGestureEventBuilder::BuildPinchUpdate( + scale, + anchorX, + anchorY, + modifiers, + blink::WebGestureDeviceTouchscreen)); + } + + // Inject synthetic GestureFlingStart events. + void SimulateGestureFlingStartEvent(float velocityX, + float velocityY, + blink::WebGestureDevice sourceDevice) { + SimulateGestureEventCore(SyntheticWebGestureEventBuilder::BuildFling( + velocityX, velocityY, sourceDevice)); + } + + void SendInputEventACK(WebInputEvent::Type type, + InputEventAckState ack_result) { + InputHostMsg_HandleInputEvent_ACK_Params ack; + ack.type = type; + ack.state = ack_result; + InputHostMsg_HandleInputEvent_ACK response(0, ack); + widget_host_->OnMessageReceived(response); + } + + 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(); + } + + void SendTouchEvent() { + widget_host_->ForwardTouchEventWithLatencyInfo(touch_event_, + ui::LatencyInfo()); + touch_event_.ResetPoints(); + } + + void PressTouchPoint(int x, int y) { + touch_event_.PressPoint(x, y); + SendTouchEvent(); + } + + void MoveTouchPoint(int index, int x, int y) { + touch_event_.MovePoint(index, x, y); + SendTouchEvent(); + } + + void ReleaseTouchPoint(int index) { + touch_event_.ReleasePoint(index); + SendTouchEvent(); + } + + 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; + } + + if (WebInputEventTraits::IgnoresAckDisposition(*params.a)) + return; + + SendInputEventACK(params.a->type, ack_result); + } + + SyntheticWebTouchEvent touch_event_; + + scoped_ptr<TestOverscrollDelegate> overscroll_delegate_; + private: - aura::Window* owner_; - DISALLOW_COPY_AND_ASSIGN(FullscreenLayoutManager); + DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraOverscrollTest); }; -class MockWindowObserver : public aura::WindowObserver { +class RenderWidgetHostViewAuraShutdownTest + : public RenderWidgetHostViewAuraTest { public: - MOCK_METHOD2(OnWindowPaintScheduled, void(aura::Window*, const gfx::Rect&)); -}; + RenderWidgetHostViewAuraShutdownTest() {} -} // namespace + virtual void TearDown() OVERRIDE { + // No TearDownEnvironment here, we do this explicitly during the test. + } + + private: + DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraShutdownTest); +}; // Checks that a fullscreen view has the correct show-state and receives the // focus. @@ -331,7 +685,7 @@ TEST_F(RenderWidgetHostViewAuraTest, DestroyFullscreenOnBlur) { TestWindowObserver observer(window); aura::test::TestWindowDelegate delegate; scoped_ptr<aura::Window> sibling(new aura::Window(&delegate)); - sibling->Init(ui::LAYER_TEXTURED); + sibling->Init(aura::WINDOW_LAYER_TEXTURED); sibling->Show(); window->parent()->AddChild(sibling.get()); sibling->Focus(); @@ -342,21 +696,75 @@ TEST_F(RenderWidgetHostViewAuraTest, DestroyFullscreenOnBlur) { view_ = NULL; } +// 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 != NULL); + + 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); + aura::test::EventGenerator generator(window->GetRootWindow(), click_point); + generator.ClickLeftButton(); + ASSERT_TRUE(parent_view_->HasFocus()); + ASSERT_TRUE(observer.destroyed()); + + widget_host_ = NULL; + view_ = NULL; +} + +// 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 != NULL); + + 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); + aura::test::EventGenerator generator(window->GetRootWindow(), tap_point); + generator.GestureTapAt(tap_point); + ASSERT_TRUE(parent_view_->HasFocus()); + ASSERT_TRUE(observer.destroyed()); + + widget_host_ = NULL; + view_ = NULL; +} + // Checks that IME-composition-event state is maintained correctly. TEST_F(RenderWidgetHostViewAuraTest, SetCompositionText) { view_->InitAsChild(NULL); view_->Show(); ui::CompositionText composition_text; - composition_text.text = ASCIIToUTF16("|a|b"); + composition_text.text = base::ASCIIToUTF16("|a|b"); // Focused segment composition_text.underlines.push_back( - ui::CompositionUnderline(0, 3, 0xff000000, true)); + ui::CompositionUnderline(0, 3, 0xff000000, true, 0x78563412)); - // Non-focused segment + // Non-focused segment, with different background color. composition_text.underlines.push_back( - ui::CompositionUnderline(3, 4, 0xff000000, false)); + ui::CompositionUnderline(3, 4, 0xff000000, false, 0xefcdab90)); const ui::CompositionUnderlines& underlines = composition_text.underlines; @@ -382,6 +790,7 @@ TEST_F(RenderWidgetHostViewAuraTest, SetCompositionText) { EXPECT_EQ(underlines[i].end_offset, params.b[i].endOffset); EXPECT_EQ(underlines[i].color, params.b[i].color); EXPECT_EQ(underlines[i].thick, params.b[i].thick); + EXPECT_EQ(underlines[i].background_color, params.b[i].backgroundColor); } // highlighted range EXPECT_EQ(4, params.c) << "Should be the same to the caret pos"; @@ -417,6 +826,7 @@ TEST_F(RenderWidgetHostViewAuraTest, TouchEventState) { view_->OnTouchEvent(&press); EXPECT_FALSE(press.handled()); EXPECT_EQ(blink::WebInputEvent::TouchStart, view_->touch_event_.type); + EXPECT_TRUE(view_->touch_event_.cancelable); EXPECT_EQ(1U, view_->touch_event_.touchesLength); EXPECT_EQ(blink::WebTouchPoint::StatePressed, view_->touch_event_.touches[0].state); @@ -424,6 +834,7 @@ TEST_F(RenderWidgetHostViewAuraTest, TouchEventState) { view_->OnTouchEvent(&move); EXPECT_FALSE(move.handled()); EXPECT_EQ(blink::WebInputEvent::TouchMove, view_->touch_event_.type); + EXPECT_TRUE(view_->touch_event_.cancelable); EXPECT_EQ(1U, view_->touch_event_.touchesLength); EXPECT_EQ(blink::WebTouchPoint::StateMoved, view_->touch_event_.touches[0].state); @@ -431,6 +842,7 @@ TEST_F(RenderWidgetHostViewAuraTest, TouchEventState) { view_->OnTouchEvent(&release); EXPECT_FALSE(release.handled()); EXPECT_EQ(blink::WebInputEvent::TouchEnd, view_->touch_event_.type); + EXPECT_TRUE(view_->touch_event_.cancelable); EXPECT_EQ(0U, view_->touch_event_.touchesLength); // Now install some touch-event handlers and do the same steps. The touch @@ -442,6 +854,7 @@ TEST_F(RenderWidgetHostViewAuraTest, TouchEventState) { view_->OnTouchEvent(&press); EXPECT_TRUE(press.stopped_propagation()); EXPECT_EQ(blink::WebInputEvent::TouchStart, view_->touch_event_.type); + EXPECT_TRUE(view_->touch_event_.cancelable); EXPECT_EQ(1U, view_->touch_event_.touchesLength); EXPECT_EQ(blink::WebTouchPoint::StatePressed, view_->touch_event_.touches[0].state); @@ -449,6 +862,7 @@ TEST_F(RenderWidgetHostViewAuraTest, TouchEventState) { view_->OnTouchEvent(&move); EXPECT_TRUE(move.stopped_propagation()); EXPECT_EQ(blink::WebInputEvent::TouchMove, view_->touch_event_.type); + EXPECT_TRUE(view_->touch_event_.cancelable); EXPECT_EQ(1U, view_->touch_event_.touchesLength); EXPECT_EQ(blink::WebTouchPoint::StateMoved, view_->touch_event_.touches[0].state); @@ -456,6 +870,7 @@ TEST_F(RenderWidgetHostViewAuraTest, TouchEventState) { view_->OnTouchEvent(&release); EXPECT_TRUE(release.stopped_propagation()); EXPECT_EQ(blink::WebInputEvent::TouchEnd, view_->touch_event_.type); + EXPECT_TRUE(view_->touch_event_.cancelable); EXPECT_EQ(0U, view_->touch_event_.touchesLength); // Now start a touch event, and remove the event-handlers before the release. @@ -678,33 +1093,49 @@ TEST_F(RenderWidgetHostViewAuraTest, CursorVisibilityChange) { cursor_client.RemoveObserver(view_); } -scoped_ptr<cc::CompositorFrame> MakeGLFrame(float scale_factor, - gfx::Size size, - gfx::Rect damage) { - scoped_ptr<cc::CompositorFrame> frame(new cc::CompositorFrame); - frame->metadata.device_scale_factor = scale_factor; - frame->gl_frame_data.reset(new cc::GLFrameData); - frame->gl_frame_data->sync_point = 1; - memset(frame->gl_frame_data->mailbox.name, '1', 64); - frame->gl_frame_data->size = size; - frame->gl_frame_data->sub_buffer_rect = damage; - return frame.Pass(); -} +TEST_F(RenderWidgetHostViewAuraTest, UpdateCursorIfOverSelf) { + view_->InitAsChild(NULL); + aura::client::ParentWindowWithContext( + view_->GetNativeView(), + parent_view_->GetNativeView()->GetRootWindow(), + gfx::Rect()); -scoped_ptr<cc::CompositorFrame> MakeSoftwareFrame(float scale_factor, - gfx::Size size, - gfx::Rect damage) { - scoped_ptr<cc::CompositorFrame> frame(new cc::CompositorFrame); - frame->metadata.device_scale_factor = scale_factor; - frame->software_frame_data.reset(new cc::SoftwareFrameData); - frame->software_frame_data->id = 1; - frame->software_frame_data->size = size; - frame->software_frame_data->damage_rect = damage; - base::SharedMemory shm; - shm.CreateAndMapAnonymous(size.GetArea() * 4); - shm.GiveToProcess(base::GetCurrentProcessHandle(), - &frame->software_frame_data->handle); - return frame.Pass(); + // 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()); } scoped_ptr<cc::CompositorFrame> MakeDelegatedFrame(float scale_factor, @@ -715,10 +1146,8 @@ scoped_ptr<cc::CompositorFrame> MakeDelegatedFrame(float scale_factor, frame->delegated_frame_data.reset(new cc::DelegatedFrameData); scoped_ptr<cc::RenderPass> pass = cc::RenderPass::Create(); - pass->SetNew(cc::RenderPass::Id(1, 1), - gfx::Rect(size), - gfx::RectF(damage), - gfx::Transform()); + pass->SetNew( + cc::RenderPass::Id(1, 1), gfx::Rect(size), damage, gfx::Transform()); frame->delegated_frame_data->render_pass_list.push_back(pass.Pass()); return frame.Pass(); } @@ -746,9 +1175,11 @@ TEST_F(RenderWidgetHostViewAuraTest, FullscreenResize) { // Resizes are blocked until we swapped a frame of the correct size, and // we've committed it. view_->OnSwapCompositorFrame( - 0, MakeGLFrame(1.f, params.a.new_size, gfx::Rect(params.a.new_size))); + 0, + MakeDelegatedFrame( + 1.f, params.a.new_size, gfx::Rect(params.a.new_size))); ui::DrawWaiterForTest::WaitForCommit( - root_window->GetDispatcher()->compositor()); + root_window->GetHost()->compositor()); } widget_host_->ResetSizeAndRepaintPendingFlags(); @@ -767,9 +1198,11 @@ TEST_F(RenderWidgetHostViewAuraTest, FullscreenResize) { gfx::Rect(params.a.screen_info.availableRect).ToString()); EXPECT_EQ("1600x1200", params.a.new_size.ToString()); view_->OnSwapCompositorFrame( - 0, MakeGLFrame(1.f, params.a.new_size, gfx::Rect(params.a.new_size))); + 0, + MakeDelegatedFrame( + 1.f, params.a.new_size, gfx::Rect(params.a.new_size))); ui::DrawWaiterForTest::WaitForCommit( - root_window->GetDispatcher()->compositor()); + root_window->GetHost()->compositor()); } } @@ -789,78 +1222,105 @@ TEST_F(RenderWidgetHostViewAuraTest, SwapNotifiesWindow) { MockWindowObserver observer; view_->window_->AddObserver(&observer); - // Swap a frame through the GPU path. - GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params params; - params.surface_id = widget_host_->surface_id(); - params.route_id = widget_host_->GetRoutingID(); - params.mailbox_name = std::string(64, '1'); - params.size = view_size; - params.scale_factor = 1.f; - - EXPECT_CALL(observer, OnWindowPaintScheduled(view_->window_, view_rect)); - view_->AcceleratedSurfaceBuffersSwapped(params, 0); - testing::Mock::VerifyAndClearExpectations(&observer); - - // DSF = 2 - params.size = gfx::Size(200, 200); - params.scale_factor = 2.f; + // Delegated renderer path EXPECT_CALL(observer, OnWindowPaintScheduled(view_->window_, view_rect)); - view_->AcceleratedSurfaceBuffersSwapped(params, 0); + view_->OnSwapCompositorFrame( + 0, MakeDelegatedFrame(1.f, view_size, view_rect)); testing::Mock::VerifyAndClearExpectations(&observer); - // Partial frames though GPU path - GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params post_params; - post_params.surface_id = widget_host_->surface_id(); - post_params.route_id = widget_host_->GetRoutingID(); - post_params.mailbox_name = std::string(64, '1'); - post_params.surface_size = gfx::Size(200, 200); - post_params.surface_scale_factor = 2.f; - post_params.x = 40; - post_params.y = 40; - post_params.width = 80; - post_params.height = 80; - // rect from params is upside down, and is inflated in RWHVA, just because. EXPECT_CALL(observer, OnWindowPaintScheduled(view_->window_, - gfx::Rect(19, 39, 42, 42))); - view_->AcceleratedSurfacePostSubBuffer(post_params, 0); + gfx::Rect(5, 5, 5, 5))); + view_->OnSwapCompositorFrame( + 0, MakeDelegatedFrame(1.f, view_size, gfx::Rect(5, 5, 5, 5))); testing::Mock::VerifyAndClearExpectations(&observer); - // Composite-to-mailbox path - EXPECT_CALL(observer, OnWindowPaintScheduled(view_->window_, view_rect)); - view_->OnSwapCompositorFrame(0, MakeGLFrame(1.f, view_size, view_rect)); - testing::Mock::VerifyAndClearExpectations(&observer); + view_->window_->RemoveObserver(&observer); +} - // rect from GL frame is upside down, and is inflated in RWHVA, just because. - EXPECT_CALL(observer, OnWindowPaintScheduled(view_->window_, - gfx::Rect(4, 89, 7, 7))); +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(NULL); + aura::client::ParentWindowWithContext( + view_->GetNativeView(), root_window, gfx::Rect(size1)); + view_->WasShown(); + view_->SetSize(size1); view_->OnSwapCompositorFrame( - 0, MakeGLFrame(1.f, view_size, gfx::Rect(5, 5, 5, 5))); - testing::Mock::VerifyAndClearExpectations(&observer); + 0, 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.scale_factor = 1.f; + 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()); - // Software path - EXPECT_CALL(observer, OnWindowPaintScheduled(view_->window_, view_rect)); - view_->OnSwapCompositorFrame(0, MakeSoftwareFrame(1.f, view_size, view_rect)); - testing::Mock::VerifyAndClearExpectations(&observer); + // 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(), params.a.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(); - EXPECT_CALL(observer, OnWindowPaintScheduled(view_->window_, - gfx::Rect(5, 5, 5, 5))); - view_->OnSwapCompositorFrame( - 0, MakeSoftwareFrame(1.f, view_size, gfx::Rect(5, 5, 5, 5))); - testing::Mock::VerifyAndClearExpectations(&observer); + // 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()); - // Delegated renderer path - EXPECT_CALL(observer, OnWindowPaintScheduled(view_->window_, view_rect)); + // Receive a frame of the new size, should be skipped and not produce a Resize + // message. view_->OnSwapCompositorFrame( - 0, MakeDelegatedFrame(1.f, view_size, view_rect)); - testing::Mock::VerifyAndClearExpectations(&observer); + 0, MakeDelegatedFrame(1.f, size3, gfx::Rect(size3))); + // Expect the frame ack; + EXPECT_EQ(1u, sink_->message_count()); + EXPECT_EQ(ViewMsg_SwapCompositorFrameAck::ID, sink_->GetMessageAt(0)->type()); + sink_->ClearMessages(); + EXPECT_EQ(size2.ToString(), view_->GetRequestedRendererSize().ToString()); - EXPECT_CALL(observer, OnWindowPaintScheduled(view_->window_, - gfx::Rect(5, 5, 5, 5))); + // Receive a frame of the correct size, should not be skipped and, and should + // produce a Resize message after the commit. view_->OnSwapCompositorFrame( - 0, MakeDelegatedFrame(1.f, view_size, gfx::Rect(5, 5, 5, 5))); - testing::Mock::VerifyAndClearExpectations(&observer); - - view_->window_->RemoveObserver(&observer); + 0, MakeDelegatedFrame(1.f, size2, gfx::Rect(size2))); + // No frame ack yet. + EXPECT_EQ(0u, sink_->message_count()); + 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()); + EXPECT_EQ(size3.ToString(), view_->GetRequestedRendererSize().ToString()); + EXPECT_EQ(2u, sink_->message_count()); + EXPECT_EQ(ViewMsg_SwapCompositorFrameAck::ID, sink_->GetMessageAt(0)->type()); + { + const IPC::Message* msg = sink_->GetMessageAt(1); + EXPECT_EQ(ViewMsg_Resize::ID, msg->type()); + ViewMsg_Resize::Param params; + ViewMsg_Resize::Read(msg, ¶ms); + EXPECT_EQ(size3.ToString(), params.a.new_size.ToString()); + } + update_params.view_size = size3; + widget_host_->OnMessageReceived( + ViewHostMsg_UpdateRect(widget_host_->GetRoutingID(), update_params)); + sink_->ClearMessages(); } // Skipped frames should not drop their damage. @@ -897,7 +1357,6 @@ TEST_F(RenderWidgetHostViewAuraTest, SkippedDelegatedFrames) { // Lock the compositor. Now we should drop frames. view_rect = gfx::Rect(150, 150); view_->SetSize(view_rect.size()); - view_->MaybeCreateResizeLock(); // This frame is dropped. gfx::Rect dropped_damage_rect_1(10, 20, 30, 40); @@ -934,6 +1393,21 @@ TEST_F(RenderWidgetHostViewAuraTest, SkippedDelegatedFrames) { view_->RunOnCompositingDidCommit(); + // Resize to something empty. + view_rect = gfx::Rect(100, 0); + view_->SetSize(view_rect.size()); + + // We're never expecting empty frames, resize to something non-empty. + view_rect = gfx::Rect(100, 100); + view_->SetSize(view_rect.size()); + + // This frame should not be dropped. + EXPECT_CALL(observer, OnWindowPaintScheduled(view_->window_, view_rect)); + view_->OnSwapCompositorFrame( + 0, MakeDelegatedFrame(1.f, view_rect.size(), view_rect)); + testing::Mock::VerifyAndClearExpectations(&observer); + view_->RunOnCompositingDidCommit(); + view_->window_->RemoveObserver(&observer); } @@ -988,6 +1462,7 @@ TEST_F(RenderWidgetHostViewAuraTest, DiscardDelegatedFrames) { size_t renderer_count = max_renderer_frames + 1; gfx::Rect view_rect(100, 100); gfx::Size frame_size = view_rect.size(); + DCHECK_EQ(0u, HostSharedBitmapManager::current()->AllocatedBitmapCount()); scoped_ptr<RenderWidgetHostImpl * []> hosts( new RenderWidgetHostImpl* [renderer_count]); @@ -999,8 +1474,6 @@ TEST_F(RenderWidgetHostViewAuraTest, DiscardDelegatedFrames) { hosts[i] = new RenderWidgetHostImpl( &delegate_, process_host_, MSG_ROUTING_NONE, false); hosts[i]->Init(); - hosts[i]->OnMessageReceived( - ViewHostMsg_DidActivateAcceleratedCompositing(0, true)); views[i] = new FakeRenderWidgetHostViewAura(hosts[i]); views[i]->InitAsChild(NULL); aura::client::ParentWindowWithContext( @@ -1015,37 +1488,37 @@ TEST_F(RenderWidgetHostViewAuraTest, DiscardDelegatedFrames) { views[i]->WasShown(); views[i]->OnSwapCompositorFrame( 1, MakeDelegatedFrame(1.f, frame_size, view_rect)); - EXPECT_TRUE(views[i]->frame_provider_); + EXPECT_TRUE(views[i]->frame_provider()); views[i]->WasHidden(); } // 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]->frame_provider_); + EXPECT_FALSE(views[0]->frame_provider()); for (size_t i = 1; i < renderer_count; ++i) - EXPECT_TRUE(views[i]->frame_provider_); + EXPECT_TRUE(views[i]->frame_provider()); // LRU renderer is [0], make it visible, it shouldn't evict anything yet. views[0]->WasShown(); - EXPECT_FALSE(views[0]->frame_provider_); - EXPECT_TRUE(views[1]->frame_provider_); + EXPECT_FALSE(views[0]->frame_provider()); + EXPECT_TRUE(views[1]->frame_provider()); // Swap a frame on it, it should evict the next LRU [1]. views[0]->OnSwapCompositorFrame( 1, MakeDelegatedFrame(1.f, frame_size, view_rect)); - EXPECT_TRUE(views[0]->frame_provider_); - EXPECT_FALSE(views[1]->frame_provider_); + EXPECT_TRUE(views[0]->frame_provider()); + EXPECT_FALSE(views[1]->frame_provider()); views[0]->WasHidden(); // LRU renderer is [1], still hidden. Swap a frame on it, it should evict // the next LRU [2]. views[1]->OnSwapCompositorFrame( 1, MakeDelegatedFrame(1.f, frame_size, view_rect)); - EXPECT_TRUE(views[0]->frame_provider_); - EXPECT_TRUE(views[1]->frame_provider_); - EXPECT_FALSE(views[2]->frame_provider_); + EXPECT_TRUE(views[0]->frame_provider()); + EXPECT_TRUE(views[1]->frame_provider()); + EXPECT_FALSE(views[2]->frame_provider()); for (size_t i = 3; i < renderer_count; ++i) - EXPECT_TRUE(views[i]->frame_provider_); + EXPECT_TRUE(views[i]->frame_provider()); // Make all renderers but [0] visible and swap a frame on them, keep [0] // hidden, it becomes the LRU. @@ -1053,14 +1526,14 @@ TEST_F(RenderWidgetHostViewAuraTest, DiscardDelegatedFrames) { views[i]->WasShown(); views[i]->OnSwapCompositorFrame( 1, MakeDelegatedFrame(1.f, frame_size, view_rect)); - EXPECT_TRUE(views[i]->frame_provider_); + EXPECT_TRUE(views[i]->frame_provider()); } - EXPECT_FALSE(views[0]->frame_provider_); + EXPECT_FALSE(views[0]->frame_provider()); // Swap a frame on [0], it should be evicted immediately. views[0]->OnSwapCompositorFrame( 1, MakeDelegatedFrame(1.f, frame_size, view_rect)); - EXPECT_FALSE(views[0]->frame_provider_); + EXPECT_FALSE(views[0]->frame_provider()); // Make [0] visible, and swap a frame on it. Nothing should be evicted // although we're above the limit. @@ -1068,11 +1541,100 @@ TEST_F(RenderWidgetHostViewAuraTest, DiscardDelegatedFrames) { views[0]->OnSwapCompositorFrame( 1, MakeDelegatedFrame(1.f, frame_size, view_rect)); for (size_t i = 0; i < renderer_count; ++i) - EXPECT_TRUE(views[i]->frame_provider_); + EXPECT_TRUE(views[i]->frame_provider()); // Make [0] hidden, it should evict its frame. views[0]->WasHidden(); - EXPECT_FALSE(views[0]->frame_provider_); + EXPECT_FALSE(views[0]->frame_provider()); + + for (size_t i = 0; i < renderer_count - 1; ++i) + views[i]->WasHidden(); + + // Allocate enough bitmaps so that two frames (proportionally) would be + // enough hit the handle limit. + int handles_per_frame = 5; + RendererFrameManager::GetInstance()->set_max_handles(handles_per_frame * 2); + + for (size_t i = 0; i < (renderer_count - 1) * handles_per_frame; i++) { + HostSharedBitmapManager::current()->ChildAllocatedSharedBitmap( + 1, + base::SharedMemory::NULLHandle(), + base::GetCurrentProcessHandle(), + cc::SharedBitmap::GenerateId()); + } + + // Hiding this last bitmap should evict all but two frames. + views[renderer_count - 1]->WasHidden(); + for (size_t i = 0; i < renderer_count; ++i) { + if (i + 2 < renderer_count) + EXPECT_FALSE(views[i]->frame_provider()); + else + EXPECT_TRUE(views[i]->frame_provider()); + } + HostSharedBitmapManager::current()->ProcessRemoved( + base::GetCurrentProcessHandle()); + RendererFrameManager::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) { + size_t max_renderer_frames = + RendererFrameManager::GetInstance()->max_number_of_saved_frames(); + 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, HostSharedBitmapManager::current()->AllocatedBitmapCount()); + + scoped_ptr<RenderWidgetHostImpl * []> hosts( + new RenderWidgetHostImpl* [renderer_count]); + scoped_ptr<FakeRenderWidgetHostViewAura * []> views( + new FakeRenderWidgetHostViewAura* [renderer_count]); + + // Create a bunch of renderers. + for (size_t i = 0; i < renderer_count; ++i) { + hosts[i] = new RenderWidgetHostImpl( + &delegate_, process_host_, MSG_ROUTING_NONE, false); + hosts[i]->Init(); + views[i] = new FakeRenderWidgetHostViewAura(hosts[i]); + views[i]->InitAsChild(NULL); + 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]->WasShown(); + views[i]->OnSwapCompositorFrame( + 1, MakeDelegatedFrame(1.f, frame_size, view_rect)); + EXPECT_TRUE(views[i]->frame_provider()); + } + + // If we hide [0], then [0] should be evicted. + views[0]->WasHidden(); + EXPECT_FALSE(views[0]->frame_provider()); + + // If we lock [0] before hiding it, then [0] should not be evicted. + views[0]->WasShown(); + views[0]->OnSwapCompositorFrame( + 1, MakeDelegatedFrame(1.f, frame_size, view_rect)); + EXPECT_TRUE(views[0]->frame_provider()); + views[0]->GetDelegatedFrameHost()->LockResources(); + views[0]->WasHidden(); + EXPECT_TRUE(views[0]->frame_provider()); + + // If we unlock [0] now, then [0] should be evicted. + views[0]->GetDelegatedFrameHost()->UnlockResources(); + EXPECT_FALSE(views[0]->frame_provider()); for (size_t i = 0; i < renderer_count; ++i) { views[i]->Destroy(); @@ -1098,7 +1660,7 @@ TEST_F(RenderWidgetHostViewAuraTest, SoftwareDPIChange) { // Save the frame provider. scoped_refptr<cc::DelegatedFrameProvider> frame_provider = - view_->frame_provider_; + view_->frame_provider(); // This frame will have the same number of physical pixels, but has a new // scale on it. @@ -1108,7 +1670,1016 @@ TEST_F(RenderWidgetHostViewAuraTest, SoftwareDPIChange) { // When we get a new frame with the same frame size in physical pixels, but a // different scale, we should generate a new frame provider, as the final // result will need to be scaled differently to the screen. - EXPECT_NE(frame_provider.get(), view_->frame_provider_.get()); + EXPECT_NE(frame_provider.get(), view_->frame_provider()); +} + +class RenderWidgetHostViewAuraCopyRequestTest + : public RenderWidgetHostViewAuraShutdownTest { + public: + RenderWidgetHostViewAuraCopyRequestTest() + : callback_count_(0), result_(false) {} + + void CallbackMethod(const base::Closure& quit_closure, bool result) { + result_ = result; + callback_count_++; + quit_closure.Run(); + } + + int callback_count_; + bool result_; + + private: + DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraCopyRequestTest); +}; + +TEST_F(RenderWidgetHostViewAuraCopyRequestTest, DestroyedAfterCopyRequest) { + base::RunLoop run_loop; + + gfx::Rect view_rect(100, 100); + scoped_ptr<cc::CopyOutputRequest> request; + + view_->InitAsChild(NULL); + aura::client::ParentWindowWithContext( + view_->GetNativeView(), + parent_view_->GetNativeView()->GetRootWindow(), + gfx::Rect()); + view_->SetSize(view_rect.size()); + view_->WasShown(); + + scoped_ptr<FakeFrameSubscriber> frame_subscriber(new FakeFrameSubscriber( + view_rect.size(), + base::Bind(&RenderWidgetHostViewAuraCopyRequestTest::CallbackMethod, + base::Unretained(this), + run_loop.QuitClosure()))); + + EXPECT_EQ(0, callback_count_); + EXPECT_FALSE(view_->last_copy_request_); + + view_->BeginFrameSubscription( + frame_subscriber.PassAs<RenderWidgetHostViewFrameSubscriber>()); + view_->OnSwapCompositorFrame( + 1, MakeDelegatedFrame(1.f, view_rect.size(), gfx::Rect(view_rect))); + + EXPECT_EQ(0, callback_count_); + EXPECT_TRUE(view_->last_copy_request_); + EXPECT_TRUE(view_->last_copy_request_->has_texture_mailbox()); + request = view_->last_copy_request_.Pass(); + + // Send back the mailbox included in the request. There's no release callback + // since the mailbox came from the RWHVA originally. + request->SendTextureResult(view_rect.size(), + request->texture_mailbox(), + scoped_ptr<cc::SingleReleaseCallback>()); + + // This runs until the callback happens. + run_loop.Run(); + + // The callback should succeed. + EXPECT_EQ(1, callback_count_); + EXPECT_TRUE(result_); + + view_->OnSwapCompositorFrame( + 1, MakeDelegatedFrame(1.f, view_rect.size(), gfx::Rect(view_rect))); + + EXPECT_EQ(1, callback_count_); + request = view_->last_copy_request_.Pass(); + + // Destroy the RenderWidgetHostViewAura and ImageTransportFactory. + TearDownEnvironment(); + + // Send back the mailbox included in the request. There's no release callback + // since the mailbox came from the RWHVA originally. + request->SendTextureResult(view_rect.size(), + request->texture_mailbox(), + scoped_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(RenderWidgetHostViewAuraTest, VisibleViewportTest) { + gfx::Rect view_rect(100, 100); + + view_->InitAsChild(NULL); + aura::client::ParentWindowWithContext( + view_->GetNativeView(), + parent_view_->GetNativeView()->GetRootWindow(), + gfx::Rect()); + view_->SetSize(view_rect.size()); + view_->WasShown(); + + // 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 != NULL); + + ViewMsg_Resize::Param params; + ViewMsg_Resize::Read(message, ¶ms); + EXPECT_EQ(60, params.a.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(NULL); + view_->Show(); + + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, + gfx::PointF(kX, kY), + 0, + ui::EventTimeForNow()); + + view_->OnTouchEvent(&press); + EXPECT_EQ(blink::WebInputEvent::TouchStart, view_->touch_event_.type); + EXPECT_TRUE(view_->touch_event_.cancelable); + EXPECT_EQ(1U, view_->touch_event_.touchesLength); + EXPECT_EQ(blink::WebTouchPoint::StatePressed, + view_->touch_event_.touches[0].state); + EXPECT_EQ(kX, view_->touch_event_.touches[0].screenPosition.x); + EXPECT_EQ(kX, view_->touch_event_.touches[0].position.x); + EXPECT_EQ(kY, view_->touch_event_.touches[0].screenPosition.y); + EXPECT_EQ(kY, view_->touch_event_.touches[0].position.y); +} + +// Tests that scroll ACKs are correctly handled by the overscroll-navigation +// controller. +TEST_F(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::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // 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 event will be sent to the renderer. + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + 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()); +} + +// Tests that if some scroll events are consumed towards the start, then +// subsequent scrolls do not horizontal overscroll. +TEST_F(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::MouseWheel, INPUT_EVENT_ACK_STATE_CONSUMED); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // 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::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(0U, sink_->message_count()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + + // Indicate the end of the scrolling from the touchpad. + SimulateGestureFlingStartEvent(-1200.f, 0.f, blink::WebGestureDeviceTouchpad); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Start another scroll. This time, do not consume any scroll events. + SimulateWheelEvent(0, -5, 0, true); // sent directly + SimulateWheelEvent(0, -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 for the first wheel and the subsequent coalesced event as not + // processed. This should start a back-overscroll. + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode()); +} + +// Tests that wheel-scrolling correctly turns overscroll on and off. +TEST_F(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); + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Scroll some more so as to not overscroll. + SimulateWheelEvent(10, 0, 0, true); + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Scroll some more to initiate an overscroll. + SimulateWheelEvent(40, 0, 0, true); + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + 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(1U, GetSentMessageCountAndResetSink()); + + // Scroll in the reverse direction enough to abort the overscroll. + SimulateWheelEvent(-20, 0, 0, true); + EXPECT_EQ(0U, sink_->message_count()); + 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); + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Continue to scroll in the reverse direction enough to initiate overscroll + // in that direction. + SimulateWheelEvent(-55, 0, 0, true); + EXPECT_EQ(1U, sink_->message_count()); + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + 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, + 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); + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Scroll some more so as to not overscroll. + SimulateWheelEvent(20, 0, 0, true); + EXPECT_EQ(1U, sink_->message_count()); + SendInputEventACK(WebInputEvent::MouseWheel, + 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); + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + 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(1U, 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. + SimulateGestureFlingStartEvent(0.f, 0.1f, blink::WebGestureDeviceTouchpad); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(1U, sink_->message_count()); +} + +// Same as ScrollEventsOverscrollWithFling, but with zero velocity. Checks that +// the zero-velocity fling does not reach the renderer. +TEST_F(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); + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Scroll some more so as to not overscroll. + SimulateWheelEvent(20, 0, 0, true); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + 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); + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + 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(1U, 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. + SimulateGestureFlingStartEvent(10.f, 0.f, blink::WebGestureDeviceTouchpad); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(1U, sink_->message_count()); +} + +// 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::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + SimulateGestureScrollUpdateEvent(300, -5, 0); + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); + sink_->ClearMessages(); + + SimulateGestureEvent(WebInputEvent::GestureScrollEnd, + blink::WebGestureDeviceTouchscreen); + 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::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + SimulateGestureScrollUpdateEvent(-300, -5, 0); + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + 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::WebGestureDeviceTouchscreen); + 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::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + 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::GestureScrollUpdate, + 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(1U, 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::GestureScrollEnd, + blink::WebGestureDeviceTouchscreen); + 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::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + SimulateGestureScrollUpdateEvent(10, 0, 0); + + // Start scrolling on content. ACK both events as being processed. + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + 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::GestureScrollUpdate, + 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::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Send update events. + SimulateGestureScrollUpdateEvent(25, 0, 0); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Quickly end and restart the scroll gesture. These two events should get + // discarded. + SimulateGestureEvent(WebInputEvent::GestureScrollEnd, + blink::WebGestureDeviceTouchscreen); + EXPECT_EQ(0U, sink_->message_count()); + + SimulateGestureEvent(WebInputEvent::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + EXPECT_EQ(0U, sink_->message_count()); + + // Send another update event. This should get into the queue. + SimulateGestureScrollUpdateEvent(30, 0, 0); + EXPECT_EQ(0U, sink_->message_count()); + + // 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::GestureScrollUpdate, + 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::GestureScrollUpdate, + 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::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Send update events. + SimulateGestureScrollUpdateEvent(55, 0, 0); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Send an end event. This should get in the debounce queue. + SimulateGestureEvent(WebInputEvent::GestureScrollEnd, + blink::WebGestureDeviceTouchscreen); + EXPECT_EQ(0U, sink_->message_count()); + + // Receive ACK for the scroll-update event. + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + 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::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::MessageLoop::QuitClosure(), + base::TimeDelta::FromMilliseconds(15)); + base::MessageLoop::current()->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); + SendInputEventACK(WebInputEvent::TouchStart, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + MoveTouchPoint(0, 20, 5); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + SendInputEventACK(WebInputEvent::TouchMove, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + + SimulateGestureEvent(WebInputEvent::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + SimulateGestureScrollUpdateEvent(20, 0, 0); + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(1U, 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); + AckLastSentInputEventIfNecessary(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + SimulateGestureScrollUpdateEvent(45, 0, 0); + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + 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); + 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); + 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); + AckLastSentInputEventIfNecessary(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + ReleaseTouchPoint(0); + AckLastSentInputEventIfNecessary(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + SimulateGestureEvent(blink::WebInputEvent::GestureScrollEnd, + blink::WebGestureDeviceTouchscreen); + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::MessageLoop::QuitClosure(), + base::TimeDelta::FromMilliseconds(10)); + base::MessageLoop::current()->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::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + 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(1U, GetSentMessageCountAndResetSink()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + 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::GestureScrollEnd, + blink::WebGestureDeviceTouchscreen); + EXPECT_EQ(0U, sink_->message_count()); + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::MessageLoop::QuitClosure(), + base::TimeDelta::FromMilliseconds(10)); + base::MessageLoop::current()->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::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + 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(1U, GetSentMessageCountAndResetSink()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + 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::GestureScrollEnd, + blink::WebGestureDeviceTouchscreen); + EXPECT_EQ(0U, sink_->message_count()); + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::MessageLoop::QuitClosure(), + base::TimeDelta::FromMilliseconds(10)); + base::MessageLoop::current()->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::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Send update events and receive ack as not consumed. + SimulateGestureScrollUpdateEvent(125, -5, 0); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + 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 consume the event, and reset the overscroll mode. + SimulateGestureScrollUpdateEvent(-260, 0, 0); + EXPECT_EQ(0U, 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()); +} + +// Tests that if a mouse-move event completes the overscroll gesture, future +// move events do reach the renderer. +TEST_F(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::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Receive ACK for the second (coalesced) event as not processed. This will + // start an overcroll gesture. + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_WEST, overscroll_delegate()->current_mode()); + EXPECT_EQ(0U, sink_->message_count()); + + // 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::MouseMove, + 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::MouseMove, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Now try with gestures. + SimulateGestureEvent(WebInputEvent::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + SimulateGestureScrollUpdateEvent(300, -5, 0); + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + 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::MouseMove, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + + SimulateGestureEvent(WebInputEvent::GestureScrollEnd, + blink::WebGestureDeviceTouchscreen); + 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::MouseMove, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); +} + +// Tests that if a page scrolled, then the overscroll controller's states are +// reset after the end of the scroll. +TEST_F(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::MouseWheel, INPUT_EVENT_ACK_STATE_CONSUMED); + EXPECT_TRUE(ScrollStateIsContentScrolling()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // The second wheel event is consumed. + SendInputEventACK(WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_CONSUMED); + 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. + SimulateGestureFlingStartEvent(0.f, 0.f, blink::WebGestureDeviceTouchpad); + EXPECT_TRUE(ScrollStateIsUnknown()); + EXPECT_EQ(0U, sink_->message_count()); + + 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::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_TRUE(ScrollStateIsUnknown()); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + SendInputEventACK(WebInputEvent::MouseWheel, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode()); + EXPECT_TRUE(ScrollStateIsOverscrolling()); + EXPECT_EQ(0U, sink_->message_count()); + + SimulateGestureFlingStartEvent(0.f, 0.f, blink::WebGestureDeviceTouchpad); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_WEST, overscroll_delegate()->completed_mode()); + EXPECT_TRUE(ScrollStateIsUnknown()); + EXPECT_EQ(0U, sink_->message_count()); +} + +TEST_F(RenderWidgetHostViewAuraOverscrollTest, OverscrollResetsOnBlur) { + SetUpOverscrollEnvironment(); + + // Start an overscroll with gesture scroll. In the middle of the scroll, blur + // the host. + SimulateGestureEvent(WebInputEvent::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + SimulateGestureScrollUpdateEvent(300, -5, 0); + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + INPUT_EVENT_ACK_STATE_NOT_CONSUMED); + EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode()); + EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->current_mode()); + EXPECT_EQ(2U, GetSentMessageCountAndResetSink()); + + view_->OnWindowFocused(NULL, view_->GetAttachedWindow()); + 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::GestureScrollEnd, + blink::WebGestureDeviceTouchscreen); + EXPECT_EQ(1U, GetSentMessageCountAndResetSink()); + + // Start a scroll gesture again. This should correctly start the overscroll + // after the threshold. + SimulateGestureEvent(WebInputEvent::GestureScrollBegin, + blink::WebGestureDeviceTouchscreen); + SimulateGestureScrollUpdateEvent(300, -5, 0); + SendInputEventACK(WebInputEvent::GestureScrollUpdate, + 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::GestureScrollEnd, + blink::WebGestureDeviceTouchscreen); + EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode()); + EXPECT_EQ(OVERSCROLL_EAST, overscroll_delegate()->completed_mode()); + EXPECT_EQ(3U, sink_->message_count()); } } // namespace content |