chromium/ui/events/win/media_keyboard_hook_win_unittest.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <vector>

#include "base/functional/bind.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/event.h"
#include "ui/events/keyboard_hook.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/win/keyboard_hook_win_base.h"

namespace ui {

class MediaKeyboardHookWinTest : public testing::Test {
 public:
  MediaKeyboardHookWinTest();

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

  ~MediaKeyboardHookWinTest() override;

  // testing::Test overrides.
  void SetUp() override;

  void HandleKeyPress(KeyEvent* key_event);

 protected:
  KeyboardHookWinBase* keyboard_hook() { return keyboard_hook_.get(); }

  uint32_t next_time_stamp() { return time_stamp_++; }

  std::vector<KeyEvent>* key_events() { return &key_events_; }

  // Used for sending key events which are handled by the hook.
  void SendMediaKeyDownEvent(KeyboardCode key_code,
                             DomCode dom_code,
                             int repeat_count = 1);
  void SendMediaKeyUpEvent(KeyboardCode key_code, DomCode dom_code);

  // Set the return value for the HandleKeyPress callback.
  void StartHandlingKeys() { should_handle_keys_ = true; }
  void StopHandlingKeys() { should_handle_keys_ = false; }

 private:
  uint32_t time_stamp_ = 0;
  std::unique_ptr<KeyboardHookWinBase> keyboard_hook_;
  std::vector<KeyEvent> key_events_;
  bool should_handle_keys_ = true;
};

MediaKeyboardHookWinTest::MediaKeyboardHookWinTest() = default;

MediaKeyboardHookWinTest::~MediaKeyboardHookWinTest() = default;

void MediaKeyboardHookWinTest::SetUp() {
  keyboard_hook_ = KeyboardHookWinBase::CreateMediaKeyboardHookForTesting(
      base::BindRepeating(&MediaKeyboardHookWinTest::HandleKeyPress,
                          base::Unretained(this)));
}

void MediaKeyboardHookWinTest::HandleKeyPress(KeyEvent* key_event) {
  key_events_.push_back(*key_event);
  if (should_handle_keys_)
    key_event->SetHandled();
}

void MediaKeyboardHookWinTest::SendMediaKeyDownEvent(KeyboardCode key_code,
                                                     DomCode dom_code,
                                                     int repeat_count /*=1*/) {
  ASSERT_GT(repeat_count, 0);
  // This should only be used when we're handling keys.
  ASSERT_TRUE(should_handle_keys_);

  for (int i = 0; i < repeat_count; i++) {
    ASSERT_TRUE(keyboard_hook()->ProcessKeyEventMessage(
        WM_KEYDOWN, key_code,
        KeycodeConverter::DomCodeToNativeKeycode(dom_code), next_time_stamp()));
  }
}

void MediaKeyboardHookWinTest::SendMediaKeyUpEvent(KeyboardCode key_code,
                                                   DomCode dom_code) {
  // This should only be used when we're handling keys.
  ASSERT_TRUE(should_handle_keys_);

  ASSERT_TRUE(keyboard_hook()->ProcessKeyEventMessage(
      WM_KEYUP, key_code, KeycodeConverter::DomCodeToNativeKeycode(dom_code),
      next_time_stamp()));
}

namespace {
void VerifyKeyEvent(KeyEvent* key_event,
                    KeyboardCode non_located_key_code,
                    DomCode dom_code,
                    bool key_down,
                    bool is_repeat) {
  if (key_down) {
    ASSERT_EQ(key_event->type(), EventType::kKeyPressed);
    ASSERT_EQ(key_event->is_repeat(), is_repeat);
  } else {
    ASSERT_EQ(key_event->type(), EventType::kKeyReleased);
    ASSERT_FALSE(key_event->is_repeat());
  }
  ASSERT_EQ(key_event->key_code(), non_located_key_code);
  ASSERT_EQ(key_event->code(), dom_code);
}
}  // namespace

TEST_F(MediaKeyboardHookWinTest, SimpleKeypressTest) {
  const KeyboardCode key_code = KeyboardCode::VKEY_MEDIA_PLAY_PAUSE;
  const DomCode dom_code = DomCode::MEDIA_PLAY_PAUSE;
  SendMediaKeyDownEvent(key_code, dom_code);
  ASSERT_EQ(key_events()->size(), 1u);
  SendMediaKeyUpEvent(key_code, dom_code);
  ASSERT_EQ(key_events()->size(), 2u);

  KeyEvent down_event = key_events()->at(0);
  ASSERT_NO_FATAL_FAILURE(
      VerifyKeyEvent(&down_event, key_code, dom_code, true, false));

  KeyEvent up_event = key_events()->at(1);
  ASSERT_NO_FATAL_FAILURE(
      VerifyKeyEvent(&up_event, key_code, dom_code, false, false));
}

TEST_F(MediaKeyboardHookWinTest, RepeatingKeypressTest) {
  const int repeat_count = 10;
  const KeyboardCode key_code = KeyboardCode::VKEY_MEDIA_PLAY_PAUSE;
  const DomCode dom_code = DomCode::MEDIA_PLAY_PAUSE;
  SendMediaKeyDownEvent(key_code, dom_code, repeat_count);
  ASSERT_EQ(static_cast<int>(key_events()->size()), repeat_count);
  SendMediaKeyUpEvent(key_code, dom_code);
  ASSERT_EQ(static_cast<int>(key_events()->size()), repeat_count + 1);

  bool should_repeat = false;
  for (int i = 0; i < repeat_count; i++) {
    KeyEvent event = key_events()->at(i);
    ASSERT_NO_FATAL_FAILURE(
        VerifyKeyEvent(&event, key_code, dom_code, true, should_repeat));
    should_repeat = true;
  }

  KeyEvent up_event = key_events()->at(repeat_count);
  ASSERT_NO_FATAL_FAILURE(
      VerifyKeyEvent(&up_event, key_code, dom_code, false, false));
}

TEST_F(MediaKeyboardHookWinTest, UnhandledKeysArePropagated) {
  StopHandlingKeys();

  // Ensure media keys are propagated to the OS.
  ASSERT_FALSE(keyboard_hook()->ProcessKeyEventMessage(
      WM_KEYDOWN, KeyboardCode::VKEY_MEDIA_STOP,
      KeycodeConverter::DomCodeToNativeKeycode(DomCode::MEDIA_STOP),
      next_time_stamp()));
  ASSERT_FALSE(keyboard_hook()->ProcessKeyEventMessage(
      WM_KEYUP, KeyboardCode::VKEY_MEDIA_STOP,
      KeycodeConverter::DomCodeToNativeKeycode(DomCode::MEDIA_STOP),
      next_time_stamp()));

  StartHandlingKeys();

  // Ensure media keys are not propagated to the OS.
  ASSERT_TRUE(keyboard_hook()->ProcessKeyEventMessage(
      WM_KEYDOWN, KeyboardCode::VKEY_MEDIA_STOP,
      KeycodeConverter::DomCodeToNativeKeycode(DomCode::MEDIA_STOP),
      next_time_stamp()));
  ASSERT_TRUE(keyboard_hook()->ProcessKeyEventMessage(
      WM_KEYUP, KeyboardCode::VKEY_MEDIA_STOP,
      KeycodeConverter::DomCodeToNativeKeycode(DomCode::MEDIA_STOP),
      next_time_stamp()));
}

TEST_F(MediaKeyboardHookWinTest, NonInterceptedKeysTest) {
  // Here we try a few keys we do not expect to be intercepted / handled.
  ASSERT_FALSE(keyboard_hook()->ProcessKeyEventMessage(
      WM_KEYDOWN, KeyboardCode::VKEY_RSHIFT,
      KeycodeConverter::DomCodeToNativeKeycode(DomCode::SHIFT_RIGHT),
      next_time_stamp()));
  ASSERT_FALSE(keyboard_hook()->ProcessKeyEventMessage(
      WM_KEYUP, KeyboardCode::VKEY_RSHIFT,
      KeycodeConverter::DomCodeToNativeKeycode(DomCode::SHIFT_RIGHT),
      next_time_stamp()));

  ASSERT_FALSE(keyboard_hook()->ProcessKeyEventMessage(
      WM_KEYDOWN, KeyboardCode::VKEY_ESCAPE,
      KeycodeConverter::DomCodeToNativeKeycode(DomCode::ESCAPE),
      next_time_stamp()));
  ASSERT_FALSE(keyboard_hook()->ProcessKeyEventMessage(
      WM_KEYUP, KeyboardCode::VKEY_ESCAPE,
      KeycodeConverter::DomCodeToNativeKeycode(DomCode::ESCAPE),
      next_time_stamp()));

  ASSERT_FALSE(keyboard_hook()->ProcessKeyEventMessage(
      WM_KEYDOWN, KeyboardCode::VKEY_A,
      KeycodeConverter::DomCodeToNativeKeycode(DomCode::US_A),
      next_time_stamp()));
  ASSERT_FALSE(keyboard_hook()->ProcessKeyEventMessage(
      WM_KEYUP, KeyboardCode::VKEY_A,
      KeycodeConverter::DomCodeToNativeKeycode(DomCode::US_A),
      next_time_stamp()));
}

}  // namespace ui