910e62b5创建于 1月15日历史提交
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fidl/fuchsia.input.virtualkeyboard/cpp/wire_messaging.h>
#include <fidl/fuchsia.ui.input3/cpp/fidl.h>
#include <lib/async/default.h>

#include <memory>
#include <string_view>

#include "base/fuchsia/scoped_service_binding.h"
#include "base/fuchsia/test_component_context_for_process.h"
#include "base/strings/string_number_conversions.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_for_test.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/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"
#include "ui/ozone/public/ozone_platform.h"

using fuchsia_input::Key;
using fuchsia_ui_input3::KeyEvent;
using fuchsia_ui_input3::KeyEventType;

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) {
  return {{
      .timestamp = base::TimeTicks::Now().ToZxTime(),
      .type = event_type,
      .key_meaning = fuchsia_ui_input3::KeyMeaning::WithCodepoint(codepoint),
  }};
}

struct KeyEventOptions {
  bool repeat;
  std::vector<fuchsia_ui_input3::Modifiers> modifiers;
};

// Returns a KeyEvent with both |key| and |key_meaning| set.
KeyEvent CreateKeyEvent(Key key,
                        fuchsia_ui_input3::KeyMeaning key_meaning,
                        KeyEventType event_type,
                        KeyEventOptions options = {}) {
  KeyEvent key_event{{
      .timestamp = base::TimeTicks::Now().ToZxTime(),
      .type = event_type,
      .key = key,
      .key_meaning = std::move(key_meaning),
  }};

  if (options.repeat) {
    // Chromium doesn't look at the value of this, it just check if the field is
    // present.
    key_event.repeat_sequence(1);
  }
  if (!options.modifiers.empty()) {
    fuchsia_ui_input3::Modifiers modifiers;
    for (const auto modifier : options.modifiers) {
      modifiers |= modifier;
    }
    key_event.modifiers(modifiers);
  }
  return key_event;
}
KeyEvent CreateKeyEvent(Key key,
                        uint32_t codepoint,
                        KeyEventType event_type,
                        KeyEventOptions options = {}) {
  return CreateKeyEvent(
      key, fuchsia_ui_input3::KeyMeaning::WithCodepoint(std::move(codepoint)),
      event_type, options);
}
KeyEvent CreateKeyEvent(Key key,
                        fuchsia_ui_input3::NonPrintableKey non_printable_key,
                        KeyEventType event_type,
                        KeyEventOptions options = {}) {
  return CreateKeyEvent(key,
                        fuchsia_ui_input3::KeyMeaning::WithNonPrintableKey(
                            std::move(non_printable_key)),
                        event_type, options);
}

base::Value::List FuchsiaModifiersToWebModifiers(
    const std::vector<fuchsia_ui_input3::Modifiers> fuchsia_modifiers) {
  base::Value::List web_modifiers;
  for (const auto modifier : fuchsia_modifiers) {
    if (modifier == fuchsia_ui_input3::Modifiers::kAlt) {
      web_modifiers.Append("Alt");
    } else if (modifier == fuchsia_ui_input3::Modifiers::kAltGraph) {
      web_modifiers.Append("AltGraph");
    } else if (modifier == fuchsia_ui_input3::Modifiers::kCapsLock) {
      web_modifiers.Append("CapsLock");
    } else if (modifier == fuchsia_ui_input3::Modifiers::kCtrl) {
      web_modifiers.Append("Control");
    } else if (modifier == fuchsia_ui_input3::Modifiers::kMeta) {
      web_modifiers.Append("Meta");
    } else if (modifier == fuchsia_ui_input3::Modifiers::kNumLock) {
      web_modifiers.Append("NumLock");
    } else if (modifier == fuchsia_ui_input3::Modifiers::kScrollLock) {
      web_modifiers.Append("ScrollLock");
    } else if (modifier == fuchsia_ui_input3::Modifiers::kShift) {
      web_modifiers.Append("Shift");
    } else {
      NOTREACHED() << static_cast<uint64_t>(modifier) << " has no web mapping";
    }
  }
  return web_modifiers;
}

base::Value ExpectedKeyValue(std::string_view code,
                             std::string_view key,
                             std::string_view type,
                             KeyEventOptions options = {}) {
  base::Value::Dict expected;
  expected.Set("code", code);
  expected.Set("key", key);
  expected.Set("type", type);
  expected.Set("repeat", options.repeat);
  expected.Set("modifiers", FuchsiaModifiersToWebModifiers(options.modifiers));
  return base::Value(std::move(expected));
}

