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