chromium/ui/events/event_unittest.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ui/events/event.h"

#include <stddef.h>
#include <stdint.h>

#include <limits>
#include <memory>
#include <string>

#include "base/strings/strcat.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/events/test/events_test_utils.h"
#include "ui/events/test/keyboard_layout.h"
#include "ui/events/test/test_event_target.h"
#include "ui/gfx/geometry/transform.h"

#if BUILDFLAG(IS_WIN)
#include "ui/events/win/events_win_utils.h"
#endif

namespace ui {

TEST(EventTest, NoNativeEvent) {}

TEST(EventTest, NativeEvent) {}

TEST(EventTest, GetCharacter) {}

TEST(EventTest, ClickCount) {}

TEST(EventTest, RepeatedClick) {}

// Automatic repeat flag setting is disabled on Lacros,
// because the repeated event is generated inside ui/ozone/platform/wayland
// and reliable.
TEST(EventTest, RepeatedKeyEvent) {}

TEST(EventTest, NoRepeatedKeyEvent) {}

// Tests that re-processing the same mouse press event (detected by timestamp)
// does not yield a double click event: http://crbug.com/389162
TEST(EventTest, DoubleClickRequiresUniqueTimestamp) {}

// Tests that right clicking, then left clicking does not yield double clicks.
TEST(EventTest, SingleClickRightLeft) {}

TEST(EventTest, KeyEvent) {}

TEST(EventTest, KeyEventDirectUnicode) {}

TEST(EventTest, NormalizeKeyEventFlags) {}

TEST(EventTest, KeyEventCopy) {}

TEST(EventTest, KeyEventCode) {}

TEST(EventTest, TouchEventRadiusDefaultsToOtherAxis) {}

TEST(EventTest, TouchEventRotationAngleFixing) {}

TEST(EventTest, PointerDetailsTouch) {}

TEST(EventTest, PointerDetailsMouse) {}

TEST(EventTest, PointerDetailsStylus) {}

TEST(EventTest, PointerDetailsCustomTouch) {}

TEST(EventTest, MouseEventLatencyUIComponentExists) {}

TEST(EventTest, MouseWheelEventLatencyUIComponentExists) {}

TEST(EventTest, MouseWheelEventLinearTickCalculation) {}

TEST(EventTest, OrdinalMotionConversion) {}

// Checks that Event.Latency.OS2.MOUSE_WHEEL histogram is computed properly.
TEST(EventTest, EventLatencyOSMouseWheelHistogram) {}

TEST(EventTest, UpdateForRootTransformation) {}

TEST(EventTest, OperatorEqual) {}

// Verifies that ToString() generates something and doesn't crash. The specific
// format isn't important.
TEST(EventTest, ToStringNotEmpty) {}

#if BUILDFLAG(IS_WIN)
namespace {

const struct AltGraphEventTestCase {
  KeyboardCode key_code;
  KeyboardLayout layout;
  std::vector<KeyboardCode> modifier_key_codes;
  int expected_flags;
} kAltGraphEventTestCases[] = {
    // US English -> AltRight never behaves as AltGraph.
    {VKEY_C,
     KEYBOARD_LAYOUT_ENGLISH_US,
     {VKEY_RMENU, VKEY_LCONTROL, VKEY_MENU, VKEY_CONTROL},
     EF_ALT_DOWN | EF_CONTROL_DOWN},
    {VKEY_E,
     KEYBOARD_LAYOUT_ENGLISH_US,
     {VKEY_RMENU, VKEY_LCONTROL, VKEY_MENU, VKEY_CONTROL},
     EF_ALT_DOWN | EF_CONTROL_DOWN},

    // French -> Always expect AltGraph if VKEY_RMENU is pressed.
    {VKEY_C,
     KEYBOARD_LAYOUT_FRENCH,
     {VKEY_RMENU, VKEY_LCONTROL, VKEY_MENU, VKEY_CONTROL},
     EF_ALTGR_DOWN},
    {VKEY_E,
     KEYBOARD_LAYOUT_FRENCH,
     {VKEY_RMENU, VKEY_LCONTROL, VKEY_MENU, VKEY_CONTROL},
     EF_ALTGR_DOWN},

    // French -> Expect Control+Alt is AltGraph on AltGraph-shifted keys.
    {VKEY_C,
     KEYBOARD_LAYOUT_FRENCH,
     {VKEY_LMENU, VKEY_LCONTROL, VKEY_MENU, VKEY_CONTROL},
     EF_ALT_DOWN | EF_CONTROL_DOWN},
    {VKEY_E,
     KEYBOARD_LAYOUT_FRENCH,
     {VKEY_LMENU, VKEY_LCONTROL, VKEY_MENU, VKEY_CONTROL},
     EF_ALTGR_DOWN},
};

class AltGraphEventTest
    : public testing::TestWithParam<std::tuple<UINT, AltGraphEventTestCase>> {
 public:
  AltGraphEventTest()
      : msg_({nullptr, message_type(),
              static_cast<WPARAM>(test_case().key_code)}) {
    // Save the current keyboard layout and state, to restore later.
    CHECK(GetKeyboardState(original_keyboard_state_));
    original_keyboard_layout_ = GetKeyboardLayout(0);

    // Configure specified layout, and update keyboard state for specified
    // modifier keys.
    CHECK(ActivateKeyboardLayout(GetPlatformKeyboardLayout(test_case().layout),
                                 0));
    BYTE test_keyboard_state[256] = {};
    for (const auto& key_code : test_case().modifier_key_codes)
      test_keyboard_state[key_code] = 0x80;
    CHECK(SetKeyboardState(test_keyboard_state));
  }

  ~AltGraphEventTest() {
    // Restore the original keyboard layout & key states.
    CHECK(ActivateKeyboardLayout(original_keyboard_layout_, 0));
    CHECK(SetKeyboardState(original_keyboard_state_));
  }

 protected:
  UINT message_type() const { return std::get<0>(GetParam()); }
  const AltGraphEventTestCase& test_case() const {
    return std::get<1>(GetParam());
  }

  const CHROME_MSG msg_;
  BYTE original_keyboard_state_[256] = {};
  HKL original_keyboard_layout_ = nullptr;
};

}  // namespace

TEST_P(AltGraphEventTest, KeyEventAltGraphModifer) {
  KeyEvent event(msg_);
  if (message_type() == WM_CHAR) {
    // By definition, if we receive a WM_CHAR message when Control and Alt are
    // pressed, it indicates AltGraph.
    EXPECT_EQ(event.flags() & (EF_CONTROL_DOWN | EF_ALT_DOWN | EF_ALTGR_DOWN),
              EF_ALTGR_DOWN);
  } else {
    EXPECT_EQ(event.flags() & (EF_CONTROL_DOWN | EF_ALT_DOWN | EF_ALTGR_DOWN),
              test_case().expected_flags);
  }
}

INSTANTIATE_TEST_SUITE_P(
    WM_KEY,
    AltGraphEventTest,
    ::testing::Combine(::testing::Values(WM_KEYDOWN, WM_KEYUP),
                       ::testing::ValuesIn(kAltGraphEventTestCases)));
INSTANTIATE_TEST_SUITE_P(
    WM_CHAR,
    AltGraphEventTest,
    ::testing::Combine(::testing::Values(WM_CHAR),
                       ::testing::ValuesIn(kAltGraphEventTestCases)));

// Tests for ComputeEventLatencyOS variants.

class EventLatencyTest : public ::testing::Test {
 public:
  EventLatencyTest() { SetEventLatencyTickClockForTesting(&tick_clock_); }

