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/. */
7 #include "mozilla/dom/KeyboardEvent.h"
9 #include "mozilla/StaticPrefs_dom.h"
10 #include "mozilla/TextEvents.h"
11 #include "mozilla/dom/Document.h"
12 #include "nsContentUtils.h"
13 #include "nsIPrincipal.h"
14 #include "nsRFPService.h"
17 namespace mozilla::dom
{
19 KeyboardEvent::KeyboardEvent(EventTarget
* aOwner
, nsPresContext
* aPresContext
,
20 WidgetKeyboardEvent
* aEvent
)
21 : UIEvent(aOwner
, aPresContext
,
23 : new WidgetKeyboardEvent(false, eVoidEvent
, nullptr)),
24 mInitializedByJS(false),
25 mInitializedByCtor(false),
26 mInitializedWhichValue(0) {
28 mEventIsInternal
= false;
30 mEventIsInternal
= true;
31 mEvent
->mTime
= PR_Now();
32 mEvent
->AsKeyboardEvent()->mKeyNameIndex
= KEY_NAME_INDEX_USE_STRING
;
36 bool KeyboardEvent::AltKey(CallerType aCallerType
) {
37 bool altState
= mEvent
->AsKeyboardEvent()->IsAlt();
39 if (!ShouldResistFingerprinting(aCallerType
)) {
43 // We need to give a spoofed state for Alt key since it could be used as a
44 // modifier key in certain keyboard layout. For example, the '@' key for
45 // German keyboard for MAC is Alt+L.
46 return GetSpoofedModifierStates(Modifier::MODIFIER_ALT
, altState
);
49 bool KeyboardEvent::CtrlKey(CallerType aCallerType
) {
50 // We don't spoof this key when privacy.resistFingerprinting
51 // is enabled, because it is often used for command key
52 // combinations in web apps.
53 return mEvent
->AsKeyboardEvent()->IsControl();
56 bool KeyboardEvent::ShiftKey(CallerType aCallerType
) {
57 bool shiftState
= mEvent
->AsKeyboardEvent()->IsShift();
59 if (!ShouldResistFingerprinting(aCallerType
)) {
63 return GetSpoofedModifierStates(Modifier::MODIFIER_SHIFT
, shiftState
);
66 bool KeyboardEvent::MetaKey() {
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()->IsMeta();
73 bool KeyboardEvent::Repeat() { return mEvent
->AsKeyboardEvent()->mIsRepeat
; }
75 bool KeyboardEvent::IsComposing() {
76 return mEvent
->AsKeyboardEvent()->mIsComposing
;
79 void KeyboardEvent::GetKey(nsAString
& aKeyName
) const {
80 mEvent
->AsKeyboardEvent()->GetDOMKeyName(aKeyName
);
83 void KeyboardEvent::GetCode(nsAString
& aCodeName
, CallerType aCallerType
) {
84 if (!ShouldResistFingerprinting(aCallerType
)) {
85 mEvent
->AsKeyboardEvent()->GetDOMCodeName(aCodeName
);
89 // When fingerprinting resistance is enabled, we will give a spoofed code
90 // according to the content-language of the document.
91 nsCOMPtr
<Document
> doc
= GetDocument();
93 nsRFPService::GetSpoofedCode(doc
, mEvent
->AsKeyboardEvent(), aCodeName
);
96 void KeyboardEvent::GetInitDict(KeyboardEventInit
& aParam
) {
98 GetCode(aParam
.mCode
);
99 aParam
.mLocation
= Location();
100 aParam
.mRepeat
= Repeat();
101 aParam
.mIsComposing
= IsComposing();
104 aParam
.mKeyCode
= KeyCode();
105 aParam
.mCharCode
= CharCode();
106 aParam
.mWhich
= Which();
108 // modifiers from EventModifierInit
109 aParam
.mCtrlKey
= CtrlKey();
110 aParam
.mShiftKey
= ShiftKey();
111 aParam
.mAltKey
= AltKey();
112 aParam
.mMetaKey
= MetaKey();
114 WidgetKeyboardEvent
* internalEvent
= mEvent
->AsKeyboardEvent();
115 aParam
.mModifierAltGraph
= internalEvent
->IsAltGraph();
116 aParam
.mModifierCapsLock
= internalEvent
->IsCapsLocked();
117 aParam
.mModifierFn
= internalEvent
->IsFn();
118 aParam
.mModifierFnLock
= internalEvent
->IsFnLocked();
119 aParam
.mModifierNumLock
= internalEvent
->IsNumLocked();
120 aParam
.mModifierOS
= internalEvent
->IsOS();
121 aParam
.mModifierScrollLock
= internalEvent
->IsScrollLocked();
122 aParam
.mModifierSymbol
= internalEvent
->IsSymbol();
123 aParam
.mModifierSymbolLock
= internalEvent
->IsSymbolLocked();
126 aParam
.mBubbles
= internalEvent
->mFlags
.mBubbles
;
127 aParam
.mCancelable
= internalEvent
->mFlags
.mCancelable
;
130 bool KeyboardEvent::ShouldUseSameValueForCharCodeAndKeyCode(
131 const WidgetKeyboardEvent
& aWidgetKeyboardEvent
,
132 CallerType aCallerType
) const {
133 // - If this event is initialized by JS, we don't need to return same value
134 // for keyCode and charCode since they can be initialized separately.
135 // - If this is not a keypress event, we shouldn't return same value for
136 // keyCode and charCode.
137 // - If we need to return legacy keyCode and charCode values for the web
138 // app due to in the blacklist.
139 // - If this event is referred by default handler, i.e., the caller is
140 // system or this event is now in the system group, we don't need to use
141 // hack for web-compat.
142 if (mInitializedByJS
|| aWidgetKeyboardEvent
.mMessage
!= eKeyPress
||
143 aWidgetKeyboardEvent
.mUseLegacyKeyCodeAndCharCodeValues
||
144 aCallerType
== CallerType::System
||
145 aWidgetKeyboardEvent
.mFlags
.mInSystemGroup
) {
149 MOZ_ASSERT(aCallerType
== CallerType::NonSystem
);
152 dom_keyboardevent_keypress_set_keycode_and_charcode_to_same_value();
155 uint32_t KeyboardEvent::CharCode(CallerType aCallerType
) {
156 WidgetKeyboardEvent
* widgetKeyboardEvent
= mEvent
->AsKeyboardEvent();
157 if (mInitializedByJS
) {
158 // If this is initialized by Ctor, we should return the initialized value.
159 if (mInitializedByCtor
) {
160 return widgetKeyboardEvent
->mCharCode
;
162 // Otherwise, i.e., initialized by InitKey*Event(), we should return the
163 // initialized value only when eKeyPress or eAccessKeyNotFound event.
164 // Although this is odd, but our traditional behavior.
165 return widgetKeyboardEvent
->mMessage
== eKeyPress
||
166 widgetKeyboardEvent
->mMessage
== eAccessKeyNotFound
167 ? widgetKeyboardEvent
->mCharCode
171 // If the key is a function key, we should return the result of KeyCode()
172 // even from CharCode(). Otherwise, i.e., the key may be a printable
173 // key or actually a printable key, we should return the given charCode
176 if (widgetKeyboardEvent
->mKeyNameIndex
!= KEY_NAME_INDEX_USE_STRING
&&
177 ShouldUseSameValueForCharCodeAndKeyCode(*widgetKeyboardEvent
,
179 return ComputeTraditionalKeyCode(*widgetKeyboardEvent
, aCallerType
);
182 return widgetKeyboardEvent
->mCharCode
;
185 uint32_t KeyboardEvent::KeyCode(CallerType aCallerType
) {
186 WidgetKeyboardEvent
* widgetKeyboardEvent
= mEvent
->AsKeyboardEvent();
187 if (mInitializedByJS
) {
188 // If this is initialized by Ctor, we should return the initialized value.
189 if (mInitializedByCtor
) {
190 return widgetKeyboardEvent
->mKeyCode
;
192 // Otherwise, i.e., initialized by InitKey*Event(), we should return the
193 // initialized value only when the event message is a valid keyboard event
194 // message. Although this is odd, but our traditional behavior.
195 // NOTE: The fix of bug 1222285 changed the behavior temporarily if
196 // spoofing is enabled. However, the behavior does not make sense
197 // since if the event is generated by JS, the behavior shouldn't
198 // be changed by whether spoofing is enabled or not. Therefore,
199 // we take back the original behavior.
200 return widgetKeyboardEvent
->HasKeyEventMessage()
201 ? widgetKeyboardEvent
->mKeyCode
205 // If the key is not a function key, i.e., the key may be a printable key
206 // or a function key mapped as a printable key, we should use charCode value
207 // for keyCode value if this is a "keypress" event.
209 if (widgetKeyboardEvent
->mKeyNameIndex
== KEY_NAME_INDEX_USE_STRING
&&
210 ShouldUseSameValueForCharCodeAndKeyCode(*widgetKeyboardEvent
,
212 return widgetKeyboardEvent
->mCharCode
;
215 return ComputeTraditionalKeyCode(*widgetKeyboardEvent
, aCallerType
);
218 uint32_t KeyboardEvent::ComputeTraditionalKeyCode(
219 WidgetKeyboardEvent
& aKeyboardEvent
, CallerType aCallerType
) {
220 if (!ShouldResistFingerprinting(aCallerType
)) {
221 return aKeyboardEvent
.mKeyCode
;
224 // In Netscape style (i.e., traditional behavior of Gecko), the keyCode
225 // should be zero if the char code is given.
226 if ((mEvent
->mMessage
== eKeyPress
||
227 mEvent
->mMessage
== eAccessKeyNotFound
) &&
228 aKeyboardEvent
.mCharCode
) {
232 // When fingerprinting resistance is enabled, we will give a spoofed keyCode
233 // according to the content-language of the document.
234 nsCOMPtr
<Document
> doc
= GetDocument();
235 uint32_t spoofedKeyCode
;
237 if (nsRFPService::GetSpoofedKeyCode(doc
, &aKeyboardEvent
, spoofedKeyCode
)) {
238 return spoofedKeyCode
;
244 uint32_t KeyboardEvent::Which(CallerType aCallerType
) {
245 // If this event is initialized with ctor, which can have independent value.
246 if (mInitializedByCtor
) {
247 return mInitializedWhichValue
;
250 switch (mEvent
->mMessage
) {
253 return KeyCode(aCallerType
);
255 // Special case for 4xp bug 62878. Try to make value of which
256 // more closely mirror the values that 4.x gave for RETURN and BACKSPACE
258 uint32_t keyCode
= mEvent
->AsKeyboardEvent()->mKeyCode
;
259 if (keyCode
== NS_VK_RETURN
|| keyCode
== NS_VK_BACK
) {
271 uint32_t KeyboardEvent::Location() {
272 return mEvent
->AsKeyboardEvent()->mLocation
;
276 already_AddRefed
<KeyboardEvent
> KeyboardEvent::ConstructorJS(
277 const GlobalObject
& aGlobal
, const nsAString
& aType
,
278 const KeyboardEventInit
& aParam
) {
279 nsCOMPtr
<EventTarget
> target
= do_QueryInterface(aGlobal
.GetAsSupports());
280 RefPtr
<KeyboardEvent
> newEvent
= new KeyboardEvent(target
, nullptr, nullptr);
281 newEvent
->InitWithKeyboardEventInit(target
, aType
, aParam
);
283 return newEvent
.forget();
286 void KeyboardEvent::InitWithKeyboardEventInit(EventTarget
* aOwner
,
287 const nsAString
& aType
,
288 const KeyboardEventInit
& aParam
) {
289 bool trusted
= Init(aOwner
);
290 InitKeyEventJS(aType
, aParam
.mBubbles
, aParam
.mCancelable
, aParam
.mView
,
291 false, false, false, false, aParam
.mKeyCode
, aParam
.mCharCode
);
292 InitModifiers(aParam
);
294 mDetail
= aParam
.mDetail
;
295 mInitializedByJS
= true;
296 mInitializedByCtor
= true;
297 mInitializedWhichValue
= aParam
.mWhich
;
299 WidgetKeyboardEvent
* internalEvent
= mEvent
->AsKeyboardEvent();
300 internalEvent
->mLocation
= aParam
.mLocation
;
301 internalEvent
->mIsRepeat
= aParam
.mRepeat
;
302 internalEvent
->mIsComposing
= aParam
.mIsComposing
;
303 internalEvent
->mKeyNameIndex
=
304 WidgetKeyboardEvent::GetKeyNameIndex(aParam
.mKey
);
305 if (internalEvent
->mKeyNameIndex
== KEY_NAME_INDEX_USE_STRING
) {
306 internalEvent
->mKeyValue
= aParam
.mKey
;
308 internalEvent
->mCodeNameIndex
=
309 WidgetKeyboardEvent::GetCodeNameIndex(aParam
.mCode
);
310 if (internalEvent
->mCodeNameIndex
== CODE_NAME_INDEX_USE_STRING
) {
311 internalEvent
->mCodeValue
= aParam
.mCode
;
316 bool KeyboardEvent::IsInitKeyEventAvailable(JSContext
* aCx
, JSObject
*) {
317 if (StaticPrefs::dom_keyboardevent_init_key_event_enabled()) {
320 if (!StaticPrefs::dom_keyboardevent_init_key_event_enabled_in_addons()) {
323 nsIPrincipal
* principal
= nsContentUtils::SubjectPrincipal(aCx
);
324 return principal
&& principal
->GetIsAddonOrExpandedAddonPrincipal();
327 void KeyboardEvent::InitKeyEventJS(const nsAString
& aType
, bool aCanBubble
,
328 bool aCancelable
, nsGlobalWindowInner
* aView
,
329 bool aCtrlKey
, bool aAltKey
, bool aShiftKey
,
330 bool aMetaKey
, uint32_t aKeyCode
,
331 uint32_t aCharCode
) {
332 NS_ENSURE_TRUE_VOID(!mEvent
->mFlags
.mIsBeingDispatched
);
333 mInitializedByJS
= true;
334 mInitializedByCtor
= false;
336 UIEvent::InitUIEvent(aType
, aCanBubble
, aCancelable
, aView
, 0);
338 WidgetKeyboardEvent
* keyEvent
= mEvent
->AsKeyboardEvent();
339 keyEvent
->InitBasicModifiers(aCtrlKey
, aAltKey
, aShiftKey
, aMetaKey
);
340 keyEvent
->mKeyCode
= aKeyCode
;
341 keyEvent
->mCharCode
= aCharCode
;
344 void KeyboardEvent::InitKeyboardEventJS(
345 const nsAString
& aType
, bool aCanBubble
, bool aCancelable
,
346 nsGlobalWindowInner
* aView
, const nsAString
& aKey
, uint32_t aLocation
,
347 bool aCtrlKey
, bool aAltKey
, bool aShiftKey
, bool aMetaKey
) {
348 NS_ENSURE_TRUE_VOID(!mEvent
->mFlags
.mIsBeingDispatched
);
349 mInitializedByJS
= true;
350 mInitializedByCtor
= false;
352 UIEvent::InitUIEvent(aType
, aCanBubble
, aCancelable
, aView
, 0);
354 WidgetKeyboardEvent
* keyEvent
= mEvent
->AsKeyboardEvent();
355 keyEvent
->InitBasicModifiers(aCtrlKey
, aAltKey
, aShiftKey
, aMetaKey
);
356 keyEvent
->mLocation
= aLocation
;
357 keyEvent
->mKeyNameIndex
= KEY_NAME_INDEX_USE_STRING
;
358 keyEvent
->mKeyValue
= aKey
;
361 bool KeyboardEvent::ShouldResistFingerprinting(CallerType aCallerType
) {
362 // There are five situations we don't need to spoof this keyboard event.
363 // 1. This event is initialized by scripts.
364 // 2. This event is from Numpad.
365 // 3. This event is in the system group.
366 // 4. The caller type is system.
367 // 5. The pref privcy.resistFingerprinting' is false, we fast return here
368 // since we don't need to do any QI of following codes.
369 if (mInitializedByJS
|| aCallerType
== CallerType::System
||
370 mEvent
->mFlags
.mInSystemGroup
||
371 !nsContentUtils::ShouldResistFingerprinting() ||
372 mEvent
->AsKeyboardEvent()->mLocation
==
373 KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD
) {
377 nsCOMPtr
<Document
> doc
= GetDocument();
379 return nsContentUtils::ShouldResistFingerprinting(doc
);
382 bool KeyboardEvent::GetSpoofedModifierStates(const Modifiers aModifierKey
,
383 const bool aRawModifierState
) {
385 nsCOMPtr
<Document
> doc
= GetDocument();
387 if (nsRFPService::GetSpoofedModifierStates(doc
, mEvent
->AsKeyboardEvent(),
388 aModifierKey
, spoofedState
)) {
392 return aRawModifierState
;
395 } // namespace mozilla::dom
397 using namespace mozilla
;
398 using namespace mozilla::dom
;
400 already_AddRefed
<KeyboardEvent
> NS_NewDOMKeyboardEvent(
401 EventTarget
* aOwner
, nsPresContext
* aPresContext
,
402 WidgetKeyboardEvent
* aEvent
) {
403 RefPtr
<KeyboardEvent
> it
= new KeyboardEvent(aOwner
, aPresContext
, aEvent
);