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 nsCOMPtr
<nsIFormControl
> formControl
= do_QueryInterface(c
);
311 controller
->DoCommand(command
.get());
317 nsresult
KeyEventHandler::DispatchXULKeyCommand(dom::Event
* aEvent
) {
318 nsCOMPtr
<dom::Element
> handlerElement
= GetHandlerElement();
319 NS_ENSURE_STATE(handlerElement
);
320 if (handlerElement
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::disabled
,
321 nsGkAtoms::_true
, eCaseMatters
)) {
322 // Don't dispatch command events for disabled keys.
323 return NS_SUCCESS_DOM_NO_OPERATION
;
326 aEvent
->PreventDefault();
328 // Copy the modifiers from the key event.
329 RefPtr
<dom::KeyboardEvent
> domKeyboardEvent
= aEvent
->AsKeyboardEvent();
330 if (!domKeyboardEvent
) {
331 NS_ERROR("Trying to execute a key handler for a non-key event!");
332 return NS_ERROR_FAILURE
;
335 // XXX We should use mozilla::Modifiers for supporting all modifiers.
337 bool isAlt
= domKeyboardEvent
->AltKey();
338 bool isControl
= domKeyboardEvent
->CtrlKey();
339 bool isShift
= domKeyboardEvent
->ShiftKey();
340 bool isMeta
= domKeyboardEvent
->MetaKey();
342 nsContentUtils::DispatchXULCommand(handlerElement
, true, nullptr, nullptr,
343 isControl
, isAlt
, isShift
, isMeta
);
347 Modifiers
KeyEventHandler::GetModifiers() const {
348 Modifiers modifiers
= 0;
350 if (mKeyMask
& cMeta
) {
351 modifiers
|= MODIFIER_META
;
353 if (mKeyMask
& cShift
) {
354 modifiers
|= MODIFIER_SHIFT
;
356 if (mKeyMask
& cAlt
) {
357 modifiers
|= MODIFIER_ALT
;
359 if (mKeyMask
& cControl
) {
360 modifiers
|= MODIFIER_CONTROL
;
366 Modifiers
KeyEventHandler::GetModifiersMask() const {
367 Modifiers modifiersMask
= 0;
369 if (mKeyMask
& cMetaMask
) {
370 modifiersMask
|= MODIFIER_META
;
372 if (mKeyMask
& cShiftMask
) {
373 modifiersMask
|= MODIFIER_SHIFT
;
375 if (mKeyMask
& cAltMask
) {
376 modifiersMask
|= MODIFIER_ALT
;
378 if (mKeyMask
& cControlMask
) {
379 modifiersMask
|= MODIFIER_CONTROL
;
382 return modifiersMask
;
385 already_AddRefed
<nsIController
> KeyEventHandler::GetController(
386 dom::EventTarget
* aTarget
) {
391 // XXX Fix this so there's a generic interface that describes controllers,
392 // This code should have no special knowledge of what objects might have
394 nsCOMPtr
<nsIControllers
> controllers
;
396 if (nsIContent
* targetContent
= nsIContent::FromEventTarget(aTarget
)) {
397 RefPtr
<nsXULElement
> xulElement
= nsXULElement::FromNode(targetContent
);
399 controllers
= xulElement
->GetControllers(IgnoreErrors());
403 dom::HTMLTextAreaElement
* htmlTextArea
=
404 dom::HTMLTextAreaElement::FromNode(targetContent
);
406 htmlTextArea
->GetControllers(getter_AddRefs(controllers
));
411 dom::HTMLInputElement
* htmlInputElement
=
412 dom::HTMLInputElement::FromNode(targetContent
);
413 if (htmlInputElement
) {
414 htmlInputElement
->GetControllers(getter_AddRefs(controllers
));
420 if (nsCOMPtr
<nsPIDOMWindowOuter
> domWindow
=
421 nsPIDOMWindowOuter::FromEventTarget(aTarget
)) {
422 domWindow
->GetControllers(getter_AddRefs(controllers
));
426 // Return the first controller.
427 // XXX This code should be checking the command name and using supportscommand
428 // and iscommandenabled.
429 nsCOMPtr
<nsIController
> controller
;
431 controllers
->GetControllerAt(0, getter_AddRefs(controller
));
434 return controller
.forget();
437 bool KeyEventHandler::KeyEventMatched(
438 dom::KeyboardEvent
* aDomKeyboardEvent
, uint32_t aCharCode
,
439 const IgnoreModifierState
& aIgnoreModifierState
) {
441 // Get the keycode or charcode of the key event.
448 code
= aDomKeyboardEvent
->CharCode();
450 if (IS_IN_BMP(code
)) {
451 code
= ToLowerCase(char16_t(code
));
454 code
= aDomKeyboardEvent
->KeyCode();
457 if (code
!= static_cast<uint32_t>(mDetail
)) {
462 return ModifiersMatchMask(aDomKeyboardEvent
, aIgnoreModifierState
);
471 // All of these must be uppercase, since the function below does
472 // case-insensitive comparison by converting to uppercase.
473 // XXX: be sure to check this periodically for new symbol additions!
474 static const keyCodeData gKeyCodes
[] = {
476 #define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \
477 {#aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode},
478 #include "mozilla/VirtualKeyCodeList.h"
483 int32_t KeyEventHandler::GetMatchingKeyCode(const nsAString
& aKeyName
) {
484 nsAutoCString keyName
;
485 LossyCopyUTF16toASCII(aKeyName
, keyName
);
486 ToUpperCase(keyName
); // We want case-insensitive comparison with data
487 // stored as uppercase.
489 uint32_t keyNameLength
= keyName
.Length();
490 const char* keyNameStr
= keyName
.get();
491 for (unsigned long i
= 0; i
< ArrayLength(gKeyCodes
) - 1; ++i
) {
492 if (keyNameLength
== gKeyCodes
[i
].strlength
&&
493 !nsCRT::strcmp(gKeyCodes
[i
].str
, keyNameStr
)) {
494 return gKeyCodes
[i
].keycode
;
501 int32_t KeyEventHandler::KeyToMask(uint32_t key
) {
503 case dom::KeyboardEvent_Binding::DOM_VK_META
:
504 case dom::KeyboardEvent_Binding::DOM_VK_WIN
:
505 return cMeta
| cMetaMask
;
507 case dom::KeyboardEvent_Binding::DOM_VK_ALT
:
508 return cAlt
| cAltMask
;
510 case dom::KeyboardEvent_Binding::DOM_VK_CONTROL
:
512 return cControl
| cControlMask
;
517 int32_t KeyEventHandler::AccelKeyMask() {
518 switch (WidgetInputEvent::AccelModifier()) {
520 return KeyToMask(dom::KeyboardEvent_Binding::DOM_VK_ALT
);
521 case MODIFIER_CONTROL
:
522 return KeyToMask(dom::KeyboardEvent_Binding::DOM_VK_CONTROL
);
524 return KeyToMask(dom::KeyboardEvent_Binding::DOM_VK_META
);
526 MOZ_CRASH("Handle the new result of WidgetInputEvent::AccelModifier()");
531 void KeyEventHandler::GetEventType(nsAString
& aEvent
) {
532 nsCOMPtr
<dom::Element
> handlerElement
= GetHandlerElement();
533 if (!handlerElement
) {
537 handlerElement
->GetAttr(nsGkAtoms::event
, aEvent
);
539 if (aEvent
.IsEmpty() && mIsXULKey
) {
540 // If no type is specified for a XUL <key> element, let's assume that we're
542 aEvent
.AssignLiteral("keypress");
546 void KeyEventHandler::ConstructPrototype(dom::Element
* aKeyElement
,
547 const char16_t
* aEvent
,
548 const char16_t
* aCommand
,
549 const char16_t
* aKeyCode
,
550 const char16_t
* aCharCode
,
551 const char16_t
* aModifiers
) {
555 nsAutoString modifiers
;
558 nsWeakPtr weak
= do_GetWeakReference(aKeyElement
);
562 weak
.swap(mHandlerElement
);
566 if (event
.IsEmpty()) {
569 mEventName
= NS_Atomize(event
);
571 aKeyElement
->GetAttr(nsGkAtoms::modifiers
, modifiers
);
573 mCommand
= ToNewUnicode(nsDependentString(aCommand
));
574 mEventName
= NS_Atomize(aEvent
);
575 modifiers
= aModifiers
;
578 BuildModifiers(modifiers
);
580 nsAutoString
key(aCharCode
);
583 aKeyElement
->GetAttr(nsGkAtoms::key
, key
);
585 aKeyElement
->GetAttr(nsGkAtoms::charcode
, key
);
590 if (!key
.IsEmpty()) {
592 mKeyMask
= cAllModifiers
;
596 // We have a charcode.
599 const uint8_t GTK2Modifiers
= cShift
| cControl
| cShiftMask
| cControlMask
;
600 if (mIsXULKey
&& (mKeyMask
& GTK2Modifiers
) == GTK2Modifiers
&&
601 modifiers
.First() != char16_t(',') &&
602 (mDetail
== 'u' || mDetail
== 'U')) {
603 ReportKeyConflict(key
.get(), modifiers
.get(), aKeyElement
,
606 const uint8_t WinModifiers
= cControl
| cAlt
| cControlMask
| cAltMask
;
607 if (mIsXULKey
&& (mKeyMask
& WinModifiers
) == WinModifiers
&&
608 modifiers
.First() != char16_t(',') &&
609 (('A' <= mDetail
&& mDetail
<= 'Z') ||
610 ('a' <= mDetail
&& mDetail
<= 'z'))) {
611 ReportKeyConflict(key
.get(), modifiers
.get(), aKeyElement
,
615 key
.Assign(aKeyCode
);
617 aKeyElement
->GetAttr(nsGkAtoms::keycode
, key
);
620 if (!key
.IsEmpty()) {
622 mKeyMask
= cAllModifiers
;
624 mDetail
= GetMatchingKeyCode(key
);
629 void KeyEventHandler::BuildModifiers(nsAString
& aModifiers
) {
630 if (!aModifiers
.IsEmpty()) {
631 mKeyMask
= cAllModifiers
;
632 char* str
= ToNewCString(aModifiers
);
634 char* token
= nsCRT::strtok(str
, ", \t", &newStr
);
635 while (token
!= nullptr) {
636 if (strcmp(token
, "shift") == 0) {
637 mKeyMask
|= cShift
| cShiftMask
;
638 } else if (strcmp(token
, "alt") == 0) {
639 mKeyMask
|= cAlt
| cAltMask
;
640 } else if (strcmp(token
, "meta") == 0) {
641 mKeyMask
|= cMeta
| cMetaMask
;
642 } else if (strcmp(token
, "control") == 0) {
643 mKeyMask
|= cControl
| cControlMask
;
644 } else if (strcmp(token
, "accel") == 0) {
645 mKeyMask
|= AccelKeyMask();
646 } else if (strcmp(token
, "access") == 0) {
647 mKeyMask
|= KeyToMask(LookAndFeel::GetMenuAccessKey());
648 } else if (strcmp(token
, "any") == 0) {
649 mKeyMask
&= ~(mKeyMask
<< 5);
652 token
= nsCRT::strtok(newStr
, ", \t", &newStr
);
659 void KeyEventHandler::ReportKeyConflict(const char16_t
* aKey
,
660 const char16_t
* aModifiers
,
661 dom::Element
* aKeyElement
,
662 const char* aMessageName
) {
663 nsCOMPtr
<dom::Document
> doc
= aKeyElement
->OwnerDoc();
666 aKeyElement
->GetAttr(nsGkAtoms::id
, id
);
667 AutoTArray
<nsString
, 3> params
;
668 params
.AppendElement(aKey
);
669 params
.AppendElement(aModifiers
);
670 params
.AppendElement(id
);
671 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag
,
672 "Key dom::Event Handler"_ns
, doc
,
673 nsContentUtils::eDOM_PROPERTIES
, aMessageName
,
674 params
, nullptr, u
""_ns
, 0);
677 bool KeyEventHandler::ModifiersMatchMask(
678 dom::UIEvent
* aEvent
, const IgnoreModifierState
& aIgnoreModifierState
) {
679 WidgetInputEvent
* inputEvent
= aEvent
->WidgetEventPtr()->AsInputEvent();
680 NS_ENSURE_TRUE(inputEvent
, false);
682 if ((mKeyMask
& cMetaMask
) && !aIgnoreModifierState
.mMeta
) {
683 if (inputEvent
->IsMeta() != ((mKeyMask
& cMeta
) != 0)) {
688 if ((mKeyMask
& cShiftMask
) && !aIgnoreModifierState
.mShift
) {
689 if (inputEvent
->IsShift() != ((mKeyMask
& cShift
) != 0)) {
694 if (mKeyMask
& cAltMask
) {
695 if (inputEvent
->IsAlt() != ((mKeyMask
& cAlt
) != 0)) {
700 if (mKeyMask
& cControlMask
) {
701 if (inputEvent
->IsControl() != ((mKeyMask
& cControl
) != 0)) {
709 size_t KeyEventHandler::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
711 for (const KeyEventHandler
* handler
= this; handler
;
712 handler
= handler
->mNextHandler
) {
713 n
+= aMallocSizeOf(handler
);
715 n
+= aMallocSizeOf(handler
->mCommand
);
721 } // namespace mozilla