  ~EventLatencyTest() override { SetEventLatencyTickClockForTesting(nullptr); }

 protected:
  void UpdateTickClock(DWORD timestamp) {
    tick_clock_.SetNowTicks(base::TimeTicks() + base::Milliseconds(timestamp));
  }

  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

  // |task_environment_| mocks the base::TimeTicks clock while |tick_clock_|
  // mocks ::GetTickCount.
  base::SimpleTestTickClock tick_clock_;
};

TEST_F(EventLatencyTest, ComputeEventLatencyOSFromTickCount) {
  // Mock a tick clock at 16ms (it's 15.625ms and alternates between 15 and 16ms
  // in practice but that's irrelevant for this mock).
  constexpr base::TimeDelta kTickInterval = base::Milliseconds(16);

  // Create events whose timestamps are 5 ticks away from looping around the max
  // range of ::GetTickCount.
  constexpr DWORD timestamp_msec =
      std::numeric_limits<DWORD>::max() -
      // Remove any portion that's not kTickInterval aligned.
      (std::numeric_limits<DWORD>::max() % kTickInterval.InMilliseconds()) -
      4 * kTickInterval.InMilliseconds();
  constexpr TOUCHINPUT touch_input = {
      .dwTime = timestamp_msec,
  };
  constexpr POINTER_INFO pointer_info = {
      .dwTime = timestamp_msec,
      .PerformanceCount = 0UL,
  };

  // This test will create several events with the same timestamp, and change
  // the mocked result of ::GetTickCount for each measurement. This makes it
  // easier to test the edge case when the 32-bit ::GetTickCount overflows.

  // Expect 0 within the same tick.
  UpdateTickClock(timestamp_msec);
  {
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromTOUCHINPUT(EventType::kTouchPressed, touch_input,
                                        base::TimeTicks::Now());
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            base::Milliseconds(0), 2);
  }

