diff options
Diffstat (limited to '')
-rw-r--r-- | dom/events/KeyboardEvent.cpp | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/dom/events/KeyboardEvent.cpp b/dom/events/KeyboardEvent.cpp new file mode 100644 index 0000000000..2b70c84c6a --- /dev/null +++ b/dom/events/KeyboardEvent.cpp @@ -0,0 +1,422 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "mozilla/dom/KeyboardEvent.h" + +#include "mozilla/BasicEvents.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/TextEvents.h" +#include "mozilla/dom/Document.h" +#include "mozilla/LookAndFeel.h" +#include "nsContentUtils.h" +#include "nsIPrincipal.h" +#include "nsRFPService.h" +#include "prtime.h" + +namespace mozilla::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; + mEvent->AsKeyboardEvent()->mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; + } +} + +bool KeyboardEvent::IsMenuAccessKeyPressed() const { + Modifiers mask = LookAndFeel::GetMenuAccessKeyModifiers(); + Modifiers modifiers = GetModifiersForMenuAccessKey(); + return mask != MODIFIER_SHIFT && (modifiers & mask) && + (modifiers & ~(mask | MODIFIER_SHIFT)) == 0; +} + +static constexpr Modifiers kPossibleModifiersForAccessKey = + MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META; + +Modifiers KeyboardEvent::GetModifiersForMenuAccessKey() const { + const WidgetInputEvent* inputEvent = WidgetEventPtr()->AsInputEvent(); + MOZ_ASSERT(inputEvent); + return inputEvent->mModifiers & kPossibleModifiersForAccessKey; +} + +bool KeyboardEvent::AltKey(CallerType aCallerType) { + bool altState = mEvent->AsKeyboardEvent()->IsAlt(); + + if (!ShouldResistFingerprinting(aCallerType)) { + return altState; + } + + // We need to give a spoofed state for Alt key since it could be used as a + // modifier key in certain keyboard layout. For example, the '@' key for + // German keyboard for MAC is Alt+L. + return GetSpoofedModifierStates(Modifier::MODIFIER_ALT, altState); +} + +bool KeyboardEvent::CtrlKey(CallerType aCallerType) { + // We don't spoof this key when privacy.resistFingerprinting + // is enabled, because it is often used for command key + // combinations in web apps. + return mEvent->AsKeyboardEvent()->IsControl(); +} + +bool KeyboardEvent::ShiftKey(CallerType aCallerType) { + bool shiftState = mEvent->AsKeyboardEvent()->IsShift(); + + if (!ShouldResistFingerprinting(aCallerType)) { + return shiftState; + } + + return GetSpoofedModifierStates(Modifier::MODIFIER_SHIFT, shiftState); +} + +bool KeyboardEvent::MetaKey() { + // We don't spoof this key when privacy.resistFingerprinting + // is enabled, because it is often used for command key + // combinations in web apps. + return mEvent->AsKeyboardEvent()->IsMeta(); +} + +bool KeyboardEvent::Repeat() { return mEvent->AsKeyboardEvent()->mIsRepeat; } + +bool KeyboardEvent::IsComposing() { + return mEvent->AsKeyboardEvent()->mIsComposing; +} + +void KeyboardEvent::GetKey(nsAString& aKeyName) const { + mEvent->AsKeyboardEvent()->GetDOMKeyName(aKeyName); +} + +void KeyboardEvent::GetCode(nsAString& aCodeName, CallerType aCallerType) { + if (!ShouldResistFingerprinting(aCallerType)) { + mEvent->AsKeyboardEvent()->GetDOMCodeName(aCodeName); + return; + } + + // When fingerprinting resistance is enabled, we will give a spoofed code + // according to the content-language of the document. + nsCOMPtr<Document> doc = GetDocument(); + + nsRFPService::GetSpoofedCode(doc, mEvent->AsKeyboardEvent(), aCodeName); +} + +void KeyboardEvent::GetInitDict(KeyboardEventInit& aParam) { + GetKey(aParam.mKey); + GetCode(aParam.mCode); + aParam.mLocation = Location(); + aParam.mRepeat = Repeat(); + aParam.mIsComposing = IsComposing(); + + // legacy attributes + aParam.mKeyCode = KeyCode(); + aParam.mCharCode = CharCode(); + aParam.mWhich = Which(); + + // modifiers from EventModifierInit + aParam.mCtrlKey = CtrlKey(); + aParam.mShiftKey = ShiftKey(); + aParam.mAltKey = AltKey(); + aParam.mMetaKey = MetaKey(); + + WidgetKeyboardEvent* internalEvent = mEvent->AsKeyboardEvent(); + aParam.mModifierAltGraph = internalEvent->IsAltGraph(); + aParam.mModifierCapsLock = internalEvent->IsCapsLocked(); + aParam.mModifierFn = internalEvent->IsFn(); + aParam.mModifierFnLock = internalEvent->IsFnLocked(); + aParam.mModifierNumLock = internalEvent->IsNumLocked(); + aParam.mModifierScrollLock = internalEvent->IsScrollLocked(); + aParam.mModifierSymbol = internalEvent->IsSymbol(); + aParam.mModifierSymbolLock = internalEvent->IsSymbolLocked(); + + // EventInit + aParam.mBubbles = internalEvent->mFlags.mBubbles; + aParam.mCancelable = internalEvent->mFlags.mCancelable; +} + +bool KeyboardEvent::ShouldUseSameValueForCharCodeAndKeyCode( + const WidgetKeyboardEvent& aWidgetKeyboardEvent, + CallerType aCallerType) const { + // - 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 we need to return legacy keyCode and charCode values for the web + // app due to in the blacklist. + // - 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 || aWidgetKeyboardEvent.mMessage != eKeyPress || + aWidgetKeyboardEvent.mUseLegacyKeyCodeAndCharCodeValues || + aCallerType == CallerType::System || + aWidgetKeyboardEvent.mFlags.mInSystemGroup) { + return false; + } + + 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; + } + + // 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(*widgetKeyboardEvent, + aCallerType)) { + return ComputeTraditionalKeyCode(*widgetKeyboardEvent, aCallerType); + } + + return widgetKeyboardEvent->mCharCode; +} + +uint32_t KeyboardEvent::KeyCode(CallerType aCallerType) { + 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 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(*widgetKeyboardEvent, + aCallerType)) { + return widgetKeyboardEvent->mCharCode; + } + + return ComputeTraditionalKeyCode(*widgetKeyboardEvent, aCallerType); +} + +uint32_t KeyboardEvent::ComputeTraditionalKeyCode( + WidgetKeyboardEvent& aKeyboardEvent, CallerType aCallerType) { + if (!ShouldResistFingerprinting(aCallerType)) { + return aKeyboardEvent.mKeyCode; + } + + // 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<Document> doc = GetDocument(); + uint32_t spoofedKeyCode; + + if (nsRFPService::GetSpoofedKeyCode(doc, &aKeyboardEvent, spoofedKeyCode)) { + return spoofedKeyCode; + } + + return 0; +} + +uint32_t KeyboardEvent::Which(CallerType aCallerType) { + // If this event is initialized with ctor, which can have independent value. + if (mInitializedByCtor) { + return mInitializedWhichValue; + } + + switch (mEvent->mMessage) { + case eKeyDown: + case eKeyUp: + return KeyCode(aCallerType); + case eKeyPress: + // Special case for 4xp bug 62878. Try to make value of which + // more closely mirror the values that 4.x gave for RETURN and BACKSPACE + { + uint32_t keyCode = mEvent->AsKeyboardEvent()->mKeyCode; + if (keyCode == NS_VK_RETURN || keyCode == NS_VK_BACK) { + return keyCode; + } + return CharCode(); + } + default: + break; + } + + return 0; +} + +uint32_t KeyboardEvent::Location() { + return mEvent->AsKeyboardEvent()->mLocation; +} + +// static +already_AddRefed<KeyboardEvent> KeyboardEvent::ConstructorJS( + const GlobalObject& aGlobal, const nsAString& aType, + const KeyboardEventInit& aParam) { + nsCOMPtr<EventTarget> target = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<KeyboardEvent> newEvent = new KeyboardEvent(target, nullptr, nullptr); + newEvent->InitWithKeyboardEventInit(target, aType, aParam); + + return newEvent.forget(); +} + +void KeyboardEvent::InitWithKeyboardEventInit(EventTarget* aOwner, + const nsAString& aType, + const KeyboardEventInit& aParam) { + bool trusted = Init(aOwner); + 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 = + WidgetKeyboardEvent::GetKeyNameIndex(aParam.mKey); + if (internalEvent->mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) { + internalEvent->mKeyValue = aParam.mKey; + } + internalEvent->mCodeNameIndex = + WidgetKeyboardEvent::GetCodeNameIndex(aParam.mCode); + if (internalEvent->mCodeNameIndex == CODE_NAME_INDEX_USE_STRING) { + internalEvent->mCodeValue = aParam.mCode; + } +} + +// static +bool KeyboardEvent::IsInitKeyEventAvailable(JSContext* aCx, JSObject*) { + if (StaticPrefs::dom_keyboardevent_init_key_event_enabled()) { + return true; + } + if (!StaticPrefs::dom_keyboardevent_init_key_event_enabled_in_addons()) { + return false; + } + nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx); + return principal && principal->GetIsAddonOrExpandedAddonPrincipal(); +} + +void 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::InitKeyboardEventJS( + const nsAString& aType, bool aCanBubble, bool aCancelable, + nsGlobalWindowInner* aView, const nsAString& aKey, uint32_t aLocation, + bool aCtrlKey, bool aAltKey, bool aShiftKey, bool aMetaKey) { + 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; +} + +bool KeyboardEvent::ShouldResistFingerprinting(CallerType aCallerType) { + // There are five situations we don't need to spoof this keyboard event. + // 1. The pref privcy.resistFingerprinting' is false, we fast return here. + // 2. This event is initialized by scripts. + // 3. This event is from Numpad. + // 4. This event is in the system group. + // 5. The caller type is system. + if (!nsContentUtils::ShouldResistFingerprinting("Efficiency Check", + RFPTarget::KeyboardEvents) || + mInitializedByJS || aCallerType == CallerType::System || + mEvent->mFlags.mInSystemGroup || + mEvent->AsKeyboardEvent()->mLocation == + KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) { + return false; + } + + nsCOMPtr<Document> doc = GetDocument(); + // We've checked the pref above, so use true as fallback if doc is null. + return doc ? doc->ShouldResistFingerprinting(RFPTarget::KeyboardEvents) + : true; +} + +bool KeyboardEvent::GetSpoofedModifierStates(const Modifiers aModifierKey, + const bool aRawModifierState) { + bool spoofedState; + nsCOMPtr<Document> doc = GetDocument(); + + if (nsRFPService::GetSpoofedModifierStates(doc, mEvent->AsKeyboardEvent(), + aModifierKey, spoofedState)) { + return spoofedState; + } + + return aRawModifierState; +} + +} // namespace mozilla::dom + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<KeyboardEvent> NS_NewDOMKeyboardEvent( + EventTarget* aOwner, nsPresContext* aPresContext, + WidgetKeyboardEvent* aEvent) { + RefPtr<KeyboardEvent> it = new KeyboardEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} |