| // 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 |