Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / events / KeyboardEvent.cpp
blob2b70c84c6a42f5469561cc84533c0f406f5d2924
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/BasicEvents.h"
10 #include "mozilla/StaticPrefs_dom.h"
11 #include "mozilla/TextEvents.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/LookAndFeel.h"
14 #include "nsContentUtils.h"
15 #include "nsIPrincipal.h"
16 #include "nsRFPService.h"
17 #include "prtime.h"
19 namespace mozilla::dom {
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;
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;
44 static constexpr Modifiers kPossibleModifiersForAccessKey =
45 MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META;
47 Modifiers KeyboardEvent::GetModifiersForMenuAccessKey() const {
48 const WidgetInputEvent* inputEvent = WidgetEventPtr()->AsInputEvent();
49 MOZ_ASSERT(inputEvent);
50 return inputEvent->mModifiers & kPossibleModifiersForAccessKey;
53 bool KeyboardEvent::AltKey(CallerType aCallerType) {
54 bool altState = mEvent->AsKeyboardEvent()->IsAlt();
56 if (!ShouldResistFingerprinting(aCallerType)) {
57 return altState;
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);
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();
73 bool KeyboardEvent::ShiftKey(CallerType aCallerType) {
74 bool shiftState = mEvent->AsKeyboardEvent()->IsShift();
76 if (!ShouldResistFingerprinting(aCallerType)) {
77 return shiftState;
80 return GetSpoofedModifierStates(Modifier::MODIFIER_SHIFT, shiftState);
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();
90 bool KeyboardEvent::Repeat() { return mEvent->AsKeyboardEvent()->mIsRepeat; }
92 bool KeyboardEvent::IsComposing() {
93 return mEvent->AsKeyboardEvent()->mIsComposing;
96 void KeyboardEvent::GetKey(nsAString& aKeyName) const {
97 mEvent->AsKeyboardEvent()->GetDOMKeyName(aKeyName);
100 void KeyboardEvent::GetCode(nsAString& aCodeName, CallerType aCallerType) {
101 if (!ShouldResistFingerprinting(aCallerType)) {
102 mEvent->AsKeyboardEvent()->GetDOMCodeName(aCodeName);
103 return;
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();
110 nsRFPService::GetSpoofedCode(doc, mEvent->AsKeyboardEvent(), aCodeName);
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();
120 // legacy attributes
121 aParam.mKeyCode = KeyCode();
122 aParam.mCharCode = CharCode();
123 aParam.mWhich = Which();
125 // modifiers from EventModifierInit
126 aParam.mCtrlKey = CtrlKey();
127 aParam.mShiftKey = ShiftKey();
128 aParam.mAltKey = AltKey();
129 aParam.mMetaKey = MetaKey();
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();
141 // EventInit
142 aParam.mBubbles = internalEvent->mFlags.mBubbles;
143 aParam.mCancelable = internalEvent->mFlags.mCancelable;
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;
165 MOZ_ASSERT(aCallerType == CallerType::NonSystem);
167 return StaticPrefs::
168 dom_keyboardevent_keypress_set_keycode_and_charcode_to_same_value();
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;
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;
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.
192 if (widgetKeyboardEvent->mKeyNameIndex != KEY_NAME_INDEX_USE_STRING &&
193 ShouldUseSameValueForCharCodeAndKeyCode(*widgetKeyboardEvent,
194 aCallerType)) {
195 return ComputeTraditionalKeyCode(*widgetKeyboardEvent, aCallerType);
198 return widgetKeyboardEvent->mCharCode;
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;
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;
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.
225 if (widgetKeyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
226 ShouldUseSameValueForCharCodeAndKeyCode(*widgetKeyboardEvent,
227 aCallerType)) {
228 return widgetKeyboardEvent->mCharCode;
231 return ComputeTraditionalKeyCode(*widgetKeyboardEvent, aCallerType);
234 uint32_t KeyboardEvent::ComputeTraditionalKeyCode(
235 WidgetKeyboardEvent& aKeyboardEvent, CallerType aCallerType) {
236 if (!ShouldResistFingerprinting(aCallerType)) {
237 return aKeyboardEvent.mKeyCode;
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;
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;
253 if (nsRFPService::GetSpoofedKeyCode(doc, &aKeyboardEvent, spoofedKeyCode)) {
254 return spoofedKeyCode;
257 return 0;
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;
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
274 uint32_t keyCode = mEvent->AsKeyboardEvent()->mKeyCode;
275 if (keyCode == NS_VK_RETURN || keyCode == NS_VK_BACK) {
276 return keyCode;
278 return CharCode();
280 default:
281 break;
284 return 0;
287 uint32_t KeyboardEvent::Location() {
288 return mEvent->AsKeyboardEvent()->mLocation;
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);
299 return newEvent.forget();
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;
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;
324 internalEvent->mCodeNameIndex =
325 WidgetKeyboardEvent::GetCodeNameIndex(aParam.mCode);
326 if (internalEvent->mCodeNameIndex == CODE_NAME_INDEX_USE_STRING) {
327 internalEvent->mCodeValue = aParam.mCode;
331 // static
332 bool KeyboardEvent::IsInitKeyEventAvailable(JSContext* aCx, JSObject*) {
333 if (StaticPrefs::dom_keyboardevent_init_key_event_enabled()) {
334 return true;
336 if (!StaticPrefs::dom_keyboardevent_init_key_event_enabled_in_addons()) {
337 return false;
339 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
340 return principal && principal->GetIsAddonOrExpandedAddonPrincipal();
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;
352 UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0);
354 WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent();
355 keyEvent->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
356 keyEvent->mKeyCode = aKeyCode;
357 keyEvent->mCharCode = aCharCode;
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;
368 UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0);
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;
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;
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;
399 bool KeyboardEvent::GetSpoofedModifierStates(const Modifiers aModifierKey,
400 const bool aRawModifierState) {
401 bool spoofedState;
402 nsCOMPtr<Document> doc = GetDocument();
404 if (nsRFPService::GetSpoofedModifierStates(doc, mEvent->AsKeyboardEvent(),
405 aModifierKey, spoofedState)) {
406 return spoofedState;
409 return aRawModifierState;
412 } // namespace mozilla::dom
414 using namespace mozilla;
415 using namespace mozilla::dom;
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();