Bug 1479964 - Set KeyboardEvent.keyCode and KeyboardEvent.charCode to same value if the event is "keypress" event r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 09 Oct 2018 04:43:37 +0000
changeset 495858 a9db3033d9cbf95c87e8c26c361ffe558ef6e3a9
parent 495857 ac590db9a29403e4d42559f394f7ffab7d2c2eb5
child 495859 a3a767ad55fbf01762d41f7e8bff91188e736daf
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1479964, 1222285
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1479964 - Set KeyboardEvent.keyCode and KeyboardEvent.charCode to same value if the event is "keypress" event r=smaug Chrome sets both KeyboardEvent.keyCode and KeyboardEvent.charCode of "keypress" event to same value. On the other hand, our traditional behavior is, sets one of them to 0. Therefore, we need to set keyCode value to charCode value if the keypress event is caused by a non-function key, i.e., it may be a printable key with specific modifier state and/or different keyboard layout for compatibility with Chrome. Similarly, we need to set charCode value to keyCode value if the keypress event is caused by a function key which is not mapped to producing a character. Note that this hack is for compatibility with Chrome. So, for now, it's enough to change the behavior only for "keypress" event handlers in web content. If we completely change the behavior, we need to fix a lot of default handlers and mochitests too. However, it's really difficult because default handlers check whether keypress events are printable or not with following code: > if (event.charCode && > !event.altKey && !event.ctrlKey && !event.metaKey) { or > if (!event.keyCode && > !event.altKey && !event.ctrlKey && !event.metaKey) { So, until we stop dispatching "keypress" events for non-printable keys, we need complicated check in each of them. And also note that this patch changes the behavior of KeyboardEvent::KeyCode() when spoofing is enabled and the instance is initialized by initKeyEvent() or initKeyboardEvent(). That was changed by bug 1222285 unexpectedly and keeping the behavior makes patched code really ugly. Therefore, this takes back the old behavior even if spoofing is enabled. Differential Revision: https://phabricator.services.mozilla.com/D7974
browser/components/resistfingerprinting/test/browser/browser_spoofing_keyboard_event.js
dom/bindings/Bindings.conf
dom/events/KeyboardEvent.cpp
dom/events/KeyboardEvent.h
dom/events/test/test_dom_keyboard_event.html
dom/webidl/KeyEvent.webidl
dom/webidl/KeyboardEvent.webidl
modules/libpref/init/StaticPrefList.h
--- a/browser/components/resistfingerprinting/test/browser/browser_spoofing_keyboard_event.js
+++ b/browser/components/resistfingerprinting/test/browser/browser_spoofing_keyboard_event.js
@@ -2,17 +2,18 @@
  * Bug 1222285 - A test case for testing whether keyboard events be spoofed correctly
  *   when fingerprinting resistance is enable.
  */
 
 const CC = Components.Constructor;
 
 const kStrictKeyPressEvents =
   SpecialPowers.getBoolPref("dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content");
-
+const kSameKeyCodeAndCharCodeValue =
+  SpecialPowers.getBoolPref("dom.keyboardevent.keypress.set_keycode_and_charcode_to_same_value");
 const SHOULD_DELIVER_KEYDOWN          = 0x1;
 const SHOULD_DELIVER_KEYPRESS         = 0x2;
 const SHOULD_DELIVER_KEYUP            = 0x4;
 const SHOULD_DELIVER_ALL_FOR_PRINTABLE = SHOULD_DELIVER_KEYDOWN |
                                          SHOULD_DELIVER_KEYPRESS |
                                          SHOULD_DELIVER_KEYUP;
 const SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE =
   kStrictKeyPressEvents ? (SHOULD_DELIVER_KEYDOWN | SHOULD_DELIVER_KEYUP) : SHOULD_DELIVER_ALL_FOR_PRINTABLE;
@@ -647,47 +648,55 @@ async function testKeyEvent(aTab, aTestC
 
   if (aTestCase.expectedKeyEvent & SHOULD_DELIVER_KEYUP) {
     testEvents.push("keyup");
   }
 
   let allKeyEventPromises = [];
 
   for (let testEvent of testEvents) {
-    let keyEventPromise = ContentTask.spawn(aTab.linkedBrowser, {testEvent, result: aTestCase.result}, async (aInput) => {
-      function verifyKeyboardEvent(aEvent, aResult) {
+    let keyEventPromise = ContentTask.spawn(aTab.linkedBrowser, {testEvent, result: aTestCase.result, kSameKeyCodeAndCharCodeValue}, async (aInput) => {
+      function verifyKeyboardEvent(aEvent, aResult, aSameKeyCodeAndCharCodeValue) {
         is(aEvent.key, aResult.key, "KeyboardEvent.key is correctly spoofed.");
         is(aEvent.code, aResult.code, "KeyboardEvent.code is correctly spoofed.");
         is(aEvent.location, aResult.location, "KeyboardEvent.location is correctly spoofed.");
         is(aEvent.altKey, aResult.altKey, "KeyboardEvent.altKey is correctly spoofed.");
         is(aEvent.shiftKey, aResult.shiftKey, "KeyboardEvent.shiftKey is correctly spoofed.");
         is(aEvent.ctrlKey, aResult.ctrlKey, "KeyboardEvent.ctrlKey is correctly spoofed.");
 
         // If the charCode is not 0, this is a character. The keyCode will be remained as 0.
         // Otherwise, we should check the keyCode.
-        if (aEvent.charCode != 0) {
-          is(aEvent.keyCode, 0, "KeyboardEvent.keyCode should be 0 for this case.");
+        if (!aSameKeyCodeAndCharCodeValue) {
+          if (aEvent.charCode != 0) {
+            is(aEvent.keyCode, 0, "KeyboardEvent.keyCode should be 0 for this case.");
+            is(aEvent.charCode, aResult.charCode, "KeyboardEvent.charCode is correctly spoofed.");
+          } else {
+            is(aEvent.keyCode, aResult.keyCode, "KeyboardEvent.keyCode is correctly spoofed.");
+            is(aEvent.charCode, 0, "KeyboardEvent.charCode should be 0 for this case.");
+          }
+        } else if (aResult.charCode) {
+          is(aEvent.keyCode, aResult.charCode, "KeyboardEvent.keyCode should be same as expected charCode for this case.");
           is(aEvent.charCode, aResult.charCode, "KeyboardEvent.charCode is correctly spoofed.");
         } else {
           is(aEvent.keyCode, aResult.keyCode, "KeyboardEvent.keyCode is correctly spoofed.");
-          is(aEvent.charCode, 0, "KeyboardEvent.charCode should be 0 for this case.");
+          is(aEvent.charCode, aResult.keyCode, "KeyboardEvent.charCode should be same as expected keyCode for this case.");
         }
 
         // Check getModifierState().
         is(aEvent.modifierState.Alt, aResult.altKey,
             "KeyboardEvent.getModifierState() reports a correctly spoofed value for 'Alt'.");
         is(aEvent.modifierState.AltGraph, aResult.altGraphKey,
             "KeyboardEvent.getModifierState() reports a correctly spoofed value for 'AltGraph'.");
         is(aEvent.modifierState.Shift, aResult.shiftKey,
             `KeyboardEvent.getModifierState() reports a correctly spoofed value for 'Shift'.`);
         is(aEvent.modifierState.Control, aResult.ctrlKey,
             `KeyboardEvent.getModifierState() reports a correctly spoofed value for 'Control'.`);
       }
 
-      let {testEvent: eventType, result} = aInput;
+      let {testEvent: eventType, result, kSameKeyCodeAndCharCodeValue: sameKeyCodeAndCharCodeValue} = aInput;
       let inputBox = content.document.getElementById("test");
 
       // We need to put the real access of event object into the content page instead of
       // here, ContentTask.spawn, since the script running here is under chrome privilege.
       // So the fingerprinting resistance won't work here.
       let resElement = content.document.getElementById("result-" + eventType);
 
       // First, try to focus on the input box.
@@ -703,17 +712,18 @@ async function testKeyEvent(aTab, aTestC
         }
       });
 
       // Once the result of the keyboard event ready, the content page will send
       // a custom event 'resultAvailable' for informing the script to check the
       // result.
       await new Promise(resolve => {
         function eventHandler(aEvent) {
-          verifyKeyboardEvent(JSON.parse(resElement.value), result);
+          verifyKeyboardEvent(JSON.parse(resElement.value), result,
+                              eventType == "keypress" && sameKeyCodeAndCharCodeValue);
           resElement.removeEventListener("resultAvailable", eventHandler, true);
           resolve();
         }
 
         resElement.addEventListener("resultAvailable", eventHandler, true);
       });
     });
 
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -489,16 +489,20 @@ DOMInterfaces = {
     'nativeType': 'mozilla::dom::DOMIntersectionObserver',
 },
 
 'IntersectionObserverEntry': {
     'nativeType': 'mozilla::dom::DOMIntersectionObserverEntry',
     'headerFile': 'DOMIntersectionObserver.h',
 },
 
+'KeyboardEvent': {
+    'binaryNames': { 'constructor': 'ConstructorJS' },
+},
+
 'KeyEvent': {
     'concrete': False
 },
 
 'LegacyMozTCPSocket': {
     'headerFile': 'TCPSocket.h',
     'wrapperCache': False,
 },
--- a/dom/events/KeyboardEvent.cpp
+++ b/dom/events/KeyboardEvent.cpp
@@ -13,16 +13,17 @@ namespace mozilla {
 namespace dom {
 
 KeyboardEvent::KeyboardEvent(EventTarget* aOwner,
                              nsPresContext* aPresContext,
                              WidgetKeyboardEvent* aEvent)
   : UIEvent(aOwner, aPresContext,
             aEvent ? aEvent :
                      new WidgetKeyboardEvent(false, eVoidEvent, nullptr))
+  , mInitializedByJS(false)
   , mInitializedByCtor(false)
   , mInitializedWhichValue(0)
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
@@ -140,67 +141,125 @@ void KeyboardEvent::GetInitDict(Keyboard
   aParam.mModifierSymbol = internalEvent->IsSymbol();
   aParam.mModifierSymbolLock = internalEvent->IsSymbolLocked();
 
   // EventInit
   aParam.mBubbles =  internalEvent->mFlags.mBubbles;
   aParam.mCancelable = internalEvent->mFlags.mCancelable;
 }
 
-uint32_t
-KeyboardEvent::CharCode()
+bool
+KeyboardEvent::ShouldUseSameValueForCharCodeAndKeyCode(
+                 CallerType aCallerType) const
 {
-  // If this event is initialized with ctor, we shouldn't check event type.
-  if (mInitializedByCtor) {
-    return mEvent->AsKeyboardEvent()->mCharCode;
+  // - If this event is initialized by JS, we don't need to return same value
+  //   for keyCode and charCode since they can be initialized separately.
+  // - If this is not a keypress event, we shouldn't return same value for
+  //   keyCode and charCode.
+  // - If this event is referred by default handler, i.e., the caller is
+  //   system or this event is now in the system group, we don't need to use
+  //   hack for web-compat.
+  if (mInitializedByJS ||
+      mEvent->mMessage != eKeyPress ||
+      aCallerType == CallerType::System ||
+      mEvent->mFlags.mInSystemGroup) {
+    return false;
   }
 
-  switch (mEvent->mMessage) {
-  case eKeyDown:
-  case eKeyDownOnPlugin:
-  case eKeyUp:
-  case eKeyUpOnPlugin:
-    return 0;
-  case eKeyPress:
-  case eAccessKeyNotFound:
-    return mEvent->AsKeyboardEvent()->mCharCode;
-  default:
-    break;
+  MOZ_ASSERT(aCallerType == CallerType::NonSystem);
+
+  return StaticPrefs::
+    dom_keyboardevent_keypress_set_keycode_and_charcode_to_same_value();
+}
+
+uint32_t
+KeyboardEvent::CharCode(CallerType aCallerType)
+{
+  WidgetKeyboardEvent* widgetKeyboardEvent = mEvent->AsKeyboardEvent();
+  if (mInitializedByJS) {
+    // If this is initialized by Ctor, we should return the initialized value.
+    if (mInitializedByCtor) {
+      return widgetKeyboardEvent->mCharCode;
+    }
+    // Otherwise, i.e., initialized by InitKey*Event(), we should return the
+    // initialized value only when eKeyPress or eAccessKeyNotFound event.
+    // Although this is odd, but our traditional behavior.
+    return widgetKeyboardEvent->mMessage == eKeyPress ||
+           widgetKeyboardEvent->mMessage == eAccessKeyNotFound ?
+             widgetKeyboardEvent->mCharCode : 0;
   }
-  return 0;
+
+  // If the key is a function key, we should return the result of KeyCode()
+  // even from CharCode().  Otherwise, i.e., the key may be a printable
+  // key or actually a printable key, we should return the given charCode
+  // value.
+
+  if (widgetKeyboardEvent->mKeyNameIndex != KEY_NAME_INDEX_USE_STRING &&
+      ShouldUseSameValueForCharCodeAndKeyCode(aCallerType)) {
+    return ComputeTraditionalKeyCode(*widgetKeyboardEvent, aCallerType);
+  }
+
+  return widgetKeyboardEvent->mCharCode;
 }
 
 uint32_t
 KeyboardEvent::KeyCode(CallerType aCallerType)
 {
-  // If this event is initialized with ctor, we shouldn't check event type.
-  if (mInitializedByCtor) {
-    return mEvent->AsKeyboardEvent()->mKeyCode;
+  WidgetKeyboardEvent* widgetKeyboardEvent = mEvent->AsKeyboardEvent();
+  if (mInitializedByJS) {
+    // If this is initialized by Ctor, we should return the initialized value.
+    if (mInitializedByCtor) {
+      return widgetKeyboardEvent->mKeyCode;
+    }
+    // Otherwise, i.e., initialized by InitKey*Event(), we should return the
+    // initialized value only when the event message is a valid keyboard event
+    // message.  Although this is odd, but our traditional behavior.
+    // NOTE: The fix of bug 1222285 changed the behavior temporarily if
+    //       spoofing is enabled.  However, the behavior does not make sense
+    //       since if the event is generated by JS, the behavior shouldn't
+    //       be changed by whether spoofing is enabled or not.  Therefore,
+    //       we take back the original behavior.
+    return widgetKeyboardEvent->HasKeyEventMessage() ?
+             widgetKeyboardEvent->mKeyCode : 0;
   }
 
-  if (!mEvent->HasKeyEventMessage()) {
-    return 0;
+  // If the key is not a function key, i.e., the key may be a printable key
+  // or a function key mapped as a printable key, we should use charCode value
+  // for keyCode value if this is a "keypress" event.
+
+  if (widgetKeyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
+      ShouldUseSameValueForCharCodeAndKeyCode(aCallerType)) {
+    return widgetKeyboardEvent->mCharCode;
   }
 
+  return ComputeTraditionalKeyCode(*widgetKeyboardEvent, aCallerType);
+}
+
+uint32_t
+KeyboardEvent::ComputeTraditionalKeyCode(WidgetKeyboardEvent& aKeyboardEvent,
+                                         CallerType aCallerType)
+{
   if (!ShouldResistFingerprinting(aCallerType)) {
-    return mEvent->AsKeyboardEvent()->mKeyCode;
+    return aKeyboardEvent.mKeyCode;
   }
 
-  // The keyCode should be zero if the char code is given.
-  if (CharCode()) {
+  // In Netscape style (i.e., traditional behavior of Gecko), the keyCode
+  // should be zero if the char code is given.
+  if ((mEvent->mMessage == eKeyPress ||
+       mEvent->mMessage == eAccessKeyNotFound) &&
+      aKeyboardEvent.mCharCode) {
     return 0;
   }
 
   // When fingerprinting resistance is enabled, we will give a spoofed keyCode
   // according to the content-language of the document.
   nsCOMPtr<nsIDocument> doc = GetDocument();
   uint32_t spoofedKeyCode;
 
-  if (nsRFPService::GetSpoofedKeyCode(doc, mEvent->AsKeyboardEvent(),
-                                      spoofedKeyCode)) {
+  if (nsRFPService::GetSpoofedKeyCode(doc, &aKeyboardEvent, spoofedKeyCode)) {
     return spoofedKeyCode;
   }
 
   return 0;
 }
 
 uint32_t
 KeyboardEvent::Which(CallerType aCallerType)
@@ -236,42 +295,43 @@ KeyboardEvent::Which(CallerType aCallerT
 uint32_t
 KeyboardEvent::Location()
 {
   return mEvent->AsKeyboardEvent()->mLocation;
 }
 
 // static
 already_AddRefed<KeyboardEvent>
-KeyboardEvent::Constructor(const GlobalObject& aGlobal,
-                           const nsAString& aType,
-                           const KeyboardEventInit& aParam,
-                           ErrorResult& aRv)
+KeyboardEvent::ConstructorJS(const GlobalObject& aGlobal,
+                             const nsAString& aType,
+                             const KeyboardEventInit& aParam,
+                             ErrorResult& aRv)
 {
   nsCOMPtr<EventTarget> target = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<KeyboardEvent> newEvent =
     new KeyboardEvent(target, nullptr, nullptr);
   newEvent->InitWithKeyboardEventInit(target, aType, aParam, aRv);
 
   return newEvent.forget();
 }
 
 void
 KeyboardEvent::InitWithKeyboardEventInit(EventTarget* aOwner,
                                          const nsAString& aType,
                                          const KeyboardEventInit& aParam,
                                          ErrorResult& aRv)
 {
   bool trusted = Init(aOwner);
-  InitKeyEvent(aType, aParam.mBubbles, aParam.mCancelable,
-               aParam.mView, false, false, false, false,
-               aParam.mKeyCode, aParam.mCharCode);
+  InitKeyEventJS(aType, aParam.mBubbles, aParam.mCancelable,
+                 aParam.mView, false, false, false, false,
+                 aParam.mKeyCode, aParam.mCharCode);
   InitModifiers(aParam);
   SetTrusted(trusted);
   mDetail = aParam.mDetail;
+  mInitializedByJS = true;
   mInitializedByCtor = true;
   mInitializedWhichValue = aParam.mWhich;
 
   WidgetKeyboardEvent* internalEvent = mEvent->AsKeyboardEvent();
   internalEvent->mLocation = aParam.mLocation;
   internalEvent->mIsRepeat = aParam.mRepeat;
   internalEvent->mIsComposing = aParam.mIsComposing;
   internalEvent->mKeyNameIndex =
@@ -282,46 +342,50 @@ KeyboardEvent::InitWithKeyboardEventInit
   internalEvent->mCodeNameIndex =
     WidgetKeyboardEvent::GetCodeNameIndex(aParam.mCode);
   if (internalEvent->mCodeNameIndex == CODE_NAME_INDEX_USE_STRING) {
     internalEvent->mCodeValue = aParam.mCode;
   }
 }
 
 void
-KeyboardEvent::InitKeyEvent(const nsAString& aType, bool aCanBubble,
-                            bool aCancelable, nsGlobalWindowInner* aView,
-                            bool aCtrlKey, bool aAltKey,
-                            bool aShiftKey, bool aMetaKey,
-                            uint32_t aKeyCode, uint32_t aCharCode)
+KeyboardEvent::InitKeyEventJS(const nsAString& aType, bool aCanBubble,
+                              bool aCancelable, nsGlobalWindowInner* aView,
+                              bool aCtrlKey, bool aAltKey,
+                              bool aShiftKey, bool aMetaKey,
+                              uint32_t aKeyCode, uint32_t aCharCode)
 {
   NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+  mInitializedByJS = true;
+  mInitializedByCtor = false;
 
   UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0);
 
   WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent();
   keyEvent->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
   keyEvent->mKeyCode = aKeyCode;
   keyEvent->mCharCode = aCharCode;
 }
 
 void
-KeyboardEvent::InitKeyboardEvent(const nsAString& aType,
-                                 bool aCanBubble,
-                                 bool aCancelable,
-                                 nsGlobalWindowInner* aView,
-                                 const nsAString& aKey,
-                                 uint32_t aLocation,
-                                 bool aCtrlKey,
-                                 bool aAltKey,
-                                 bool aShiftKey,
-                                 bool aMetaKey,
-                                 ErrorResult& aRv)
+KeyboardEvent::InitKeyboardEventJS(const nsAString& aType,
+                                   bool aCanBubble,
+                                   bool aCancelable,
+                                   nsGlobalWindowInner* aView,
+                                   const nsAString& aKey,
+                                   uint32_t aLocation,
+                                   bool aCtrlKey,
+                                   bool aAltKey,
+                                   bool aShiftKey,
+                                   bool aMetaKey,
+                                   ErrorResult& aRv)
 {
   NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+  mInitializedByJS = true;
+  mInitializedByCtor = false;
 
   UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0);
 
   WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent();
   keyEvent->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
   keyEvent->mLocation = aLocation;
   keyEvent->mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
   keyEvent->mKeyValue = aKey;
@@ -344,23 +408,23 @@ KeyboardEvent::GetDocument()
 
   return doc.forget();
 }
 
 bool
 KeyboardEvent::ShouldResistFingerprinting(CallerType aCallerType)
 {
   // There are five situations we don't need to spoof this keyboard event.
-  //   1. This event is generated by scripts.
+  //   1. This event is initialized by scripts.
   //   2. This event is from Numpad.
   //   3. This event is in the system group.
   //   4. The caller type is system.
   //   5. The pref privcy.resistFingerprinting' is false, we fast return here since
   //      we don't need to do any QI of following codes.
-  if (mInitializedByCtor ||
+  if (mInitializedByJS ||
       aCallerType == CallerType::System ||
       mEvent->mFlags.mInSystemGroup ||
       !nsContentUtils::ShouldResistFingerprinting() ||
       mEvent->AsKeyboardEvent()->mLocation ==
         KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) {
     return false;
   }
 
--- a/dom/events/KeyboardEvent.h
+++ b/dom/events/KeyboardEvent.h
@@ -24,17 +24,17 @@ public:
 
   NS_INLINE_DECL_REFCOUNTING_INHERITED(KeyboardEvent, UIEvent)
 
   virtual KeyboardEvent* AsKeyboardEvent() override
   {
     return this;
   }
 
-  static already_AddRefed<KeyboardEvent> Constructor(
+  static already_AddRefed<KeyboardEvent> ConstructorJS(
                                            const GlobalObject& aGlobal,
                                            const nsAString& aType,
                                            const KeyboardEventInit& aParam,
                                            ErrorResult& aRv);
 
   virtual JSObject*
     WrapObjectInternal(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override
@@ -58,45 +58,47 @@ public:
 
     Modifiers modifier = WidgetInputEvent::GetModifier(aKey);
     return GetSpoofedModifierStates(modifier, modifierState);
   }
 
   bool Repeat();
   bool IsComposing();
   void GetKey(nsAString& aKey) const;
-  uint32_t CharCode();
+  uint32_t CharCode(CallerType aCallerType = CallerType::System);
   uint32_t KeyCode(CallerType aCallerType = CallerType::System);
   virtual uint32_t Which(CallerType aCallerType = CallerType::System) override;
   uint32_t Location();
 
   void GetCode(nsAString& aCode, CallerType aCallerType = CallerType::System);
   void GetInitDict(KeyboardEventInit& aParam);
 
-  void InitKeyEvent(const nsAString& aType, bool aCanBubble, bool aCancelable,
-                    nsGlobalWindowInner* aView, bool aCtrlKey, bool aAltKey,
-                    bool aShiftKey, bool aMetaKey,
-                    uint32_t aKeyCode, uint32_t aCharCode);
+  void InitKeyEventJS(const nsAString& aType, bool aCanBubble, bool aCancelable,
+                      nsGlobalWindowInner* aView, bool aCtrlKey, bool aAltKey,
+                      bool aShiftKey, bool aMetaKey,
+                      uint32_t aKeyCode, uint32_t aCharCode);
 
-  void InitKeyboardEvent(const nsAString& aType,
-                         bool aCanBubble, bool aCancelable,
-                         nsGlobalWindowInner* aView, const nsAString& aKey,
-                         uint32_t aLocation, bool aCtrlKey, bool aAltKey,
-                         bool aShiftKey, bool aMetaKey, ErrorResult& aRv);
+  void InitKeyboardEventJS(const nsAString& aType,
+                           bool aCanBubble, bool aCancelable,
+                           nsGlobalWindowInner* aView, const nsAString& aKey,
+                           uint32_t aLocation, bool aCtrlKey, bool aAltKey,
+                           bool aShiftKey, bool aMetaKey, ErrorResult& aRv);
 
 protected:
   ~KeyboardEvent() {}
 
   void InitWithKeyboardEventInit(EventTarget* aOwner,
                                  const nsAString& aType,
                                  const KeyboardEventInit& aParam,
                                  ErrorResult& aRv);
 
 private:
-  // True, if the instance is created with Constructor().
+  // True, if the instance is initialized by JS.
+  bool mInitializedByJS;
+  // True, if the instance is initialized by Ctor.
   bool mInitializedByCtor;
 
   // If the instance is created with Constructor(), which may have independent
   // value.  mInitializedWhichValue stores it.  I.e., this is invalid when
   // mInitializedByCtor is false.
   uint32_t mInitializedWhichValue;
 
   // This method returns the boolean to indicate whether spoofing keyboard
@@ -108,16 +110,36 @@ private:
   // This method returns the nsIDocument which is associated with the event
   // target.
   already_AddRefed<nsIDocument> GetDocument();
 
   // This method returns the spoofed modifier state of the given modifier key
   // for fingerprinting resistance.
   bool GetSpoofedModifierStates(const Modifiers aModifierKey,
                                 const bool aRawModifierState);
+
+  /**
+   * ComputeTraditionalKeyCode() computes traditional keyCode value.  I.e.,
+   * returns 0 if this event should return non-zero from CharCode().
+   * In spite of the name containing "traditional", this computes spoof
+   * keyCode value if user wants it.
+   *
+   * @param aKeyboardEvent  Should be |*mEvent->AsKeyboardEvent()|.
+   * @param aCallerType     Set caller type of KeyCode() or CharCode().
+   * @return                If traditional charCode value is 0, returns
+   *                        the raw keyCode value or spoof keyCode value.
+   *                        Otherwise, 0.
+   */
+  uint32_t ComputeTraditionalKeyCode(WidgetKeyboardEvent& aKeyboardEvent,
+                                     CallerType aCallerType);
+  /**
+   * ShouldUseSameValueForCharCodeAndKeyCode() returns true if KeyCode() and
+   * CharCode() should return same value.
+   */
+  bool ShouldUseSameValueForCharCodeAndKeyCode(CallerType aCallerType) const;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 already_AddRefed<mozilla::dom::KeyboardEvent>
 NS_NewDOMKeyboardEvent(mozilla::dom::EventTarget* aOwner,
                        nsPresContext* aPresContext,
--- a/dom/events/test/test_dom_keyboard_event.html
+++ b/dom/events/test/test_dom_keyboard_event.html
@@ -35,17 +35,17 @@ function testInitializingUntrustedEvent(
 
     { createEventArg: "keyboardevent", useInitKeyboardEvent: false,
       type: "keyup", bubbles: false, cancelable: true, view: window,
       ctrlKey: true, altKey: false, shiftKey: false, metaKey: false,
       keyCode: 0x10, charCode: 0x00, detail: 0, key: "", location: 0,
     }, // 1
 
     { createEventArg: "Keyboardevent", useInitKeyboardEvent: false,
-      type: "keypess", bubbles: true, cancelable: false, view: null,
+      type: "keypress", bubbles: true, cancelable: false, view: null,
       ctrlKey: false, altKey: true, shiftKey: false, metaKey: false,
       keyCode: 0x11, charCode: 0x30, detail: 0, key: "", location: 0,
     }, // 2
 
     { createEventArg: "keyboardEvent", useInitKeyboardEvent: false,
       type: "boo", bubbles: false, cancelable: false, view: window,
       ctrlKey: false, altKey: false, shiftKey: true, metaKey: false,
       keyCode: 0x30, charCode: 0x40, detail: 0, key: "", location: 0,
@@ -96,17 +96,17 @@ function testInitializingUntrustedEvent(
 
     { createEventArg: "keyboardevent", useInitKeyboardEvent: true,
       type: "keyup", bubbles: false, cancelable: true, view: window,
       ctrlKey: true, altKey: false, shiftKey: false, metaKey: false,
       keyCode: 0x00, charCode: 0x00, key: "Unidentified", location: 1,
     }, // 11
 
     { createEventArg: "Keyboardevent", useInitKeyboardEvent: true,
-      type: "keypess", bubbles: true, cancelable: false, view: null,
+      type: "keypress", bubbles: true, cancelable: false, view: null,
       ctrlKey: false, altKey: true, shiftKey: false, metaKey: false,
       keyCode: 0x00, charCode: 0x00, key: "FooBar", location: 2,
     }, // 12
 
     { createEventArg: "keyboardevent", useInitKeyboardEvent: true,
       type: "foo", bubbles: true, cancelable: true, view: null,
       ctrlKey: false, altKey: false, shiftKey: false, metaKey: true,
       keyCode: 0x00, charCode: 0x00, key: "a", location: 0,
--- a/dom/webidl/KeyEvent.webidl
+++ b/dom/webidl/KeyEvent.webidl
@@ -225,16 +225,17 @@ interface KeyEvent
   const unsigned long DOM_VK_PLAY           = 0xFA;
   const unsigned long DOM_VK_ZOOM           = 0xFB;
   const unsigned long DOM_VK_PA1            = 0xFD;
 
   // OEM specific virtual keyCode of Windows should pass through DOM keyCode
   // for compatibility with the other web browsers on Windows.
   const unsigned long DOM_VK_WIN_OEM_CLEAR  = 0xFE;
 
+  [BinaryName="initKeyEventJS"]
   void initKeyEvent(DOMString type,
                     optional boolean canBubble = false,
                     optional boolean cancelable = false,
                     optional Window? view = null,
                     optional boolean ctrlKey = false,
                     optional boolean altKey = false,
                     optional boolean shiftKey = false,
                     optional boolean metaKey = false,
--- a/dom/webidl/KeyboardEvent.webidl
+++ b/dom/webidl/KeyboardEvent.webidl
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 [Constructor(DOMString typeArg, optional KeyboardEventInit keyboardEventInitDict)]
 interface KeyboardEvent : UIEvent
 {
+  [NeedsCallerType]
   readonly attribute unsigned long    charCode;
   [NeedsCallerType]
   readonly attribute unsigned long    keyCode;
 
   [NeedsCallerType]
   readonly attribute boolean          altKey;
   [NeedsCallerType]
   readonly attribute boolean          ctrlKey;
@@ -30,17 +31,17 @@ interface KeyboardEvent : UIEvent
   readonly attribute unsigned long location;
   readonly attribute boolean       repeat;
   readonly attribute boolean       isComposing;
 
   readonly attribute DOMString key;
   [NeedsCallerType]
   readonly attribute DOMString code;
 
-  [Throws]
+  [Throws, BinaryName="initKeyboardEventJS"]
   void initKeyboardEvent(DOMString typeArg,
                          optional boolean bubblesArg = false,
                          optional boolean cancelableArg = false,
                          optional Window? viewArg = null,
                          optional DOMString keyArg = "",
                          optional unsigned long locationArg = 0,
                          optional boolean ctrlKey = false,
                          optional boolean altKey = false,
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -174,16 +174,30 @@ VARCACHE_PREF(
 #endif
 VARCACHE_PREF(
   "dom.animations-api.timelines.enabled",
    dom_animations_api_timelines_enabled,
   bool, PREF_VALUE
 )
 #undef PREF_VALUE
 
+// If this is true, "keypress" event's keyCode value and charCode value always
+// become same if the event is not created/initialized by JS.
+#ifdef RELEASE_OR_BETA
+# define PREF_VALUE false
+#else
+# define PREF_VALUE true
+#endif
+VARCACHE_PREF(
+  "dom.keyboardevent.keypress.set_keycode_and_charcode_to_same_value",
+   dom_keyboardevent_keypress_set_keycode_and_charcode_to_same_value,
+  bool, PREF_VALUE
+)
+#undef PREF_VALUE
+
 // NOTE: This preference is used in unit tests. If it is removed or its default
 // value changes, please update test_sharedMap_var_caches.js accordingly.
 VARCACHE_PREF(
   "dom.webcomponents.shadowdom.report_usage",
    dom_webcomponents_shadowdom_report_usage,
   bool, false
 )