class FakeKeyboard : public fidl::Server<fuchsia_ui_input3::Keyboard> {
 public:
  explicit FakeKeyboard(sys::OutgoingDirectory* additional_services)
      : binding_(additional_services, this) {}
  ~FakeKeyboard() override = default;

  FakeKeyboard(const FakeKeyboard&) = delete;
  FakeKeyboard& operator=(const FakeKeyboard&) = delete;

  base::ScopedNaturalServiceBinding<fuchsia_ui_input3::Keyboard>* binding() {
    return &binding_;
  }

  // Sends |key_event| to |listener_|;
  void SendKeyEvent(KeyEvent key_event) {
    listener_->OnKeyEvent(std::move(key_event))
        .Then([num_sent_events = num_sent_events_,
               this](const fidl::Result<
                     fuchsia_ui_input3::KeyboardListener::OnKeyEvent>& result) {
          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(AddListenerRequest& request,
                   AddListenerCompleter::Sync& completer) final {
    // This implementation is only set up to have up to one listener.
    EXPECT_FALSE(listener_);
    listener_.Bind(std::move(request.listener()),
                   async_get_default_dispatcher());
    completer.Reply();
  }

 private:
  fidl::Client<fuchsia_ui_input3::KeyboardListener> listener_;
  base::ScopedNaturalServiceBinding<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 {
    if (ui::OzonePlatform::GetPlatformNameForTest() == "headless") {
      GTEST_SKIP() << "Keyboard inputs are ignored in headless mode.";
    }

    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>(
            fidl::DiscoverableProtocolName<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());
  }

  void TearDownOnMainThread() override {
    frame_for_test_ = {};
    WebEngineBrowserTest::TearDownOnMainThread();
  }

  // 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");
  }

  void ExpectKeyEventsEqual(base::Value::List expected) {
    frame_for_test_.navigation_listener().RunUntilTitleEquals(
        base::NumberToString(expected.size()));

    std::optional<base::Value> actual =
        ExecuteJavaScript(frame_for_test_.ptr().get(), kKeyDicts);
    EXPECT_EQ(*actual, base::Value(std::move(expected)));
  }

  template <typename... Args>
  void ExpectKeyEventsEqual(Args... events) {
    base::Value::List expected =
        content::ListValueOf(std::forward<Args>(events)...).TakeList();
    ExpectKeyEventsEqual(std::move(expected));
  }

  // Used to publish fake services.
  std::optional<base::TestComponentContextForProcess> component_context_;

  FrameForTest frame_for_test_;
  ScenicTestHelper scenic_test_helper_;
  std::optional<FakeKeyboard> keyboard_service_;
  base::test::ScopedFeatureList scoped_feature_list_;
  std::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::kA, 'a', KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kKey8, '8', KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kKey8, '8', KeyEventType::kReleased));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kA, 'a', KeyEventType::kReleased));

  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::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateCharacterKeyEvent('A', KeyEventType::kReleased));
  keyboard_service_->SendKeyEvent(
      CreateCharacterKeyEvent('b', KeyEventType::kPressed));

  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::kLeftShift, 0, KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateCharacterKeyEvent('a', KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateCharacterKeyEvent('a', KeyEventType::kReleased));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kLeftShift, 0, KeyEventType::kReleased));

  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::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateCharacterKeyEvent(kSigma, KeyEventType::kReleased));

  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::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateCharacterKeyEvent(kRamenEmoji, KeyEventType::kReleased));
  keyboard_service_->SendKeyEvent(
      CreateCharacterKeyEvent('a', KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateCharacterKeyEvent('a', KeyEventType::kReleased));

  ExpectKeyEventsEqual(ExpectedKeyValue("", "a", kKeyDown),
                       ExpectedKeyValue("", "a", kKeyPress),
                       ExpectedKeyValue("", "a", kKeyUp));
}

