Bug 1863873 - Block ability to perform audio decoding outside of Utility on release...
[gecko.git] / dom / events / GlobalKeyListener.cpp
blob001ca4eedfa4874acbbba418ef9dfa257c2c8c30
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 "GlobalKeyListener.h"
8 #include "ErrorList.h"
9 #include "EventTarget.h"
11 #include <utility>
13 #include "mozilla/EventListenerManager.h"
14 #include "mozilla/EventStateManager.h"
15 #include "mozilla/HTMLEditor.h"
16 #include "mozilla/KeyEventHandler.h"
17 #include "mozilla/NativeKeyBindingsType.h"
18 #include "mozilla/Preferences.h"
19 #include "mozilla/ShortcutKeys.h"
20 #include "mozilla/StaticPtr.h"
21 #include "mozilla/TextEvents.h"
22 #include "mozilla/dom/Element.h"
23 #include "mozilla/dom/Event.h"
24 #include "mozilla/dom/EventBinding.h"
25 #include "mozilla/dom/KeyboardEvent.h"
26 #include "mozilla/widget/IMEData.h"
27 #include "nsAtom.h"
28 #include "nsCOMPtr.h"
29 #include "nsContentUtils.h"
30 #include "nsFocusManager.h"
31 #include "nsGkAtoms.h"
32 #include "nsIContent.h"
33 #include "nsIContentInlines.h"
34 #include "nsIDocShell.h"
35 #include "nsIWidget.h"
36 #include "nsNetUtil.h"
37 #include "nsPIDOMWindow.h"
39 namespace mozilla {
41 using namespace mozilla::layers;
43 GlobalKeyListener::GlobalKeyListener(dom::EventTarget* aTarget)
44 : mTarget(aTarget), mHandler(nullptr) {}
46 NS_IMPL_ISUPPORTS(GlobalKeyListener, nsIDOMEventListener)
48 static void BuildHandlerChain(nsIContent* aContent, KeyEventHandler** aResult) {
49 *aResult = nullptr;
51 // Since we chain each handler onto the next handler,
52 // we'll enumerate them here in reverse so that when we
53 // walk the chain they'll come out in the original order
54 for (nsIContent* key = aContent->GetLastChild(); key;
55 key = key->GetPreviousSibling()) {
56 if (!key->NodeInfo()->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
57 continue;
60 dom::Element* keyElement = key->AsElement();
61 // Check whether the key element has empty value at key/char attribute.
62 // Such element is used by localizers for alternative shortcut key
63 // definition on the locale. See bug 426501.
64 nsAutoString valKey, valCharCode, valKeyCode;
65 // Hopefully at least one of the attributes is set:
66 keyElement->GetAttr(nsGkAtoms::key, valKey) ||
67 keyElement->GetAttr(nsGkAtoms::charcode, valCharCode) ||
68 keyElement->GetAttr(nsGkAtoms::keycode, valKeyCode);
69 // If not, ignore this key element.
70 if (valKey.IsEmpty() && valCharCode.IsEmpty() && valKeyCode.IsEmpty()) {
71 continue;
74 // reserved="pref" is the default for <key> elements.
75 ReservedKey reserved = ReservedKey_Unset;
76 if (keyElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::reserved,
77 nsGkAtoms::_true, eCaseMatters)) {
78 reserved = ReservedKey_True;
79 } else if (keyElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::reserved,
80 nsGkAtoms::_false, eCaseMatters)) {
81 reserved = ReservedKey_False;
84 KeyEventHandler* handler = new KeyEventHandler(keyElement, reserved);
86 handler->SetNextHandler(*aResult);
87 *aResult = handler;
91 void GlobalKeyListener::WalkHandlers(dom::KeyboardEvent* aKeyEvent) {
92 if (aKeyEvent->DefaultPrevented()) {
93 return;
96 // Don't process the event if it was not dispatched from a trusted source
97 if (!aKeyEvent->IsTrusted()) {
98 return;
101 EnsureHandlers();
103 // skip keysets that are disabled
104 if (IsDisabled()) {
105 return;
108 WalkHandlersInternal(Purpose::ExecuteCommand, aKeyEvent);
111 void GlobalKeyListener::InstallKeyboardEventListenersTo(
112 EventListenerManager* aEventListenerManager) {
113 // For marking each keyboard event as if it's reserved by chrome,
114 // GlobalKeyListeners need to listen each keyboard events before
115 // web contents.
116 aEventListenerManager->AddEventListenerByType(this, u"keydown"_ns,
117 TrustedEventsAtCapture());
118 aEventListenerManager->AddEventListenerByType(this, u"keyup"_ns,
119 TrustedEventsAtCapture());
120 aEventListenerManager->AddEventListenerByType(this, u"keypress"_ns,
121 TrustedEventsAtCapture());
123 // For reducing the IPC cost, preventing to dispatch reserved keyboard
124 // events into the content process.
125 aEventListenerManager->AddEventListenerByType(
126 this, u"keydown"_ns, TrustedEventsAtSystemGroupCapture());
127 aEventListenerManager->AddEventListenerByType(
128 this, u"keyup"_ns, TrustedEventsAtSystemGroupCapture());
129 aEventListenerManager->AddEventListenerByType(
130 this, u"keypress"_ns, TrustedEventsAtSystemGroupCapture());
132 // Handle keyboard events in bubbling phase of the system event group.
133 aEventListenerManager->AddEventListenerByType(
134 this, u"keydown"_ns, TrustedEventsAtSystemGroupBubble());
135 aEventListenerManager->AddEventListenerByType(
136 this, u"keyup"_ns, TrustedEventsAtSystemGroupBubble());
137 aEventListenerManager->AddEventListenerByType(
138 this, u"keypress"_ns, TrustedEventsAtSystemGroupBubble());
139 // mozaccesskeynotfound event is fired when modifiers of keypress event
140 // matches with modifier of content access key but it's not consumed by
141 // remote content.
142 aEventListenerManager->AddEventListenerByType(
143 this, u"mozaccesskeynotfound"_ns, TrustedEventsAtSystemGroupBubble());
146 void GlobalKeyListener::RemoveKeyboardEventListenersFrom(
147 EventListenerManager* aEventListenerManager) {
148 aEventListenerManager->RemoveEventListenerByType(this, u"keydown"_ns,
149 TrustedEventsAtCapture());
150 aEventListenerManager->RemoveEventListenerByType(this, u"keyup"_ns,
151 TrustedEventsAtCapture());
152 aEventListenerManager->RemoveEventListenerByType(this, u"keypress"_ns,
153 TrustedEventsAtCapture());
155 aEventListenerManager->RemoveEventListenerByType(
156 this, u"keydown"_ns, TrustedEventsAtSystemGroupCapture());
157 aEventListenerManager->RemoveEventListenerByType(
158 this, u"keyup"_ns, TrustedEventsAtSystemGroupCapture());
159 aEventListenerManager->RemoveEventListenerByType(
160 this, u"keypress"_ns, TrustedEventsAtSystemGroupCapture());
162 aEventListenerManager->RemoveEventListenerByType(
163 this, u"keydown"_ns, TrustedEventsAtSystemGroupBubble());
164 aEventListenerManager->RemoveEventListenerByType(
165 this, u"keyup"_ns, TrustedEventsAtSystemGroupBubble());
166 aEventListenerManager->RemoveEventListenerByType(
167 this, u"keypress"_ns, TrustedEventsAtSystemGroupBubble());
168 aEventListenerManager->RemoveEventListenerByType(
169 this, u"mozaccesskeynotfound"_ns, TrustedEventsAtSystemGroupBubble());
172 NS_IMETHODIMP
173 GlobalKeyListener::HandleEvent(dom::Event* aEvent) {
174 RefPtr<dom::KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
175 NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG);
177 if (aEvent->EventPhase() == dom::Event_Binding::CAPTURING_PHASE) {
178 if (aEvent->WidgetEventPtr()->mFlags.mInSystemGroup) {
179 HandleEventOnCaptureInSystemEventGroup(keyEvent);
180 } else {
181 HandleEventOnCaptureInDefaultEventGroup(keyEvent);
183 return NS_OK;
186 // If this event was handled by APZ then don't do the default action, and
187 // preventDefault to prevent any other listeners from handling the event.
188 if (aEvent->WidgetEventPtr()->mFlags.mHandledByAPZ) {
189 aEvent->PreventDefault();
190 return NS_OK;
193 WalkHandlers(keyEvent);
194 return NS_OK;
197 void GlobalKeyListener::HandleEventOnCaptureInDefaultEventGroup(
198 dom::KeyboardEvent* aEvent) {
199 WidgetKeyboardEvent* widgetKeyboardEvent =
200 aEvent->WidgetEventPtr()->AsKeyboardEvent();
202 if (widgetKeyboardEvent->IsReservedByChrome()) {
203 return;
206 if (HasHandlerForEvent(aEvent).mReservedHandlerForChromeFound) {
207 widgetKeyboardEvent->MarkAsReservedByChrome();
211 void GlobalKeyListener::HandleEventOnCaptureInSystemEventGroup(
212 dom::KeyboardEvent* aEvent) {
213 WidgetKeyboardEvent* widgetEvent =
214 aEvent->WidgetEventPtr()->AsKeyboardEvent();
216 // If the event won't be sent to remote process, this listener needs to do
217 // nothing. Note that even if mOnlySystemGroupDispatchInContent is true,
218 // we need to send the event to remote process and check reply event
219 // before matching it with registered shortcut keys because event listeners
220 // in the system event group may want to handle the event before registered
221 // shortcut key handlers.
222 if (!widgetEvent->WillBeSentToRemoteProcess()) {
223 return;
226 if (!HasHandlerForEvent(aEvent).mMeaningfulHandlerFound) {
227 return;
230 // If this event wasn't marked as IsCrossProcessForwardingStopped,
231 // yet, it means it wasn't processed by content. We'll not call any
232 // of the handlers at this moment, and will wait the reply event.
233 // So, stop immediate propagation in this event first, then, mark it as
234 // waiting reply from remote process. Finally, when this process receives
235 // a reply from the remote process, it should be dispatched into this
236 // DOM tree again.
237 widgetEvent->StopImmediatePropagation();
238 widgetEvent->MarkAsWaitingReplyFromRemoteProcess();
242 // WalkHandlersInternal and WalkHandlersAndExecute
244 // Given a particular DOM event and a pointer to the first handler in the list,
245 // scan through the list to find something to handle the event. If aPurpose =
246 // Purpose::ExecuteHandler, the handler will be executed; otherwise just return
247 // an answer telling if a handler for that event was found.
249 GlobalKeyListener::WalkHandlersResult GlobalKeyListener::WalkHandlersInternal(
250 Purpose aPurpose, dom::KeyboardEvent* aKeyEvent) {
251 WidgetKeyboardEvent* nativeKeyboardEvent =
252 aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
253 MOZ_ASSERT(nativeKeyboardEvent);
255 AutoShortcutKeyCandidateArray shortcutKeys;
256 nativeKeyboardEvent->GetShortcutKeyCandidates(shortcutKeys);
258 if (shortcutKeys.IsEmpty()) {
259 return WalkHandlersAndExecute(aPurpose, aKeyEvent, 0,
260 IgnoreModifierState());
263 bool foundDisabledHandler = false;
264 for (const ShortcutKeyCandidate& key : shortcutKeys) {
265 const bool skipIfEarlierHandlerDisabled =
266 key.mSkipIfEarlierHandlerDisabled ==
267 ShortcutKeyCandidate::SkipIfEarlierHandlerDisabled::Yes;
268 if (foundDisabledHandler && skipIfEarlierHandlerDisabled) {
269 continue;
271 IgnoreModifierState ignoreModifierState;
272 ignoreModifierState.mShift =
273 key.mShiftState == ShortcutKeyCandidate::ShiftState::Ignorable;
274 WalkHandlersResult result = WalkHandlersAndExecute(
275 aPurpose, aKeyEvent, key.mCharCode, ignoreModifierState);
276 if (result.mMeaningfulHandlerFound) {
277 return result;
279 // Note that if the candidate should not match if an earlier handler is
280 // disabled, the char code of the candidate is a char which may be
281 // introduced with different shift state. In this case, we do NOT find a
282 // disabled handler which **exactly** matches with the keyboard event.
283 // This avoids to override a higher priority handler with a disabled lower
284 // priority handler.
285 if (!skipIfEarlierHandlerDisabled && !foundDisabledHandler) {
286 foundDisabledHandler = result.mDisabledHandlerFound;
289 return {};
292 GlobalKeyListener::WalkHandlersResult GlobalKeyListener::WalkHandlersAndExecute(
293 Purpose aPurpose, dom::KeyboardEvent* aKeyEvent, uint32_t aCharCode,
294 const IgnoreModifierState& aIgnoreModifierState) {
295 WidgetKeyboardEvent* widgetKeyboardEvent =
296 aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
297 if (NS_WARN_IF(!widgetKeyboardEvent)) {
298 return {};
301 nsAtom* eventType =
302 ShortcutKeys::ConvertEventToDOMEventType(widgetKeyboardEvent);
304 // Try all of the handlers until we find one that matches the event.
305 bool foundDisabledHandler = false;
306 for (KeyEventHandler* handler = mHandler; handler;
307 handler = handler->GetNextHandler()) {
308 bool stopped = aKeyEvent->IsDispatchStopped();
309 if (stopped) {
310 // The event is finished, don't execute any more handlers
311 return {};
314 if (aPurpose == Purpose::ExecuteCommand) {
315 if (!handler->EventTypeEquals(eventType)) {
316 continue;
318 } else {
319 if (handler->EventTypeEquals(nsGkAtoms::keypress)) {
320 // If the handler is a keypress event handler, we also need to check
321 // if coming keydown event is a preceding event of reserved key
322 // combination because if default action of a keydown event is
323 // prevented, following keypress event won't be fired. However, if
324 // following keypress event is reserved, we shouldn't allow web
325 // contents to prevent the default of the preceding keydown event.
326 if (eventType != nsGkAtoms::keydown &&
327 eventType != nsGkAtoms::keypress) {
328 continue;
330 } else if (!handler->EventTypeEquals(eventType)) {
331 // Otherwise, eventType should exactly be matched.
332 continue;
336 // Check if the keyboard event *may* execute the handler.
337 if (!handler->KeyEventMatched(aKeyEvent, aCharCode, aIgnoreModifierState)) {
338 continue; // try the next one
341 // Before executing this handler, check that it's not disabled,
342 // and that it has something to do (oncommand of the <key> or its
343 // <command> is non-empty).
344 if (!CanHandle(handler, aPurpose == Purpose::ExecuteCommand)) {
345 foundDisabledHandler = true;
346 continue;
349 if (aPurpose == Purpose::LookForCommand) {
350 if (handler->EventTypeEquals(eventType)) {
351 WalkHandlersResult result;
352 result.mMeaningfulHandlerFound = true;
353 result.mReservedHandlerForChromeFound =
354 IsReservedKey(widgetKeyboardEvent, handler);
355 return result;
358 // If the command is reserved and the event is keydown, check also if
359 // the handler is for keypress because if following keypress event is
360 // reserved, we shouldn't dispatch the event into web contents.
361 if (eventType == nsGkAtoms::keydown &&
362 handler->EventTypeEquals(nsGkAtoms::keypress)) {
363 if (IsReservedKey(widgetKeyboardEvent, handler)) {
364 WalkHandlersResult result;
365 result.mMeaningfulHandlerFound = true;
366 result.mReservedHandlerForChromeFound = true;
367 return result;
370 // Otherwise, we've not found a handler for the event yet.
371 continue;
374 nsCOMPtr<dom::EventTarget> target = GetHandlerTarget(handler);
376 // XXX Do we execute only one handler even if the handler neither stops
377 // propagation nor prevents default of the event?
378 nsresult rv = handler->ExecuteHandler(target, aKeyEvent);
379 if (NS_SUCCEEDED(rv)) {
380 WalkHandlersResult result;
381 result.mMeaningfulHandlerFound = true;
382 result.mReservedHandlerForChromeFound =
383 IsReservedKey(widgetKeyboardEvent, handler);
384 result.mDisabledHandlerFound = (rv == NS_SUCCESS_DOM_NO_OPERATION);
385 return result;
389 #ifdef XP_WIN
390 // Windows native applications ignore Windows-Logo key state when checking
391 // shortcut keys even if the key is pressed. Therefore, if there is no
392 // shortcut key which exactly matches current modifier state, we should
393 // retry to look for a shortcut key without the Windows-Logo key press.
394 if (!aIgnoreModifierState.mMeta && widgetKeyboardEvent->IsMeta()) {
395 IgnoreModifierState ignoreModifierState(aIgnoreModifierState);
396 ignoreModifierState.mMeta = true;
397 return WalkHandlersAndExecute(aPurpose, aKeyEvent, aCharCode,
398 ignoreModifierState);
400 #endif
402 WalkHandlersResult result;
403 result.mDisabledHandlerFound = foundDisabledHandler;
404 return result;
407 bool GlobalKeyListener::IsReservedKey(WidgetKeyboardEvent* aKeyEvent,
408 KeyEventHandler* aHandler) {
409 ReservedKey reserved = aHandler->GetIsReserved();
410 // reserved="true" means that the key is always reserved. reserved="false"
411 // means that the key is never reserved. Otherwise, we check site-specific
412 // permissions.
413 if (reserved == ReservedKey_False) {
414 return false;
417 if (reserved != ReservedKey_True &&
418 !nsContentUtils::ShouldBlockReservedKeys(aKeyEvent)) {
419 return false;
422 // Okay, the key handler is reserved, but if the key combination is mapped to
423 // an edit command or a selection navigation command, we should not treat it
424 // as reserved since user wants to do the mapped thing(s) in editor.
425 if (MOZ_UNLIKELY(!aKeyEvent->IsTrusted() || !aKeyEvent->mWidget)) {
426 return true;
428 widget::InputContext inputContext = aKeyEvent->mWidget->GetInputContext();
429 if (!inputContext.mIMEState.IsEditable()) {
430 return true;
432 return MOZ_UNLIKELY(!aKeyEvent->IsEditCommandsInitialized(
433 inputContext.GetNativeKeyBindingsType())) ||
434 aKeyEvent
435 ->EditCommandsConstRef(inputContext.GetNativeKeyBindingsType())
436 .IsEmpty();
439 GlobalKeyListener::WalkHandlersResult GlobalKeyListener::HasHandlerForEvent(
440 dom::KeyboardEvent* aEvent) {
441 WidgetKeyboardEvent* widgetKeyboardEvent =
442 aEvent->WidgetEventPtr()->AsKeyboardEvent();
443 if (NS_WARN_IF(!widgetKeyboardEvent) || !widgetKeyboardEvent->IsTrusted()) {
444 return {};
447 EnsureHandlers();
449 if (IsDisabled()) {
450 return {};
453 return WalkHandlersInternal(Purpose::LookForCommand, aEvent);
457 // AttachGlobalKeyHandler
459 // Creates a new key handler and prepares to listen to key events on the given
460 // event receiver (either a document or an content node). If the receiver is
461 // content, then extra work needs to be done to hook it up to the document (XXX
462 // WHY??)
464 void XULKeySetGlobalKeyListener::AttachKeyHandler(
465 dom::Element* aElementTarget) {
466 // Only attach if we're really in a document
467 nsCOMPtr<dom::Document> doc = aElementTarget->GetUncomposedDoc();
468 if (!doc) {
469 return;
472 EventListenerManager* manager = doc->GetOrCreateListenerManager();
473 if (!manager) {
474 return;
477 // the listener already exists, so skip this
478 if (aElementTarget->GetProperty(nsGkAtoms::listener)) {
479 return;
482 // Create the key handler
483 RefPtr<XULKeySetGlobalKeyListener> handler =
484 new XULKeySetGlobalKeyListener(aElementTarget, doc);
486 handler->InstallKeyboardEventListenersTo(manager);
488 aElementTarget->SetProperty(nsGkAtoms::listener, handler.forget().take(),
489 nsPropertyTable::SupportsDtorFunc, true);
493 // DetachGlobalKeyHandler
495 // Removes a key handler added by AttachKeyHandler.
497 void XULKeySetGlobalKeyListener::DetachKeyHandler(
498 dom::Element* aElementTarget) {
499 // Only attach if we're really in a document
500 nsCOMPtr<dom::Document> doc = aElementTarget->GetUncomposedDoc();
501 if (!doc) {
502 return;
505 EventListenerManager* manager = doc->GetOrCreateListenerManager();
506 if (!manager) {
507 return;
510 nsIDOMEventListener* handler = static_cast<nsIDOMEventListener*>(
511 aElementTarget->GetProperty(nsGkAtoms::listener));
512 if (!handler) {
513 return;
516 static_cast<XULKeySetGlobalKeyListener*>(handler)
517 ->RemoveKeyboardEventListenersFrom(manager);
518 aElementTarget->RemoveProperty(nsGkAtoms::listener);
521 XULKeySetGlobalKeyListener::XULKeySetGlobalKeyListener(
522 dom::Element* aElement, dom::EventTarget* aTarget)
523 : GlobalKeyListener(aTarget) {
524 mWeakPtrForElement = do_GetWeakReference(aElement);
527 dom::Element* XULKeySetGlobalKeyListener::GetElement(bool* aIsDisabled) const {
528 RefPtr<dom::Element> element = do_QueryReferent(mWeakPtrForElement);
529 if (element && aIsDisabled) {
530 *aIsDisabled = element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
531 nsGkAtoms::_true, eCaseMatters);
533 return element.get();
536 XULKeySetGlobalKeyListener::~XULKeySetGlobalKeyListener() {
537 if (mWeakPtrForElement) {
538 delete mHandler;
542 void XULKeySetGlobalKeyListener::EnsureHandlers() {
543 if (mHandler) {
544 return;
547 dom::Element* element = GetElement();
548 if (!element) {
549 return;
552 BuildHandlerChain(element, &mHandler);
555 bool XULKeySetGlobalKeyListener::IsDisabled() const {
556 bool isDisabled;
557 dom::Element* element = GetElement(&isDisabled);
558 return element && isDisabled;
561 bool XULKeySetGlobalKeyListener::GetElementForHandler(
562 KeyEventHandler* aHandler, dom::Element** aElementForHandler) const {
563 MOZ_ASSERT(aElementForHandler);
564 *aElementForHandler = nullptr;
566 RefPtr<dom::Element> keyElement = aHandler->GetHandlerElement();
567 if (!keyElement) {
568 // This should only be the case where the <key> element that generated the
569 // handler has been destroyed. Not sure why we return true here...
570 return true;
573 nsCOMPtr<dom::Element> chromeHandlerElement = GetElement();
574 if (!chromeHandlerElement) {
575 NS_WARNING_ASSERTION(keyElement->IsInUncomposedDoc(), "uncomposed");
576 keyElement.swap(*aElementForHandler);
577 return true;
580 // We are in a XUL doc. Obtain our command attribute.
581 nsAutoString command;
582 keyElement->GetAttr(nsGkAtoms::command, command);
583 if (command.IsEmpty()) {
584 // There is no command element associated with the key element.
585 NS_WARNING_ASSERTION(keyElement->IsInUncomposedDoc(), "uncomposed");
586 keyElement.swap(*aElementForHandler);
587 return true;
590 // XXX Shouldn't we check this earlier?
591 dom::Document* doc = keyElement->GetUncomposedDoc();
592 if (NS_WARN_IF(!doc)) {
593 return false;
596 nsCOMPtr<dom::Element> commandElement = doc->GetElementById(command);
597 if (!commandElement) {
598 NS_ERROR(
599 "A XUL <key> is observing a command that doesn't exist. "
600 "Unable to execute key binding!");
601 return false;
604 commandElement.swap(*aElementForHandler);
605 return true;
608 bool XULKeySetGlobalKeyListener::IsExecutableElement(
609 dom::Element* aElement) const {
610 if (!aElement) {
611 return false;
614 nsAutoString value;
615 aElement->GetAttr(nsGkAtoms::disabled, value);
616 if (value.EqualsLiteral("true")) {
617 return false;
620 // Internal keys are defined as <key> elements so that the menu label
621 // and disabled state can be updated properly, but the command is executed
622 // by some other means. This will typically be because the key is defined
623 // as a shortcut defined in ShortcutKeyDefinitions.cpp instead, or on Mac,
624 // some special system defined keys.
625 return !aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::internal,
626 nsGkAtoms::_true, eCaseMatters);
629 already_AddRefed<dom::EventTarget> XULKeySetGlobalKeyListener::GetHandlerTarget(
630 KeyEventHandler* aHandler) {
631 nsCOMPtr<dom::Element> commandElement;
632 if (!GetElementForHandler(aHandler, getter_AddRefs(commandElement))) {
633 return nullptr;
636 return commandElement.forget();
639 bool XULKeySetGlobalKeyListener::CanHandle(KeyEventHandler* aHandler,
640 bool aWillExecute) const {
641 // If the <key> element itself is disabled, ignore it.
642 if (aHandler->KeyElementIsDisabled()) {
643 return false;
646 nsCOMPtr<dom::Element> commandElement;
647 if (!GetElementForHandler(aHandler, getter_AddRefs(commandElement))) {
648 return false;
651 // The only case where commandElement can be null here is where the <key>
652 // element for the handler is already destroyed. I'm not sure why we continue
653 // in this case.
654 if (!commandElement) {
655 return true;
658 // If we're not actually going to execute here bypass the execution check.
659 return !aWillExecute || IsExecutableElement(commandElement);
662 /* static */
663 layers::KeyboardMap RootWindowGlobalKeyListener::CollectKeyboardShortcuts() {
664 KeyEventHandler* handlers = ShortcutKeys::GetHandlers(HandlerType::eBrowser);
666 // Convert the handlers into keyboard shortcuts, using an AutoTArray with
667 // the maximum amount of shortcuts used on any platform to minimize
668 // allocations
669 AutoTArray<KeyboardShortcut, 48> shortcuts;
671 // Append keyboard shortcuts for hardcoded actions like tab
672 KeyboardShortcut::AppendHardcodedShortcuts(shortcuts);
674 for (KeyEventHandler* handler = handlers; handler;
675 handler = handler->GetNextHandler()) {
676 KeyboardShortcut shortcut;
677 if (handler->TryConvertToKeyboardShortcut(&shortcut)) {
678 shortcuts.AppendElement(shortcut);
682 return layers::KeyboardMap(std::move(shortcuts));
686 // AttachGlobalKeyHandler
688 // Creates a new key handler and prepares to listen to key events on the given
689 // event receiver (either a document or an content node). If the receiver is
690 // content, then extra work needs to be done to hook it up to the document (XXX
691 // WHY??)
693 void RootWindowGlobalKeyListener::AttachKeyHandler(dom::EventTarget* aTarget) {
694 EventListenerManager* manager = aTarget->GetOrCreateListenerManager();
695 if (!manager) {
696 return;
699 // Create the key handler
700 RefPtr<RootWindowGlobalKeyListener> handler =
701 new RootWindowGlobalKeyListener(aTarget);
703 // This registers handler with the manager so the manager will keep handler
704 // alive past this point.
705 handler->InstallKeyboardEventListenersTo(manager);
708 RootWindowGlobalKeyListener::RootWindowGlobalKeyListener(
709 dom::EventTarget* aTarget)
710 : GlobalKeyListener(aTarget) {}
712 /* static */
713 bool RootWindowGlobalKeyListener::IsHTMLEditorFocused() {
714 nsFocusManager* fm = nsFocusManager::GetFocusManager();
715 if (!fm) {
716 return false;
719 nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
720 fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
721 if (!focusedWindow) {
722 return false;
725 auto* piwin = nsPIDOMWindowOuter::From(focusedWindow);
726 nsIDocShell* docShell = piwin->GetDocShell();
727 if (!docShell) {
728 return false;
731 HTMLEditor* htmlEditor = docShell->GetHTMLEditor();
732 if (!htmlEditor) {
733 return false;
736 if (htmlEditor->IsInDesignMode()) {
737 // Don't need to perform any checks in designMode documents.
738 return true;
741 nsINode* focusedNode = fm->GetFocusedElement();
742 if (focusedNode && focusedNode->IsElement()) {
743 // If there is a focused element, make sure it's in the active editing host.
744 // Note that ComputeEditingHost finds the current editing host based on
745 // the document's selection. Even though the document selection is usually
746 // collapsed to where the focus is, but the page may modify the selection
747 // without our knowledge, in which case this check will do something useful.
748 dom::Element* editingHost = htmlEditor->ComputeEditingHost();
749 if (!editingHost) {
750 return false;
752 return focusedNode->IsInclusiveDescendantOf(editingHost);
755 return false;
758 void RootWindowGlobalKeyListener::EnsureHandlers() {
759 if (IsHTMLEditorFocused()) {
760 mHandler = ShortcutKeys::GetHandlers(HandlerType::eEditor);
761 } else {
762 mHandler = ShortcutKeys::GetHandlers(HandlerType::eBrowser);
766 } // namespace mozilla