From a1f064db6dc3bba6e0388c924ff735a6e8fffe0c Mon Sep 17 00:00:00 2001 From: "rharrison@chromium.org" Date: Tue, 11 Jun 2013 18:58:54 +0000 Subject: Cause touch events to be held like mouse events during window resizing This delays the delivery of touch move events during window resizing until the current painting of the web page is completed. This corrects the issue of dragging via touch to change the window size causes graphical weirdness near and with the scroll bars. BUG=155167 TEST=Resized window to not occupy the entire screen area, went to a long page, ie slashdot.org, that causes scroll bars to appear, and used touch to resize the window. Review URL: https://chromiumcodereview.appspot.com/15825005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@205597 0039d316-1c4b-4281-b951-d872f2087c98 --- ui/aura/root_window.cc | 198 ++++++++++++++++++++++------------------ ui/aura/root_window.h | 35 +++---- ui/aura/root_window_unittest.cc | 73 +++++++++++++-- 3 files changed, 191 insertions(+), 115 deletions(-) (limited to 'ui') diff --git a/ui/aura/root_window.cc b/ui/aura/root_window.cc index 1c0bcea..e4a0fee 100644 --- a/ui/aura/root_window.cc +++ b/ui/aura/root_window.cc @@ -157,7 +157,7 @@ RootWindow::RootWindow(const CreateParams& params) waiting_on_compositing_end_(false), draw_on_compositing_end_(false), defer_draw_scheduling_(false), - mouse_move_hold_count_(0), + move_hold_count_(0), held_event_factory_(this), repostable_event_factory_(this) { SetName("RootWindow"); @@ -473,28 +473,27 @@ void RootWindow::ToggleFullScreen() { host_->ToggleFullScreen(); } -void RootWindow::HoldMouseMoves() { - if (!mouse_move_hold_count_) +void RootWindow::HoldPointerMoves() { + if (!move_hold_count_) held_event_factory_.InvalidateWeakPtrs(); - ++mouse_move_hold_count_; - TRACE_EVENT_ASYNC_BEGIN0("ui", "RootWindow::HoldMouseMoves", this); -} - -void RootWindow::ReleaseMouseMoves() { - --mouse_move_hold_count_; - DCHECK_GE(mouse_move_hold_count_, 0); - if (!mouse_move_hold_count_ && held_mouse_move_) { - // We don't want to call DispatchHeldEvents directly, because this might - // be called from a deep stack while another event, in which case - // dispatching another one may not be safe/expected. - // Instead we post a task, that we may cancel if HoldMouseMoves is called - // again before it executes. + ++move_hold_count_; + TRACE_EVENT_ASYNC_BEGIN0("ui", "RootWindow::HoldPointerMoves", this); +} + +void RootWindow::ReleasePointerMoves() { + --move_hold_count_; + DCHECK_GE(move_hold_count_, 0); + if (!move_hold_count_ && held_move_event_) { + // We don't want to call DispatchHeldEvents directly, because this might be + // called from a deep stack while another event, in which case dispatching + // another one may not be safe/expected. Instead we post a task, that we + // may cancel if HoldPointerMoves is called again before it executes. base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&RootWindow::DispatchHeldEvents, held_event_factory_.GetWeakPtr())); } - TRACE_EVENT_ASYNC_END0("ui", "RootWindow::HoldMouseMoves", this); + TRACE_EVENT_ASYNC_END0("ui", "RootWindow::HoldPointerMoves", this); } void RootWindow::SetFocusWhenShown(bool focused) { @@ -901,17 +900,16 @@ bool RootWindow::OnHostKeyEvent(ui::KeyEvent* event) { bool RootWindow::OnHostMouseEvent(ui::MouseEvent* event) { if (event->type() == ui::ET_MOUSE_DRAGGED || (event->flags() & ui::EF_IS_SYNTHESIZED)) { - if (mouse_move_hold_count_) { + if (move_hold_count_) { Window* null_window = static_cast(NULL); - held_mouse_move_.reset( + held_move_event_.reset( new ui::MouseEvent(*event, null_window, null_window)); return true; } else { - // We may have a held event for a period between the time - // mouse_move_hold_count_ fell to 0 and the DispatchHeldEvents - // executes. Since we're going to dispatch the new event directly below, - // we can reset the old one. - held_mouse_move_.reset(); + // We may have a held event for a period between the time move_hold_count_ + // fell to 0 and the DispatchHeldEvents executes. Since we're going to + // dispatch the new event directly below, we can reset the old one. + held_move_event_.reset(); } } DispatchHeldEvents(); @@ -946,70 +944,21 @@ bool RootWindow::OnHostScrollEvent(ui::ScrollEvent* event) { } bool RootWindow::OnHostTouchEvent(ui::TouchEvent* event) { - DispatchHeldEvents(); - switch (event->type()) { - case ui::ET_TOUCH_PRESSED: - touch_ids_down_ |= (1 << event->touch_id()); - Env::GetInstance()->set_touch_down(touch_ids_down_ != 0); - break; - - // Handle ET_TOUCH_CANCELLED only if it has a native event. - case ui::ET_TOUCH_CANCELLED: - if (!event->HasNativeEvent()) - break; - // fallthrough - case ui::ET_TOUCH_RELEASED: - touch_ids_down_ = (touch_ids_down_ | (1 << event->touch_id())) ^ - (1 << event->touch_id()); - Env::GetInstance()->set_touch_down(touch_ids_down_ != 0); - break; - - default: - break; - } - TransformEventForDeviceScaleFactor(false, event); - bool handled = false; - Window* target = client::GetCaptureWindow(this); - if (!target) { - target = ConsumerToWindow( - gesture_recognizer_->GetTouchLockedTarget(event)); - if (!target) { - target = ConsumerToWindow( - gesture_recognizer_->GetTargetForLocation(event->location())); - } - } - - // The gesture recognizer processes touch events in the system coordinates. So - // keep a copy of the touch event here before possibly converting the event to - // a window's local coordinate system. - ui::TouchEvent event_for_gr(*event); - - ui::EventResult result = ui::ER_UNHANDLED; - if (!target && !bounds().Contains(event->location())) { - // If the initial touch is outside the root window, target the root. - target = this; - ProcessEvent(target ? target : NULL, event); - result = event->result(); - } else { - // We only come here when the first contact was within the root window. - if (!target) { - target = GetEventHandlerForPoint(event->location()); - if (!target) - return false; + if ((event->type() == ui::ET_TOUCH_MOVED)) { + if (move_hold_count_) { + Window* null_window = static_cast(NULL); + held_move_event_.reset( + new ui::TouchEvent(*event, null_window, null_window)); + return true; + } else { + // We may have a held event for a period between the time move_hold_count_ + // fell to 0 and the DispatchHeldEvents executes. Since we're going to + // dispatch the new event directly below, we can reset the old one. + held_move_event_.reset(); } - - event->ConvertLocationToTarget(static_cast(this), target); - ProcessEvent(target, event); - handled = event->handled(); - result = event->result(); } - - // Get the list of GestureEvents from GestureRecognizer. - scoped_ptr gestures; - gestures.reset(gesture_recognizer_->ProcessTouchEventForGesture( - event_for_gr, result, target)); - - return ProcessGestures(gestures.get()) ? true : handled; + DispatchHeldEvents(); + return DispatchTouchEventImpl(event); } void RootWindow::OnHostCancelMode() { @@ -1135,6 +1084,72 @@ bool RootWindow::DispatchMouseEventToTarget(ui::MouseEvent* event, return false; } +bool RootWindow::DispatchTouchEventImpl(ui::TouchEvent* event) { + switch (event->type()) { + case ui::ET_TOUCH_PRESSED: + touch_ids_down_ |= (1 << event->touch_id()); + Env::GetInstance()->set_touch_down(touch_ids_down_ != 0); + break; + + // Handle ET_TOUCH_CANCELLED only if it has a native event. + case ui::ET_TOUCH_CANCELLED: + if (!event->HasNativeEvent()) + break; + // fallthrough + case ui::ET_TOUCH_RELEASED: + touch_ids_down_ = (touch_ids_down_ | (1 << event->touch_id())) ^ + (1 << event->touch_id()); + Env::GetInstance()->set_touch_down(touch_ids_down_ != 0); + break; + + default: + break; + } + TransformEventForDeviceScaleFactor(false, event); + bool handled = false; + Window* target = client::GetCaptureWindow(this); + if (!target) { + target = ConsumerToWindow( + gesture_recognizer_->GetTouchLockedTarget(event)); + if (!target) { + target = ConsumerToWindow( + gesture_recognizer_->GetTargetForLocation(event->location())); + } + } + + // The gesture recognizer processes touch events in the system coordinates. So + // keep a copy of the touch event here before possibly converting the event to + // a window's local coordinate system. + ui::TouchEvent event_for_gr(*event); + + ui::EventResult result = ui::ER_UNHANDLED; + if (!target && !bounds().Contains(event->location())) { + // If the initial touch is outside the root window, target the root. + target = this; + ProcessEvent(target ? target : NULL, event); + result = event->result(); + } else { + // We only come here when the first contact was within the root window. + if (!target) { + target = GetEventHandlerForPoint(event->location()); + if (!target) + return false; + } + + event->ConvertLocationToTarget(static_cast(this), target); + ProcessEvent(target, event); + handled = event->handled(); + result = event->result(); + } + + // Get the list of GestureEvents from GestureRecognizer. + scoped_ptr gestures; + gestures.reset(gesture_recognizer_->ProcessTouchEventForGesture( + event_for_gr, result, target)); + + return ProcessGestures(gestures.get()) ? true : handled; +} + void RootWindow::DispatchHeldEvents() { if (held_repostable_event_) { if (held_repostable_event_->type() == ui::ET_MOUSE_PRESSED) { @@ -1148,12 +1163,17 @@ void RootWindow::DispatchHeldEvents() { } held_repostable_event_.reset(); } - if (held_mouse_move_) { + if (held_move_event_ && held_move_event_->IsMouseEvent()) { // If a mouse move has been synthesized, the target location is suspect, // so drop the held event. if (!synthesize_mouse_move_) - DispatchMouseEventImpl(held_mouse_move_.get()); - held_mouse_move_.reset(); + DispatchMouseEventImpl( + static_cast(held_move_event_.get())); + held_move_event_.reset(); + } else if (held_move_event_ && held_move_event_->IsTouchEvent()) { + DispatchTouchEventImpl( + static_cast(held_move_event_.get())); + held_move_event_.reset(); } } diff --git a/ui/aura/root_window.h b/ui/aura/root_window.h index 4e0e9a9..dc1918e 100644 --- a/ui/aura/root_window.h +++ b/ui/aura/root_window.h @@ -226,15 +226,15 @@ class AURA_EXPORT RootWindow : public ui::CompositorDelegate, // Toggles the host's full screen state. void ToggleFullScreen(); - // These methods are used to defer the processing of mouse events related - // to resize. A client (typically a RenderWidgetHostViewAura) can call - // HoldMouseMoves when an resize is initiated and then ReleaseMouseMoves + // These methods are used to defer the processing of mouse/touch events + // related to resize. A client (typically a RenderWidgetHostViewAura) can call + // HoldPointerMoves when an resize is initiated and then ReleasePointerMoves // once the resize is completed. // // More than one hold can be invoked and each hold must be cancelled by a // release before we resume normal operation. - void HoldMouseMoves(); - void ReleaseMouseMoves(); + void HoldPointerMoves(); + void ReleasePointerMoves(); // Sets if the window should be focused when shown. void SetFocusWhenShown(bool focus_when_shown); @@ -385,15 +385,17 @@ class AURA_EXPORT RootWindow : public ui::CompositorDelegate, virtual float GetDeviceScaleFactor() OVERRIDE; virtual RootWindow* AsRootWindow() OVERRIDE; - // We hold and aggregate mouse drags as a way of throttling resizes when - // HoldMouseMoves() is called. The following methods are used to dispatch held - // and newly incoming mouse events, typically when an event other than a mouse - // drag needs dispatching or a matching ReleaseMouseMoves() is called. - // NOTE: because these methods dispatch events from RootWindowHost the - // coordinates are in terms of the root. + // We hold and aggregate mouse drags and touch moves as a way of throttling + // resizes when HoldMouseMoves() is called. The following methods are used to + // dispatch held and newly incoming mouse and touch events, typically when an + // event other than one of these needs dispatching or a matching + // ReleaseMouseMoves()/ReleaseTouchMoves() is called. NOTE: because these + // methods dispatch events from RootWindowHost the coordinates are in terms of + // the root. bool DispatchMouseEventImpl(ui::MouseEvent* event); bool DispatchMouseEventRepost(ui::MouseEvent* event); bool DispatchMouseEventToTarget(ui::MouseEvent* event, Window* target); + bool DispatchTouchEventImpl(ui::TouchEvent* event); void DispatchHeldEvents(); // Parses the switch describing the initial size for the host window and @@ -445,13 +447,12 @@ class AURA_EXPORT RootWindow : public ui::CompositorDelegate, bool defer_draw_scheduling_; - // How many holds are outstanding. We try to defer dispatching mouse moves - // while the count is > 0. - int mouse_move_hold_count_; - scoped_ptr held_mouse_move_; - // Used to schedule DispatchHeldEvents() when |mouse_move_hold_count_| goes - // to 0. + // How many move holds are outstanding. We try to defer dispatching + // touch/mouse moves while the count is > 0. + int move_hold_count_; + // Used to schedule DispatchHeldEvents() when |move_hold_count_| goes to 0. base::WeakPtrFactory held_event_factory_; + scoped_ptr held_move_event_; // Allowing for reposting of events. Used when exiting context menus. scoped_ptr held_repostable_event_; diff --git a/ui/aura/root_window_unittest.cc b/ui/aura/root_window_unittest.cc index d7f5fa8..9eef8e9 100644 --- a/ui/aura/root_window_unittest.cc +++ b/ui/aura/root_window_unittest.cc @@ -502,7 +502,7 @@ std::string EventTypesToString(const EventFilterRecorder::Events& events) { } // namespace -TEST_F(RootWindowTest, HoldMouseMove) { +TEST_F(RootWindowTest, MouseMovesHeld) { EventFilterRecorder* filter = new EventFilterRecorder; root_window()->SetEventFilter(filter); // passes ownership @@ -517,7 +517,7 @@ TEST_F(RootWindowTest, HoldMouseMove) { // Discard MOUSE_ENTER. filter->events().clear(); - root_window()->HoldMouseMoves(); + root_window()->HoldPointerMoves(); // Check that we don't immediately dispatch the MOUSE_DRAGGED event. ui::MouseEvent mouse_dragged_event(ui::ET_MOUSE_DRAGGED, gfx::Point(0, 0), @@ -550,23 +550,23 @@ TEST_F(RootWindowTest, HoldMouseMove) { EventTypesToString(filter->events())); filter->events().clear(); - // Check that on ReleaseMouseMoves, held events are not dispatched + // Check that on ReleasePointerMoves, held events are not dispatched // immediately, but posted instead. root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( &mouse_dragged_event); - root_window()->ReleaseMouseMoves(); + root_window()->ReleasePointerMoves(); EXPECT_TRUE(filter->events().empty()); RunAllPendingInMessageLoop(); EXPECT_EQ("MOUSE_DRAGGED", EventTypesToString(filter->events())); filter->events().clear(); // However if another message comes in before the dispatch, - // the Check that on ReleaseMouseMoves, held events are not dispatched + // the Check that on ReleasePointerMoves, held events are not dispatched // immediately, but posted instead. - root_window()->HoldMouseMoves(); + root_window()->HoldPointerMoves(); root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( &mouse_dragged_event); - root_window()->ReleaseMouseMoves(); + root_window()->ReleasePointerMoves(); root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( &mouse_pressed_event); EXPECT_EQ("MOUSE_DRAGGED MOUSE_PRESSED", @@ -577,10 +577,10 @@ TEST_F(RootWindowTest, HoldMouseMove) { // Check that if the other message is another MOUSE_DRAGGED, we still coalesce // them. - root_window()->HoldMouseMoves(); + root_window()->HoldPointerMoves(); root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( &mouse_dragged_event); - root_window()->ReleaseMouseMoves(); + root_window()->ReleasePointerMoves(); root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( &mouse_dragged_event2); EXPECT_EQ("MOUSE_DRAGGED", EventTypesToString(filter->events())); @@ -589,6 +589,61 @@ TEST_F(RootWindowTest, HoldMouseMove) { EXPECT_TRUE(filter->events().empty()); } +TEST_F(RootWindowTest, TouchMovesHeld) { + EventFilterRecorder* filter = new EventFilterRecorder; + root_window()->SetEventFilter(filter); // passes ownership + + test::TestWindowDelegate delegate; + scoped_ptr window(CreateTestWindowWithDelegate( + &delegate, 1, gfx::Rect(0, 0, 100, 100), root_window())); + + // Starting the touch and throwing out the first few events, since the system + // is going to generate synthetic mouse events that are not relevant to the + // test. + ui::TouchEvent touch_pressed_event(ui::ET_TOUCH_PRESSED, gfx::Point(0, 0), + 0, base::TimeDelta()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent( + &touch_pressed_event); + RunAllPendingInMessageLoop(); + filter->events().clear(); + + root_window()->HoldPointerMoves(); + + // Check that we don't immediately dispatch the TOUCH_MOVED event. + ui::TouchEvent touch_moved_event(ui::ET_TOUCH_MOVED, gfx::Point(0, 0), + 0, base::TimeDelta()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent( + &touch_moved_event); + EXPECT_TRUE(filter->events().empty()); + + // Check that on ReleasePointerMoves, held events are not dispatched + // immediately, but posted instead. + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent( + &touch_moved_event); + root_window()->ReleasePointerMoves(); + EXPECT_TRUE(filter->events().empty()); + RunAllPendingInMessageLoop(); + EXPECT_EQ("TOUCH_MOVED", EventTypesToString(filter->events())); + filter->events().clear(); + + // If another touch event occurs then the held touch should be dispatched + // immediately before it. + ui::TouchEvent touch_released_event(ui::ET_TOUCH_RELEASED, gfx::Point(0, 0), + 0, base::TimeDelta()); + filter->events().clear(); + root_window()->HoldPointerMoves(); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent( + &touch_moved_event); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent( + &touch_released_event); + EXPECT_EQ("TOUCH_MOVED TOUCH_RELEASED GESTURE_END", + EventTypesToString(filter->events())); + filter->events().clear(); + root_window()->ReleasePointerMoves(); + RunAllPendingInMessageLoop(); + EXPECT_TRUE(filter->events().empty()); +} + class DeletingEventFilter : public ui::EventHandler { public: DeletingEventFilter() -- cgit v1.1