IN_PROC_BROWSER_TEST_F(KeyboardInputTest, ShiftPrintableKeys) {
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kLeftShift, 0, KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kB, 'B', KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kKey1, '!', KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kSpace, ' ', KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kLeftShift, 0, KeyEventType::kReleased));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kDot, '.', KeyEventType::kPressed));

  // 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::kRightShift, 0, KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kEnter, fuchsia_ui_input3::NonPrintableKey::kEnter,
                     KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kLeftCtrl, 0, KeyEventType::kPressed));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kRightShift, 0, KeyEventType::kReleased));

  // 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, RepeatedKeys) {
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kA, 'a', KeyEventType::kPressed, {.repeat = true}));
  keyboard_service_->SendKeyEvent(CreateKeyEvent(
      Key::kKey8, '8', KeyEventType::kPressed, {.repeat = true}));

  // 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("KeyA", "a", kKeyDown, {.repeat = true}),
      ExpectedKeyValue("KeyA", "a", kKeyPress, {.repeat = true}),
      ExpectedKeyValue("Digit8", "8", kKeyDown, {.repeat = true}),
      ExpectedKeyValue("Digit8", "8", kKeyPress, {.repeat = true}));
}

IN_PROC_BROWSER_TEST_F(KeyboardInputTest, AllSupportedWebModifierKeys) {
  // All modifiers in the FIDL protocol that Chrome handles on the web.
  //
  // Missing modifiers:
  // * LEFT_*/RIGHT_* are not valid by themselves.
  // * FUNCTION and SYMBOL. See AllUnsupportedWebModifierKeys test.
  const std::vector kAllSupportedModifiers = {
      fuchsia_ui_input3::Modifiers::kCapsLock,
      fuchsia_ui_input3::Modifiers::kNumLock,
      fuchsia_ui_input3::Modifiers::kScrollLock,
      fuchsia_ui_input3::Modifiers::kShift,
      fuchsia_ui_input3::Modifiers::kAlt,
      fuchsia_ui_input3::Modifiers::kAltGraph,
      fuchsia_ui_input3::Modifiers::kMeta,
      fuchsia_ui_input3::Modifiers::kCtrl};
  for (const auto& modifier : kAllSupportedModifiers) {
    keyboard_service_->SendKeyEvent(CreateKeyEvent(
        Key::kM, 'm', KeyEventType::kPressed, {.modifiers = {modifier}}));
  }

  base::Value::List expected_events;
  for (const auto& modifier : kAllSupportedModifiers) {
    expected_events.Append(
        ExpectedKeyValue("KeyM", "m", kKeyDown, {.modifiers = {modifier}}));
    // Chrome doesn't emit keypress events when an ALT or CTRL modifier is
    // present.
    if (modifier != fuchsia_ui_input3::Modifiers::kAlt &&
        modifier != fuchsia_ui_input3::Modifiers::kCtrl) {
      expected_events.Append(
          ExpectedKeyValue("KeyM", "m", kKeyPress, {.modifiers = {modifier}}));
    }
  }

  ExpectKeyEventsEqual(std::move(expected_events));
}

IN_PROC_BROWSER_TEST_F(KeyboardInputTest, AllUnsupportedWebModifierKeys) {
  // All modifiers in the FIDL protocol that Chrome doesn't handle on the web
  // because they aren't included in
  // https://crsrc.org/c/ui/events/blink/blink_event_util.cc;l=268?q=EventFlagsToWebEventModifiers
  const std::vector kAllUnsupportedModifiers = {
      fuchsia_ui_input3::Modifiers::kFunction,
      fuchsia_ui_input3::Modifiers::kSymbol};
  for (const auto& modifier : kAllUnsupportedModifiers) {
    keyboard_service_->SendKeyEvent(CreateKeyEvent(
        Key::kM, 'm', KeyEventType::kPressed, {.modifiers = {modifier}}));
  }

  base::Value::List expected_events;
  for (size_t i = 0; i < kAllUnsupportedModifiers.size(); ++i) {
    expected_events.Append(ExpectedKeyValue("KeyM", "m", kKeyDown, {}));
    expected_events.Append(ExpectedKeyValue("KeyM", "m", kKeyPress, {}));
  }

  ExpectKeyEventsEqual(std::move(expected_events));
}

