Commit be979714 authored by sadrul@chromium.org's avatar sadrul@chromium.org

gesture recognizer: Make sure a combination of sync/async touch-events work correctly.

It is possible that a window starts doing synchronous touch-event handling
after doing asynchronous handling for some time. As a result, these sync
events may be processed before the acks for the queued asynchronous events
have been processed. This results in unexpected behaviour. So in such cases,
queue up the synchronous events so that they get processed only after the
asynchronous events are processed.

BUG=139339, 134486

Review URL: https://chromiumcodereview.appspot.com/10825323

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@151361 0039d316-1c4b-4281-b951-d872f2087c98
parent caf8c234
......@@ -212,11 +212,14 @@ class QueueTouchEventDelegate : public GestureEventConsumeDelegate {
public:
explicit QueueTouchEventDelegate(RootWindow* root_window)
: window_(NULL),
root_window_(root_window) {
root_window_(root_window),
queue_events_(true) {
}
virtual ~QueueTouchEventDelegate() {}
virtual ui::TouchStatus OnTouchEvent(ui::TouchEvent* event) OVERRIDE {
if (!queue_events_)
return ui::TOUCH_STATUS_UNKNOWN;
return event->type() == ui::ET_TOUCH_RELEASED ?
ui::TOUCH_STATUS_QUEUED_END : ui::TOUCH_STATUS_QUEUED;
}
......@@ -230,10 +233,12 @@ class QueueTouchEventDelegate : public GestureEventConsumeDelegate {
}
void set_window(Window* w) { window_ = w; }
void set_queue_events(bool queue) { queue_events_ = queue; }
private:
Window* window_;
RootWindow* root_window_;
bool queue_events_;
DISALLOW_COPY_AND_ASSIGN(QueueTouchEventDelegate);
};
......@@ -1930,6 +1935,53 @@ TEST_F(GestureRecognizerTest, PressDoesNotCrash) {
EXPECT_FALSE(delegate->scroll_begin());
}
// Tests that if a consumer has touch-events queued, then no touch-event gets
// processed synchronously until all the queued evets are processed.
TEST_F(GestureRecognizerTest, SyncTouchEventWithQueuedTouchEvents) {
scoped_ptr<QueueTouchEventDelegate> queued_delegate(
new QueueTouchEventDelegate(root_window()));
const int kWindowWidth = 123;
const int kWindowHeight = 45;
const int kTouchId1 = 6;
gfx::Rect bounds(10, 20, kWindowWidth, kWindowHeight);
scoped_ptr<aura::Window> queue(CreateTestWindowWithDelegate(
queued_delegate.get(), -1234, bounds, NULL));
queued_delegate->set_window(queue.get());
// Send a touch-event to the window so that the event gets queued. No gesture
// event should be created.
ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(20, 30), kTouchId1,
GetTime());
ui::TouchEvent move1(ui::ET_TOUCH_MOVED, gfx::Point(80, 25), kTouchId1,
press1.time_stamp() + base::TimeDelta::FromMilliseconds(100));
ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(80, 25), kTouchId1,
move1.time_stamp() + base::TimeDelta::FromMilliseconds(100));
root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1);
root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move1);
EXPECT_FALSE(queued_delegate->begin());
EXPECT_FALSE(queued_delegate->tap_down());
EXPECT_FALSE(queued_delegate->scroll_begin());
// Stop queueing events.
queued_delegate->set_queue_events(false);
root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1);
// Process the two queued events.
queued_delegate->ReceivedAck();
RunAllPendingInMessageLoop();
EXPECT_TRUE(queued_delegate->begin());
EXPECT_TRUE(queued_delegate->tap_down());
queued_delegate->Reset();
queued_delegate->ReceivedAck();
RunAllPendingInMessageLoop();
EXPECT_TRUE(queued_delegate->scroll_begin());
EXPECT_TRUE(queued_delegate->scroll_update());
EXPECT_TRUE(queued_delegate->scroll_end());
EXPECT_TRUE(queued_delegate->end());
}
TEST_F(GestureRecognizerTest, TwoFingerTap) {
scoped_ptr<GestureEventConsumeDelegate> delegate(
new GestureEventConsumeDelegate());
......
......@@ -891,8 +891,10 @@ bool RootWindow::OnHostTouchEvent(ui::TouchEvent* event) {
handled = status != ui::TOUCH_STATUS_UNKNOWN;
if (status == ui::TOUCH_STATUS_QUEUED ||
status == ui::TOUCH_STATUS_QUEUED_END)
status == ui::TOUCH_STATUS_QUEUED_END) {
gesture_recognizer_->QueueTouchEventForGesture(target, *event);
return true;
}
}
// Get the list of GestureEvents from GestureRecognizer.
......
......@@ -51,10 +51,27 @@ class MirroredTouchEvent : public TouchEvent {
}
private:
DISALLOW_COPY_AND_ASSIGN(MirroredTouchEvent);
};
class QueuedTouchEvent : public MirroredTouchEvent {
public:
QueuedTouchEvent(const TouchEvent* real, TouchStatus status)
: MirroredTouchEvent(real),
status_(status) {
}
virtual ~QueuedTouchEvent() {
}
TouchStatus status() const { return status_; }
private:
TouchStatus status_;
DISALLOW_COPY_AND_ASSIGN(QueuedTouchEvent);
};
// A mirrored event, except for the type, which is always ET_TOUCH_CANCELLED.
class CancelledTouchEvent : public MirroredTouchEvent {
public:
......@@ -227,10 +244,8 @@ GestureSequence* GestureRecognizerImpl::GetGestureSequenceForConsumer(
return gesture_sequence;
}
GestureSequence::Gestures* GestureRecognizerImpl::ProcessTouchEventForGesture(
const TouchEvent& event,
ui::TouchStatus status,
GestureConsumer* target) {
void GestureRecognizerImpl::SetupTargets(const TouchEvent& event,
GestureConsumer* target) {
if (event.type() == ui::ET_TOUCH_RELEASED ||
event.type() == ui::ET_TOUCH_CANCELLED) {
touch_id_target_[event.touch_id()] = NULL;
......@@ -239,6 +254,42 @@ GestureSequence::Gestures* GestureRecognizerImpl::ProcessTouchEventForGesture(
if (target)
touch_id_target_for_gestures_[event.touch_id()] = target;
}
}
GestureSequence::Gestures* GestureRecognizerImpl::AdvanceTouchQueueByOne(
GestureConsumer* consumer,
ui::TouchStatus status) {
CHECK(event_queue_[consumer]);
CHECK(!event_queue_[consumer]->empty());
ScopedPop pop(event_queue_[consumer]);
TouchEvent* event = event_queue_[consumer]->front();
GestureSequence* sequence = GetGestureSequenceForConsumer(consumer);
if (status != ui::TOUCH_STATUS_UNKNOWN &&
event->type() == ui::ET_TOUCH_RELEASED) {
// A touch release was was processed (e.g. preventDefault()ed by a
// web-page), but we still need to process a touch cancel.
CancelledTouchEvent cancelled(event);
return sequence->ProcessTouchEventForGesture(cancelled,
ui::TOUCH_STATUS_UNKNOWN);
}
return sequence->ProcessTouchEventForGesture(*event, status);
}
GestureSequence::Gestures* GestureRecognizerImpl::ProcessTouchEventForGesture(
const TouchEvent& event,
ui::TouchStatus status,
GestureConsumer* target) {
if (event_queue_[target] && event_queue_[target]->size() > 0) {
// There are some queued touch-events for this target. Processing |event|
// before those queued events will result in unexpected gestures. So
// postpone the processing of the events until the queued events have been
// processed.
event_queue_[target]->push(new QueuedTouchEvent(&event, status));
return NULL;
}
SetupTargets(event, target);
GestureSequence* gesture_sequence = GetGestureSequenceForConsumer(target);
return gesture_sequence->ProcessTouchEventForGesture(event, status);
......@@ -248,7 +299,10 @@ void GestureRecognizerImpl::QueueTouchEventForGesture(GestureConsumer* consumer,
const TouchEvent& event) {
if (!event_queue_[consumer])
event_queue_[consumer] = new std::queue<TouchEvent*>();
event_queue_[consumer]->push(new MirroredTouchEvent(&event));
event_queue_[consumer]->push(
new QueuedTouchEvent(&event, TOUCH_STATUS_QUEUED));
SetupTargets(event, consumer);
}
GestureSequence::Gestures* GestureRecognizerImpl::AdvanceTouchQueue(
......@@ -259,22 +313,30 @@ GestureSequence::Gestures* GestureRecognizerImpl::AdvanceTouchQueue(
return NULL;
}
ScopedPop pop(event_queue_[consumer]);
TouchEvent* event = event_queue_[consumer]->front();
GestureSequence* sequence = GetGestureSequenceForConsumer(consumer);
if (processed && event->type() == ui::ET_TOUCH_RELEASED) {
// A touch release was was processed (e.g. preventDefault()ed by a
// web-page), but we still need to process a touch cancel.
CancelledTouchEvent cancelled(event);
return sequence->ProcessTouchEventForGesture(cancelled,
ui::TOUCH_STATUS_UNKNOWN);
scoped_ptr<GestureSequence::Gestures> gestures(
AdvanceTouchQueueByOne(consumer, processed ? TOUCH_STATUS_CONTINUE :
TOUCH_STATUS_UNKNOWN));
// Are there any queued touch-events that should be auto-dequeued?
while (!event_queue_[consumer]->empty()) {
QueuedTouchEvent* event =
static_cast<QueuedTouchEvent*>(event_queue_[consumer]->front());
if (event->status() == TOUCH_STATUS_QUEUED)
break;
GestureSequence::Gestures* current_gestures = AdvanceTouchQueueByOne(
consumer, event->status());
if (current_gestures) {
if (!gestures.get()) {
gestures.reset(current_gestures);
} else {
gestures->insert(gestures->end(), current_gestures->begin(),
current_gestures->end());
}
}
}
return sequence->ProcessTouchEventForGesture(
*event,
processed ? ui::TOUCH_STATUS_CONTINUE : ui::TOUCH_STATUS_UNKNOWN);
return gestures.release();
}
void GestureRecognizerImpl::FlushTouchQueue(GestureConsumer* consumer) {
......
......@@ -46,6 +46,15 @@ class UI_EXPORT GestureRecognizerImpl : public GestureRecognizer {
virtual GestureSequence* GetGestureSequenceForConsumer(GestureConsumer* c);
private:
// Sets up the target consumer for gestures based on the touch-event.
void SetupTargets(const TouchEvent& event, GestureConsumer* consumer);
// Processes the next queued touch-event (and discards the touch-event). The
// called must take ownership of the returned gestures and free them when they
// are not needed anymore.
Gestures* AdvanceTouchQueueByOne(GestureConsumer* consumer,
ui::TouchStatus status);
// Overridden from GestureRecognizer
virtual Gestures* ProcessTouchEventForGesture(
const TouchEvent& event,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment