123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- // Copyright 2021 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include <fuchsia/input/virtualkeyboard/cpp/fidl.h>
- #include <fuchsia/ui/input3/cpp/fidl.h>
- #include <fuchsia/ui/input3/cpp/fidl_test_base.h>
- #include <memory>
- #include "base/fuchsia/scoped_service_binding.h"
- #include "base/fuchsia/test_component_context_for_process.h"
- #include "base/test/scoped_feature_list.h"
- #include "content/public/test/browser_test.h"
- #include "content/public/test/browser_test_utils.h"
- #include "fuchsia_web/common/test/frame_test_util.h"
- #include "fuchsia_web/common/test/test_navigation_listener.h"
- #include "fuchsia_web/webengine/browser/context_impl.h"
- #include "fuchsia_web/webengine/features.h"
- #include "fuchsia_web/webengine/test/frame_for_test.h"
- #include "fuchsia_web/webengine/test/scenic_test_helper.h"
- #include "fuchsia_web/webengine/test/scoped_connection_checker.h"
- #include "fuchsia_web/webengine/test/test_data.h"
- #include "fuchsia_web/webengine/test/web_engine_browser_test.h"
- #include "testing/gtest/include/gtest/gtest.h"
- using fuchsia::input::Key;
- using fuchsia::ui::input3::KeyEvent;
- using fuchsia::ui::input3::KeyEventType;
- using fuchsia::ui::input3::KeyMeaning;
- using fuchsia::ui::input3::NonPrintableKey;
- namespace {
- const char kKeyDown[] = "keydown";
- const char kKeyPress[] = "keypress";
- const char kKeyUp[] = "keyup";
- const char kKeyDicts[] = "keyDicts";
- // Returns a KeyEvent with |key_meaning| set based on the supplied codepoint,
- // the |key| field left not set.
- KeyEvent CreateCharacterKeyEvent(uint32_t codepoint, KeyEventType event_type) {
- KeyEvent key_event;
- fuchsia::ui::input3::KeyMeaning meaning;
- meaning.set_codepoint(codepoint);
- key_event.set_key_meaning(std::move(meaning));
- key_event.set_type(event_type);
- key_event.set_timestamp(base::TimeTicks::Now().ToZxTime());
- return key_event;
- }
- // Returns a KeyEvent with only the |key| field set, and |key_meaning| not set.
- KeyEvent CreateKeyEventNoMeaning(Key key, KeyEventType event_type) {
- KeyEvent key_event;
- key_event.set_timestamp(base::TimeTicks::Now().ToZxTime());
- key_event.set_type(event_type);
- key_event.set_key(key);
- return key_event;
- }
- // Returns a KeyEvent with both |key| and |key_meaning| set.
- KeyEvent CreateKeyEvent(Key key,
- KeyMeaning key_meaning,
- KeyEventType event_type) {
- KeyEvent key_event = CreateKeyEventNoMeaning(key, event_type);
- key_event.set_key_meaning(std::move(key_meaning));
- return key_event;
- }
- KeyEvent CreateKeyEvent(Key key, uint32_t codepoint, KeyEventType event_type) {
- return CreateKeyEvent(key, KeyMeaning::WithCodepoint(std::move(codepoint)),
- event_type);
- }
- KeyEvent CreateKeyEvent(Key key,
- NonPrintableKey non_printable_key,
- KeyEventType event_type) {
- return CreateKeyEvent(
- key, KeyMeaning::WithNonPrintableKey(std::move(non_printable_key)),
- event_type);
- }
- base::Value ExpectedKeyValue(base::StringPiece code,
- base::StringPiece key,
- base::StringPiece type) {
- base::Value::Dict expected;
- expected.Set("code", code);
- expected.Set("key", key);
- expected.Set("type", type);
- return base::Value(std::move(expected));
- }
- class FakeKeyboard : public fuchsia::ui::input3::testing::Keyboard_TestBase {
- public:
- explicit FakeKeyboard(sys::OutgoingDirectory* additional_services)
- : binding_(additional_services, this) {}
- ~FakeKeyboard() override = default;
- FakeKeyboard(const FakeKeyboard&) = delete;
- FakeKeyboard& operator=(const FakeKeyboard&) = delete;
- base::ScopedServiceBinding<fuchsia::ui::input3::Keyboard>* binding() {
- return &binding_;
- }
- // Sends |key_event| to |listener_|;
- void SendKeyEvent(KeyEvent key_event) {
- listener_->OnKeyEvent(std::move(key_event),
- [num_sent_events = num_sent_events_,
- this](fuchsia::ui::input3::KeyEventStatus status) {
- ASSERT_EQ(num_acked_events_, num_sent_events)
- << "Key events are acked out of order";
- num_acked_events_++;
- });
- num_sent_events_++;
- }
- // fuchsia::ui::input3::Keyboard implementation.
- void AddListener(
- fuchsia::ui::views::ViewRef view_ref,
- fidl::InterfaceHandle<::fuchsia::ui::input3::KeyboardListener> listener,
- AddListenerCallback callback) final {
- // This implementation is only set up to have up to one listener.
- EXPECT_FALSE(listener_);
- listener_ = listener.Bind();
- callback();
- }
- void NotImplemented_(const std::string& name) final {
- NOTIMPLEMENTED() << name;
- }
- private:
- fuchsia::ui::input3::KeyboardListenerPtr listener_;
- base::ScopedServiceBinding<fuchsia::ui::input3::Keyboard> binding_;
- // Counters to make sure key events are acked in order.
- int num_sent_events_ = 0;
- int num_acked_events_ = 0;
- };
- class KeyboardInputTest : public WebEngineBrowserTest {
- public:
- KeyboardInputTest() { set_test_server_root(base::FilePath(kTestServerRoot)); }
- ~KeyboardInputTest() override = default;
- KeyboardInputTest(const KeyboardInputTest&) = delete;
- KeyboardInputTest& operator=(const KeyboardInputTest&) = delete;
- protected:
- virtual void SetUpService() {
- keyboard_service_.emplace(component_context_->additional_services());
- }
- void SetUp() override {
- scoped_feature_list_.InitWithFeatures({features::kKeyboardInput}, {});
- WebEngineBrowserTest::SetUp();
- }
- void SetUpOnMainThread() override {
- WebEngineBrowserTest::SetUpOnMainThread();
- ASSERT_TRUE(embedded_test_server()->Start());
- fuchsia::web::CreateFrameParams params;
- frame_for_test_ = FrameForTest::Create(context(), std::move(params));
- // Set up services needed for the test. The keyboard service is included in
- // the allowed services by default. The real service needs to be removed so
- // it can be replaced by this fake implementation.
- component_context_.emplace(
- base::TestComponentContextForProcess::InitialState::kCloneAll);
- component_context_->additional_services()
- ->RemovePublicService<fuchsia::ui::input3::Keyboard>();
- SetUpService();
- virtual_keyboard_checker_.emplace(
- component_context_->additional_services());
- fuchsia::web::NavigationControllerPtr controller;
- frame_for_test_.ptr()->GetNavigationController(controller.NewRequest());
- const GURL test_url(embedded_test_server()->GetURL("/keyevents.html"));
- EXPECT_TRUE(LoadUrlAndExpectResponse(
- controller.get(), fuchsia::web::LoadUrlParams(), test_url.spec()));
- frame_for_test_.navigation_listener().RunUntilUrlEquals(test_url);
- fuchsia::web::FramePtr* frame_ptr = &(frame_for_test_.ptr());
- scenic_test_helper_.CreateScenicView(
- context_impl()->GetFrameImplForTest(frame_ptr), frame_for_test_.ptr());
- scenic_test_helper_.SetUpViewForInteraction(
- context_impl()->GetFrameImplForTest(frame_ptr)->web_contents());
- }
- // The tests expect to have input processed immediately, even if the
- // content has not been displayed yet. That's fine for the test, but
- // we need to explicitly allow it.
- void SetUpCommandLine(base::CommandLine* command_line) override {
- command_line->AppendSwitch("allow-pre-commit-input");
- }
- template <typename... Args>
- void ExpectKeyEventsEqual(Args... events) {
- base::Value::List expected;
- content::ConvertToBaseValueList(expected, std::forward<Args>(events)...);
- frame_for_test_.navigation_listener().RunUntilTitleEquals(
- base::NumberToString(expected.size()));
- absl::optional<base::Value> actual =
- ExecuteJavaScript(frame_for_test_.ptr().get(), kKeyDicts);
- EXPECT_EQ(*actual, base::Value(std::move(expected)));
- }
- // Used to publish fake services.
- absl::optional<base::TestComponentContextForProcess> component_context_;
- FrameForTest frame_for_test_;
- ScenicTestHelper scenic_test_helper_;
- absl::optional<FakeKeyboard> keyboard_service_;
- base::test::ScopedFeatureList scoped_feature_list_;
- absl::optional<
- NeverConnectedChecker<fuchsia::input::virtualkeyboard::ControllerCreator>>
- virtual_keyboard_checker_;
- };
- // Check that printable keys are sent and received correctly.
- IN_PROC_BROWSER_TEST_F(KeyboardInputTest, PrintableKeys) {
- // Send key press events from the Fuchsia keyboard service.
- // Pressing character keys will generate a JavaScript keydown event followed
- // by a keypress event. Releasing any key generates a keyup event.
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::A, 'a', KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::KEY_8, '8', KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::KEY_8, '8', KeyEventType::RELEASED));
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::A, 'a', KeyEventType::RELEASED));
- ExpectKeyEventsEqual(ExpectedKeyValue("KeyA", "a", kKeyDown),
- ExpectedKeyValue("KeyA", "a", kKeyPress),
- ExpectedKeyValue("Digit8", "8", kKeyDown),
- ExpectedKeyValue("Digit8", "8", kKeyPress),
- ExpectedKeyValue("Digit8", "8", kKeyUp),
- ExpectedKeyValue("KeyA", "a", kKeyUp));
- }
- // Check that character virtual keys are sent and received correctly.
- IN_PROC_BROWSER_TEST_F(KeyboardInputTest, Characters) {
- // Send key press events from the Fuchsia keyboard service.
- // Pressing character keys will generate a JavaScript keydown event followed
- // by a keypress event. Releasing any key generates a keyup event.
- keyboard_service_->SendKeyEvent(
- CreateCharacterKeyEvent('A', KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateCharacterKeyEvent('A', KeyEventType::RELEASED));
- keyboard_service_->SendKeyEvent(
- CreateCharacterKeyEvent('b', KeyEventType::PRESSED));
- ExpectKeyEventsEqual(
- ExpectedKeyValue("", "A", kKeyDown), ExpectedKeyValue("", "A", kKeyPress),
- ExpectedKeyValue("", "A", kKeyUp), ExpectedKeyValue("", "b", kKeyDown),
- ExpectedKeyValue("", "b", kKeyPress));
- }
- // Verify that character events are not affected by active modifiers.
- IN_PROC_BROWSER_TEST_F(KeyboardInputTest, ShiftCharacter) {
- // TODO(fxbug.dev/106600): Update the WithCodepoint(0)s when the platform is
- // fixed to provide valid KeyMeanings for these keys.
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::LEFT_SHIFT, 0, KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateCharacterKeyEvent('a', KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateCharacterKeyEvent('a', KeyEventType::RELEASED));
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::LEFT_SHIFT, 0, KeyEventType::RELEASED));
- ExpectKeyEventsEqual(
- ExpectedKeyValue("ShiftLeft", "Shift", kKeyDown),
- ExpectedKeyValue("", "a", kKeyDown), // Remains lowercase.
- ExpectedKeyValue("", "a", kKeyPress), // You guessed it! Still lowercase.
- ExpectedKeyValue("", "a", kKeyUp), // Wow, lowercase just won't quit.
- ExpectedKeyValue("ShiftLeft", "Shift", kKeyUp));
- }
- // Verifies that codepoints outside the 16-bit Unicode BMP are rejected.
- IN_PROC_BROWSER_TEST_F(KeyboardInputTest, CharacterInBmp) {
- const wchar_t kSigma = 0x03C3;
- keyboard_service_->SendKeyEvent(
- CreateCharacterKeyEvent(kSigma, KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateCharacterKeyEvent(kSigma, KeyEventType::RELEASED));
- std::string expected_utf8;
- ASSERT_TRUE(base::WideToUTF8(&kSigma, 1, &expected_utf8));
- ExpectKeyEventsEqual(ExpectedKeyValue("", expected_utf8, kKeyDown),
- ExpectedKeyValue("", expected_utf8, kKeyPress),
- ExpectedKeyValue("", expected_utf8, kKeyUp));
- }
- // Verifies that codepoints beyond the range of allowable UCS-2 values
- // are rejected.
- IN_PROC_BROWSER_TEST_F(KeyboardInputTest, CharacterBeyondBmp) {
- const uint32_t kRamenEmoji = 0x1F35C;
- keyboard_service_->SendKeyEvent(
- CreateCharacterKeyEvent(kRamenEmoji, KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateCharacterKeyEvent(kRamenEmoji, KeyEventType::RELEASED));
- keyboard_service_->SendKeyEvent(
- CreateCharacterKeyEvent('a', KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateCharacterKeyEvent('a', KeyEventType::RELEASED));
- ExpectKeyEventsEqual(ExpectedKeyValue("", "a", kKeyDown),
- ExpectedKeyValue("", "a", kKeyPress),
- ExpectedKeyValue("", "a", kKeyUp));
- }
- IN_PROC_BROWSER_TEST_F(KeyboardInputTest, ShiftPrintableKeys) {
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::LEFT_SHIFT, 0, KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::B, 'B', KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::KEY_1, '!', KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::SPACE, ' ', KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::LEFT_SHIFT, 0, KeyEventType::RELEASED));
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::DOT, '.', KeyEventType::PRESSED));
- // Note that non-character keys (e.g. shift, control) only generate key down
- // and key up web events. They do not generate key pressed events.
- ExpectKeyEventsEqual(ExpectedKeyValue("ShiftLeft", "Shift", kKeyDown),
- ExpectedKeyValue("KeyB", "B", kKeyDown),
- ExpectedKeyValue("KeyB", "B", kKeyPress),
- ExpectedKeyValue("Digit1", "!", kKeyDown),
- ExpectedKeyValue("Digit1", "!", kKeyPress),
- ExpectedKeyValue("Space", " ", kKeyDown),
- ExpectedKeyValue("Space", " ", kKeyPress),
- ExpectedKeyValue("ShiftLeft", "Shift", kKeyUp),
- ExpectedKeyValue("Period", ".", kKeyDown),
- ExpectedKeyValue("Period", ".", kKeyPress));
- }
- IN_PROC_BROWSER_TEST_F(KeyboardInputTest, ShiftNonPrintableKeys) {
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::RIGHT_SHIFT, 0, KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(CreateKeyEvent(
- Key::ENTER, NonPrintableKey::ENTER, KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::LEFT_CTRL, 0, KeyEventType::PRESSED));
- keyboard_service_->SendKeyEvent(
- CreateKeyEvent(Key::RIGHT_SHIFT, 0, KeyEventType::RELEASED));
- // Note that non-character keys (e.g. shift, control) only generate key down
- // and key up web events. They do not generate key pressed events.
- ExpectKeyEventsEqual(ExpectedKeyValue("ShiftRight", "Shift", kKeyDown),
- ExpectedKeyValue("Enter", "Enter", kKeyDown),
- ExpectedKeyValue("Enter", "Enter", kKeyPress),
- ExpectedKeyValue("ControlLeft", "Control", kKeyDown),
- ExpectedKeyValue("ShiftRight", "Shift", kKeyUp));
- }
- IN_PROC_BROWSER_TEST_F(KeyboardInputTest, Disconnect) {
- // Disconnect the keyboard service.
- keyboard_service_.reset();
- frame_for_test_.navigation_listener().RunUntilTitleEquals("loaded");
- // Make sure the page is still available and there are no crashes.
- EXPECT_TRUE(
- ExecuteJavaScript(frame_for_test_.ptr().get(), "true")->GetBool());
- }
- class KeyboardInputTestWithoutKeyboardFeature : public KeyboardInputTest {
- public:
- KeyboardInputTestWithoutKeyboardFeature() = default;
- ~KeyboardInputTestWithoutKeyboardFeature() override = default;
- protected:
- void SetUp() override {
- scoped_feature_list_.InitWithFeatures({}, {});
- WebEngineBrowserTest::SetUp();
- }
- void SetUpService() override {
- keyboard_input_checker_.emplace(component_context_->additional_services());
- }
- absl::optional<NeverConnectedChecker<fuchsia::ui::input3::Keyboard>>
- keyboard_input_checker_;
- };
- IN_PROC_BROWSER_TEST_F(KeyboardInputTestWithoutKeyboardFeature, NoFeature) {
- // Test will verify that |keyboard_input_checker_| never received a connection
- // request at teardown time.
- }
- } // namespace
|