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/ArrayUtils.h"
11 #include "nsQueryObject.h"
12 #include "KeyEventHandler.h"
13 #include "nsContentUtils.h"
14 #include "nsGlobalWindowCommands.h"
15 #include "nsIContent.h"
17 #include "nsNameSpaceManager.h"
18 #include "mozilla/dom/Document.h"
19 #include "nsIController.h"
20 #include "nsIControllers.h"
21 #include "nsXULElement.h"
22 #include "nsFocusManager.h"
23 #include "nsIFormControl.h"
24 #include "nsPIDOMWindow.h"
25 #include "nsPIWindowRoot.h"
26 #include "nsIScriptError.h"
27 #include "nsIWeakReferenceUtils.h"
29 #include "nsReadableUtils.h"
30 #include "nsGkAtoms.h"
32 #include "nsUnicharUtils.h"
34 #include "nsJSUtils.h"
35 #include "mozilla/BasicEvents.h"
36 #include "mozilla/LookAndFeel.h"
37 #include "mozilla/JSEventHandler.h"
38 #include "mozilla/Preferences.h"
39 #include "mozilla/TextEvents.h"
40 #include "mozilla/dom/Element.h"
41 #include "mozilla/dom/Event.h"
42 #include "mozilla/dom/EventHandlerBinding.h"
43 #include "mozilla/dom/HTMLInputElement.h"
44 #include "mozilla/dom/HTMLTextAreaElement.h"
45 #include "mozilla/dom/KeyboardEvent.h"
46 #include "mozilla/dom/KeyboardEventBinding.h"
47 #include "mozilla/dom/ScriptSettings.h"
48 #include "mozilla/layers/KeyboardMap.h"
49 #include "xpcpublic.h"
53 using namespace mozilla::layers
;
55 uint32_t KeyEventHandler::gRefCnt
= 0;
57 const int32_t KeyEventHandler::cShift
= (1 << 0);
58 const int32_t KeyEventHandler::cAlt
= (1 << 1);
59 const int32_t KeyEventHandler::cControl
= (1 << 2);
60 const int32_t KeyEventHandler::cMeta
= (1 << 3);
62 const int32_t KeyEventHandler::cShiftMask
= (1 << 5);
63 const int32_t KeyEventHandler::cAltMask
= (1 << 6);
64 const int32_t KeyEventHandler::cControlMask
= (1 << 7);
65 const int32_t KeyEventHandler::cMetaMask
= (1 << 8);
67 const int32_t KeyEventHandler::cAllModifiers
=
68 cShiftMask
| cAltMask
| cControlMask
| cMetaMask
;
70 KeyEventHandler::KeyEventHandler(dom::Element
* aHandlerElement
,
71 ReservedKey aReserved
)
72 : mHandlerElement(nullptr),
75 mNextHandler(nullptr) {
78 // Make sure our prototype is initialized.
79 ConstructPrototype(aHandlerElement
);
82 KeyEventHandler::KeyEventHandler(ShortcutKeyData
* aKeyData
)
85 mReserved(ReservedKey_False
),
86 mNextHandler(nullptr) {
89 ConstructPrototype(nullptr, aKeyData
->event
, aKeyData
->command
,
90 aKeyData
->keycode
, aKeyData
->key
, aKeyData
->modifiers
);
93 KeyEventHandler::~KeyEventHandler() {
96 NS_IF_RELEASE(mHandlerElement
);
97 } else if (mCommand
) {
101 // We own the next handler in the chain, so delete it now.
102 NS_CONTENT_DELETE_LIST_MEMBER(KeyEventHandler
, this, mNextHandler
);
105 void KeyEventHandler::GetCommand(nsAString
& aCommand
) const {
106 MOZ_ASSERT(aCommand
.IsEmpty());
108 MOZ_ASSERT_UNREACHABLE("Not yet implemented");
112 aCommand
.Assign(mCommand
);
116 bool KeyEventHandler::TryConvertToKeyboardShortcut(
117 KeyboardShortcut
* aOut
) const {
118 // Convert the event type
119 KeyboardInput::KeyboardEventType eventType
;
121 if (mEventName
== nsGkAtoms::keydown
) {
122 eventType
= KeyboardInput::KEY_DOWN
;
123 } else if (mEventName
== nsGkAtoms::keypress
) {
124 eventType
= KeyboardInput::KEY_PRESS
;
125 } else if (mEventName
== nsGkAtoms::keyup
) {
126 eventType
= KeyboardInput::KEY_UP
;
131 // Convert the modifiers
132 Modifiers modifiersMask
= GetModifiersMask();
133 Modifiers modifiers
= GetModifiers();
135 // Mask away any bits that won't be compared
136 modifiers
&= modifiersMask
;
138 // Convert the keyCode or charCode
144 charCode
= static_cast<uint32_t>(mDetail
);
146 keyCode
= static_cast<uint32_t>(mDetail
);
150 NS_LossyConvertUTF16toASCII
commandText(mCommand
);
151 KeyboardScrollAction action
;
152 if (!nsGlobalWindowCommands::FindScrollCommand(commandText
.get(), &action
)) {
153 // This action doesn't represent a scroll so we need to create a dispatch
154 // to content keyboard shortcut so APZ handles this command correctly
155 *aOut
= KeyboardShortcut(eventType
, keyCode
, charCode
, modifiers
,
160 // This prototype is a command which represents a scroll action, so create
161 // a keyboard shortcut to handle it
162 *aOut
= KeyboardShortcut(eventType
, keyCode
, charCode
, modifiers
,
163 modifiersMask
, action
);
167 bool KeyEventHandler::KeyElementIsDisabled() const {
168 RefPtr
<dom::Element
> keyElement
= GetHandlerElement();
170 keyElement
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::disabled
,
171 nsGkAtoms::_true
, eCaseMatters
);
174 already_AddRefed
<dom::Element
> KeyEventHandler::GetHandlerElement() const {
176 nsCOMPtr
<dom::Element
> element
= do_QueryReferent(mHandlerElement
);
177 return element
.forget();
183 nsresult
KeyEventHandler::ExecuteHandler(dom::EventTarget
* aTarget
,
184 dom::Event
* aEvent
) {
185 // In both cases the union should be defined.
186 if (!mHandlerElement
) {
187 return NS_ERROR_FAILURE
;
190 // XUL handlers and commands shouldn't be triggered by non-trusted
192 if (!aEvent
->IsTrusted()) {
197 return DispatchXULKeyCommand(aEvent
);
200 return DispatchXBLCommand(aTarget
, aEvent
);
203 nsresult
KeyEventHandler::DispatchXBLCommand(dom::EventTarget
* aTarget
,
204 dom::Event
* aEvent
) {
205 // This is a special-case optimization to make command handling fast.
206 // It isn't really a part of XBL, but it helps speed things up.
209 // See if preventDefault has been set. If so, don't execute.
210 if (aEvent
->DefaultPrevented()) {
213 bool dispatchStopped
= aEvent
->IsDispatchStopped();
214 if (dispatchStopped
) {
219 // Instead of executing JS, let's get the controller for the bound
220 // element and call doCommand on it.
221 nsCOMPtr
<nsIController
> controller
;
223 nsCOMPtr
<nsPIDOMWindowOuter
> privateWindow
;
224 nsCOMPtr
<nsPIWindowRoot
> windowRoot
=
225 nsPIWindowRoot::FromEventTargetOrNull(aTarget
);
227 privateWindow
= windowRoot
->GetWindow();
229 privateWindow
= nsPIDOMWindowOuter::FromEventTargetOrNull(aTarget
);
230 if (!privateWindow
) {
231 nsCOMPtr
<dom::Document
> doc
;
232 // XXXbz sXBL/XBL2 issue -- this should be the "scope doc" or
233 // something... whatever we use when wrapping DOM nodes
234 // normally. It's not clear that the owner doc is the right
236 if (nsIContent
* content
= nsIContent::FromEventTargetOrNull(aTarget
)) {
237 doc
= content
->OwnerDoc();
241 if (nsINode
* node
= nsINode::FromEventTargetOrNull(aTarget
)) {
242 if (node
->IsDocument()) {
243 doc
= node
->AsDocument();
249 return NS_ERROR_FAILURE
;
252 privateWindow
= doc
->GetWindow();
253 if (!privateWindow
) {
254 return NS_ERROR_FAILURE
;
258 windowRoot
= privateWindow
->GetTopWindowRoot();
261 NS_LossyConvertUTF16toASCII
command(mCommand
);
263 // If user tries to do something, user must try to do it in visible window.
264 // So, let's retrieve controller of visible window.
265 windowRoot
->GetControllerForCommand(command
.get(), true,
266 getter_AddRefs(controller
));
269 GetController(aTarget
); // We're attached to the receiver possibly.
272 // We are the default action for this command.
273 // Stop any other default action from executing.
274 aEvent
->PreventDefault();
276 if (mEventName
== nsGkAtoms::keypress
&&
277 mDetail
== dom::KeyboardEvent_Binding::DOM_VK_SPACE
&& mMisc
== 1) {
278 // get the focused element so that we can pageDown only at
281 nsCOMPtr
<nsPIDOMWindowOuter
> windowToCheck
;
283 windowToCheck
= windowRoot
->GetWindow();
285 windowToCheck
= privateWindow
->GetPrivateRoot();
288 nsCOMPtr
<nsIContent
> focusedContent
;
290 nsCOMPtr
<nsPIDOMWindowOuter
> focusedWindow
;
291 focusedContent
= nsFocusManager::GetFocusedDescendant(
292 windowToCheck
, nsFocusManager::eIncludeAllDescendants
,
293 getter_AddRefs(focusedWindow
));
296 // If the focus is in an editable region, don't scroll.
297 if (focusedContent
&& focusedContent
->IsEditable()) {
301 // If the focus is in a form control, don't scroll.
302 for (nsIContent
* c
= focusedContent
; c
; c
= c
->GetParent()) {
303 if (nsIFormControl::FromNode(c
)) {
310 controller
->DoCommand(command
.get());
316 nsresult
KeyEventHandler::DispatchXULKeyCommand(dom::Event
* aEvent
) {
317 nsCOMPtr
<dom::Element
> handlerElement
= GetHandlerElement();
318 NS_ENSURE_STATE(handlerElement
);
319 if (handlerElement
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::disabled
,
320 nsGkAtoms::_true
, eCaseMatters
)) {
321 // Don't dispatch command events for disabled keys.
322 return NS_SUCCESS_DOM_NO_OPERATION
;
325 aEvent
->PreventDefault();
327 // Copy the modifiers from the key event.
328 RefPtr
<dom::KeyboardEvent
> domKeyboardEvent
= aEvent
->AsKeyboardEvent();
329 if (!domKeyboardEvent
) {
330 NS_ERROR("Trying to execute a key handler for a non-key event!");
331 return NS_ERROR_FAILURE
;
334 // XXX We should use mozilla::Modifiers for supporting all modifiers.
336 bool isAlt
= domKeyboardEvent
->AltKey();
337 bool isControl
= domKeyboardEvent
->CtrlKey();
338 bool isShift
= domKeyboardEvent
->ShiftKey();
339 bool isMeta
= domKeyboardEvent
->MetaKey();
341 nsContentUtils::DispatchXULCommand(handlerElement
, true, nullptr, nullptr,
342 isControl
, isAlt
, isShift
, isMeta
);
346 Modifiers
KeyEventHandler::GetModifiers() const {
347 Modifiers modifiers
= 0;
349 if (mKeyMask
& cMeta
) {
350 modifiers
|= MODIFIER_META
;
352 if (mKeyMask
& cShift
) {
353 modifiers
|= MODIFIER_SHIFT
;
355 if (mKeyMask
& cAlt
) {
356 modifiers
|= MODIFIER_ALT
;
358 if (mKeyMask
& cControl
) {
359 modifiers
|= MODIFIER_CONTROL
;
365 Modifiers
KeyEventHandler::GetModifiersMask() const {
366 Modifiers modifiersMask
= 0;
368 if (mKeyMask
& cMetaMask
) {
369 modifiersMask
|= MODIFIER_META
;
371 if (mKeyMask
& cShiftMask
) {
372 modifiersMask
|= MODIFIER_SHIFT
;
374 if (mKeyMask
& cAltMask
) {
375 modifiersMask
|= MODIFIER_ALT
;
377 if (mKeyMask
& cControlMask
) {
378 modifiersMask
|= MODIFIER_CONTROL
;
381 return modifiersMask
;
384 already_AddRefed
<nsIController
> KeyEventHandler::GetController(
385 dom::EventTarget
* aTarget
) {
390 // XXX Fix this so there's a generic interface that describes controllers,
391 // This code should have no special knowledge of what objects might have
393 nsCOMPtr
<nsIControllers
> controllers
;
395 if (nsIContent
* targetContent
= nsIContent::FromEventTarget(aTarget
)) {
396 RefPtr
<nsXULElement
> xulElement
= nsXULElement::FromNode(targetContent
);
398 controllers
= xulElement
->GetControllers(IgnoreErrors());
402 dom::HTMLTextAreaElement
* htmlTextArea
=
403 dom::HTMLTextAreaElement::FromNode(targetContent
);
405 htmlTextArea
->GetControllers(getter_AddRefs(controllers
));
410 dom::HTMLInputElement
* htmlInputElement
=
411 dom::HTMLInputElement::FromNode(targetContent
);
412 if (htmlInputElement
) {
413 htmlInputElement
->GetControllers(getter_AddRefs(controllers
));
419 if (nsCOMPtr
<nsPIDOMWindowOuter
> domWindow
=
420 nsPIDOMWindowOuter::FromEventTarget(aTarget
)) {
421 domWindow
->GetControllers(getter_AddRefs(controllers
));
425 // Return the first controller.
426 // XXX This code should be checking the command name and using supportscommand
427 // and iscommandenabled.
428 nsCOMPtr
<nsIController
> controller
;
430 controllers
->GetControllerAt(0, getter_AddRefs(controller
));
433 return controller
.forget();
436 bool KeyEventHandler::KeyEventMatched(
437 dom::KeyboardEvent
* aDomKeyboardEvent
, uint32_t aCharCode
,
438 const IgnoreModifierState
& aIgnoreModifierState
) {
440 // Get the keycode or charcode of the key event.
447 code
= aDomKeyboardEvent
->CharCode();
449 if (IS_IN_BMP(code
)) {
450 code
= ToLowerCase(char16_t(code
));
453 code
= aDomKeyboardEvent
->KeyCode();
456 if (code
!= static_cast<uint32_t>(mDetail
)) {
461 return ModifiersMatchMask(aDomKeyboardEvent
, aIgnoreModifierState
);
470 // All of these must be uppercase, since the function below does
471 // case-insensitive comparison by converting to uppercase.
472 // XXX: be sure to check this periodically for new symbol additions!
473 static const keyCodeData gKeyCodes
[] = {
475 #define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \
476 {#aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode},
477 #include "mozilla/VirtualKeyCodeList.h"
482 int32_t KeyEventHandler::GetMatchingKeyCode(const nsAString
& aKeyName
) {
483 nsAutoCString keyName
;
484 LossyCopyUTF16toASCII(aKeyName
, keyName
);
485 ToUpperCase(keyName
); // We want case-insensitive comparison with data
486 // stored as uppercase.
488 uint32_t keyNameLength
= keyName
.Length();
489 const char* keyNameStr
= keyName
.get();
490 for (unsigned long i
= 0; i
< std::size(gKeyCodes
) - 1; ++i
) {
491 if (keyNameLength
== gKeyCodes
[i
].strlength
&&
492 !nsCRT::strcmp(gKeyCodes
[i
].str
, keyNameStr
)) {
493 return gKeyCodes
[i
].keycode
;
500 int32_t KeyEventHandler::KeyToMask(uint32_t key
) {
502 case dom::KeyboardEvent_Binding::DOM_VK_META
:
503 case dom::KeyboardEvent_Binding::DOM_VK_WIN
:
504 return cMeta
| cMetaMask
;
506 case dom::KeyboardEvent_Binding::DOM_VK_ALT
:
507 return cAlt
| cAltMask
;
509 case dom::KeyboardEvent_Binding::DOM_VK_CONTROL
:
511 return cControl
| cControlMask
;
516 int32_t KeyEventHandler::AccelKeyMask() {
517 switch (WidgetInputEvent::AccelModifier()) {
519 return KeyToMask(dom::KeyboardEvent_Binding::DOM_VK_ALT
);
520 case MODIFIER_CONTROL
:
521 return KeyToMask(dom::KeyboardEvent_Binding::DOM_VK_CONTROL
);
523 return KeyToMask(dom::KeyboardEvent_Binding::DOM_VK_META
);
525 MOZ_CRASH("Handle the new result of WidgetInputEvent::AccelModifier()");
530 void KeyEventHandler::GetEventType(nsAString
& aEvent
) {
531 nsCOMPtr
<dom::Element
> handlerElement
= GetHandlerElement();
532 if (!handlerElement
) {
536 handlerElement
->GetAttr(nsGkAtoms::event
, aEvent
);
538 if (aEvent
.IsEmpty() && mIsXULKey
) {
539 // If no type is specified for a XUL <key> element, let's assume that we're
541 aEvent
.AssignLiteral("keypress");
545 void KeyEventHandler::ConstructPrototype(dom::Element
* aKeyElement
,
546 const char16_t
* aEvent
,
547 const char16_t
* aCommand
,
548 const char16_t
* aKeyCode
,
549 const char16_t
* aCharCode
,
550 const char16_t
* aModifiers
) {
554 nsAutoString modifiers
;
557 nsWeakPtr weak
= do_GetWeakReference(aKeyElement
);
561 weak
.swap(mHandlerElement
);
565 if (event
.IsEmpty()) {
568 mEventName
= NS_Atomize(event
);
570 aKeyElement
->GetAttr(nsGkAtoms::modifiers
, modifiers
);
572 mCommand
= ToNewUnicode(nsDependentString(aCommand
));
573 mEventName
= NS_Atomize(aEvent
);
574 modifiers
= aModifiers
;
577 BuildModifiers(modifiers
);
579 nsAutoString
key(aCharCode
);
582 aKeyElement
->GetAttr(nsGkAtoms::key
, key
);
584 aKeyElement
->GetAttr(nsGkAtoms::charcode
, key
);
589 if (!key
.IsEmpty()) {
591 mKeyMask
= cAllModifiers
;
595 // We have a charcode.
598 const uint8_t GTK2Modifiers
= cShift
| cControl
| cShiftMask
| cControlMask
;
599 if (mIsXULKey
&& (mKeyMask
& GTK2Modifiers
) == GTK2Modifiers
&&
600 modifiers
.First() != char16_t(',') &&
601 (mDetail
== 'u' || mDetail
== 'U')) {
602 ReportKeyConflict(key
.get(), modifiers
.get(), aKeyElement
,
605 const uint8_t WinModifiers
= cControl
| cAlt
| cControlMask
| cAltMask
;
606 if (mIsXULKey
&& (mKeyMask
& WinModifiers
) == WinModifiers
&&
607 modifiers
.First() != char16_t(',') &&
608 (('A' <= mDetail
&& mDetail
<= 'Z') ||
609 ('a' <= mDetail
&& mDetail
<= 'z'))) {
610 ReportKeyConflict(key
.get(), modifiers
.get(), aKeyElement
,
614 key
.Assign(aKeyCode
);
616 aKeyElement
->GetAttr(nsGkAtoms::keycode
, key
);
619 if (!key
.IsEmpty()) {
621 mKeyMask
= cAllModifiers
;
623 mDetail
= GetMatchingKeyCode(key
);
628 void KeyEventHandler::BuildModifiers(nsAString
& aModifiers
) {
629 if (!aModifiers
.IsEmpty()) {
630 mKeyMask
= cAllModifiers
;
631 char* str
= ToNewCString(aModifiers
);
633 char* token
= nsCRT::strtok(str
, ", \t", &newStr
);
634 while (token
!= nullptr) {
635 if (strcmp(token
, "shift") == 0) {
636 mKeyMask
|= cShift
| cShiftMask
;
637 } else if (strcmp(token
, "alt") == 0) {
638 mKeyMask
|= cAlt
| cAltMask
;
639 } else if (strcmp(token
, "meta") == 0) {
640 mKeyMask
|= cMeta
| cMetaMask
;
641 } else if (strcmp(token
, "control") == 0) {
642 mKeyMask
|= cControl
| cControlMask
;
643 } else if (strcmp(token
, "accel") == 0) {
644 mKeyMask
|= AccelKeyMask();
645 } else if (strcmp(token
, "access") == 0) {
646 mKeyMask
|= KeyToMask(LookAndFeel::GetMenuAccessKey());
647 } else if (strcmp(token
, "any") == 0) {
648 mKeyMask
&= ~(mKeyMask
<< 5);
651 token
= nsCRT::strtok(newStr
, ", \t", &newStr
);
658 void KeyEventHandler::ReportKeyConflict(const char16_t
* aKey
,
659 const char16_t
* aModifiers
,
660 dom::Element
* aKeyElement
,
661 const char* aMessageName
) {
662 nsCOMPtr
<dom::Document
> doc
= aKeyElement
->OwnerDoc();
665 aKeyElement
->GetAttr(nsGkAtoms::id
, id
);
666 AutoTArray
<nsString
, 3> params
;
667 params
.AppendElement(aKey
);
668 params
.AppendElement(aModifiers
);
669 params
.AppendElement(id
);
670 nsContentUtils::ReportToConsole(
671 nsIScriptError::warningFlag
, "Key dom::Event Handler"_ns
, doc
,
672 nsContentUtils::eDOM_PROPERTIES
, aMessageName
, params
);
675 bool KeyEventHandler::ModifiersMatchMask(
676 dom::UIEvent
* aEvent
, const IgnoreModifierState
& aIgnoreModifierState
) {
677 WidgetInputEvent
* inputEvent
= aEvent
->WidgetEventPtr()->AsInputEvent();
678 NS_ENSURE_TRUE(inputEvent
, false);
680 if ((mKeyMask
& cMetaMask
) && !aIgnoreModifierState
.mMeta
) {
681 if (inputEvent
->IsMeta() != ((mKeyMask
& cMeta
) != 0)) {
686 if ((mKeyMask
& cShiftMask
) && !aIgnoreModifierState
.mShift
) {
687 if (inputEvent
->IsShift() != ((mKeyMask
& cShift
) != 0)) {
692 if (mKeyMask
& cAltMask
) {
693 if (inputEvent
->IsAlt() != ((mKeyMask
& cAlt
) != 0)) {
698 if (mKeyMask
& cControlMask
) {
699 if (inputEvent
->IsControl() != ((mKeyMask
& cControl
) != 0)) {
707 size_t KeyEventHandler::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
709 for (const KeyEventHandler
* handler
= this; handler
;
710 handler
= handler
->mNextHandler
) {
711 n
+= aMallocSizeOf(handler
);
713 n
+= aMallocSizeOf(handler
->mCommand
);
719 } // namespace mozilla