KeyboardEvent.cpp (15723B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/KeyboardEvent.h" 8 9 #include "mozilla/BasicEvents.h" 10 #include "mozilla/LookAndFeel.h" 11 #include "mozilla/StaticPrefs_dom.h" 12 #include "mozilla/TextEvents.h" 13 #include "mozilla/dom/Document.h" 14 #include "nsContentUtils.h" 15 #include "nsIPrincipal.h" 16 #include "nsRFPService.h" 17 #include "prtime.h" 18 19 namespace mozilla::dom { 20 21 KeyboardEvent::KeyboardEvent(EventTarget* aOwner, nsPresContext* aPresContext, 22 WidgetKeyboardEvent* aEvent) 23 : UIEvent(aOwner, aPresContext, 24 aEvent ? aEvent 25 : new WidgetKeyboardEvent(false, eVoidEvent, nullptr)), 26 mInitializedByJS(false), 27 mInitializedByCtor(false), 28 mInitializedWhichValue(0) { 29 if (aEvent) { 30 mEventIsInternal = false; 31 } else { 32 mEventIsInternal = true; 33 mEvent->AsKeyboardEvent()->mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; 34 } 35 } 36 37 bool KeyboardEvent::IsMenuAccessKeyPressed() const { 38 Modifiers mask = LookAndFeel::GetMenuAccessKeyModifiers(); 39 Modifiers modifiers = GetModifiersForMenuAccessKey(); 40 return mask != MODIFIER_SHIFT && (modifiers & mask) && 41 (modifiers & ~(mask | MODIFIER_SHIFT)) == 0; 42 } 43 44 static constexpr Modifiers kPossibleModifiersForAccessKey = 45 MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META; 46 47 Modifiers KeyboardEvent::GetModifiersForMenuAccessKey() const { 48 const WidgetInputEvent* inputEvent = WidgetEventPtr()->AsInputEvent(); 49 MOZ_ASSERT(inputEvent); 50 return inputEvent->mModifiers & kPossibleModifiersForAccessKey; 51 } 52 53 bool KeyboardEvent::AltKey(CallerType aCallerType) { 54 bool altState = mEvent->AsKeyboardEvent()->IsAlt(); 55 56 if (!ShouldResistFingerprinting(aCallerType)) { 57 return altState; 58 } 59 60 // We need to give a spoofed state for Alt key since it could be used as a 61 // modifier key in certain keyboard layout. For example, the '@' key for 62 // German keyboard for MAC is Alt+L. 63 return GetSpoofedModifierStates(Modifier::MODIFIER_ALT, altState); 64 } 65 66 bool KeyboardEvent::CtrlKey(CallerType aCallerType) { 67 // We don't spoof this key when privacy.resistFingerprinting 68 // is enabled, because it is often used for command key 69 // combinations in web apps. 70 return mEvent->AsKeyboardEvent()->IsControl(); 71 } 72 73 bool KeyboardEvent::ShiftKey(CallerType aCallerType) { 74 bool shiftState = mEvent->AsKeyboardEvent()->IsShift(); 75 76 if (!ShouldResistFingerprinting(aCallerType)) { 77 return shiftState; 78 } 79 80 return GetSpoofedModifierStates(Modifier::MODIFIER_SHIFT, shiftState); 81 } 82 83 bool KeyboardEvent::MetaKey() { 84 // We don't spoof this key when privacy.resistFingerprinting 85 // is enabled, because it is often used for command key 86 // combinations in web apps. 87 return mEvent->AsKeyboardEvent()->IsMeta(); 88 } 89 90 bool KeyboardEvent::Repeat() { return mEvent->AsKeyboardEvent()->mIsRepeat; } 91 92 bool KeyboardEvent::IsComposing() { 93 return mEvent->AsKeyboardEvent()->mIsComposing; 94 } 95 96 void KeyboardEvent::GetKey(nsAString& aKeyName) const { 97 mEvent->AsKeyboardEvent()->GetDOMKeyName(aKeyName); 98 } 99 100 void KeyboardEvent::GetCode(nsAString& aCodeName, CallerType aCallerType) { 101 if (!ShouldResistFingerprinting(aCallerType)) { 102 mEvent->AsKeyboardEvent()->GetDOMCodeName(aCodeName); 103 return; 104 } 105 106 // When fingerprinting resistance is enabled, we will give a spoofed code 107 // according to the content-language of the document. 108 nsCOMPtr<Document> doc = GetDocument(); 109 110 nsRFPService::GetSpoofedCode(doc, mEvent->AsKeyboardEvent(), aCodeName); 111 } 112 113 void KeyboardEvent::GetInitDict(KeyboardEventInit& aParam) { 114 GetKey(aParam.mKey); 115 GetCode(aParam.mCode); 116 aParam.mLocation = Location(); 117 aParam.mRepeat = Repeat(); 118 aParam.mIsComposing = IsComposing(); 119 120 // legacy attributes 121 aParam.mKeyCode = KeyCode(); 122 aParam.mCharCode = CharCode(); 123 aParam.mWhich = Which(); 124 125 // modifiers from EventModifierInit 126 aParam.mCtrlKey = CtrlKey(); 127 aParam.mShiftKey = ShiftKey(); 128 aParam.mAltKey = AltKey(); 129 aParam.mMetaKey = MetaKey(); 130 131 WidgetKeyboardEvent* internalEvent = mEvent->AsKeyboardEvent(); 132 aParam.mModifierAltGraph = internalEvent->IsAltGraph(); 133 aParam.mModifierCapsLock = internalEvent->IsCapsLocked(); 134 aParam.mModifierFn = internalEvent->IsFn(); 135 aParam.mModifierFnLock = internalEvent->IsFnLocked(); 136 aParam.mModifierNumLock = internalEvent->IsNumLocked(); 137 aParam.mModifierScrollLock = internalEvent->IsScrollLocked(); 138 aParam.mModifierSymbol = internalEvent->IsSymbol(); 139 aParam.mModifierSymbolLock = internalEvent->IsSymbolLocked(); 140 141 // EventInit 142 aParam.mBubbles = internalEvent->mFlags.mBubbles; 143 aParam.mCancelable = internalEvent->mFlags.mCancelable; 144 } 145 146 bool KeyboardEvent::ShouldUseSameValueForCharCodeAndKeyCode( 147 const WidgetKeyboardEvent& aWidgetKeyboardEvent, 148 CallerType aCallerType) const { 149 // - If this event is initialized by JS, we don't need to return same value 150 // for keyCode and charCode since they can be initialized separately. 151 // - If this is not a keypress event, we shouldn't return same value for 152 // keyCode and charCode. 153 // - If we need to return legacy keyCode and charCode values for the web 154 // app due to in the blacklist. 155 // - If this event is referred by default handler, i.e., the caller is 156 // system or this event is now in the system group, we don't need to use 157 // hack for web-compat. 158 if (mInitializedByJS || aWidgetKeyboardEvent.mMessage != eKeyPress || 159 aWidgetKeyboardEvent.mUseLegacyKeyCodeAndCharCodeValues || 160 aCallerType == CallerType::System || 161 aWidgetKeyboardEvent.mFlags.mInSystemGroup) { 162 return false; 163 } 164 165 MOZ_ASSERT(aCallerType == CallerType::NonSystem); 166 167 return StaticPrefs:: 168 dom_keyboardevent_keypress_set_keycode_and_charcode_to_same_value(); 169 } 170 171 uint32_t KeyboardEvent::CharCode(CallerType aCallerType) { 172 WidgetKeyboardEvent* widgetKeyboardEvent = mEvent->AsKeyboardEvent(); 173 if (mInitializedByJS) { 174 // If this is initialized by Ctor, we should return the initialized value. 175 if (mInitializedByCtor) { 176 return widgetKeyboardEvent->mCharCode; 177 } 178 // Otherwise, i.e., initialized by InitKey*Event(), we should return the 179 // initialized value only when eKeyPress or eAccessKeyNotFound event. 180 // Although this is odd, but our traditional behavior. 181 return widgetKeyboardEvent->mMessage == eKeyPress || 182 widgetKeyboardEvent->mMessage == eAccessKeyNotFound 183 ? widgetKeyboardEvent->mCharCode 184 : 0; 185 } 186 187 // If the key is a function key, we should return the result of KeyCode() 188 // even from CharCode(). Otherwise, i.e., the key may be a printable 189 // key or actually a printable key, we should return the given charCode 190 // value. 191 192 if (widgetKeyboardEvent->mKeyNameIndex != KEY_NAME_INDEX_USE_STRING && 193 ShouldUseSameValueForCharCodeAndKeyCode(*widgetKeyboardEvent, 194 aCallerType)) { 195 return ComputeTraditionalKeyCode(*widgetKeyboardEvent, aCallerType); 196 } 197 198 return widgetKeyboardEvent->mCharCode; 199 } 200 201 uint32_t KeyboardEvent::KeyCode(CallerType aCallerType) { 202 WidgetKeyboardEvent* widgetKeyboardEvent = mEvent->AsKeyboardEvent(); 203 if (mInitializedByJS) { 204 // If this is initialized by Ctor, we should return the initialized value. 205 if (mInitializedByCtor) { 206 return widgetKeyboardEvent->mKeyCode; 207 } 208 // Otherwise, i.e., initialized by InitKey*Event(), we should return the 209 // initialized value only when the event message is a valid keyboard event 210 // message. Although this is odd, but our traditional behavior. 211 // NOTE: The fix of bug 1222285 changed the behavior temporarily if 212 // spoofing is enabled. However, the behavior does not make sense 213 // since if the event is generated by JS, the behavior shouldn't 214 // be changed by whether spoofing is enabled or not. Therefore, 215 // we take back the original behavior. 216 return widgetKeyboardEvent->HasKeyEventMessage() 217 ? widgetKeyboardEvent->mKeyCode 218 : 0; 219 } 220 221 // If the key is not a function key, i.e., the key may be a printable key 222 // or a function key mapped as a printable key, we should use charCode value 223 // for keyCode value if this is a "keypress" event. 224 225 if (widgetKeyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_USE_STRING && 226 ShouldUseSameValueForCharCodeAndKeyCode(*widgetKeyboardEvent, 227 aCallerType)) { 228 return widgetKeyboardEvent->mCharCode; 229 } 230 231 return ComputeTraditionalKeyCode(*widgetKeyboardEvent, aCallerType); 232 } 233 234 uint32_t KeyboardEvent::ComputeTraditionalKeyCode( 235 WidgetKeyboardEvent& aKeyboardEvent, CallerType aCallerType) { 236 if (!ShouldResistFingerprinting(aCallerType)) { 237 return aKeyboardEvent.mKeyCode; 238 } 239 240 // In Netscape style (i.e., traditional behavior of Gecko), the keyCode 241 // should be zero if the char code is given. 242 if ((mEvent->mMessage == eKeyPress || 243 mEvent->mMessage == eAccessKeyNotFound) && 244 aKeyboardEvent.mCharCode) { 245 return 0; 246 } 247 248 // When fingerprinting resistance is enabled, we will give a spoofed keyCode 249 // according to the content-language of the document. 250 nsCOMPtr<Document> doc = GetDocument(); 251 uint32_t spoofedKeyCode; 252 253 if (nsRFPService::GetSpoofedKeyCode(doc, &aKeyboardEvent, spoofedKeyCode)) { 254 return spoofedKeyCode; 255 } 256 257 return 0; 258 } 259 260 uint32_t KeyboardEvent::Which(CallerType aCallerType) { 261 // If this event is initialized with ctor, which can have independent value. 262 if (mInitializedByCtor) { 263 return mInitializedWhichValue; 264 } 265 266 switch (mEvent->mMessage) { 267 case eKeyDown: 268 case eKeyUp: 269 return KeyCode(aCallerType); 270 case eKeyPress: 271 // Special case for 4xp bug 62878. Try to make value of which 272 // more closely mirror the values that 4.x gave for RETURN and BACKSPACE 273 { 274 uint32_t keyCode = mEvent->AsKeyboardEvent()->mKeyCode; 275 if (keyCode == NS_VK_RETURN || keyCode == NS_VK_BACK) { 276 return keyCode; 277 } 278 return CharCode(); 279 } 280 default: 281 break; 282 } 283 284 return 0; 285 } 286 287 uint32_t KeyboardEvent::Location() { 288 return mEvent->AsKeyboardEvent()->mLocation; 289 } 290 291 // static 292 already_AddRefed<KeyboardEvent> KeyboardEvent::ConstructorJS( 293 const GlobalObject& aGlobal, const nsAString& aType, 294 const KeyboardEventInit& aParam) { 295 nsCOMPtr<EventTarget> target = do_QueryInterface(aGlobal.GetAsSupports()); 296 RefPtr<KeyboardEvent> newEvent = new KeyboardEvent(target, nullptr, nullptr); 297 newEvent->InitWithKeyboardEventInit(target, aType, aParam); 298 299 return newEvent.forget(); 300 } 301 302 void KeyboardEvent::InitWithKeyboardEventInit(EventTarget* aOwner, 303 const nsAString& aType, 304 const KeyboardEventInit& aParam) { 305 bool trusted = Init(aOwner); 306 InitKeyEventJS(aType, aParam.mBubbles, aParam.mCancelable, aParam.mView, 307 false, false, false, false, aParam.mKeyCode, aParam.mCharCode); 308 InitModifiers(aParam); 309 SetTrusted(trusted); 310 mDetail = aParam.mDetail; 311 mInitializedByJS = true; 312 mInitializedByCtor = true; 313 mInitializedWhichValue = aParam.mWhich; 314 315 WidgetKeyboardEvent* internalEvent = mEvent->AsKeyboardEvent(); 316 internalEvent->mLocation = aParam.mLocation; 317 internalEvent->mIsRepeat = aParam.mRepeat; 318 internalEvent->mIsComposing = aParam.mIsComposing; 319 internalEvent->mKeyNameIndex = 320 WidgetKeyboardEvent::GetKeyNameIndex(aParam.mKey); 321 if (internalEvent->mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) { 322 internalEvent->mKeyValue = aParam.mKey; 323 } 324 internalEvent->mCodeNameIndex = 325 WidgetKeyboardEvent::GetCodeNameIndex(aParam.mCode); 326 if (internalEvent->mCodeNameIndex == CODE_NAME_INDEX_USE_STRING) { 327 internalEvent->mCodeValue = aParam.mCode; 328 } 329 } 330 331 // static 332 bool KeyboardEvent::IsInitKeyEventAvailable(JSContext* aCx, JSObject*) { 333 if (StaticPrefs::dom_keyboardevent_init_key_event_enabled()) { 334 return true; 335 } 336 if (!StaticPrefs::dom_keyboardevent_init_key_event_enabled_in_addons()) { 337 return false; 338 } 339 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx); 340 return principal && principal->GetIsAddonOrExpandedAddonPrincipal(); 341 } 342 343 void KeyboardEvent::InitKeyEventJS(const nsAString& aType, bool aCanBubble, 344 bool aCancelable, nsGlobalWindowInner* aView, 345 bool aCtrlKey, bool aAltKey, bool aShiftKey, 346 bool aMetaKey, uint32_t aKeyCode, 347 uint32_t aCharCode) { 348 NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); 349 mInitializedByJS = true; 350 mInitializedByCtor = false; 351 352 UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0); 353 354 WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent(); 355 keyEvent->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey); 356 keyEvent->mKeyCode = aKeyCode; 357 keyEvent->mCharCode = aCharCode; 358 } 359 360 void KeyboardEvent::InitKeyboardEventJS( 361 const nsAString& aType, bool aCanBubble, bool aCancelable, 362 nsGlobalWindowInner* aView, const nsAString& aKey, uint32_t aLocation, 363 bool aCtrlKey, bool aAltKey, bool aShiftKey, bool aMetaKey) { 364 NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); 365 mInitializedByJS = true; 366 mInitializedByCtor = false; 367 368 UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0); 369 370 WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent(); 371 keyEvent->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey); 372 keyEvent->mLocation = aLocation; 373 keyEvent->mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; 374 keyEvent->mKeyValue = aKey; 375 } 376 377 bool KeyboardEvent::ShouldResistFingerprinting(CallerType aCallerType) { 378 // There are five situations we don't need to spoof this keyboard event. 379 // 1. The pref privcy.resistFingerprinting' is false, we fast return here. 380 // 2. This event is initialized by scripts. 381 // 3. This event is from Numpad. 382 // 4. This event is in the system group. 383 // 5. The caller type is system. 384 if (!nsContentUtils::ShouldResistFingerprinting("Efficiency Check", 385 RFPTarget::KeyboardEvents) || 386 mInitializedByJS || aCallerType == CallerType::System || 387 mEvent->mFlags.mInSystemGroup || 388 mEvent->AsKeyboardEvent()->mLocation == 389 KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) { 390 return false; 391 } 392 393 nsCOMPtr<Document> doc = GetDocument(); 394 // We've checked the pref above, so use true as fallback if doc is null. 395 return doc ? doc->ShouldResistFingerprinting(RFPTarget::KeyboardEvents) 396 : true; 397 } 398 399 bool KeyboardEvent::GetSpoofedModifierStates(const Modifiers aModifierKey, 400 const bool aRawModifierState) { 401 bool spoofedState; 402 nsCOMPtr<Document> doc = GetDocument(); 403 404 if (nsRFPService::GetSpoofedModifierStates(doc, mEvent->AsKeyboardEvent(), 405 aModifierKey, spoofedState)) { 406 return spoofedState; 407 } 408 409 return aRawModifierState; 410 } 411 412 } // namespace mozilla::dom 413 414 using namespace mozilla; 415 using namespace mozilla::dom; 416 417 already_AddRefed<KeyboardEvent> NS_NewDOMKeyboardEvent( 418 EventTarget* aOwner, nsPresContext* aPresContext, 419 WidgetKeyboardEvent* aEvent) { 420 RefPtr<KeyboardEvent> it = new KeyboardEvent(aOwner, aPresContext, aEvent); 421 return it.forget(); 422 }