  // Expect 0 within the next tick (optimistically assume the event could have
  // been generated at the very end of the last tick).
  UpdateTickClock(timestamp_msec + kTickInterval.InMilliseconds());
  {
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromTOUCHINPUT(EventType::kTouchPressed, touch_input,
                                        base::TimeTicks::Now());
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            base::Milliseconds(0), 2);
  }

  // Expect 16ms within two ticks (again, optimistic for the first tick
  // interval).
  UpdateTickClock(timestamp_msec + 2 * kTickInterval.InMilliseconds());
  {
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromTOUCHINPUT(EventType::kTouchPressed, touch_input,
                                        base::TimeTicks::Now());
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            base::Milliseconds(16), 2);
  }

  // Expect 16ms within two ticks even if both ticked at the lower-end of the
  // 64hZ clock (15ms).
  constexpr DWORD kTickIntervalLowEnd = base::Hertz(64).InMilliseconds();
  static_assert(kTickIntervalLowEnd == 15);
  UpdateTickClock(timestamp_msec + 2 * kTickIntervalLowEnd);
  {
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromTOUCHINPUT(EventType::kTouchPressed, touch_input,
                                        base::TimeTicks::Now());
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            base::Milliseconds(16), 2);
  }

  // Simulate ::GetTickCount wrapping around (expecting 4 * kTickInterval
  // reported as 1 * kTickInterval is discounted).
  constexpr DWORD wrapped_timestamp_msec =
      timestamp_msec + 5 * static_cast<DWORD>(kTickInterval.InMilliseconds());
  static_assert(wrapped_timestamp_msec == 0,
                "timestamp should have wrapped around");
  UpdateTickClock(wrapped_timestamp_msec);
  {
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromTOUCHINPUT(EventType::kTouchPressed, touch_input,
                                        base::TimeTicks::Now());
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            4 * kTickInterval, 2);
  }

  // Simulate ::GetTickCount wrapping around multiple intervals. Conveniently,
  // 15 intervals yields an expected optimistic 14 intervals which is 224ms and
  // lands precisely on the boundary of the logarithmic timing histogram being
  // used, catching off-by-one errors (which a previous implementation had when
  // it reported 223ms in this test).
  constexpr DWORD wrapped_timestamp_msec2 =
      timestamp_msec + 15 * static_cast<DWORD>(kTickInterval.InMilliseconds());
  static_assert(wrapped_timestamp_msec2 == 10 * kTickInterval.InMilliseconds(),
                "timestamp should have wrapped around");
  UpdateTickClock(wrapped_timestamp_msec2);
  {
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromTOUCHINPUT(EventType::kTouchPressed, touch_input,
                                        base::TimeTicks::Now());
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            14 * kTickInterval, 2);
  }

  // Expect 0 if the clock is somehow reported as behind the event time.
  UpdateTickClock(timestamp_msec - kTickInterval.InMilliseconds());
  {
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromTOUCHINPUT(EventType::kTouchPressed, touch_input,
                                        base::TimeTicks::Now());
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            base::TimeDelta(), 2);
  }

  // Expect 0 if the clock is reported as too far ahead (protection against
  // bogus event time stamps).
  UpdateTickClock(timestamp_msec + 300 * 1000);
  {
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromTOUCHINPUT(EventType::kTouchPressed, touch_input,
                                        base::TimeTicks::Now());
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            base::TimeDelta(), 2);
  }
}

