tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

GlobalKeyListener.cpp (27649B)


      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/. */
      6 
      7 #include "GlobalKeyListener.h"
      8 
      9 #include <utility>
     10 
     11 #include "ErrorList.h"
     12 #include "EventTarget.h"
     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"
     38 
     39 namespace mozilla {
     40 
     41 using namespace mozilla::layers;
     42 
     43 GlobalKeyListener::GlobalKeyListener(dom::EventTarget* aTarget)
     44    : mTarget(aTarget), mHandler(nullptr) {}
     45 
     46 NS_IMPL_ISUPPORTS(GlobalKeyListener, nsIDOMEventListener)
     47 
     48 static void BuildHandlerChain(nsIContent* aContent, KeyEventHandler** aResult) {
     49  *aResult = nullptr;
     50 
     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;
     58    }
     59 
     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;
     72    }
     73 
     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;
     82    }
     83 
     84    KeyEventHandler* handler = new KeyEventHandler(keyElement, reserved);
     85 
     86    handler->SetNextHandler(*aResult);
     87    *aResult = handler;
     88  }
     89 }
     90 
     91 void GlobalKeyListener::WalkHandlers(dom::KeyboardEvent* aKeyEvent) {
     92  if (aKeyEvent->DefaultPrevented()) {
     93    return;
     94  }
     95 
     96  // Don't process the event if it was not dispatched from a trusted source
     97  if (!aKeyEvent->IsTrusted()) {
     98    return;
     99  }
    100 
    101  EnsureHandlers();
    102 
    103  // skip keysets that are disabled
    104  if (IsDisabled()) {
    105    return;
    106  }
    107 
    108  WalkHandlersInternal(Purpose::ExecuteCommand, aKeyEvent);
    109 }
    110 
    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());
    122 
    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());
    131 
    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());
    144 }
    145 
    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());
    154 
    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());
    161 
    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());
    170 }
    171 
    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);
    176 
    177  if (aEvent->EventPhase() == dom::Event_Binding::CAPTURING_PHASE) {
    178    if (aEvent->WidgetEventPtr()->mFlags.mInSystemGroup) {
    179      HandleEventOnCaptureInSystemEventGroup(keyEvent);
    180    } else {
    181      HandleEventOnCaptureInDefaultEventGroup(keyEvent);
    182    }
    183    return NS_OK;
    184  }
    185 
    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;
    191  }
    192 
    193  WalkHandlers(keyEvent);
    194  return NS_OK;
    195 }
    196 
    197 void GlobalKeyListener::HandleEventOnCaptureInDefaultEventGroup(
    198    dom::KeyboardEvent* aEvent) {
    199  WidgetKeyboardEvent* widgetKeyboardEvent =
    200      aEvent->WidgetEventPtr()->AsKeyboardEvent();
    201 
    202  if (widgetKeyboardEvent->IsReservedByChrome()) {
    203    return;
    204  }
    205 
    206  if (HasHandlerForEvent(aEvent).mReservedHandlerForChromeFound) {
    207    widgetKeyboardEvent->MarkAsReservedByChrome();
    208  }
    209 }
    210 
    211 void GlobalKeyListener::HandleEventOnCaptureInSystemEventGroup(
    212    dom::KeyboardEvent* aEvent) {
    213  WidgetKeyboardEvent* widgetEvent =
    214      aEvent->WidgetEventPtr()->AsKeyboardEvent();
    215 
    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;
    224  }
    225 
    226  WalkHandlersResult result = HasHandlerForEvent(aEvent);
    227  widgetEvent->mFlags.mIsShortcutKey |= result.mRelevantHandlerFound;
    228  if (!result.mMeaningfulHandlerFound) {
    229    return;
    230  }
    231 
    232  // If this event wasn't marked as IsCrossProcessForwardingStopped,
    233  // yet, it means it wasn't processed by content. We'll not call any
    234  // of the handlers at this moment, and will wait the reply event.
    235  // So, stop immediate propagation in this event first, then, mark it as
    236  // waiting reply from remote process.  Finally, when this process receives
    237  // a reply from the remote process, it should be dispatched into this
    238  // DOM tree again.
    239  widgetEvent->StopImmediatePropagation();
    240  widgetEvent->MarkAsWaitingReplyFromRemoteProcess();
    241 }
    242 
    243 //
    244 // WalkHandlersInternal and WalkHandlersAndExecute
    245 //
    246 // Given a particular DOM event and a pointer to the first handler in the list,
    247 // scan through the list to find something to handle the event. If aPurpose =
    248 // Purpose::ExecuteHandler, the handler will be executed; otherwise just return
    249 // an answer telling if a handler for that event was found.
    250 //
    251 GlobalKeyListener::WalkHandlersResult GlobalKeyListener::WalkHandlersInternal(
    252    Purpose aPurpose, dom::KeyboardEvent* aKeyEvent) {
    253  WidgetKeyboardEvent* nativeKeyboardEvent =
    254      aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
    255  MOZ_ASSERT(nativeKeyboardEvent);
    256 
    257  AutoShortcutKeyCandidateArray shortcutKeys;
    258  nativeKeyboardEvent->GetShortcutKeyCandidates(shortcutKeys);
    259 
    260  if (shortcutKeys.IsEmpty()) {
    261    return WalkHandlersAndExecute(aPurpose, aKeyEvent, 0,
    262                                  IgnoreModifierState());
    263  }
    264 
    265  bool foundDisabledHandler = false;
    266  bool foundRelevantHandler = false;
    267  for (const ShortcutKeyCandidate& key : shortcutKeys) {
    268    const bool skipIfEarlierHandlerDisabled =
    269        key.mSkipIfEarlierHandlerDisabled ==
    270        ShortcutKeyCandidate::SkipIfEarlierHandlerDisabled::Yes;
    271    if (foundDisabledHandler && skipIfEarlierHandlerDisabled) {
    272      continue;
    273    }
    274    IgnoreModifierState ignoreModifierState;
    275    ignoreModifierState.mShift =
    276        key.mShiftState == ShortcutKeyCandidate::ShiftState::Ignorable;
    277    WalkHandlersResult result = WalkHandlersAndExecute(
    278        aPurpose, aKeyEvent, key.mCharCode, ignoreModifierState);
    279    if (result.mMeaningfulHandlerFound) {
    280      return result;
    281    }
    282    foundRelevantHandler |= result.mRelevantHandlerFound;
    283    // Note that if the candidate should not match if an earlier handler is
    284    // disabled, the char code of the candidate is a char which may be
    285    // introduced with different shift state. In this case, we do NOT find a
    286    // disabled handler which **exactly** matches with the keyboard event.
    287    // This avoids to override a higher priority handler with a disabled lower
    288    // priority handler.
    289    if (!skipIfEarlierHandlerDisabled && !foundDisabledHandler) {
    290      foundDisabledHandler = result.mDisabledHandlerFound;
    291    }
    292  }
    293  WalkHandlersResult result;
    294  result.mRelevantHandlerFound = foundRelevantHandler;
    295  return result;
    296 }
    297 
    298 GlobalKeyListener::WalkHandlersResult GlobalKeyListener::WalkHandlersAndExecute(
    299    Purpose aPurpose, dom::KeyboardEvent* aKeyEvent, uint32_t aCharCode,
    300    const IgnoreModifierState& aIgnoreModifierState) {
    301  WidgetKeyboardEvent* widgetKeyboardEvent =
    302      aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
    303  if (NS_WARN_IF(!widgetKeyboardEvent)) {
    304    return {};
    305  }
    306 
    307  nsAtom* eventType =
    308      ShortcutKeys::ConvertEventToDOMEventType(widgetKeyboardEvent);
    309 
    310  // Try all of the handlers until we find one that matches the event.
    311  bool foundDisabledHandler = false;
    312  bool foundRelevantHandler = false;
    313  for (KeyEventHandler* handler = mHandler; handler;
    314       handler = handler->GetNextHandler()) {
    315    bool stopped = aKeyEvent->IsDispatchStopped();
    316    if (stopped) {
    317      // The event is finished, don't execute any more handlers
    318      return {};
    319    }
    320 
    321    if (aPurpose == Purpose::ExecuteCommand) {
    322      if (!handler->EventTypeEquals(eventType)) {
    323        continue;
    324      }
    325    } else {
    326      if (handler->EventTypeEquals(nsGkAtoms::keypress)) {
    327        // If the handler is a keypress event handler, we also need to check
    328        // if coming keydown event is a preceding event of reserved key
    329        // combination because if default action of a keydown event is
    330        // prevented, following keypress event won't be fired.  However, if
    331        // following keypress event is reserved, we shouldn't allow web
    332        // contents to prevent the default of the preceding keydown event.
    333        if (eventType != nsGkAtoms::keydown &&
    334            eventType != nsGkAtoms::keypress) {
    335          continue;
    336        }
    337      } else if (!handler->EventTypeEquals(eventType)) {
    338        // Otherwise, eventType should exactly be matched.
    339        continue;
    340      }
    341    }
    342 
    343    // Check if the keyboard event *may* execute the handler.
    344    if (!handler->KeyEventMatched(aKeyEvent, aCharCode, aIgnoreModifierState)) {
    345      continue;  // try the next one
    346    }
    347 
    348    // Before executing this handler, check that it's not disabled,
    349    // and that it has something to do (oncommand of the <key> or its
    350    // <command> is non-empty).
    351    if (!CanHandle(handler, aPurpose == Purpose::ExecuteCommand)) {
    352      foundDisabledHandler = true;
    353      continue;
    354    }
    355 
    356    if (aPurpose == Purpose::LookForCommand) {
    357      if (handler->EventTypeEquals(eventType)) {
    358        WalkHandlersResult result;
    359        result.mMeaningfulHandlerFound = true;
    360        result.mReservedHandlerForChromeFound =
    361            IsReservedKey(widgetKeyboardEvent, handler);
    362        result.mRelevantHandlerFound = true;
    363        return result;
    364      }
    365 
    366      // If the command is reserved and the event is keydown, check also if
    367      // the handler is for keypress because if following keypress event is
    368      // reserved, we shouldn't dispatch the event into web contents.
    369      if (eventType == nsGkAtoms::keydown &&
    370          handler->EventTypeEquals(nsGkAtoms::keypress)) {
    371        if (IsReservedKey(widgetKeyboardEvent, handler)) {
    372          WalkHandlersResult result;
    373          result.mMeaningfulHandlerFound = true;
    374          result.mReservedHandlerForChromeFound = true;
    375          result.mRelevantHandlerFound = true;
    376          return result;
    377        }
    378        foundRelevantHandler = true;
    379      }
    380      // Otherwise, we've not found a handler for the event yet.
    381      continue;
    382    }
    383 
    384    nsCOMPtr<dom::EventTarget> target = GetHandlerTarget(handler);
    385 
    386    // XXX Do we execute only one handler even if the handler neither stops
    387    //     propagation nor prevents default of the event?
    388    nsresult rv = handler->ExecuteHandler(target, aKeyEvent);
    389    if (NS_SUCCEEDED(rv)) {
    390      WalkHandlersResult result;
    391      result.mMeaningfulHandlerFound = true;
    392      result.mReservedHandlerForChromeFound =
    393          IsReservedKey(widgetKeyboardEvent, handler);
    394      result.mDisabledHandlerFound = (rv == NS_SUCCESS_DOM_NO_OPERATION);
    395      result.mRelevantHandlerFound = true;
    396      return result;
    397    }
    398  }
    399 
    400 #ifdef XP_WIN
    401  // Windows native applications ignore Windows-Logo key state when checking
    402  // shortcut keys even if the key is pressed.  Therefore, if there is no
    403  // shortcut key which exactly matches current modifier state, we should
    404  // retry to look for a shortcut key without the Windows-Logo key press.
    405  if (!aIgnoreModifierState.mMeta && widgetKeyboardEvent->IsMeta()) {
    406    IgnoreModifierState ignoreModifierState(aIgnoreModifierState);
    407    ignoreModifierState.mMeta = true;
    408    return WalkHandlersAndExecute(aPurpose, aKeyEvent, aCharCode,
    409                                  ignoreModifierState);
    410  }
    411 #endif
    412 
    413  WalkHandlersResult result;
    414  result.mDisabledHandlerFound = foundDisabledHandler;
    415  result.mRelevantHandlerFound = foundRelevantHandler;
    416  return result;
    417 }
    418 
    419 bool GlobalKeyListener::IsReservedKey(WidgetKeyboardEvent* aKeyEvent,
    420                                      KeyEventHandler* aHandler) {
    421  ReservedKey reserved = aHandler->GetIsReserved();
    422  // reserved="true" means that the key is always reserved. reserved="false"
    423  // means that the key is never reserved. Otherwise, we check site-specific
    424  // permissions.
    425  if (reserved == ReservedKey_False) {
    426    return false;
    427  }
    428 
    429  if (reserved != ReservedKey_True &&
    430      !nsContentUtils::ShouldBlockReservedKeys(aKeyEvent)) {
    431    return false;
    432  }
    433 
    434  // Okay, the key handler is reserved, but if the key combination is mapped to
    435  // an edit command or a selection navigation command, we should not treat it
    436  // as reserved since user wants to do the mapped thing(s) in editor.
    437  if (MOZ_UNLIKELY(!aKeyEvent->IsTrusted() || !aKeyEvent->mWidget)) {
    438    return true;
    439  }
    440  widget::InputContext inputContext = aKeyEvent->mWidget->GetInputContext();
    441  if (!inputContext.mIMEState.IsEditable()) {
    442    return true;
    443  }
    444  return MOZ_UNLIKELY(!aKeyEvent->IsEditCommandsInitialized(
    445             inputContext.GetNativeKeyBindingsType())) ||
    446         aKeyEvent
    447             ->EditCommandsConstRef(inputContext.GetNativeKeyBindingsType())
    448             .IsEmpty();
    449 }
    450 
    451 GlobalKeyListener::WalkHandlersResult GlobalKeyListener::HasHandlerForEvent(
    452    dom::KeyboardEvent* aEvent) {
    453  WidgetKeyboardEvent* widgetKeyboardEvent =
    454      aEvent->WidgetEventPtr()->AsKeyboardEvent();
    455  if (NS_WARN_IF(!widgetKeyboardEvent) || !widgetKeyboardEvent->IsTrusted()) {
    456    return {};
    457  }
    458 
    459  EnsureHandlers();
    460 
    461  if (IsDisabled()) {
    462    return {};
    463  }
    464 
    465  return WalkHandlersInternal(Purpose::LookForCommand, aEvent);
    466 }
    467 
    468 //
    469 // AttachGlobalKeyHandler
    470 //
    471 // Creates a new key handler and prepares to listen to key events on the given
    472 // event receiver (either a document or an content node). If the receiver is
    473 // content, then extra work needs to be done to hook it up to the document (XXX
    474 // WHY??)
    475 //
    476 void XULKeySetGlobalKeyListener::AttachKeyHandler(
    477    dom::Element* aElementTarget) {
    478  // Only attach if we're really in a document
    479  nsCOMPtr<dom::Document> doc = aElementTarget->GetUncomposedDoc();
    480  if (!doc) {
    481    return;
    482  }
    483 
    484  EventListenerManager* manager = doc->GetOrCreateListenerManager();
    485  if (!manager) {
    486    return;
    487  }
    488 
    489  // the listener already exists, so skip this
    490  if (aElementTarget->GetProperty(nsGkAtoms::listener)) {
    491    return;
    492  }
    493 
    494  // Create the key handler
    495  RefPtr<XULKeySetGlobalKeyListener> handler =
    496      new XULKeySetGlobalKeyListener(aElementTarget, doc);
    497 
    498  handler->InstallKeyboardEventListenersTo(manager);
    499 
    500  aElementTarget->SetProperty(nsGkAtoms::listener, handler.forget().take(),
    501                              nsPropertyTable::SupportsDtorFunc, true);
    502 }
    503 
    504 //
    505 // DetachGlobalKeyHandler
    506 //
    507 // Removes a key handler added by AttachKeyHandler.
    508 //
    509 void XULKeySetGlobalKeyListener::DetachKeyHandler(
    510    dom::Element* aElementTarget) {
    511  // Only attach if we're really in a document
    512  nsCOMPtr<dom::Document> doc = aElementTarget->GetUncomposedDoc();
    513  if (!doc) {
    514    return;
    515  }
    516 
    517  EventListenerManager* manager = doc->GetOrCreateListenerManager();
    518  if (!manager) {
    519    return;
    520  }
    521 
    522  nsIDOMEventListener* handler = static_cast<nsIDOMEventListener*>(
    523      aElementTarget->GetProperty(nsGkAtoms::listener));
    524  if (!handler) {
    525    return;
    526  }
    527 
    528  static_cast<XULKeySetGlobalKeyListener*>(handler)
    529      ->RemoveKeyboardEventListenersFrom(manager);
    530  aElementTarget->RemoveProperty(nsGkAtoms::listener);
    531 }
    532 
    533 XULKeySetGlobalKeyListener::XULKeySetGlobalKeyListener(
    534    dom::Element* aElement, dom::EventTarget* aTarget)
    535    : GlobalKeyListener(aTarget) {
    536  mWeakPtrForElement = do_GetWeakReference(aElement);
    537 }
    538 
    539 dom::Element* XULKeySetGlobalKeyListener::GetElement(bool* aIsDisabled) const {
    540  RefPtr<dom::Element> element = do_QueryReferent(mWeakPtrForElement);
    541  if (element && aIsDisabled) {
    542    *aIsDisabled = element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
    543                                        nsGkAtoms::_true, eCaseMatters);
    544  }
    545  return element.get();
    546 }
    547 
    548 XULKeySetGlobalKeyListener::~XULKeySetGlobalKeyListener() {
    549  if (mWeakPtrForElement) {
    550    delete mHandler;
    551  }
    552 }
    553 
    554 void XULKeySetGlobalKeyListener::EnsureHandlers() {
    555  if (mHandler) {
    556    return;
    557  }
    558 
    559  dom::Element* element = GetElement();
    560  if (!element) {
    561    return;
    562  }
    563 
    564  BuildHandlerChain(element, &mHandler);
    565 }
    566 
    567 bool XULKeySetGlobalKeyListener::IsDisabled() const {
    568  bool isDisabled;
    569  dom::Element* element = GetElement(&isDisabled);
    570  return element && isDisabled;
    571 }
    572 
    573 bool XULKeySetGlobalKeyListener::GetElementForHandler(
    574    KeyEventHandler* aHandler, dom::Element** aElementForHandler) const {
    575  MOZ_ASSERT(aElementForHandler);
    576  *aElementForHandler = nullptr;
    577 
    578  RefPtr<dom::Element> keyElement = aHandler->GetHandlerElement();
    579  if (!keyElement) {
    580    // This should only be the case where the <key> element that generated the
    581    // handler has been destroyed. Not sure why we return true here...
    582    return true;
    583  }
    584 
    585  nsCOMPtr<dom::Element> chromeHandlerElement = GetElement();
    586  if (!chromeHandlerElement) {
    587    NS_WARNING_ASSERTION(keyElement->IsInUncomposedDoc(), "uncomposed");
    588    keyElement.swap(*aElementForHandler);
    589    return true;
    590  }
    591 
    592  // We are in a XUL doc.  Obtain our command attribute.
    593  nsAutoString command;
    594  keyElement->GetAttr(nsGkAtoms::command, command);
    595  if (command.IsEmpty()) {
    596    // There is no command element associated with the key element.
    597    NS_WARNING_ASSERTION(keyElement->IsInUncomposedDoc(), "uncomposed");
    598    keyElement.swap(*aElementForHandler);
    599    return true;
    600  }
    601 
    602  // XXX Shouldn't we check this earlier?
    603  dom::Document* doc = keyElement->GetUncomposedDoc();
    604  if (NS_WARN_IF(!doc)) {
    605    return false;
    606  }
    607 
    608  nsCOMPtr<dom::Element> commandElement = doc->GetElementById(command);
    609  if (!commandElement) {
    610    NS_ERROR(
    611        "A XUL <key> is observing a command that doesn't exist. "
    612        "Unable to execute key binding!");
    613    return false;
    614  }
    615 
    616  commandElement.swap(*aElementForHandler);
    617  return true;
    618 }
    619 
    620 bool XULKeySetGlobalKeyListener::IsExecutableElement(
    621    dom::Element* aElement) const {
    622  if (!aElement) {
    623    return false;
    624  }
    625 
    626  nsAutoString value;
    627  aElement->GetAttr(nsGkAtoms::disabled, value);
    628  if (value.EqualsLiteral("true")) {
    629    return false;
    630  }
    631 
    632  // Internal keys are defined as <key> elements so that the menu label
    633  // and disabled state can be updated properly, but the command is executed
    634  // by some other means. This will typically be because the key is defined
    635  // as a shortcut defined in ShortcutKeyDefinitions.cpp instead, or on Mac,
    636  // some special system defined keys.
    637  return !aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::internal,
    638                                nsGkAtoms::_true, eCaseMatters);
    639 }
    640 
    641 already_AddRefed<dom::EventTarget> XULKeySetGlobalKeyListener::GetHandlerTarget(
    642    KeyEventHandler* aHandler) {
    643  nsCOMPtr<dom::Element> commandElement;
    644  if (!GetElementForHandler(aHandler, getter_AddRefs(commandElement))) {
    645    return nullptr;
    646  }
    647 
    648  return commandElement.forget();
    649 }
    650 
    651 bool XULKeySetGlobalKeyListener::CanHandle(KeyEventHandler* aHandler,
    652                                           bool aWillExecute) const {
    653  // If the <key> element itself is disabled, ignore it.
    654  if (aHandler->KeyElementIsDisabled()) {
    655    return false;
    656  }
    657 
    658  nsCOMPtr<dom::Element> commandElement;
    659  if (!GetElementForHandler(aHandler, getter_AddRefs(commandElement))) {
    660    return false;
    661  }
    662 
    663  // The only case where commandElement can be null here is where the <key>
    664  // element for the handler is already destroyed. I'm not sure why we continue
    665  // in this case.
    666  if (!commandElement) {
    667    return true;
    668  }
    669 
    670  // If we're not actually going to execute here bypass the execution check.
    671  return !aWillExecute || IsExecutableElement(commandElement);
    672 }
    673 
    674 /* static */
    675 layers::KeyboardMap RootWindowGlobalKeyListener::CollectKeyboardShortcuts() {
    676  KeyEventHandler* handlers = ShortcutKeys::GetHandlers(HandlerType::eBrowser);
    677 
    678  // Convert the handlers into keyboard shortcuts, using an AutoTArray with
    679  // the maximum amount of shortcuts used on any platform to minimize
    680  // allocations
    681  AutoTArray<KeyboardShortcut, 48> shortcuts;
    682 
    683  // Append keyboard shortcuts for hardcoded actions like tab
    684  KeyboardShortcut::AppendHardcodedShortcuts(shortcuts);
    685 
    686  for (KeyEventHandler* handler = handlers; handler;
    687       handler = handler->GetNextHandler()) {
    688    KeyboardShortcut shortcut;
    689    if (handler->TryConvertToKeyboardShortcut(&shortcut)) {
    690      shortcuts.AppendElement(shortcut);
    691    }
    692  }
    693 
    694  return layers::KeyboardMap(std::move(shortcuts));
    695 }
    696 
    697 //
    698 // AttachGlobalKeyHandler
    699 //
    700 // Creates a new key handler and prepares to listen to key events on the given
    701 // event receiver (either a document or an content node). If the receiver is
    702 // content, then extra work needs to be done to hook it up to the document (XXX
    703 // WHY??)
    704 //
    705 void RootWindowGlobalKeyListener::AttachKeyHandler(dom::EventTarget* aTarget) {
    706  EventListenerManager* manager = aTarget->GetOrCreateListenerManager();
    707  if (!manager) {
    708    return;
    709  }
    710 
    711  // Create the key handler
    712  RefPtr<RootWindowGlobalKeyListener> handler =
    713      new RootWindowGlobalKeyListener(aTarget);
    714 
    715  // This registers handler with the manager so the manager will keep handler
    716  // alive past this point.
    717  handler->InstallKeyboardEventListenersTo(manager);
    718 }
    719 
    720 RootWindowGlobalKeyListener::RootWindowGlobalKeyListener(
    721    dom::EventTarget* aTarget)
    722    : GlobalKeyListener(aTarget) {}
    723 
    724 /* static */
    725 bool RootWindowGlobalKeyListener::IsHTMLEditorFocused() {
    726  nsFocusManager* fm = nsFocusManager::GetFocusManager();
    727  if (!fm) {
    728    return false;
    729  }
    730 
    731  nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
    732  fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
    733  if (!focusedWindow) {
    734    return false;
    735  }
    736 
    737  auto* piwin = nsPIDOMWindowOuter::From(focusedWindow);
    738  nsIDocShell* docShell = piwin->GetDocShell();
    739  if (!docShell) {
    740    return false;
    741  }
    742 
    743  HTMLEditor* htmlEditor = docShell->GetHTMLEditor();
    744  if (!htmlEditor) {
    745    return false;
    746  }
    747 
    748  if (htmlEditor->IsInDesignMode()) {
    749    // Don't need to perform any checks in designMode documents.
    750    return true;
    751  }
    752 
    753  nsINode* focusedNode = fm->GetFocusedElement();
    754  if (focusedNode && focusedNode->IsElement()) {
    755    // If there is a focused element, make sure it's in the active editing host.
    756    // Note that ComputeEditingHost finds the current editing host based on
    757    // the document's selection.  Even though the document selection is usually
    758    // collapsed to where the focus is, but the page may modify the selection
    759    // without our knowledge, in which case this check will do something useful.
    760    dom::Element* editingHost = htmlEditor->ComputeEditingHost();
    761    if (!editingHost) {
    762      return false;
    763    }
    764    return focusedNode->IsInclusiveDescendantOf(editingHost);
    765  }
    766 
    767  return false;
    768 }
    769 
    770 void RootWindowGlobalKeyListener::EnsureHandlers() {
    771  if (IsHTMLEditorFocused()) {
    772    mHandler = ShortcutKeys::GetHandlers(HandlerType::eEditor);
    773  } else {
    774    mHandler = ShortcutKeys::GetHandlers(HandlerType::eBrowser);
    775  }
    776 }
    777 
    778 }  // namespace mozilla