// This is a spot check to make sure that modifiers work with other keys and in
// combination with each other.
IN_PROC_BROWSER_TEST_F(KeyboardInputTest, AssortedModifierKeyCombos) {
  // Test that sending LEFT/RIGHT SHIFT with agnostic SHIFT passes DCHECK.
  keyboard_service_->SendKeyEvent(CreateKeyEvent(
      Key::kA, 'a', KeyEventType::kPressed,
      {.modifiers = {fuchsia_ui_input3::Modifiers::kShift,
                     fuchsia_ui_input3::Modifiers::kLeftShift,
                     fuchsia_ui_input3::Modifiers::kRightShift}}));
  // Test that sending LEFT/RIGHT ALT with agnostic ALT passes DCHECK.
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kKey8, '8', KeyEventType::kPressed,
                     {.modifiers = {fuchsia_ui_input3::Modifiers::kAlt,
                                    fuchsia_ui_input3::Modifiers::kLeftAlt,
                                    fuchsia_ui_input3::Modifiers::kRightAlt}}));
  // Test that sending LEFT/RIGHT META with agnostic META passes DCHECK.
  keyboard_service_->SendKeyEvent(CreateKeyEvent(
      Key::kB, 'b', KeyEventType::kPressed,
      {.modifiers = {fuchsia_ui_input3::Modifiers::kMeta,
                     fuchsia_ui_input3::Modifiers::kLeftMeta,
                     fuchsia_ui_input3::Modifiers::kRightMeta}}));
  // Test that sending LEFT/RIGHT CTRL with agnostic CTRL passes DCHECK.
  keyboard_service_->SendKeyEvent(CreateKeyEvent(
      Key::kLeft, 0, KeyEventType::kPressed,
      {.modifiers = {fuchsia_ui_input3::Modifiers::kCtrl,
                     fuchsia_ui_input3::Modifiers::kLeftCtrl,
                     fuchsia_ui_input3::Modifiers::kRightCtrl}}));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kP, 'p', KeyEventType::kPressed,
                     {.modifiers = {fuchsia_ui_input3::Modifiers::kCtrl,
                                    fuchsia_ui_input3::Modifiers::kShift}}));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kRight, 0, KeyEventType::kPressed,
                     {.modifiers = {fuchsia_ui_input3::Modifiers::kAltGraph}}));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kUp, 0, KeyEventType::kPressed,
                     {.modifiers = {fuchsia_ui_input3::Modifiers::kCapsLock}}));
  keyboard_service_->SendKeyEvent(
      CreateKeyEvent(Key::kDown, 0, KeyEventType::kPressed,
                     {.modifiers = {fuchsia_ui_input3::Modifiers::kNumLock}}));
  keyboard_service_->SendKeyEvent(CreateKeyEvent(
      Key::kLeft, 0, KeyEventType::kPressed,
      {.modifiers = {fuchsia_ui_input3::Modifiers::kScrollLock}}));

  ExpectKeyEventsEqual(
      ExpectedKeyValue("KeyA", "a", kKeyDown,
                       {.modifiers = {fuchsia_ui_input3::Modifiers::kShift}}),
      ExpectedKeyValue("KeyA", "a", kKeyPress,
                       {.modifiers = {fuchsia_ui_input3::Modifiers::kShift}}),
      ExpectedKeyValue("Digit8", "8", kKeyDown,
                       {.modifiers = {fuchsia_ui_input3::Modifiers::kAlt}}),
      ExpectedKeyValue("KeyB", "b", kKeyDown,
                       {.modifiers = {fuchsia_ui_input3::Modifiers::kMeta}}),
      ExpectedKeyValue("KeyB", "b", kKeyPress,
                       {.modifiers = {fuchsia_ui_input3::Modifiers::kMeta}}),
      ExpectedKeyValue("ArrowLeft", "ArrowLeft", kKeyDown,
                       {.modifiers = {fuchsia_ui_input3::Modifiers::kCtrl}}),
      ExpectedKeyValue("KeyP", "p", kKeyDown,
                       {.modifiers = {fuchsia_ui_input3::Modifiers::kCtrl,
                                      fuchsia_ui_input3::Modifiers::kShift}}),
      ExpectedKeyValue("KeyP", "p", kKeyPress,
                       {.modifiers = {fuchsia_ui_input3::Modifiers::kCtrl,
                                      fuchsia_ui_input3::Modifiers::kShift}}),
      ExpectedKeyValue(
          "ArrowRight", "ArrowRight", kKeyDown,
          {.modifiers = {fuchsia_ui_input3::Modifiers::kAltGraph}}),
      ExpectedKeyValue(
          "ArrowUp", "ArrowUp", kKeyDown,
          {.modifiers = {fuchsia_ui_input3::Modifiers::kCapsLock}}),
      ExpectedKeyValue("ArrowDown", "ArrowDown", kKeyDown,
                       {.modifiers = {fuchsia_ui_input3::Modifiers::kNumLock}}),
      ExpectedKeyValue(
          "ArrowLeft", "ArrowLeft", kKeyDown,
          {.modifiers = {fuchsia_ui_input3::Modifiers::kScrollLock}}));
}

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());
  }

  std::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