blob: f9fbe4a5bc4788beb27dcde7a6df3cb3f592c999 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/events/event_rewriter.h"
#include <list>
#include <map>
#include <set>
#include <utility>
#include "base/check_op.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/test/test_event_source.h"
#include "ui/events/test/test_event_target.h"
namespace ui {
namespace {
using test::TestEventTarget;
// TestEventRewriteSink is set up with a sequence of event types,
// and fails if the events received via OnEventFromSource() do not match
// this sequence. These expected event types are consumed on receipt.
class TestEventRewriteSink : public EventSink {
public:
explicit TestEventRewriteSink(EventTarget* expected_target)
: expected_target_(expected_target) {}
TestEventRewriteSink(const TestEventRewriteSink&) = delete;
TestEventRewriteSink& operator=(const TestEventRewriteSink&) = delete;
~TestEventRewriteSink() override { CheckAllReceived(); }
void AddExpectedEvent(EventType type) { expected_events_.push_back(type); }
// Test that all expected events have been received.
void CheckAllReceived() { EXPECT_TRUE(expected_events_.empty()); }
// EventSink override:
EventDispatchDetails OnEventFromSource(Event* event) override {
EXPECT_FALSE(expected_events_.empty());
EXPECT_EQ(expected_events_.front(), event->type());
expected_events_.pop_front();
EXPECT_EQ(expected_target_, event->target());
return EventDispatchDetails();
}
private:
std::list<EventType> expected_events_;
const raw_ptr<EventTarget> expected_target_;
};
std::unique_ptr<Event> CreateEventForType(EventType type) {
switch (type) {
case ET_CANCEL_MODE:
return std::make_unique<CancelModeEvent>();
case ET_MOUSE_DRAGGED:
case ET_MOUSE_PRESSED:
case ET_MOUSE_RELEASED:
return std::make_unique<MouseEvent>(type, gfx::Point(), gfx::Point(),
base::TimeTicks::Now(), 0, 0);
case ET_KEY_PRESSED:
case ET_KEY_RELEASED:
return std::make_unique<KeyEvent>(type, ui::VKEY_TAB, DomCode::NONE, 0);
case ET_SCROLL_FLING_CANCEL:
case ET_SCROLL_FLING_START:
return std::make_unique<ScrollEvent>(
type, gfx::Point(), base::TimeTicks::Now(), 0, 0, 0, 0, 0, 0);
default:
NOTREACHED() << type;
return nullptr;
}
}
class TestEventRewriteSource : public test::TestEventSource {
public:
TestEventRewriteSource(EventSink* sink, EventTarget* target)
: TestEventSource(sink), target_(target) {}
EventDispatchDetails Send(EventType type) {
std::unique_ptr<Event> event = CreateEventForType(type);
Event::DispatcherApi(event.get()).set_target(target_);
return TestEventSource::Send(event.get());
}
private:
const raw_ptr<EventTarget> target_;
};
// This EventRewriter always returns the same status, and if rewriting, the
// same event type; it is used to test simple rewriting, and rewriter addition,
// removal, and sequencing. Consequently EVENT_REWRITE_DISPATCH_ANOTHER is not
// supported here (calls to NextDispatchEvent() would continue indefinitely).
class TestConstantEventRewriterOld : public EventRewriter {
public:
TestConstantEventRewriterOld(EventRewriteStatus status, EventType type)
: status_(status), type_(type) {
CHECK_NE(EVENT_REWRITE_DISPATCH_ANOTHER, status);
}
EventRewriteStatus RewriteEvent(
const Event& event,
std::unique_ptr<Event>* rewritten_event) override {
if (status_ == EVENT_REWRITE_REWRITTEN)
*rewritten_event = CreateEventForType(type_);
return status_;
}
EventRewriteStatus NextDispatchEvent(
const Event& last_event,
std::unique_ptr<Event>* new_event) override {
NOTREACHED();
return status_;
}
bool SupportsNonRootLocation() const override { return true; }
private:
EventRewriteStatus status_;
EventType type_;
};
// This EventRewriter runs a simple state machine; it is used to test
// EVENT_REWRITE_DISPATCH_ANOTHER.
class TestStateMachineEventRewriterOld : public EventRewriter {
public:
TestStateMachineEventRewriterOld() = default;
void AddRule(int from_state,
EventType from_type,
int to_state,
EventType to_type,
EventRewriteStatus to_status) {
RewriteResult r = {to_state, to_type, to_status};
rules_.emplace(RewriteCase(from_state, from_type), r);
}
EventRewriteStatus RewriteEvent(
const Event& event,
std::unique_ptr<Event>* rewritten_event) override {
auto find = rules_.find(RewriteCase(state_, event.type()));
if (find == rules_.end())
return EVENT_REWRITE_CONTINUE;
if ((find->second.status == EVENT_REWRITE_REWRITTEN) ||
(find->second.status == EVENT_REWRITE_DISPATCH_ANOTHER)) {
*rewritten_event = CreateEventForType(find->second.type);
has_rewritten_event_ = true;
} else {
has_rewritten_event_ = false;
}
state_ = find->second.state;
return find->second.status;
}
EventRewriteStatus NextDispatchEvent(
const Event& last_event,
std::unique_ptr<Event>* new_event) override {
EXPECT_TRUE(has_rewritten_event_);
EXPECT_FALSE(new_event->get() && new_event->get() == &last_event);
return RewriteEvent(last_event, new_event);
}
bool SupportsNonRootLocation() const override { return true; }
private:
typedef std::pair<int, EventType> RewriteCase;
struct RewriteResult {
int state;
EventType type;
EventRewriteStatus status;
};
typedef std::map<RewriteCase, RewriteResult> RewriteRules;
RewriteRules rules_;
bool has_rewritten_event_ = false;
int state_ = 0;
};
// This EventRewriter always accepts the original event. It is used to test
// simple rewriting, and rewriter addition, removal, and sequencing.
class TestAlwaysAcceptEventRewriter : public EventRewriter {
public:
TestAlwaysAcceptEventRewriter() {}
EventDispatchDetails RewriteEvent(const Event& event,
const Continuation continuation) override {
return SendEvent(continuation, &event);
}
};
// This EventRewriter always rewrites with the same event type; it is used
// to test simple rewriting, and rewriter addition, removal, and sequencing.
class TestConstantEventRewriter : public EventRewriter {
public:
explicit TestConstantEventRewriter(EventType type) : type_(type) {}
EventDispatchDetails RewriteEvent(const Event& event,
const Continuation continuation) override {
std::unique_ptr<Event> replacement_event = CreateEventForType(type_);
SetEventTarget(*replacement_event, event.target());
return SendEventFinally(continuation, replacement_event.get());
}
private:
EventType type_;
};
// This EventRewriter runs a simple state machine; it is used to test
// EVENT_REWRITE_DISPATCH_ANOTHER.
class TestStateMachineEventRewriter : public EventRewriter {
public:
enum RewriteAction { ACCEPT, DISCARD, REPLACE };
enum StateAction { RETURN, PROCEED };
TestStateMachineEventRewriter() : state_(0) {}
void AddRule(int from_state,
EventType from_type,
int to_state,
EventType to_type,
RewriteAction rewrite_action,
StateAction state_action) {
RewriteResult r = {to_state, to_type, rewrite_action, state_action};
rules_.insert({RewriteCase(from_state, from_type), r});
}
EventDispatchDetails RewriteEvent(const Event& event,
const Continuation continuation) override {
for (;;) {
RewriteRules::iterator find =
rules_.find(RewriteCase(state_, event.type()));
if (find == rules_.end())
return SendEvent(continuation, &event);
state_ = find->second.state;
EventDispatchDetails details;
switch (find->second.rewrite_action) {
case ACCEPT:
details = SendEvent(continuation, &event);
break;
case DISCARD:
break;
case REPLACE:
auto rewritten_event = CreateEventForType(find->second.type);
SetEventTarget(*rewritten_event, event.target());
details = SendEventFinally(continuation, rewritten_event.get());
break;
}
if (details.dispatcher_destroyed || find->second.state_action == RETURN)
return details;
}
NOTREACHED();
}
private:
typedef std::pair<int, EventType> RewriteCase;
struct RewriteResult {
int state;
EventType type;
RewriteAction rewrite_action;
StateAction state_action;
};
typedef std::map<RewriteCase, RewriteResult> RewriteRules;
RewriteRules rules_;
int state_;
};
} // namespace
TEST(EventRewriterTest, EventRewritingOld) {
// TestEventRewriter r0 always rewrites events to ET_CANCEL_MODE;
// it is placed at the beginning of the chain and later removed,
// to verify that rewriter removal works.
TestConstantEventRewriterOld r0(EVENT_REWRITE_REWRITTEN, ET_CANCEL_MODE);
// TestEventRewriter r1 always returns EVENT_REWRITE_CONTINUE;
// it is at the beginning of the chain (once r0 is removed)
// to verify that a later rewriter sees the events.
TestConstantEventRewriterOld r1(EVENT_REWRITE_CONTINUE, ET_UNKNOWN);
// TestEventRewriter r2 has a state machine, primarily to test
// |EVENT_REWRITE_DISPATCH_ANOTHER|.
TestStateMachineEventRewriterOld r2;
// TestEventRewriter r3 always rewrites events to ET_CANCEL_MODE;
// it is placed at the end of the chain to verify that previously
// rewritten events are not passed further down the chain.
TestConstantEventRewriterOld r3(EVENT_REWRITE_REWRITTEN, ET_CANCEL_MODE);
TestEventTarget t;
TestEventRewriteSink p(&t);
TestEventRewriteSource s(&p, &t);
s.AddEventRewriter(&r0);
s.AddEventRewriter(&r1);
s.AddEventRewriter(&r2);
// These events should be rewritten by r0 to ET_CANCEL_MODE.
p.AddExpectedEvent(ET_CANCEL_MODE);
s.Send(ET_MOUSE_DRAGGED);
p.AddExpectedEvent(ET_CANCEL_MODE);
s.Send(ET_MOUSE_PRESSED);
p.CheckAllReceived();
// Remove r0, and verify that it's gone and that events make it through.
// - r0 is removed, so the resulting event should NOT be ET_CANCEL_MODE.
// - r2 should rewrite ET_SCROLL_FLING_START to ET_SCROLL_FLING_CANCEL,
// and skip subsequent rewriters, so the resulting event should be
// ET_SCROLL_FLING_CANCEL.
// - r3 should be skipped after r2 returns, so the resulting event
// should NOT be ET_CANCEL_MODE.
s.AddEventRewriter(&r3);
s.RemoveEventRewriter(&r0);
// clang-format off
r2.AddRule(0, ET_SCROLL_FLING_START,
0, ET_SCROLL_FLING_CANCEL, EVENT_REWRITE_REWRITTEN);
// clang-format on
p.AddExpectedEvent(ET_SCROLL_FLING_CANCEL);
s.Send(ET_SCROLL_FLING_START);
p.CheckAllReceived();
s.RemoveEventRewriter(&r3);
// Verify EVENT_REWRITE_DISPATCH_ANOTHER using a state machine
// (that happens to be analogous to sticky keys).
// clang-format off
r2.AddRule(0, ET_KEY_PRESSED,
1, ET_KEY_PRESSED, EVENT_REWRITE_CONTINUE);
r2.AddRule(1, ET_MOUSE_PRESSED,
0, ET_MOUSE_PRESSED, EVENT_REWRITE_CONTINUE);
r2.AddRule(1, ET_KEY_RELEASED,
2, ET_KEY_RELEASED, EVENT_REWRITE_DISCARD);
r2.AddRule(2, ET_MOUSE_RELEASED,
3, ET_MOUSE_RELEASED, EVENT_REWRITE_DISPATCH_ANOTHER);
r2.AddRule(3, ET_MOUSE_RELEASED,
0, ET_KEY_RELEASED, EVENT_REWRITE_REWRITTEN);
// clang-format on
p.AddExpectedEvent(ET_KEY_PRESSED);
s.Send(ET_KEY_PRESSED);
s.Send(ET_KEY_RELEASED);
p.AddExpectedEvent(ET_MOUSE_PRESSED);
s.Send(ET_MOUSE_PRESSED);
// Removing rewriter r1 shouldn't affect r2.
s.RemoveEventRewriter(&r1);
// Continue with the state-based rewriting.
p.AddExpectedEvent(ET_MOUSE_RELEASED);
p.AddExpectedEvent(ET_KEY_RELEASED);
s.Send(ET_MOUSE_RELEASED);
p.CheckAllReceived();
}
TEST(EventRewriterTest, EventRewriting) {
// TestEventRewriter r0 always rewrites events to ET_CANCEL_MODE;
// it is placed at the beginning of the chain and later removed,
// to verify that rewriter removal works.
TestConstantEventRewriter r0(ET_CANCEL_MODE);
// TestEventRewriter r1 always returns EVENT_REWRITE_CONTINUE;
// it is at the beginning of the chain (once r0 is removed)
// to verify that a later rewriter sees the events.
TestAlwaysAcceptEventRewriter r1;
// TestEventRewriter r2 has a state machine, primarily to test
// |EVENT_REWRITE_DISPATCH_ANOTHER|.
TestStateMachineEventRewriter r2;
// TestEventRewriter r3 always rewrites events to ET_CANCEL_MODE;
// it is placed at the end of the chain to verify that previously
// rewritten events are not passed further down the chain.
TestConstantEventRewriter r3(ET_CANCEL_MODE);
TestEventTarget t;
TestEventRewriteSink p(&t);
TestEventRewriteSource s(&p, &t);
s.AddEventRewriter(&r0);
s.AddEventRewriter(&r1);
s.AddEventRewriter(&r2);
// These events should be rewritten by r0 to ET_CANCEL_MODE.
p.AddExpectedEvent(ET_CANCEL_MODE);
s.Send(ET_MOUSE_DRAGGED);
p.AddExpectedEvent(ET_CANCEL_MODE);
s.Send(ET_MOUSE_PRESSED);
p.CheckAllReceived();
// Remove r0, and verify that it's gone and that events make it through.
// - r0 is removed, so the resulting event should NOT be ET_CANCEL_MODE.
// - r2 should rewrite ET_SCROLL_FLING_START to ET_SCROLL_FLING_CANCEL,
// and skip subsequent rewriters, so the resulting event should be
// ET_SCROLL_FLING_CANCEL.
// - r3 should be skipped after r2 returns, so the resulting event
// should NOT be ET_CANCEL_MODE.
s.AddEventRewriter(&r3);
s.RemoveEventRewriter(&r0);
r2.AddRule(0, ET_SCROLL_FLING_START, 0, ET_SCROLL_FLING_CANCEL,
TestStateMachineEventRewriter::REPLACE,
TestStateMachineEventRewriter::RETURN);
p.AddExpectedEvent(ET_SCROLL_FLING_CANCEL);
s.Send(ET_SCROLL_FLING_START);
p.CheckAllReceived();
s.RemoveEventRewriter(&r3);
// Verify replacing an event with multiple events using a state machine
// (that happens to be analogous to sticky keys).
r2.AddRule(0, ET_KEY_PRESSED, 1, ET_UNKNOWN,
TestStateMachineEventRewriter::ACCEPT,
TestStateMachineEventRewriter::RETURN);
r2.AddRule(1, ET_MOUSE_PRESSED, 0, ET_UNKNOWN,
TestStateMachineEventRewriter::ACCEPT,
TestStateMachineEventRewriter::RETURN);
r2.AddRule(1, ET_KEY_RELEASED, 2, ET_UNKNOWN,
TestStateMachineEventRewriter::DISCARD,
TestStateMachineEventRewriter::RETURN);
r2.AddRule(2, ET_MOUSE_RELEASED, 3, ET_MOUSE_RELEASED,
TestStateMachineEventRewriter::REPLACE,
TestStateMachineEventRewriter::PROCEED);
r2.AddRule(3, ET_MOUSE_RELEASED, 0, ET_KEY_RELEASED,
TestStateMachineEventRewriter::REPLACE,
TestStateMachineEventRewriter::RETURN);
p.AddExpectedEvent(ET_KEY_PRESSED);
s.Send(ET_KEY_PRESSED); // state 0 ET_KEY_PRESSED -> 1 ACCEPT ET_KEY_PRESSED
s.Send(ET_KEY_RELEASED); // state 1 ET_KEY_RELEASED -> 2 DISCARD
p.AddExpectedEvent(ET_MOUSE_PRESSED);
s.Send(ET_MOUSE_PRESSED); // no matching rule; pass event through.
// Removing rewriter r1 shouldn't affect r2.
s.RemoveEventRewriter(&r1);
// Continue with the state-based rewriting.
p.AddExpectedEvent(ET_MOUSE_RELEASED);
p.AddExpectedEvent(ET_KEY_RELEASED);
s.Send(
ET_MOUSE_RELEASED); // 2 ET_MOUSE_RELEASED -> 3 PROCEED ET_MOUSE_RELEASED
// 3 ET_MOUSE_RELEASED -> 0 REPLACE ET_KEY_RELEASED
p.CheckAllReceived();
}
} // namespace ui