Commit 4580f0c8 authored by Masayuki Nakano's avatar Masayuki Nakano
Browse files

Bug 1879765 - part 1: Make `NativeKey` consume printable `eKeyDown` event...

Bug 1879765 - part 1: Make `NativeKey` consume printable `eKeyDown` event before dispatch if the key down is fired only with `WM_SYSKEYDOWN` r=m_kato

When a printable key is pressed with `Alt`, Windows notify us of `WM_SYSKEYDOWN`
and `WM_SYSCHAR`.  However, if the builtin IME for inputting a Unicode character
consumes some printable key presses, Windows does not notify us of `WM_SYSCHAR`.
Therefore, if `WM_SYSKEYDOWN` comes without `WM_SYSCHAR`, we should consume
the `eKeyDown` before dispatching the event so that the key press won't be
handled by access key of the menu nor the content.

Differential Revision: https://phabricator.services.mozilla.com/D207956
parent 6c0234e6
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@ skip-if = [
  "os == 'linux'", # Bug 1792749
]

["browser_test_consumed_alt_char_on_windows.js"]
skip-if = ["os != 'win'"]

["browser_test_fullscreen_size.js"]

["browser_test_ime_state_in_contenteditable_on_focus_move_in_remote_content.js"]
+143 −0
Original line number Diff line number Diff line
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

add_task(async function () {
  await BrowserTestUtils.withNewTab(
    "https://example.com/browser/toolkit/content/tests/browser/file_empty.html",
    async function (browser) {
      let NativeKeyConstants = {};
      Services.scriptloader.loadSubScript(
        "chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js",
        NativeKeyConstants
      );

      await SpecialPowers.spawn(browser, [], () => {
        content.document.body.innerHTML = "<input>";
        content.document.querySelector("input").focus();
      });

      function promiseAltKeyUp() {
        return new Promise(resolve =>
          addEventListener(
            "keyup",
            function onKeyUp(event) {
              if (event.key == "Alt") {
                removeEventListener("keyup", onKeyUp, { capture: true });
                requestAnimationFrame(resolve);
              }
            },
            { capture: true }
          )
        );
      }

      /**
       * This assumes that Alt + X does not match with any mnemonic.  If UI is
       * changed, change the character to unused one.
       *
       * @param {boolean} aWithChars    true if WM_SYSCHAR should be synthesized.
       * @returns {Promise}             Promise resolved when the window gets a
       *                                keyup event.
       */
      function promiseSynthesizeAltX(aWithChars) {
        const waitForAltKeyUp = promiseAltKeyUp();
        EventUtils.synthesizeNativeKey(
          EventUtils.KEYBOARD_LAYOUT_EN_US,
          NativeKeyConstants.WIN_VK_X,
          { altKey: true },
          aWithChars ? "x" : "",
          "x"
        );
        return waitForAltKeyUp;
      }

      function promiseSynthesizeAltArrowUp() {
        const waitForAltKeyUp = promiseAltKeyUp();
        EventUtils.synthesizeNativeKey(
          EventUtils.KEYBOARD_LAYOUT_EN_US,
          NativeKeyConstants.WIN_VK_UP,
          { altKey: true },
          "",
          ""
        );
        return waitForAltKeyUp;
      }

      function stringifyEvents(aEvents) {
        if (!aEvents.length) {
          return "[]";
        }
        function stringifyEvent(aEvent) {
          return `{ type: "${aEvent.type}", key: "${aEvent.key}", defaultPrevented: ${aEvent.defaultPrevented}}`;
        }
        let result = "";
        for (const event of aEvents) {
          if (result == "") {
            result = "[ ";
          } else {
            result += ", ";
          }
          result += stringifyEvent(event);
        }
        return result + " ]";
      }

      let events = [];
      function onKeyDownOrKeyPress(aEvent) {
        if (aEvent.key == "Alt") {
          return;
        }
        events.push({
          type: aEvent.type,
          key: aEvent.key,
          defaultPrevented: aEvent.defaultPrevented,
        });
      }
      browser.addEventListener("keydown", onKeyDownOrKeyPress);
      browser.addEventListener("keypress", onKeyDownOrKeyPress);

      // If Alt + F is fired with WM_SYSKEYDOWN and WM_SYSCHAR, the keydown and
      // keypress events should be fired.
      events = [];
      await promiseSynthesizeAltX(true);
      is(
        stringifyEvents(events),
        stringifyEvents([
          { type: "keydown", key: "x", defaultPrevented: false },
          { type: "keypress", key: "x", defaultPrevented: false },
        ]),
        "Alt + X with WM_SYSCHAR message should cause keydown and keypress"
      );

      // If Alt + F is fired only with WM_SYSKEYDOWN the keydown should be
      // consumed and keypress events should not be fired.
      events = [];
      await promiseSynthesizeAltX(false);
      is(
        stringifyEvents(events),
        stringifyEvents([
          { type: "keydown", key: "x", defaultPrevented: true },
        ]),
        "Alt + X without WM_SYSCHAR message should cause only consumed keydown"
      );

      // If Alt + <function key> is fired only with WM_SYSKEYDOWN, then, the
      // keydown should not be consumed and keypress events should be fired.
      events = [];
      await promiseSynthesizeAltArrowUp();
      is(
        stringifyEvents(events),
        stringifyEvents([
          { type: "keydown", key: "ArrowUp", defaultPrevented: false },
          { type: "keypress", key: "ArrowUp", defaultPrevented: false },
        ]),
        "Alt + ArrowUp without WM_SYSCHAR message should cause keydown and keypress"
      );

      browser.removeEventListener("keydown", onKeyDownOrKeyPress);
      browser.removeEventListener("keypress", onKeyDownOrKeyPress);
    }
  );
});
+6 −2
Original line number Diff line number Diff line
@@ -1643,7 +1643,7 @@ void NativeKey::InitWithKeyOrChar() {
                 "The high surrogate input is discarded",
                 this));
      }
    } else if (!mFollowingCharMsgs.IsEmpty()) {
    } else if (pendingHighSurrogate && !mFollowingCharMsgs.IsEmpty()) {
      MOZ_LOG(gKeyLog, LogLevel::Warning,
              ("%p   NativeKey::InitWithKeyOrChar(), there is pending "
               "high surrogate input, but received 2 or more character input.  "
@@ -2073,7 +2073,11 @@ nsEventStatus NativeKey::InitKeyEvent(
      // and we should prevent to do "double action" for the key operation.
      // However, for compatibility with older version and other browsers,
      // we should dispatch the events even in the web content.
      if (mCharMessageHasGone) {
      // And also if it's a WM_SYSKEYDOWN which is not followed by WM_SYSCHAR,
      // the input may be consumed by the builtin IME to input a Unicode
      // character from the code point.
      if (mCharMessageHasGone || (IsSysKeyDownMessage() && mIsPrintableKey &&
                                  mFollowingCharMsgs.IsEmpty())) {
        aKeyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
      }
      aKeyEvent.mKeyCode = mDOMKeyCode;
+1 −0
Original line number Diff line number Diff line
@@ -637,6 +637,7 @@ class MOZ_STACK_CLASS NativeKey final {
  bool IsKeyDownMessage() const {
    return mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN;
  }
  bool IsSysKeyDownMessage() const { return mMsg.message == WM_SYSKEYDOWN; }
  bool IsKeyUpMessage() const {
    return mMsg.message == WM_KEYUP || mMsg.message == WM_SYSKEYUP;
  }