TEST_F(EventLatencyTest, ComputeEventLatencyOSFromPerformanceCounter) {
  // Make sure there's enough time before Now() to create an event that's
  // several minutes old.
  task_environment_.AdvanceClock(base::Minutes(5));

  // Convert the current time to units directly compatible with the Performance
  // Counter.
  LARGE_INTEGER ticks_per_sec = {};
  if (!::QueryPerformanceFrequency(&ticks_per_sec) ||
      ticks_per_sec.QuadPart <= 0 || !base::TimeTicks::IsHighResolution()) {
    // Skip this test when the performance counter is unavailable or
    // unreliable. (It's unlikely, but possible, that IsHighResolution is false
    // even if the performance counter works - see InitializeNowFunctionPointer
    // in time_win.cc - so also skip the test in this case.)
    return;
  }
  const auto ticks_per_second = ticks_per_sec.QuadPart;
  UINT64 current_timestamp =
      base::TimeTicks::Now().since_origin().InSecondsF() * ticks_per_second;

  // Event created shortly before now.
  {
    const POINTER_INFO pointer_info = {
        .dwTime = 0U,
        .PerformanceCount = current_timestamp - ticks_per_second,
    };
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            base::Seconds(1), 1);
  }

  // Event created several minutes before now (IsValidTimebase should return
  // false). The delta should be recorded as 0.
  {
    const POINTER_INFO pointer_info = {
        .dwTime = 0U,
        .PerformanceCount = current_timestamp - 5 * 60 * ticks_per_second,
    };
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            base::TimeDelta(), 1);
  }

  // Event created in the future (IsValidTimebase should return false). The
  // delta should be recorded as 0.
  {
    const POINTER_INFO pointer_info = {
        .dwTime = 0U,
        .PerformanceCount = current_timestamp + ticks_per_second,
    };
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            base::TimeDelta(), 1);
  }

  // Invalid event with no timestamp.
  {
    const POINTER_INFO pointer_info = {
        .dwTime = 0U,
        .PerformanceCount = 0UL,
    };
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectTotalCount("Event.Latency.OS2.TOUCH_PRESSED", 0);
  }

  // Invalid event with 2 timestamps should take the higher-precision one.
  {
    const DWORD now_msec = 1000;
    UpdateTickClock(now_msec);

    const POINTER_INFO pointer_info = {
        // 10 milliseconds ago.
        .dwTime = now_msec - 10,
        // 1 second ago.
        .PerformanceCount = current_timestamp - ticks_per_second,
    };
    base::HistogramTester histogram_tester;
    ComputeEventLatencyOSFromPOINTER_INFO(EventType::kTouchPressed,
                                          pointer_info, base::TimeTicks::Now());
    histogram_tester.ExpectUniqueTimeSample("Event.Latency.OS2.TOUCH_PRESSED",
                                            base::Seconds(1), 1);
  }
}

#endif  // BUILDFLAG(IS_WIN)

// Verifies that copied events never copy target_.
TEST(EventTest, NeverCopyTarget) {}

// Verify if a character event is created.
TEST(EventTest, CreateCharcterEvent) {}

}  // namespace ui