Bug 1688354 [wpt PR 27298] - Treat 'rem' as an absolute unit for font size, a=testonly
[gecko.git] / dom / events / GlobalKeyListener.cpp
blob8029ebaf46b1f918246ed78db1194406efb4f809
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 "EventTarget.h"
10 #include <utility>
12 #include "mozilla/EventListenerManager.h"
13 #include "mozilla/EventStateManager.h"
14 #include "mozilla/HTMLEditor.h"
15 #include "mozilla/KeyEventHandler.h"
16 #include "mozilla/Preferences.h"
17 #include "mozilla/ShortcutKeys.h"
18 #include "mozilla/StaticPtr.h"
19 #include "mozilla/TextEvents.h"
20 #include "mozilla/dom/Element.h"
21 #include "mozilla/dom/Event.h"
22 #include "mozilla/dom/EventBinding.h"
23 #include "mozilla/dom/KeyboardEvent.h"
24 #include "nsAtom.h"
25 #include "nsCOMPtr.h"
26 #include "nsContentUtils.h"
27 #include "nsFocusManager.h"
28 #include "nsGkAtoms.h"
29 #include "nsIContent.h"
30 #include "nsIDocShell.h"
31 #include "nsNetUtil.h"
32 #include "nsPIDOMWindow.h"
34 namespace mozilla {
36 using namespace mozilla::layers;
38 GlobalKeyListener::GlobalKeyListener(dom::EventTarget* aTarget)
39 : mTarget(aTarget), mHandler(nullptr) {}
41 NS_IMPL_ISUPPORTS(GlobalKeyListener, nsIDOMEventListener)
43 static void BuildHandlerChain(nsIContent* aContent, KeyEventHandler** aResult) {
44 *aResult = nullptr;
46 // Since we chain each handler onto the next handler,
47 // we'll enumerate them here in reverse so that when we
48 // walk the chain they'll come out in the original order
49 for (nsIContent* key = aContent->GetLastChild(); key;
50 key = key->GetPreviousSibling()) {
51 if (!key->NodeInfo()->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
52 continue;
55 dom::Element* keyElement = key->AsElement();
56 // Check whether the key element has empty value at key/char attribute.
57 // Such element is used by localizers for alternative shortcut key
58 // definition on the locale. See bug 426501.
59 nsAutoString valKey, valCharCode, valKeyCode;
60 // Hopefully at least one of the attributes is set:
61 keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, valKey) ||
62 keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::charcode,
63 valCharCode) ||
64 keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, valKeyCode);
65 // If not, ignore this key element.
66 if (valKey.IsEmpty() && valCharCode.IsEmpty() && valKeyCode.IsEmpty()) {
67 continue;
70 // reserved="pref" is the default for <key> elements.
71 ReservedKey reserved = ReservedKey_Unset;
72 if (keyElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::reserved,
73 nsGkAtoms::_true, eCaseMatters)) {
74 reserved = ReservedKey_True;
75 } else if (keyElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::reserved,
76 nsGkAtoms::_false, eCaseMatters)) {
77 reserved = ReservedKey_False;
80 KeyEventHandler* handler = new KeyEventHandler(keyElement, reserved);
82 handler->SetNextHandler(*aResult);
83 *aResult = handler;
87 void GlobalKeyListener::WalkHandlers(dom::KeyboardEvent* aKeyEvent) {
88 if (aKeyEvent->DefaultPrevented()) {
89 return;
92 // Don't process the event if it was not dispatched from a trusted source
93 if (!aKeyEvent->IsTrusted()) {
94 return;
97 EnsureHandlers();
99 // skip keysets that are disabled
100 if (IsDisabled()) {
101 return;
104 WalkHandlersInternal(aKeyEvent, true);
107 void GlobalKeyListener::InstallKeyboardEventListenersTo(
108 EventListenerManager* aEventListenerManager) {
109 // For marking each keyboard event as if it's reserved by chrome,
110 // GlobalKeyListeners need to listen each keyboard events before
111 // web contents.
112 aEventListenerManager->AddEventListenerByType(this, u"keydown"_ns,
113 TrustedEventsAtCapture());
114 aEventListenerManager->AddEventListenerByType(this, u"keyup"_ns,
115 TrustedEventsAtCapture());
116 aEventListenerManager->AddEventListenerByType(this, u"keypress"_ns,
117 TrustedEventsAtCapture());
119 // For reducing the IPC cost, preventing to dispatch reserved keyboard
120 // events into the content process.
121 aEventListenerManager->AddEventListenerByType(
122 this, u"keydown"_ns, TrustedEventsAtSystemGroupCapture());
123 aEventListenerManager->AddEventListenerByType(
124 this, u"keyup"_ns, TrustedEventsAtSystemGroupCapture());
125 aEventListenerManager->AddEventListenerByType(
126 this, u"keypress"_ns, TrustedEventsAtSystemGroupCapture());
128 // Handle keyboard events in bubbling phase of the system event group.
129 aEventListenerManager->AddEventListenerByType(
130 this, u"keydown"_ns, TrustedEventsAtSystemGroupBubble());
131 aEventListenerManager->AddEventListenerByType(
132 this, u"keyup"_ns, TrustedEventsAtSystemGroupBubble());
133 aEventListenerManager->AddEventListenerByType(
134 this, u"keypress"_ns, TrustedEventsAtSystemGroupBubble());
135 // mozaccesskeynotfound event is fired when modifiers of keypress event
136 // matches with modifier of content access key but it's not consumed by
137 // remote content.
138 aEventListenerManager->AddEventListenerByType(
139 this, u"mozaccesskeynotfound"_ns, TrustedEventsAtSystemGroupBubble());
142 void GlobalKeyListener::RemoveKeyboardEventListenersFrom(
143 EventListenerManager* aEventListenerManager) {
144 aEventListenerManager->RemoveEventListenerByType(this, u"keydown"_ns,
145 TrustedEventsAtCapture());
146 aEventListenerManager->RemoveEventListenerByType(this, u"keyup"_ns,
147 TrustedEventsAtCapture());
148 aEventListenerManager->RemoveEventListenerByType(this, u"keypress"_ns,
149 TrustedEventsAtCapture());
151 aEventListenerManager->RemoveEventListenerByType(
152 this, u"keydown"_ns, TrustedEventsAtSystemGroupCapture());
153 aEventListenerManager->RemoveEventListenerByType(
154 this, u"keyup"_ns, TrustedEventsAtSystemGroupCapture());
155 aEventListenerManager->RemoveEventListenerByType(
156 this, u"keypress"_ns, TrustedEventsAtSystemGroupCapture());
158 aEventListenerManager->RemoveEventListenerByType(
159 this, u"keydown"_ns, TrustedEventsAtSystemGroupBubble());
160 aEventListenerManager->RemoveEventListenerByType(
161 this, u"keyup"_ns, TrustedEventsAtSystemGroupBubble());
162 aEventListenerManager->RemoveEventListenerByType(
163 this, u"keypress"_ns, TrustedEventsAtSystemGroupBubble());
164 aEventListenerManager->RemoveEventListenerByType(
165 this, u"mozaccesskeynotfound"_ns, TrustedEventsAtSystemGroupBubble());
168 NS_IMETHODIMP
169 GlobalKeyListener::HandleEvent(dom::Event* aEvent) {
170 RefPtr<dom::KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
171 NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG);
173 if (aEvent->EventPhase() == dom::Event_Binding::CAPTURING_PHASE) {
174 if (aEvent->WidgetEventPtr()->mFlags.mInSystemGroup) {
175 HandleEventOnCaptureInSystemEventGroup(keyEvent);
176 } else {
177 HandleEventOnCaptureInDefaultEventGroup(keyEvent);
179 return NS_OK;
182 // If this event was handled by APZ then don't do the default action, and
183 // preventDefault to prevent any other listeners from handling the event.
184 if (aEvent->WidgetEventPtr()->mFlags.mHandledByAPZ) {
185 aEvent->PreventDefault();
186 return NS_OK;
189 WalkHandlers(keyEvent);
190 return NS_OK;
193 void GlobalKeyListener::HandleEventOnCaptureInDefaultEventGroup(
194 dom::KeyboardEvent* aEvent) {
195 WidgetKeyboardEvent* widgetKeyboardEvent =
196 aEvent->WidgetEventPtr()->AsKeyboardEvent();
198 if (widgetKeyboardEvent->IsReservedByChrome()) {
199 return;
202 bool isReserved = false;
203 if (HasHandlerForEvent(aEvent, &isReserved) && isReserved) {
204 widgetKeyboardEvent->MarkAsReservedByChrome();
208 void GlobalKeyListener::HandleEventOnCaptureInSystemEventGroup(
209 dom::KeyboardEvent* aEvent) {
210 WidgetKeyboardEvent* widgetEvent =
211 aEvent->WidgetEventPtr()->AsKeyboardEvent();
213 // If the event won't be sent to remote process, this listener needs to do
214 // nothing. Note that even if mOnlySystemGroupDispatchInContent is true,
215 // we need to send the event to remote process and check reply event
216 // before matching it with registered shortcut keys because event listeners
217 // in the system event group may want to handle the event before registered
218 // shortcut key handlers.
219 if (!widgetEvent->WillBeSentToRemoteProcess()) {
220 return;
223 if (!HasHandlerForEvent(aEvent)) {
224 return;
227 // If this event wasn't marked as IsCrossProcessForwardingStopped,
228 // yet, it means it wasn't processed by content. We'll not call any
229 // of the handlers at this moment, and will wait the reply event.
230 // So, stop immediate propagation in this event first, then, mark it as
231 // waiting reply from remote process. Finally, when this process receives
232 // a reply from the remote process, it should be dispatched into this
233 // DOM tree again.
234 widgetEvent->StopImmediatePropagation();
235 widgetEvent->MarkAsWaitingReplyFromRemoteProcess();
239 // WalkHandlersInternal and WalkHandlersAndExecute
241 // Given a particular DOM event and a pointer to the first handler in the list,
242 // scan through the list to find something to handle the event. If aExecute =
243 // true, the handler will be executed; otherwise just return an answer telling
244 // if a handler for that event was found.
246 bool GlobalKeyListener::WalkHandlersInternal(dom::KeyboardEvent* aKeyEvent,
247 bool aExecute,
248 bool* aOutReservedForChrome) {
249 WidgetKeyboardEvent* nativeKeyboardEvent =
250 aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
251 MOZ_ASSERT(nativeKeyboardEvent);
253 AutoShortcutKeyCandidateArray shortcutKeys;
254 nativeKeyboardEvent->GetShortcutKeyCandidates(shortcutKeys);
256 if (shortcutKeys.IsEmpty()) {
257 return WalkHandlersAndExecute(aKeyEvent, 0, IgnoreModifierState(), aExecute,
258 aOutReservedForChrome);
261 for (unsigned long i = 0; i < shortcutKeys.Length(); ++i) {
262 ShortcutKeyCandidate& key = shortcutKeys[i];
263 IgnoreModifierState ignoreModifierState;
264 ignoreModifierState.mShift = key.mIgnoreShift;
265 if (WalkHandlersAndExecute(aKeyEvent, key.mCharCode, ignoreModifierState,
266 aExecute, aOutReservedForChrome)) {
267 return true;
270 return false;
273 bool GlobalKeyListener::WalkHandlersAndExecute(
274 dom::KeyboardEvent* aKeyEvent, uint32_t aCharCode,
275 const IgnoreModifierState& aIgnoreModifierState, bool aExecute,
276 bool* aOutReservedForChrome) {
277 if (aOutReservedForChrome) {
278 *aOutReservedForChrome = false;
281 WidgetKeyboardEvent* widgetKeyboardEvent =
282 aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
283 if (NS_WARN_IF(!widgetKeyboardEvent)) {
284 return false;
287 nsAtom* eventType =
288 ShortcutKeys::ConvertEventToDOMEventType(widgetKeyboardEvent);
290 // Try all of the handlers until we find one that matches the event.
291 for (KeyEventHandler* handler = mHandler; handler;
292 handler = handler->GetNextHandler()) {
293 bool stopped = aKeyEvent->IsDispatchStopped();
294 if (stopped) {
295 // The event is finished, don't execute any more handlers
296 return false;
299 if (aExecute) {
300 if (!handler->EventTypeEquals(eventType)) {
301 continue;
303 } else {
304 if (handler->EventTypeEquals(nsGkAtoms::keypress)) {
305 // If the handler is a keypress event handler, we also need to check
306 // if coming keydown event is a preceding event of reserved key
307 // combination because if default action of a keydown event is
308 // prevented, following keypress event won't be fired. However, if
309 // following keypress event is reserved, we shouldn't allow web
310 // contents to prevent the default of the preceding keydown event.
311 if (eventType != nsGkAtoms::keydown &&
312 eventType != nsGkAtoms::keypress) {
313 continue;
315 } else if (!handler->EventTypeEquals(eventType)) {
316 // Otherwise, eventType should exactly be matched.
317 continue;
321 // Check if the keyboard event *may* execute the handler.
322 if (!handler->KeyEventMatched(aKeyEvent, aCharCode, aIgnoreModifierState)) {
323 continue; // try the next one
326 // Before executing this handler, check that it's not disabled,
327 // and that it has something to do (oncommand of the <key> or its
328 // <command> is non-empty).
329 if (!CanHandle(handler, aExecute)) {
330 continue;
333 if (!aExecute) {
334 if (handler->EventTypeEquals(eventType)) {
335 if (aOutReservedForChrome) {
336 *aOutReservedForChrome = IsReservedKey(widgetKeyboardEvent, handler);
339 return true;
342 // If the command is reserved and the event is keydown, check also if
343 // the handler is for keypress because if following keypress event is
344 // reserved, we shouldn't dispatch the event into web contents.
345 if (eventType == nsGkAtoms::keydown &&
346 handler->EventTypeEquals(nsGkAtoms::keypress)) {
347 if (IsReservedKey(widgetKeyboardEvent, handler)) {
348 if (aOutReservedForChrome) {
349 *aOutReservedForChrome = true;
352 return true;
355 // Otherwise, we've not found a handler for the event yet.
356 continue;
359 // This should only be assigned when aExecute is false.
360 MOZ_ASSERT(!aOutReservedForChrome);
362 nsCOMPtr<dom::EventTarget> target = GetHandlerTarget(handler);
364 // XXX Do we execute only one handler even if the handler neither stops
365 // propagation nor prevents default of the event?
366 nsresult rv = handler->ExecuteHandler(target, aKeyEvent);
367 if (NS_SUCCEEDED(rv)) {
368 return true;
372 #ifdef XP_WIN
373 // Windows native applications ignore Windows-Logo key state when checking
374 // shortcut keys even if the key is pressed. Therefore, if there is no
375 // shortcut key which exactly matches current modifier state, we should
376 // retry to look for a shortcut key without the Windows-Logo key press.
377 if (!aIgnoreModifierState.mOS && widgetKeyboardEvent->IsOS()) {
378 IgnoreModifierState ignoreModifierState(aIgnoreModifierState);
379 ignoreModifierState.mOS = true;
380 return WalkHandlersAndExecute(aKeyEvent, aCharCode, ignoreModifierState,
381 aExecute);
383 #endif
385 return false;
388 bool GlobalKeyListener::IsReservedKey(WidgetKeyboardEvent* aKeyEvent,
389 KeyEventHandler* aHandler) {
390 ReservedKey reserved = aHandler->GetIsReserved();
391 // reserved="true" means that the key is always reserved. reserved="false"
392 // means that the key is never reserved. Otherwise, we check site-specific
393 // permissions.
394 if (reserved == ReservedKey_False) {
395 return false;
398 if (reserved == ReservedKey_True) {
399 return true;
402 return nsContentUtils::ShouldBlockReservedKeys(aKeyEvent);
405 bool GlobalKeyListener::HasHandlerForEvent(dom::KeyboardEvent* aEvent,
406 bool* aOutReservedForChrome) {
407 WidgetKeyboardEvent* widgetKeyboardEvent =
408 aEvent->WidgetEventPtr()->AsKeyboardEvent();
409 if (NS_WARN_IF(!widgetKeyboardEvent) || !widgetKeyboardEvent->IsTrusted()) {
410 return false;
413 EnsureHandlers();
415 if (IsDisabled()) {
416 return false;
419 return WalkHandlersInternal(aEvent, false, aOutReservedForChrome);
423 // AttachGlobalKeyHandler
425 // Creates a new key handler and prepares to listen to key events on the given
426 // event receiver (either a document or an content node). If the receiver is
427 // content, then extra work needs to be done to hook it up to the document (XXX
428 // WHY??)
430 void XULKeySetGlobalKeyListener::AttachKeyHandler(
431 dom::Element* aElementTarget) {
432 // Only attach if we're really in a document
433 nsCOMPtr<dom::Document> doc = aElementTarget->GetUncomposedDoc();
434 if (!doc) {
435 return;
438 EventListenerManager* manager = doc->GetOrCreateListenerManager();
439 if (!manager) {
440 return;
443 // the listener already exists, so skip this
444 if (aElementTarget->GetProperty(nsGkAtoms::listener)) {
445 return;
448 // Create the key handler
449 RefPtr<XULKeySetGlobalKeyListener> handler =
450 new XULKeySetGlobalKeyListener(aElementTarget, doc);
452 handler->InstallKeyboardEventListenersTo(manager);
454 aElementTarget->SetProperty(nsGkAtoms::listener, handler.forget().take(),
455 nsPropertyTable::SupportsDtorFunc, true);
459 // DetachGlobalKeyHandler
461 // Removes a key handler added by AttachKeyHandler.
463 void XULKeySetGlobalKeyListener::DetachKeyHandler(
464 dom::Element* aElementTarget) {
465 // Only attach if we're really in a document
466 nsCOMPtr<dom::Document> doc = aElementTarget->GetUncomposedDoc();
467 if (!doc) {
468 return;
471 EventListenerManager* manager = doc->GetOrCreateListenerManager();
472 if (!manager) {
473 return;
476 nsIDOMEventListener* handler = static_cast<nsIDOMEventListener*>(
477 aElementTarget->GetProperty(nsGkAtoms::listener));
478 if (!handler) {
479 return;
482 static_cast<XULKeySetGlobalKeyListener*>(handler)
483 ->RemoveKeyboardEventListenersFrom(manager);
484 aElementTarget->RemoveProperty(nsGkAtoms::listener);
487 XULKeySetGlobalKeyListener::XULKeySetGlobalKeyListener(
488 dom::Element* aElement, dom::EventTarget* aTarget)
489 : GlobalKeyListener(aTarget) {
490 mWeakPtrForElement = do_GetWeakReference(aElement);
493 dom::Element* XULKeySetGlobalKeyListener::GetElement(bool* aIsDisabled) const {
494 RefPtr<dom::Element> element = do_QueryReferent(mWeakPtrForElement);
495 if (element && aIsDisabled) {
496 *aIsDisabled = element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
497 nsGkAtoms::_true, eCaseMatters);
499 return element.get();
502 XULKeySetGlobalKeyListener::~XULKeySetGlobalKeyListener() {
503 if (mWeakPtrForElement) {
504 delete mHandler;
508 void XULKeySetGlobalKeyListener::EnsureHandlers() {
509 if (mHandler) {
510 return;
513 dom::Element* element = GetElement();
514 if (!element) {
515 return;
518 BuildHandlerChain(element, &mHandler);
521 bool XULKeySetGlobalKeyListener::IsDisabled() const {
522 bool isDisabled;
523 dom::Element* element = GetElement(&isDisabled);
524 return element && isDisabled;
527 bool XULKeySetGlobalKeyListener::GetElementForHandler(
528 KeyEventHandler* aHandler, dom::Element** aElementForHandler) const {
529 MOZ_ASSERT(aElementForHandler);
530 *aElementForHandler = nullptr;
532 RefPtr<dom::Element> keyElement = aHandler->GetHandlerElement();
533 if (!keyElement) {
534 // This should only be the case where the <key> element that generated the
535 // handler has been destroyed. Not sure why we return true here...
536 return true;
539 nsCOMPtr<dom::Element> chromeHandlerElement = GetElement();
540 if (!chromeHandlerElement) {
541 NS_WARNING_ASSERTION(keyElement->IsInUncomposedDoc(), "uncomposed");
542 keyElement.swap(*aElementForHandler);
543 return true;
546 // We are in a XUL doc. Obtain our command attribute.
547 nsAutoString command;
548 keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
549 if (command.IsEmpty()) {
550 // There is no command element associated with the key element.
551 NS_WARNING_ASSERTION(keyElement->IsInUncomposedDoc(), "uncomposed");
552 keyElement.swap(*aElementForHandler);
553 return true;
556 // XXX Shouldn't we check this earlier?
557 dom::Document* doc = keyElement->GetUncomposedDoc();
558 if (NS_WARN_IF(!doc)) {
559 return false;
562 nsCOMPtr<dom::Element> commandElement = doc->GetElementById(command);
563 if (!commandElement) {
564 NS_ERROR(
565 "A XUL <key> is observing a command that doesn't exist. "
566 "Unable to execute key binding!");
567 return false;
570 commandElement.swap(*aElementForHandler);
571 return true;
574 bool XULKeySetGlobalKeyListener::IsExecutableElement(
575 dom::Element* aElement) const {
576 if (!aElement) {
577 return false;
580 nsAutoString value;
581 aElement->GetAttr(nsGkAtoms::disabled, value);
582 if (value.EqualsLiteral("true")) {
583 return false;
586 aElement->GetAttr(nsGkAtoms::oncommand, value);
587 return !value.IsEmpty();
590 already_AddRefed<dom::EventTarget> XULKeySetGlobalKeyListener::GetHandlerTarget(
591 KeyEventHandler* aHandler) {
592 nsCOMPtr<dom::Element> commandElement;
593 if (!GetElementForHandler(aHandler, getter_AddRefs(commandElement))) {
594 return nullptr;
597 return commandElement.forget();
600 bool XULKeySetGlobalKeyListener::CanHandle(KeyEventHandler* aHandler,
601 bool aWillExecute) const {
602 nsCOMPtr<dom::Element> commandElement;
603 if (!GetElementForHandler(aHandler, getter_AddRefs(commandElement))) {
604 return false;
607 // The only case where commandElement can be null here is where the <key>
608 // element for the handler is already destroyed. I'm not sure why we continue
609 // in this case.
610 if (!commandElement) {
611 return true;
614 // If we're not actually going to execute here bypass the execution check.
615 return !aWillExecute || IsExecutableElement(commandElement);
618 /* static */
619 layers::KeyboardMap RootWindowGlobalKeyListener::CollectKeyboardShortcuts() {
620 KeyEventHandler* handlers = ShortcutKeys::GetHandlers(HandlerType::eBrowser);
622 // Convert the handlers into keyboard shortcuts, using an AutoTArray with
623 // the maximum amount of shortcuts used on any platform to minimize
624 // allocations
625 AutoTArray<KeyboardShortcut, 48> shortcuts;
627 // Append keyboard shortcuts for hardcoded actions like tab
628 KeyboardShortcut::AppendHardcodedShortcuts(shortcuts);
630 for (KeyEventHandler* handler = handlers; handler;
631 handler = handler->GetNextHandler()) {
632 KeyboardShortcut shortcut;
633 if (handler->TryConvertToKeyboardShortcut(&shortcut)) {
634 shortcuts.AppendElement(shortcut);
638 return layers::KeyboardMap(std::move(shortcuts));
642 // AttachGlobalKeyHandler
644 // Creates a new key handler and prepares to listen to key events on the given
645 // event receiver (either a document or an content node). If the receiver is
646 // content, then extra work needs to be done to hook it up to the document (XXX
647 // WHY??)
649 void RootWindowGlobalKeyListener::AttachKeyHandler(dom::EventTarget* aTarget) {
650 EventListenerManager* manager = aTarget->GetOrCreateListenerManager();
651 if (!manager) {
652 return;
655 // Create the key handler
656 RefPtr<RootWindowGlobalKeyListener> handler =
657 new RootWindowGlobalKeyListener(aTarget);
659 // This registers handler with the manager so the manager will keep handler
660 // alive past this point.
661 handler->InstallKeyboardEventListenersTo(manager);
664 RootWindowGlobalKeyListener::RootWindowGlobalKeyListener(
665 dom::EventTarget* aTarget)
666 : GlobalKeyListener(aTarget) {}
668 /* static */
669 bool RootWindowGlobalKeyListener::IsHTMLEditorFocused() {
670 nsFocusManager* fm = nsFocusManager::GetFocusManager();
671 if (!fm) {
672 return false;
675 nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
676 fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
677 if (!focusedWindow) {
678 return false;
681 auto* piwin = nsPIDOMWindowOuter::From(focusedWindow);
682 nsIDocShell* docShell = piwin->GetDocShell();
683 if (!docShell) {
684 return false;
687 HTMLEditor* htmlEditor = docShell->GetHTMLEditor();
688 if (!htmlEditor) {
689 return false;
692 dom::Document* doc = htmlEditor->GetDocument();
693 if (doc->HasFlag(NODE_IS_EDITABLE)) {
694 // Don't need to perform any checks in designMode documents.
695 return true;
698 nsINode* focusedNode = fm->GetFocusedElement();
699 if (focusedNode && focusedNode->IsElement()) {
700 // If there is a focused element, make sure it's in the active editing host.
701 // Note that GetActiveEditingHost finds the current editing host based on
702 // the document's selection. Even though the document selection is usually
703 // collapsed to where the focus is, but the page may modify the selection
704 // without our knowledge, in which case this check will do something useful.
705 nsCOMPtr<dom::Element> activeEditingHost =
706 htmlEditor->GetActiveEditingHost();
707 if (!activeEditingHost) {
708 return false;
710 return focusedNode->IsInclusiveDescendantOf(activeEditingHost);
713 return false;
716 void RootWindowGlobalKeyListener::EnsureHandlers() {
717 if (IsHTMLEditorFocused()) {
718 mHandler = ShortcutKeys::GetHandlers(HandlerType::eEditor);
719 } else {
720 mHandler = ShortcutKeys::GetHandlers(HandlerType::eBrowser);
724 } // namespace mozilla