Line data Source code
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 "nsCOMPtr.h"
8 : #include "nsXBLPrototypeHandler.h"
9 : #include "nsXBLWindowKeyHandler.h"
10 : #include "nsIContent.h"
11 : #include "nsIAtom.h"
12 : #include "nsIDOMKeyEvent.h"
13 : #include "nsXBLService.h"
14 : #include "nsIServiceManager.h"
15 : #include "nsGkAtoms.h"
16 : #include "nsXBLDocumentInfo.h"
17 : #include "nsIDOMElement.h"
18 : #include "nsFocusManager.h"
19 : #include "nsIURI.h"
20 : #include "nsNetUtil.h"
21 : #include "nsContentUtils.h"
22 : #include "nsXBLPrototypeBinding.h"
23 : #include "nsPIDOMWindow.h"
24 : #include "nsIDocShell.h"
25 : #include "nsIPresShell.h"
26 : #include "mozilla/EventListenerManager.h"
27 : #include "mozilla/EventStateManager.h"
28 : #include "mozilla/Move.h"
29 : #include "nsISelectionController.h"
30 : #include "mozilla/Preferences.h"
31 : #include "mozilla/StaticPtr.h"
32 : #include "mozilla/TextEvents.h"
33 : #include "mozilla/dom/Element.h"
34 : #include "mozilla/dom/Event.h"
35 : #include "mozilla/layers/KeyboardMap.h"
36 : #include "nsIEditor.h"
37 : #include "nsIHTMLEditor.h"
38 : #include "nsIDOMDocument.h"
39 :
40 : using namespace mozilla;
41 : using namespace mozilla::dom;
42 : using namespace mozilla::layers;
43 :
44 : class nsXBLSpecialDocInfo : public nsIObserver
45 : {
46 : public:
47 : RefPtr<nsXBLDocumentInfo> mHTMLBindings;
48 : RefPtr<nsXBLDocumentInfo> mUserHTMLBindings;
49 :
50 : static const char sHTMLBindingStr[];
51 : static const char sUserHTMLBindingStr[];
52 :
53 : bool mInitialized;
54 :
55 : public:
56 : NS_DECL_ISUPPORTS
57 : NS_DECL_NSIOBSERVER
58 :
59 : void LoadDocInfo();
60 : void GetAllHandlers(const char* aType,
61 : nsXBLPrototypeHandler** handler,
62 : nsXBLPrototypeHandler** userHandler);
63 : void GetHandlers(nsXBLDocumentInfo* aInfo,
64 : const nsACString& aRef,
65 : nsXBLPrototypeHandler** aResult);
66 :
67 0 : nsXBLSpecialDocInfo() : mInitialized(false) {}
68 :
69 : protected:
70 0 : virtual ~nsXBLSpecialDocInfo() {}
71 :
72 : };
73 :
74 : const char nsXBLSpecialDocInfo::sHTMLBindingStr[] =
75 : "chrome://global/content/platformHTMLBindings.xml";
76 :
77 0 : NS_IMPL_ISUPPORTS(nsXBLSpecialDocInfo, nsIObserver)
78 :
79 : NS_IMETHODIMP
80 0 : nsXBLSpecialDocInfo::Observe(nsISupports* aSubject,
81 : const char* aTopic,
82 : const char16_t* aData)
83 : {
84 0 : MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"), "wrong topic");
85 :
86 : // On shutdown, clear our fields to avoid an extra cycle collection.
87 0 : mHTMLBindings = nullptr;
88 0 : mUserHTMLBindings = nullptr;
89 0 : mInitialized = false;
90 0 : nsContentUtils::UnregisterShutdownObserver(this);
91 :
92 0 : return NS_OK;
93 : }
94 :
95 0 : void nsXBLSpecialDocInfo::LoadDocInfo()
96 : {
97 0 : if (mInitialized)
98 0 : return;
99 0 : mInitialized = true;
100 0 : nsContentUtils::RegisterShutdownObserver(this);
101 :
102 0 : nsXBLService* xblService = nsXBLService::GetInstance();
103 0 : if (!xblService)
104 0 : return;
105 :
106 : // Obtain the platform doc info
107 0 : nsCOMPtr<nsIURI> bindingURI;
108 0 : NS_NewURI(getter_AddRefs(bindingURI), sHTMLBindingStr);
109 0 : if (!bindingURI) {
110 0 : return;
111 : }
112 0 : xblService->LoadBindingDocumentInfo(nullptr, nullptr,
113 : bindingURI,
114 : nullptr,
115 : true,
116 0 : getter_AddRefs(mHTMLBindings));
117 : }
118 :
119 : //
120 : // GetHandlers
121 : //
122 : //
123 : void
124 0 : nsXBLSpecialDocInfo::GetHandlers(nsXBLDocumentInfo* aInfo,
125 : const nsACString& aRef,
126 : nsXBLPrototypeHandler** aResult)
127 : {
128 0 : nsXBLPrototypeBinding* binding = aInfo->GetPrototypeBinding(aRef);
129 :
130 0 : NS_ASSERTION(binding, "No binding found for the XBL window key handler.");
131 0 : if (!binding)
132 0 : return;
133 :
134 0 : *aResult = binding->GetPrototypeHandlers();
135 : }
136 :
137 : void
138 0 : nsXBLSpecialDocInfo::GetAllHandlers(const char* aType,
139 : nsXBLPrototypeHandler** aHandler,
140 : nsXBLPrototypeHandler** aUserHandler)
141 : {
142 0 : if (mUserHTMLBindings) {
143 0 : nsAutoCString type(aType);
144 0 : type.AppendLiteral("User");
145 0 : GetHandlers(mUserHTMLBindings, type, aUserHandler);
146 : }
147 0 : if (mHTMLBindings) {
148 0 : GetHandlers(mHTMLBindings, nsDependentCString(aType), aHandler);
149 : }
150 0 : }
151 :
152 : // Init statics
153 3 : static StaticRefPtr<nsXBLSpecialDocInfo> sXBLSpecialDocInfo;
154 : uint32_t nsXBLWindowKeyHandler::sRefCnt = 0;
155 :
156 : /* static */ void
157 0 : nsXBLWindowKeyHandler::EnsureSpecialDocInfo()
158 : {
159 0 : if (!sXBLSpecialDocInfo) {
160 0 : sXBLSpecialDocInfo = new nsXBLSpecialDocInfo();
161 : }
162 0 : sXBLSpecialDocInfo->LoadDocInfo();
163 0 : }
164 :
165 7 : nsXBLWindowKeyHandler::nsXBLWindowKeyHandler(nsIDOMElement* aElement,
166 7 : EventTarget* aTarget)
167 : : mTarget(aTarget),
168 : mHandler(nullptr),
169 7 : mUserHandler(nullptr)
170 : {
171 7 : mWeakPtrForElement = do_GetWeakReference(aElement);
172 7 : ++sRefCnt;
173 7 : }
174 :
175 3 : nsXBLWindowKeyHandler::~nsXBLWindowKeyHandler()
176 : {
177 : // If mWeakPtrForElement is non-null, we created a prototype handler.
178 1 : if (mWeakPtrForElement)
179 1 : delete mHandler;
180 :
181 1 : --sRefCnt;
182 1 : if (!sRefCnt) {
183 0 : sXBLSpecialDocInfo = nullptr;
184 : }
185 3 : }
186 :
187 266 : NS_IMPL_ISUPPORTS(nsXBLWindowKeyHandler,
188 : nsIDOMEventListener)
189 :
190 : static void
191 0 : BuildHandlerChain(nsIContent* aContent, nsXBLPrototypeHandler** aResult)
192 : {
193 0 : *aResult = nullptr;
194 :
195 : // Since we chain each handler onto the next handler,
196 : // we'll enumerate them here in reverse so that when we
197 : // walk the chain they'll come out in the original order
198 0 : for (nsIContent* key = aContent->GetLastChild();
199 0 : key;
200 0 : key = key->GetPreviousSibling()) {
201 :
202 0 : if (key->NodeInfo()->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
203 : // Check whether the key element has empty value at key/char attribute.
204 : // Such element is used by localizers for alternative shortcut key
205 : // definition on the locale. See bug 426501.
206 0 : nsAutoString valKey, valCharCode, valKeyCode;
207 : bool attrExists =
208 0 : key->GetAttr(kNameSpaceID_None, nsGkAtoms::key, valKey) ||
209 0 : key->GetAttr(kNameSpaceID_None, nsGkAtoms::charcode, valCharCode) ||
210 0 : key->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, valKeyCode);
211 0 : if (attrExists &&
212 0 : valKey.IsEmpty() && valCharCode.IsEmpty() && valKeyCode.IsEmpty())
213 0 : continue;
214 :
215 0 : bool reserved = key->AttrValueIs(kNameSpaceID_None, nsGkAtoms::reserved,
216 0 : nsGkAtoms::_true, eCaseMatters);
217 0 : nsXBLPrototypeHandler* handler = new nsXBLPrototypeHandler(key, reserved);
218 :
219 0 : handler->SetNextHandler(*aResult);
220 0 : *aResult = handler;
221 : }
222 : }
223 0 : }
224 :
225 : //
226 : // EnsureHandlers
227 : //
228 : // Lazily load the XBL handlers. Overridden to handle being attached
229 : // to a particular element rather than the document
230 : //
231 : nsresult
232 0 : nsXBLWindowKeyHandler::EnsureHandlers()
233 : {
234 0 : nsCOMPtr<Element> el = GetElement();
235 0 : NS_ENSURE_STATE(!mWeakPtrForElement || el);
236 0 : if (el) {
237 : // We are actually a XUL <keyset>.
238 0 : if (mHandler)
239 0 : return NS_OK;
240 :
241 0 : nsCOMPtr<nsIContent> content(do_QueryInterface(el));
242 0 : BuildHandlerChain(content, &mHandler);
243 : } else { // We are an XBL file of handlers.
244 0 : EnsureSpecialDocInfo();
245 :
246 : // Now determine which handlers we should be using.
247 0 : if (IsHTMLEditableFieldFocused()) {
248 0 : sXBLSpecialDocInfo->GetAllHandlers("editor", &mHandler, &mUserHandler);
249 : }
250 : else {
251 0 : sXBLSpecialDocInfo->GetAllHandlers("browser", &mHandler, &mUserHandler);
252 : }
253 : }
254 :
255 0 : return NS_OK;
256 : }
257 :
258 : nsresult
259 0 : nsXBLWindowKeyHandler::WalkHandlers(nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventType)
260 : {
261 : bool prevent;
262 0 : aKeyEvent->AsEvent()->GetDefaultPrevented(&prevent);
263 0 : if (prevent)
264 0 : return NS_OK;
265 :
266 0 : bool trustedEvent = false;
267 : // Don't process the event if it was not dispatched from a trusted source
268 0 : aKeyEvent->AsEvent()->GetIsTrusted(&trustedEvent);
269 :
270 0 : if (!trustedEvent)
271 0 : return NS_OK;
272 :
273 0 : nsresult rv = EnsureHandlers();
274 0 : NS_ENSURE_SUCCESS(rv, rv);
275 :
276 : bool isDisabled;
277 0 : nsCOMPtr<Element> el = GetElement(&isDisabled);
278 0 : if (!el) {
279 0 : if (mUserHandler) {
280 0 : WalkHandlersInternal(aKeyEvent, aEventType, mUserHandler, true);
281 0 : aKeyEvent->AsEvent()->GetDefaultPrevented(&prevent);
282 0 : if (prevent)
283 0 : return NS_OK; // Handled by the user bindings. Our work here is done.
284 : }
285 : }
286 :
287 : // skip keysets that are disabled
288 0 : if (el && isDisabled) {
289 0 : return NS_OK;
290 : }
291 :
292 0 : WalkHandlersInternal(aKeyEvent, aEventType, mHandler, true);
293 :
294 0 : return NS_OK;
295 : }
296 :
297 : void
298 7 : nsXBLWindowKeyHandler::InstallKeyboardEventListenersTo(
299 : EventListenerManager* aEventListenerManager)
300 : {
301 : // For marking each keyboard event as if it's reserved by chrome,
302 : // nsXBLWindowKeyHandlers need to listen each keyboard events before
303 : // web contents.
304 7 : aEventListenerManager->AddEventListenerByType(
305 14 : this, NS_LITERAL_STRING("keydown"),
306 28 : TrustedEventsAtCapture());
307 7 : aEventListenerManager->AddEventListenerByType(
308 14 : this, NS_LITERAL_STRING("keyup"),
309 28 : TrustedEventsAtCapture());
310 7 : aEventListenerManager->AddEventListenerByType(
311 14 : this, NS_LITERAL_STRING("keypress"),
312 28 : TrustedEventsAtCapture());
313 7 : aEventListenerManager->AddEventListenerByType(
314 14 : this, NS_LITERAL_STRING("mozkeydownonplugin"),
315 28 : TrustedEventsAtCapture());
316 7 : aEventListenerManager->AddEventListenerByType(
317 14 : this, NS_LITERAL_STRING("mozkeyuponplugin"),
318 28 : TrustedEventsAtCapture());
319 :
320 : // For reducing the IPC cost, preventing to dispatch reserved keyboard
321 : // events into the content process.
322 7 : aEventListenerManager->AddEventListenerByType(
323 14 : this, NS_LITERAL_STRING("keydown"),
324 28 : TrustedEventsAtSystemGroupCapture());
325 7 : aEventListenerManager->AddEventListenerByType(
326 14 : this, NS_LITERAL_STRING("keyup"),
327 28 : TrustedEventsAtSystemGroupCapture());
328 7 : aEventListenerManager->AddEventListenerByType(
329 14 : this, NS_LITERAL_STRING("keypress"),
330 28 : TrustedEventsAtSystemGroupCapture());
331 7 : aEventListenerManager->AddEventListenerByType(
332 14 : this, NS_LITERAL_STRING("mozkeydownonplugin"),
333 28 : TrustedEventsAtSystemGroupCapture());
334 7 : aEventListenerManager->AddEventListenerByType(
335 14 : this, NS_LITERAL_STRING("mozkeyuponplugin"),
336 28 : TrustedEventsAtSystemGroupCapture());
337 :
338 : // Handle keyboard events in bubbling phase of the system event group.
339 7 : aEventListenerManager->AddEventListenerByType(
340 14 : this, NS_LITERAL_STRING("keydown"),
341 28 : TrustedEventsAtSystemGroupBubble());
342 7 : aEventListenerManager->AddEventListenerByType(
343 14 : this, NS_LITERAL_STRING("keyup"),
344 28 : TrustedEventsAtSystemGroupBubble());
345 7 : aEventListenerManager->AddEventListenerByType(
346 14 : this, NS_LITERAL_STRING("keypress"),
347 28 : TrustedEventsAtSystemGroupBubble());
348 7 : aEventListenerManager->AddEventListenerByType(
349 14 : this, NS_LITERAL_STRING("mozkeydownonplugin"),
350 28 : TrustedEventsAtSystemGroupBubble());
351 7 : aEventListenerManager->AddEventListenerByType(
352 14 : this, NS_LITERAL_STRING("mozkeyuponplugin"),
353 28 : TrustedEventsAtSystemGroupBubble());
354 7 : }
355 :
356 : void
357 1 : nsXBLWindowKeyHandler::RemoveKeyboardEventListenersFrom(
358 : EventListenerManager* aEventListenerManager)
359 : {
360 1 : aEventListenerManager->RemoveEventListenerByType(
361 2 : this, NS_LITERAL_STRING("keydown"),
362 4 : TrustedEventsAtCapture());
363 1 : aEventListenerManager->RemoveEventListenerByType(
364 2 : this, NS_LITERAL_STRING("keyup"),
365 4 : TrustedEventsAtCapture());
366 1 : aEventListenerManager->RemoveEventListenerByType(
367 2 : this, NS_LITERAL_STRING("keypress"),
368 4 : TrustedEventsAtCapture());
369 1 : aEventListenerManager->RemoveEventListenerByType(
370 2 : this, NS_LITERAL_STRING("mozkeydownonplugin"),
371 4 : TrustedEventsAtCapture());
372 1 : aEventListenerManager->RemoveEventListenerByType(
373 2 : this, NS_LITERAL_STRING("mozkeyuponplugin"),
374 4 : TrustedEventsAtCapture());
375 :
376 1 : aEventListenerManager->RemoveEventListenerByType(
377 2 : this, NS_LITERAL_STRING("keydown"),
378 4 : TrustedEventsAtSystemGroupCapture());
379 1 : aEventListenerManager->RemoveEventListenerByType(
380 2 : this, NS_LITERAL_STRING("keyup"),
381 4 : TrustedEventsAtSystemGroupCapture());
382 1 : aEventListenerManager->RemoveEventListenerByType(
383 2 : this, NS_LITERAL_STRING("keypress"),
384 4 : TrustedEventsAtSystemGroupCapture());
385 1 : aEventListenerManager->RemoveEventListenerByType(
386 2 : this, NS_LITERAL_STRING("mozkeydownonplugin"),
387 4 : TrustedEventsAtSystemGroupCapture());
388 1 : aEventListenerManager->RemoveEventListenerByType(
389 2 : this, NS_LITERAL_STRING("mozkeyuponplugin"),
390 4 : TrustedEventsAtSystemGroupCapture());
391 :
392 1 : aEventListenerManager->RemoveEventListenerByType(
393 2 : this, NS_LITERAL_STRING("keydown"),
394 4 : TrustedEventsAtSystemGroupBubble());
395 1 : aEventListenerManager->RemoveEventListenerByType(
396 2 : this, NS_LITERAL_STRING("keyup"),
397 4 : TrustedEventsAtSystemGroupBubble());
398 1 : aEventListenerManager->RemoveEventListenerByType(
399 2 : this, NS_LITERAL_STRING("keypress"),
400 4 : TrustedEventsAtSystemGroupBubble());
401 1 : aEventListenerManager->RemoveEventListenerByType(
402 2 : this, NS_LITERAL_STRING("mozkeydownonplugin"),
403 4 : TrustedEventsAtSystemGroupBubble());
404 1 : aEventListenerManager->RemoveEventListenerByType(
405 2 : this, NS_LITERAL_STRING("mozkeyuponplugin"),
406 4 : TrustedEventsAtSystemGroupBubble());
407 1 : }
408 :
409 : /* static */ KeyboardMap
410 0 : nsXBLWindowKeyHandler::CollectKeyboardShortcuts()
411 : {
412 : // Load the XBL handlers
413 0 : EnsureSpecialDocInfo();
414 :
415 0 : nsXBLPrototypeHandler* handlers = nullptr;
416 0 : nsXBLPrototypeHandler* userHandlers = nullptr;
417 0 : sXBLSpecialDocInfo->GetAllHandlers("browser", &handlers, &userHandlers);
418 :
419 : // Convert the handlers into keyboard shortcuts, using an AutoTArray with
420 : // the maximum amount of shortcuts used on any platform to minimize allocations
421 0 : AutoTArray<KeyboardShortcut, 48> shortcuts;
422 :
423 : // Append keyboard shortcuts for hardcoded actions like tab
424 0 : KeyboardShortcut::AppendHardcodedShortcuts(shortcuts);
425 :
426 0 : for (nsXBLPrototypeHandler* handler = handlers;
427 0 : handler;
428 : handler = handler->GetNextHandler()) {
429 0 : KeyboardShortcut shortcut;
430 0 : if (handler->TryConvertToKeyboardShortcut(&shortcut)) {
431 0 : shortcuts.AppendElement(shortcut);
432 : }
433 : }
434 :
435 0 : for (nsXBLPrototypeHandler* handler = userHandlers;
436 0 : handler;
437 : handler = handler->GetNextHandler()) {
438 0 : KeyboardShortcut shortcut;
439 0 : if (handler->TryConvertToKeyboardShortcut(&shortcut)) {
440 0 : shortcuts.AppendElement(shortcut);
441 : }
442 : }
443 :
444 0 : return KeyboardMap(mozilla::Move(shortcuts));
445 : }
446 :
447 : nsIAtom*
448 0 : nsXBLWindowKeyHandler::ConvertEventToDOMEventType(
449 : const WidgetKeyboardEvent& aWidgetKeyboardEvent) const
450 : {
451 0 : if (aWidgetKeyboardEvent.IsKeyDownOrKeyDownOnPlugin()) {
452 0 : return nsGkAtoms::keydown;
453 : }
454 0 : if (aWidgetKeyboardEvent.IsKeyUpOrKeyUpOnPlugin()) {
455 0 : return nsGkAtoms::keyup;
456 : }
457 0 : if (aWidgetKeyboardEvent.mMessage == eKeyPress) {
458 0 : return nsGkAtoms::keypress;
459 : }
460 0 : MOZ_ASSERT_UNREACHABLE(
461 : "All event messages which this instance listens to should be handled");
462 : return nullptr;
463 : }
464 :
465 : NS_IMETHODIMP
466 0 : nsXBLWindowKeyHandler::HandleEvent(nsIDOMEvent* aEvent)
467 : {
468 0 : nsCOMPtr<nsIDOMKeyEvent> keyEvent(do_QueryInterface(aEvent));
469 0 : NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG);
470 :
471 : uint16_t eventPhase;
472 0 : aEvent->GetEventPhase(&eventPhase);
473 0 : if (eventPhase == nsIDOMEvent::CAPTURING_PHASE) {
474 0 : if (aEvent->WidgetEventPtr()->mFlags.mInSystemGroup) {
475 0 : HandleEventOnCaptureInSystemEventGroup(keyEvent);
476 : } else {
477 0 : HandleEventOnCaptureInDefaultEventGroup(keyEvent);
478 : }
479 0 : return NS_OK;
480 : }
481 :
482 : WidgetKeyboardEvent* widgetKeyboardEvent =
483 0 : aEvent->WidgetEventPtr()->AsKeyboardEvent();
484 0 : if (widgetKeyboardEvent->IsKeyEventOnPlugin()) {
485 : // key events on plugin shouldn't execute shortcut key handlers which are
486 : // not reserved.
487 0 : if (!widgetKeyboardEvent->IsReservedByChrome()) {
488 0 : return NS_OK;
489 : }
490 :
491 : // If the event is untrusted event or was already consumed, do nothing.
492 0 : if (!widgetKeyboardEvent->IsTrusted() ||
493 0 : widgetKeyboardEvent->DefaultPrevented()) {
494 0 : return NS_OK;
495 : }
496 :
497 : // XXX Don't check isReserved here because even if the handler in this
498 : // instance isn't reserved but another instance reserves the key
499 : // combination, it will be executed when the event is normal keyboard
500 : // events...
501 0 : bool isReserved = false;
502 0 : if (!HasHandlerForEvent(keyEvent, &isReserved)) {
503 0 : return NS_OK;
504 : }
505 : }
506 :
507 : nsCOMPtr<nsIAtom> eventTypeAtom =
508 0 : ConvertEventToDOMEventType(*widgetKeyboardEvent);
509 0 : return WalkHandlers(keyEvent, eventTypeAtom);
510 : }
511 :
512 : void
513 0 : nsXBLWindowKeyHandler::HandleEventOnCaptureInDefaultEventGroup(
514 : nsIDOMKeyEvent* aEvent)
515 : {
516 : WidgetKeyboardEvent* widgetKeyboardEvent =
517 0 : aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
518 :
519 0 : if (widgetKeyboardEvent->IsReservedByChrome()) {
520 0 : return;
521 : }
522 :
523 0 : bool isReserved = false;
524 0 : if (HasHandlerForEvent(aEvent, &isReserved) && isReserved) {
525 0 : widgetKeyboardEvent->MarkAsReservedByChrome();
526 : }
527 : }
528 :
529 : void
530 0 : nsXBLWindowKeyHandler::HandleEventOnCaptureInSystemEventGroup(
531 : nsIDOMKeyEvent* aEvent)
532 : {
533 : WidgetKeyboardEvent* widgetEvent =
534 0 : aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
535 :
536 0 : if (widgetEvent->IsCrossProcessForwardingStopped() ||
537 0 : widgetEvent->mFlags.mOnlySystemGroupDispatchInContent) {
538 0 : return;
539 : }
540 :
541 : nsCOMPtr<mozilla::dom::Element> originalTarget =
542 0 : do_QueryInterface(aEvent->AsEvent()->WidgetEventPtr()->mOriginalTarget);
543 0 : if (!EventStateManager::IsRemoteTarget(originalTarget)) {
544 0 : return;
545 : }
546 :
547 0 : if (!HasHandlerForEvent(aEvent)) {
548 0 : return;
549 : }
550 :
551 : // If this event wasn't marked as IsCrossProcessForwardingStopped,
552 : // yet, it means it wasn't processed by content. We'll not call any
553 : // of the handlers at this moment, and will wait the reply event.
554 : // So, stop immediate propagation in this event first, then, mark it as
555 : // waiting reply from remote process. Finally, when this process receives
556 : // a reply from the remote process, it should be dispatched into this
557 : // DOM tree again.
558 0 : widgetEvent->StopImmediatePropagation();
559 0 : widgetEvent->MarkAsWaitingReplyFromRemoteProcess();
560 : }
561 :
562 : bool
563 0 : nsXBLWindowKeyHandler::IsHTMLEditableFieldFocused()
564 : {
565 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
566 0 : if (!fm)
567 0 : return false;
568 :
569 0 : nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
570 0 : fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
571 0 : if (!focusedWindow)
572 0 : return false;
573 :
574 0 : auto* piwin = nsPIDOMWindowOuter::From(focusedWindow);
575 0 : nsIDocShell *docShell = piwin->GetDocShell();
576 0 : if (!docShell) {
577 0 : return false;
578 : }
579 :
580 0 : nsCOMPtr<nsIEditor> editor;
581 0 : docShell->GetEditor(getter_AddRefs(editor));
582 0 : nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(editor);
583 0 : if (!htmlEditor) {
584 0 : return false;
585 : }
586 :
587 0 : nsCOMPtr<nsIDOMDocument> domDocument;
588 0 : editor->GetDocument(getter_AddRefs(domDocument));
589 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDocument);
590 0 : if (doc->HasFlag(NODE_IS_EDITABLE)) {
591 : // Don't need to perform any checks in designMode documents.
592 0 : return true;
593 : }
594 :
595 0 : nsCOMPtr<nsIDOMElement> focusedElement;
596 0 : fm->GetFocusedElement(getter_AddRefs(focusedElement));
597 0 : nsCOMPtr<nsINode> focusedNode = do_QueryInterface(focusedElement);
598 0 : if (focusedNode) {
599 : // If there is a focused element, make sure it's in the active editing host.
600 : // Note that GetActiveEditingHost finds the current editing host based on
601 : // the document's selection. Even though the document selection is usually
602 : // collapsed to where the focus is, but the page may modify the selection
603 : // without our knowledge, in which case this check will do something useful.
604 0 : nsCOMPtr<Element> activeEditingHost = htmlEditor->GetActiveEditingHost();
605 0 : if (!activeEditingHost) {
606 0 : return false;
607 : }
608 0 : return nsContentUtils::ContentIsDescendantOf(focusedNode, activeEditingHost);
609 : }
610 :
611 0 : return false;
612 : }
613 :
614 : //
615 : // WalkHandlersInternal and WalkHandlersAndExecute
616 : //
617 : // Given a particular DOM event and a pointer to the first handler in the list,
618 : // scan through the list to find something to handle the event. If aExecute = true,
619 : // the handler will be executed; otherwise just return an answer telling if a handler
620 : // for that event was found.
621 : //
622 : bool
623 0 : nsXBLWindowKeyHandler::WalkHandlersInternal(nsIDOMKeyEvent* aKeyEvent,
624 : nsIAtom* aEventType,
625 : nsXBLPrototypeHandler* aHandler,
626 : bool aExecute,
627 : bool* aOutReservedForChrome)
628 : {
629 : WidgetKeyboardEvent* nativeKeyboardEvent =
630 0 : aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
631 0 : MOZ_ASSERT(nativeKeyboardEvent);
632 :
633 0 : AutoShortcutKeyCandidateArray shortcutKeys;
634 0 : nativeKeyboardEvent->GetShortcutKeyCandidates(shortcutKeys);
635 :
636 0 : if (shortcutKeys.IsEmpty()) {
637 0 : return WalkHandlersAndExecute(aKeyEvent, aEventType, aHandler,
638 0 : 0, IgnoreModifierState(),
639 0 : aExecute, aOutReservedForChrome);
640 : }
641 :
642 0 : for (uint32_t i = 0; i < shortcutKeys.Length(); ++i) {
643 0 : ShortcutKeyCandidate& key = shortcutKeys[i];
644 0 : IgnoreModifierState ignoreModifierState;
645 0 : ignoreModifierState.mShift = key.mIgnoreShift;
646 0 : if (WalkHandlersAndExecute(aKeyEvent, aEventType, aHandler,
647 : key.mCharCode, ignoreModifierState,
648 : aExecute, aOutReservedForChrome)) {
649 0 : return true;
650 : }
651 : }
652 0 : return false;
653 : }
654 :
655 : bool
656 0 : nsXBLWindowKeyHandler::WalkHandlersAndExecute(
657 : nsIDOMKeyEvent* aKeyEvent,
658 : nsIAtom* aEventType,
659 : nsXBLPrototypeHandler* aFirstHandler,
660 : uint32_t aCharCode,
661 : const IgnoreModifierState& aIgnoreModifierState,
662 : bool aExecute,
663 : bool* aOutReservedForChrome)
664 : {
665 0 : if (aOutReservedForChrome) {
666 0 : *aOutReservedForChrome = false;
667 : }
668 :
669 : WidgetKeyboardEvent* widgetKeyboardEvent =
670 0 : aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
671 0 : if (NS_WARN_IF(!widgetKeyboardEvent)) {
672 0 : return false;
673 : }
674 :
675 : // Try all of the handlers until we find one that matches the event.
676 0 : for (nsXBLPrototypeHandler* handler = aFirstHandler;
677 0 : handler;
678 : handler = handler->GetNextHandler()) {
679 0 : bool stopped = aKeyEvent->AsEvent()->IsDispatchStopped();
680 0 : if (stopped) {
681 : // The event is finished, don't execute any more handlers
682 0 : return false;
683 : }
684 :
685 0 : if (aExecute) {
686 : // If the event is eKeyDownOnPlugin, it should execute either keydown
687 : // handler or keypress handler because eKeyDownOnPlugin events are
688 : // never followed by keypress events.
689 0 : if (widgetKeyboardEvent->mMessage == eKeyDownOnPlugin) {
690 0 : if (!handler->EventTypeEquals(nsGkAtoms::keydown) &&
691 0 : !handler->EventTypeEquals(nsGkAtoms::keypress)) {
692 0 : continue;
693 : }
694 : // The other event types should exactly be matched with the handler's
695 : // event type.
696 0 : } else if (!handler->EventTypeEquals(aEventType)) {
697 0 : continue;
698 : }
699 : } else {
700 0 : if (handler->EventTypeEquals(nsGkAtoms::keypress)) {
701 : // If the handler is a keypress event handler, we also need to check
702 : // if coming keydown event is a preceding event of reserved key
703 : // combination because if default action of a keydown event is
704 : // prevented, following keypress event won't be fired. However, if
705 : // following keypress event is reserved, we shouldn't allow web
706 : // contents to prevent the default of the preceding keydown event.
707 0 : if (aEventType != nsGkAtoms::keydown &&
708 0 : aEventType != nsGkAtoms::keypress) {
709 0 : continue;
710 : }
711 0 : } else if (!handler->EventTypeEquals(aEventType)) {
712 : // Otherwise, aEventType should exactly be matched.
713 0 : continue;
714 : }
715 : }
716 :
717 : // Check if the keyboard event *may* execute the handler.
718 0 : if (!handler->KeyEventMatched(aKeyEvent, aCharCode, aIgnoreModifierState)) {
719 0 : continue; // try the next one
720 : }
721 :
722 : // Before executing this handler, check that it's not disabled,
723 : // and that it has something to do (oncommand of the <key> or its
724 : // <command> is non-empty).
725 0 : nsCOMPtr<Element> commandElement;
726 0 : if (!GetElementForHandler(handler, getter_AddRefs(commandElement))) {
727 0 : continue;
728 : }
729 :
730 0 : bool isReserved = handler->GetIsReserved();
731 0 : if (aOutReservedForChrome) {
732 0 : *aOutReservedForChrome = isReserved;
733 : }
734 :
735 0 : if (commandElement) {
736 0 : if (aExecute && !IsExecutableElement(commandElement)) {
737 0 : continue;
738 : }
739 : }
740 :
741 0 : if (!aExecute) {
742 0 : if (handler->EventTypeEquals(aEventType)) {
743 0 : return true;
744 : }
745 : // If the command is reserved and the event is keydown, check also if
746 : // the handler is for keypress because if following keypress event is
747 : // reserved, we shouldn't dispatch the event into web contents.
748 0 : if (isReserved &&
749 0 : aEventType == nsGkAtoms::keydown &&
750 0 : handler->EventTypeEquals(nsGkAtoms::keypress)) {
751 0 : return true;
752 : }
753 : // Otherwise, we've not found a handler for the event yet.
754 0 : continue;
755 : }
756 :
757 : // If it's not reserved and the event is a key event on a plugin,
758 : // the handler shouldn't be executed.
759 0 : if (!isReserved && widgetKeyboardEvent->IsKeyEventOnPlugin()) {
760 0 : return false;
761 : }
762 :
763 0 : nsCOMPtr<EventTarget> target;
764 0 : nsCOMPtr<Element> chromeHandlerElement = GetElement();
765 0 : if (chromeHandlerElement) {
766 : // XXX commandElement may be nullptr...
767 0 : target = commandElement;
768 : } else {
769 0 : target = mTarget;
770 : }
771 :
772 : // XXX Do we execute only one handler even if the handler neither stops
773 : // propagation nor prevents default of the event?
774 0 : nsresult rv = handler->ExecuteHandler(target, aKeyEvent->AsEvent());
775 0 : if (NS_SUCCEEDED(rv)) {
776 0 : return true;
777 : }
778 : }
779 :
780 : #ifdef XP_WIN
781 : // Windows native applications ignore Windows-Logo key state when checking
782 : // shortcut keys even if the key is pressed. Therefore, if there is no
783 : // shortcut key which exactly matches current modifier state, we should
784 : // retry to look for a shortcut key without the Windows-Logo key press.
785 : if (!aIgnoreModifierState.mOS && widgetKeyboardEvent->IsOS()) {
786 : IgnoreModifierState ignoreModifierState(aIgnoreModifierState);
787 : ignoreModifierState.mOS = true;
788 : return WalkHandlersAndExecute(aKeyEvent, aEventType, aFirstHandler,
789 : aCharCode, ignoreModifierState, aExecute);
790 : }
791 : #endif
792 :
793 0 : return false;
794 : }
795 :
796 : bool
797 0 : nsXBLWindowKeyHandler::HasHandlerForEvent(nsIDOMKeyEvent* aEvent,
798 : bool* aOutReservedForChrome)
799 : {
800 : WidgetKeyboardEvent* widgetKeyboardEvent =
801 0 : aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
802 0 : if (NS_WARN_IF(!widgetKeyboardEvent) || !widgetKeyboardEvent->IsTrusted()) {
803 0 : return false;
804 : }
805 :
806 0 : nsresult rv = EnsureHandlers();
807 0 : NS_ENSURE_SUCCESS(rv, false);
808 :
809 : bool isDisabled;
810 0 : nsCOMPtr<Element> el = GetElement(&isDisabled);
811 0 : if (el && isDisabled) {
812 0 : return false;
813 : }
814 :
815 : nsCOMPtr<nsIAtom> eventTypeAtom =
816 0 : ConvertEventToDOMEventType(*widgetKeyboardEvent);
817 0 : return WalkHandlersInternal(aEvent, eventTypeAtom, mHandler, false,
818 0 : aOutReservedForChrome);
819 : }
820 :
821 : already_AddRefed<Element>
822 0 : nsXBLWindowKeyHandler::GetElement(bool* aIsDisabled)
823 : {
824 0 : nsCOMPtr<Element> element = do_QueryReferent(mWeakPtrForElement);
825 0 : if (element && aIsDisabled) {
826 0 : *aIsDisabled = element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
827 : nsGkAtoms::_true, eCaseMatters);
828 : }
829 0 : return element.forget();
830 : }
831 :
832 : bool
833 0 : nsXBLWindowKeyHandler::GetElementForHandler(nsXBLPrototypeHandler* aHandler,
834 : Element** aElementForHandler)
835 : {
836 0 : MOZ_ASSERT(aElementForHandler);
837 0 : *aElementForHandler = nullptr;
838 :
839 0 : nsCOMPtr<nsIContent> keyContent = aHandler->GetHandlerElement();
840 0 : if (!keyContent) {
841 0 : return true; // XXX Even though no key element?
842 : }
843 :
844 0 : nsCOMPtr<Element> chromeHandlerElement = GetElement();
845 0 : if (!chromeHandlerElement) {
846 0 : NS_WARNING_ASSERTION(keyContent->IsInUncomposedDoc(), "uncomposed");
847 0 : nsCOMPtr<Element> keyElement = do_QueryInterface(keyContent);
848 0 : keyElement.swap(*aElementForHandler);
849 0 : return true;
850 : }
851 :
852 : // We are in a XUL doc. Obtain our command attribute.
853 0 : nsAutoString command;
854 0 : keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
855 0 : if (command.IsEmpty()) {
856 : // There is no command element associated with the key element.
857 0 : NS_WARNING_ASSERTION(keyContent->IsInUncomposedDoc(), "uncomposed");
858 0 : nsCOMPtr<Element> keyElement = do_QueryInterface(keyContent);
859 0 : keyElement.swap(*aElementForHandler);
860 0 : return true;
861 : }
862 :
863 : // XXX Shouldn't we check this earlier?
864 0 : nsIDocument* doc = keyContent->GetUncomposedDoc();
865 0 : if (NS_WARN_IF(!doc)) {
866 0 : return false;
867 : }
868 :
869 : nsCOMPtr<Element> commandElement =
870 0 : do_QueryInterface(doc->GetElementById(command));
871 0 : if (!commandElement) {
872 0 : NS_ERROR("A XUL <key> is observing a command that doesn't exist. "
873 : "Unable to execute key binding!");
874 0 : return false;
875 : }
876 :
877 0 : commandElement.swap(*aElementForHandler);
878 0 : return true;
879 : }
880 :
881 : bool
882 0 : nsXBLWindowKeyHandler::IsExecutableElement(Element* aElement) const
883 : {
884 0 : if (!aElement) {
885 0 : return false;
886 : }
887 :
888 0 : nsAutoString value;
889 0 : aElement->GetAttribute(NS_LITERAL_STRING("disabled"), value);
890 0 : if (value.EqualsLiteral("true")) {
891 0 : return false;
892 : }
893 :
894 0 : aElement->GetAttribute(NS_LITERAL_STRING("oncommand"), value);
895 0 : if (value.IsEmpty()) {
896 0 : return false;
897 : }
898 :
899 0 : return true;
900 : }
901 :
902 : ///////////////////////////////////////////////////////////////////////////////////
903 :
904 : already_AddRefed<nsXBLWindowKeyHandler>
905 7 : NS_NewXBLWindowKeyHandler(nsIDOMElement* aElement, EventTarget* aTarget)
906 : {
907 : RefPtr<nsXBLWindowKeyHandler> result =
908 14 : new nsXBLWindowKeyHandler(aElement, aTarget);
909 14 : return result.forget();
910 9 : }
|