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 "mozilla/ArrayUtils.h"
8 
9 #include "nsCOMPtr.h"
10 #include "nsQueryObject.h"
11 #include "nsXBLPrototypeHandler.h"
12 #include "nsXBLPrototypeBinding.h"
13 #include "nsContentUtils.h"
14 #include "nsGlobalWindow.h"
15 #include "nsIContent.h"
16 #include "nsIAtom.h"
17 #include "nsIDOMKeyEvent.h"
18 #include "nsIDOMMouseEvent.h"
19 #include "nsNameSpaceManager.h"
20 #include "nsIDocument.h"
21 #include "nsIController.h"
22 #include "nsIControllers.h"
23 #include "nsIDOMXULElement.h"
24 #include "nsIURI.h"
25 #include "nsIDOMHTMLTextAreaElement.h"
26 #include "nsIDOMHTMLInputElement.h"
27 #include "nsFocusManager.h"
28 #include "nsIFormControl.h"
29 #include "nsIDOMEventListener.h"
30 #include "nsPIDOMWindow.h"
31 #include "nsPIWindowRoot.h"
32 #include "nsIDOMWindow.h"
33 #include "nsIServiceManager.h"
34 #include "nsIScriptError.h"
35 #include "nsXPIDLString.h"
36 #include "nsReadableUtils.h"
37 #include "nsGkAtoms.h"
38 #include "nsIXPConnect.h"
39 #include "mozilla/AddonPathService.h"
40 #include "nsDOMCID.h"
41 #include "nsUnicharUtils.h"
42 #include "nsCRT.h"
43 #include "nsXBLEventHandler.h"
44 #include "nsXBLSerialize.h"
45 #include "nsJSUtils.h"
46 #include "mozilla/BasicEvents.h"
47 #include "mozilla/JSEventHandler.h"
48 #include "mozilla/Preferences.h"
49 #include "mozilla/dom/Element.h"
50 #include "mozilla/dom/EventHandlerBinding.h"
51 #include "mozilla/dom/ScriptSettings.h"
52 #include "xpcpublic.h"
53 
54 using namespace mozilla;
55 using namespace mozilla::dom;
56 
57 uint32_t nsXBLPrototypeHandler::gRefCnt = 0;
58 
59 int32_t nsXBLPrototypeHandler::kMenuAccessKey = -1;
60 
61 const int32_t nsXBLPrototypeHandler::cShift = (1<<0);
62 const int32_t nsXBLPrototypeHandler::cAlt = (1<<1);
63 const int32_t nsXBLPrototypeHandler::cControl = (1<<2);
64 const int32_t nsXBLPrototypeHandler::cMeta = (1<<3);
65 const int32_t nsXBLPrototypeHandler::cOS = (1<<4);
66 
67 const int32_t nsXBLPrototypeHandler::cShiftMask = (1<<5);
68 const int32_t nsXBLPrototypeHandler::cAltMask = (1<<6);
69 const int32_t nsXBLPrototypeHandler::cControlMask = (1<<7);
70 const int32_t nsXBLPrototypeHandler::cMetaMask = (1<<8);
71 const int32_t nsXBLPrototypeHandler::cOSMask = (1<<9);
72 
73 const int32_t nsXBLPrototypeHandler::cAllModifiers =
74   cShiftMask | cAltMask | cControlMask | cMetaMask | cOSMask;
75 
nsXBLPrototypeHandler(const char16_t * aEvent,const char16_t * aPhase,const char16_t * aAction,const char16_t * aCommand,const char16_t * aKeyCode,const char16_t * aCharCode,const char16_t * aModifiers,const char16_t * aButton,const char16_t * aClickCount,const char16_t * aGroup,const char16_t * aPreventDefault,const char16_t * aAllowUntrusted,nsXBLPrototypeBinding * aBinding,uint32_t aLineNumber)76 nsXBLPrototypeHandler::nsXBLPrototypeHandler(const char16_t* aEvent,
77                                              const char16_t* aPhase,
78                                              const char16_t* aAction,
79                                              const char16_t* aCommand,
80                                              const char16_t* aKeyCode,
81                                              const char16_t* aCharCode,
82                                              const char16_t* aModifiers,
83                                              const char16_t* aButton,
84                                              const char16_t* aClickCount,
85                                              const char16_t* aGroup,
86                                              const char16_t* aPreventDefault,
87                                              const char16_t* aAllowUntrusted,
88                                              nsXBLPrototypeBinding* aBinding,
89                                              uint32_t aLineNumber)
90   : mHandlerText(nullptr),
91     mLineNumber(aLineNumber),
92     mNextHandler(nullptr),
93     mPrototypeBinding(aBinding)
94 {
95   Init();
96 
97   ConstructPrototype(nullptr, aEvent, aPhase, aAction, aCommand, aKeyCode,
98                      aCharCode, aModifiers, aButton, aClickCount,
99                      aGroup, aPreventDefault, aAllowUntrusted);
100 }
101 
nsXBLPrototypeHandler(nsIContent * aHandlerElement)102 nsXBLPrototypeHandler::nsXBLPrototypeHandler(nsIContent* aHandlerElement)
103   : mHandlerElement(nullptr),
104     mLineNumber(0),
105     mNextHandler(nullptr),
106     mPrototypeBinding(nullptr)
107 {
108   Init();
109 
110   // Make sure our prototype is initialized.
111   ConstructPrototype(aHandlerElement);
112 }
113 
nsXBLPrototypeHandler(nsXBLPrototypeBinding * aBinding)114 nsXBLPrototypeHandler::nsXBLPrototypeHandler(nsXBLPrototypeBinding* aBinding)
115   : mHandlerText(nullptr),
116     mLineNumber(0),
117     mNextHandler(nullptr),
118     mPrototypeBinding(aBinding)
119 {
120   Init();
121 }
122 
~nsXBLPrototypeHandler()123 nsXBLPrototypeHandler::~nsXBLPrototypeHandler()
124 {
125   --gRefCnt;
126   if (mType & NS_HANDLER_TYPE_XUL) {
127     NS_IF_RELEASE(mHandlerElement);
128   } else if (mHandlerText) {
129     free(mHandlerText);
130   }
131 
132   // We own the next handler in the chain, so delete it now.
133   NS_CONTENT_DELETE_LIST_MEMBER(nsXBLPrototypeHandler, this, mNextHandler);
134 }
135 
136 already_AddRefed<nsIContent>
GetHandlerElement()137 nsXBLPrototypeHandler::GetHandlerElement()
138 {
139   if (mType & NS_HANDLER_TYPE_XUL) {
140     nsCOMPtr<nsIContent> element = do_QueryReferent(mHandlerElement);
141     return element.forget();
142   }
143 
144   return nullptr;
145 }
146 
147 void
AppendHandlerText(const nsAString & aText)148 nsXBLPrototypeHandler::AppendHandlerText(const nsAString& aText)
149 {
150   if (mHandlerText) {
151     // Append our text to the existing text.
152     char16_t* temp = mHandlerText;
153     mHandlerText = ToNewUnicode(nsDependentString(temp) + aText);
154     free(temp);
155   }
156   else {
157     mHandlerText = ToNewUnicode(aText);
158   }
159 }
160 
161 /////////////////////////////////////////////////////////////////////////////
162 // Get the menu access key from prefs.
163 // XXX Eventually pick up using CSS3 key-equivalent property or somesuch
164 void
InitAccessKeys()165 nsXBLPrototypeHandler::InitAccessKeys()
166 {
167   if (kMenuAccessKey >= 0) {
168     return;
169   }
170 
171   // Compiled-in defaults, in case we can't get the pref --
172   // mac doesn't have menu shortcuts, other platforms use alt.
173 #ifdef XP_MACOSX
174   kMenuAccessKey = 0;
175 #else
176   kMenuAccessKey = nsIDOMKeyEvent::DOM_VK_ALT;
177 #endif
178 
179   // Get the menu access key value from prefs, overriding the default:
180   kMenuAccessKey =
181     Preferences::GetInt("ui.key.menuAccessKey", kMenuAccessKey);
182 }
183 
184 nsresult
ExecuteHandler(EventTarget * aTarget,nsIDOMEvent * aEvent)185 nsXBLPrototypeHandler::ExecuteHandler(EventTarget* aTarget,
186                                       nsIDOMEvent* aEvent)
187 {
188   nsresult rv = NS_ERROR_FAILURE;
189 
190   // Prevent default action?
191   if (mType & NS_HANDLER_TYPE_PREVENTDEFAULT) {
192     aEvent->PreventDefault();
193     // If we prevent default, then it's okay for
194     // mHandlerElement and mHandlerText to be null
195     rv = NS_OK;
196   }
197 
198   if (!mHandlerElement) // This works for both types of handlers.  In both cases, the union's var should be defined.
199     return rv;
200 
201   // See if our event receiver is a content node (and not us).
202   bool isXULKey = !!(mType & NS_HANDLER_TYPE_XUL);
203   bool isXBLCommand = !!(mType & NS_HANDLER_TYPE_XBL_COMMAND);
204   NS_ASSERTION(!(isXULKey && isXBLCommand),
205                "can't be both a key and xbl command handler");
206 
207   // XUL handlers and commands shouldn't be triggered by non-trusted
208   // events.
209   if (isXULKey || isXBLCommand) {
210     bool trustedEvent = false;
211     aEvent->GetIsTrusted(&trustedEvent);
212 
213     if (!trustedEvent)
214       return NS_OK;
215   }
216 
217   if (isXBLCommand) {
218     return DispatchXBLCommand(aTarget, aEvent);
219   }
220 
221   // If we're executing on a XUL key element, just dispatch a command
222   // event at the element.  It will take care of retargeting it to its
223   // command element, if applicable, and executing the event handler.
224   if (isXULKey) {
225     return DispatchXULKeyCommand(aEvent);
226   }
227 
228   // Look for a compiled handler on the element.
229   // Should be compiled and bound with "on" in front of the name.
230   nsCOMPtr<nsIAtom> onEventAtom = NS_Atomize(NS_LITERAL_STRING("onxbl") +
231                                              nsDependentAtomString(mEventName));
232 
233   // Compile the handler and bind it to the element.
234   nsCOMPtr<nsIScriptGlobalObject> boundGlobal;
235   nsCOMPtr<nsPIWindowRoot> winRoot = do_QueryInterface(aTarget);
236   if (winRoot) {
237     if (nsCOMPtr<nsPIDOMWindowOuter> window = winRoot->GetWindow()) {
238       nsPIDOMWindowInner* innerWindow = window->GetCurrentInnerWindow();
239       NS_ENSURE_TRUE(innerWindow, NS_ERROR_UNEXPECTED);
240 
241       boundGlobal = do_QueryInterface(innerWindow->GetPrivateRoot());
242     }
243   }
244   else boundGlobal = do_QueryInterface(aTarget);
245 
246   if (!boundGlobal) {
247     nsCOMPtr<nsIDocument> boundDocument(do_QueryInterface(aTarget));
248     if (!boundDocument) {
249       // We must be an element.
250       nsCOMPtr<nsIContent> content(do_QueryInterface(aTarget));
251       if (!content)
252         return NS_OK;
253       boundDocument = content->OwnerDoc();
254     }
255 
256     boundGlobal = do_QueryInterface(boundDocument->GetScopeObject());
257   }
258 
259   if (!boundGlobal)
260     return NS_OK;
261 
262   nsISupports *scriptTarget;
263 
264   if (winRoot) {
265     scriptTarget = boundGlobal;
266   } else {
267     scriptTarget = aTarget;
268   }
269 
270   // We're about to create a new JSEventHandler, which means that we need to
271   // Initiatize an AutoJSAPI with aTarget's bound global to make sure any errors
272   // are reported to the correct place.
273   AutoJSAPI jsapi;
274   if (NS_WARN_IF(!jsapi.Init(boundGlobal))) {
275     return NS_OK;
276   }
277   JSContext* cx = jsapi.cx();
278   JS::Rooted<JSObject*> handler(cx);
279 
280   rv = EnsureEventHandler(jsapi, onEventAtom, &handler);
281   NS_ENSURE_SUCCESS(rv, rv);
282 
283   JSAddonId* addonId = MapURIToAddonID(mPrototypeBinding->DocURI());
284 
285   JS::Rooted<JSObject*> globalObject(cx, boundGlobal->GetGlobalJSObject());
286   JS::Rooted<JSObject*> scopeObject(cx, xpc::GetScopeForXBLExecution(cx, globalObject, addonId));
287   NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
288 
289   // Bind it to the bound element. Note that if we're using a separate XBL scope,
290   // we'll actually be binding the event handler to a cross-compartment wrapper
291   // to the bound element's reflector.
292 
293   // First, enter our XBL scope. This is where the generic handler should have
294   // been compiled, above.
295   JSAutoCompartment ac(cx, scopeObject);
296   JS::Rooted<JSObject*> genericHandler(cx, handler.get());
297   bool ok = JS_WrapObject(cx, &genericHandler);
298   NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
299   MOZ_ASSERT(!js::IsCrossCompartmentWrapper(genericHandler));
300 
301   // Build a scope chain in the XBL scope.
302   RefPtr<Element> targetElement = do_QueryObject(scriptTarget);
303   JS::AutoObjectVector scopeChain(cx);
304   ok = nsJSUtils::GetScopeChainForElement(cx, targetElement, scopeChain);
305   NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
306 
307   // Next, clone the generic handler with our desired scope chain.
308   JS::Rooted<JSObject*> bound(cx, JS::CloneFunctionObject(cx, genericHandler,
309                                                           scopeChain));
310   NS_ENSURE_TRUE(bound, NS_ERROR_FAILURE);
311 
312   RefPtr<EventHandlerNonNull> handlerCallback =
313     new EventHandlerNonNull(nullptr, bound, /* aIncumbentGlobal = */ nullptr);
314 
315   TypedEventHandler typedHandler(handlerCallback);
316 
317   // Execute it.
318   nsCOMPtr<JSEventHandler> jsEventHandler;
319   rv = NS_NewJSEventHandler(scriptTarget, onEventAtom,
320                             typedHandler,
321                             getter_AddRefs(jsEventHandler));
322   NS_ENSURE_SUCCESS(rv, rv);
323 
324   // Handle the event.
325   jsEventHandler->HandleEvent(aEvent);
326   jsEventHandler->Disconnect();
327   return NS_OK;
328 }
329 
330 nsresult
EnsureEventHandler(AutoJSAPI & jsapi,nsIAtom * aName,JS::MutableHandle<JSObject * > aHandler)331 nsXBLPrototypeHandler::EnsureEventHandler(AutoJSAPI& jsapi, nsIAtom* aName,
332                                           JS::MutableHandle<JSObject*> aHandler)
333 {
334   JSContext* cx = jsapi.cx();
335 
336   // Check to see if we've already compiled this
337   JS::Rooted<JSObject*> globalObject(cx, JS::CurrentGlobalOrNull(cx));
338   nsCOMPtr<nsPIDOMWindowInner> pWindow = xpc::WindowOrNull(globalObject)->AsInner();
339   if (pWindow) {
340     JS::Rooted<JSObject*> cachedHandler(cx, pWindow->GetCachedXBLPrototypeHandler(this));
341     if (cachedHandler) {
342       JS::ExposeObjectToActiveJS(cachedHandler);
343       aHandler.set(cachedHandler);
344       NS_ENSURE_TRUE(aHandler, NS_ERROR_FAILURE);
345       return NS_OK;
346     }
347   }
348 
349   // Ensure that we have something to compile
350   nsDependentString handlerText(mHandlerText);
351   NS_ENSURE_TRUE(!handlerText.IsEmpty(), NS_ERROR_FAILURE);
352 
353   JSAddonId* addonId = MapURIToAddonID(mPrototypeBinding->DocURI());
354 
355   JS::Rooted<JSObject*> scopeObject(cx, xpc::GetScopeForXBLExecution(cx, globalObject, addonId));
356   NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
357 
358   nsAutoCString bindingURI;
359   nsresult rv = mPrototypeBinding->DocURI()->GetSpec(bindingURI);
360   NS_ENSURE_SUCCESS(rv, rv);
361 
362   uint32_t argCount;
363   const char **argNames;
364   nsContentUtils::GetEventArgNames(kNameSpaceID_XBL, aName, false, &argCount,
365                                    &argNames);
366 
367   // Compile the event handler in the xbl scope.
368   JSAutoCompartment ac(cx, scopeObject);
369   JS::CompileOptions options(cx);
370   options.setFileAndLine(bindingURI.get(), mLineNumber)
371          .setVersion(JSVERSION_LATEST);
372 
373   JS::Rooted<JSObject*> handlerFun(cx);
374   JS::AutoObjectVector emptyVector(cx);
375   rv = nsJSUtils::CompileFunction(jsapi, emptyVector, options,
376                                   nsAtomCString(aName), argCount,
377                                   argNames, handlerText,
378                                   handlerFun.address());
379   NS_ENSURE_SUCCESS(rv, rv);
380   NS_ENSURE_TRUE(handlerFun, NS_ERROR_FAILURE);
381 
382   // Wrap the handler into the content scope, since we're about to stash it
383   // on the DOM window and such.
384   JSAutoCompartment ac2(cx, globalObject);
385   bool ok = JS_WrapObject(cx, &handlerFun);
386   NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
387   aHandler.set(handlerFun);
388   NS_ENSURE_TRUE(aHandler, NS_ERROR_FAILURE);
389 
390   if (pWindow) {
391     pWindow->CacheXBLPrototypeHandler(this, aHandler);
392   }
393 
394   return NS_OK;
395 }
396 
397 nsresult
DispatchXBLCommand(EventTarget * aTarget,nsIDOMEvent * aEvent)398 nsXBLPrototypeHandler::DispatchXBLCommand(EventTarget* aTarget, nsIDOMEvent* aEvent)
399 {
400   // This is a special-case optimization to make command handling fast.
401   // It isn't really a part of XBL, but it helps speed things up.
402 
403   if (aEvent) {
404     // See if preventDefault has been set.  If so, don't execute.
405     bool preventDefault = false;
406     aEvent->GetDefaultPrevented(&preventDefault);
407     if (preventDefault) {
408       return NS_OK;
409     }
410     bool dispatchStopped = aEvent->IsDispatchStopped();
411     if (dispatchStopped) {
412       return NS_OK;
413     }
414   }
415 
416   // Instead of executing JS, let's get the controller for the bound
417   // element and call doCommand on it.
418   nsCOMPtr<nsIController> controller;
419 
420   nsCOMPtr<nsPIDOMWindowOuter> privateWindow;
421   nsCOMPtr<nsPIWindowRoot> windowRoot = do_QueryInterface(aTarget);
422   if (windowRoot) {
423     privateWindow = windowRoot->GetWindow();
424   }
425   else {
426     privateWindow = do_QueryInterface(aTarget);
427     if (!privateWindow) {
428       nsCOMPtr<nsIContent> elt(do_QueryInterface(aTarget));
429       nsCOMPtr<nsIDocument> doc;
430       // XXXbz sXBL/XBL2 issue -- this should be the "scope doc" or
431       // something... whatever we use when wrapping DOM nodes
432       // normally.  It's not clear that the owner doc is the right
433       // thing.
434       if (elt)
435         doc = elt->OwnerDoc();
436 
437       if (!doc)
438         doc = do_QueryInterface(aTarget);
439 
440       if (!doc)
441         return NS_ERROR_FAILURE;
442 
443       privateWindow = doc->GetWindow();
444       if (!privateWindow)
445         return NS_ERROR_FAILURE;
446     }
447 
448     windowRoot = privateWindow->GetTopWindowRoot();
449   }
450 
451   NS_LossyConvertUTF16toASCII command(mHandlerText);
452   if (windowRoot)
453     windowRoot->GetControllerForCommand(command.get(), getter_AddRefs(controller));
454   else
455     controller = GetController(aTarget); // We're attached to the receiver possibly.
456 
457   // We are the default action for this command.
458   // Stop any other default action from executing.
459   aEvent->PreventDefault();
460 
461   if (mEventName == nsGkAtoms::keypress &&
462       mDetail == nsIDOMKeyEvent::DOM_VK_SPACE &&
463       mMisc == 1) {
464     // get the focused element so that we can pageDown only at
465     // certain times.
466 
467     nsCOMPtr<nsPIDOMWindowOuter> windowToCheck;
468     if (windowRoot)
469       windowToCheck = windowRoot->GetWindow();
470     else
471       windowToCheck = privateWindow->GetPrivateRoot();
472 
473     nsCOMPtr<nsIContent> focusedContent;
474     if (windowToCheck) {
475       nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
476       focusedContent =
477         nsFocusManager::GetFocusedDescendant(windowToCheck, true, getter_AddRefs(focusedWindow));
478     }
479 
480     // If the focus is in an editable region, don't scroll.
481     if (focusedContent && focusedContent->IsEditable()) {
482       return NS_OK;
483     }
484 
485     // If the focus is in a form control, don't scroll.
486     for (nsIContent* c = focusedContent; c; c = c->GetParent()) {
487       nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(c);
488       if (formControl) {
489         return NS_OK;
490       }
491     }
492   }
493 
494   if (controller)
495     controller->DoCommand(command.get());
496 
497   return NS_OK;
498 }
499 
500 nsresult
DispatchXULKeyCommand(nsIDOMEvent * aEvent)501 nsXBLPrototypeHandler::DispatchXULKeyCommand(nsIDOMEvent* aEvent)
502 {
503   nsCOMPtr<nsIContent> handlerElement = GetHandlerElement();
504   NS_ENSURE_STATE(handlerElement);
505   if (handlerElement->AttrValueIs(kNameSpaceID_None,
506                                   nsGkAtoms::disabled,
507                                   nsGkAtoms::_true,
508                                   eCaseMatters)) {
509     // Don't dispatch command events for disabled keys.
510     return NS_OK;
511   }
512 
513   aEvent->PreventDefault();
514 
515   // Copy the modifiers from the key event.
516   nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
517   if (!keyEvent) {
518     NS_ERROR("Trying to execute a key handler for a non-key event!");
519     return NS_ERROR_FAILURE;
520   }
521 
522   // XXX We should use mozilla::Modifiers for supporting all modifiers.
523 
524   bool isAlt = false;
525   bool isControl = false;
526   bool isShift = false;
527   bool isMeta = false;
528   keyEvent->GetAltKey(&isAlt);
529   keyEvent->GetCtrlKey(&isControl);
530   keyEvent->GetShiftKey(&isShift);
531   keyEvent->GetMetaKey(&isMeta);
532 
533   nsContentUtils::DispatchXULCommand(handlerElement, true,
534                                      nullptr, nullptr,
535                                      isControl, isAlt, isShift, isMeta);
536   return NS_OK;
537 }
538 
539 already_AddRefed<nsIAtom>
GetEventName()540 nsXBLPrototypeHandler::GetEventName()
541 {
542   nsCOMPtr<nsIAtom> eventName = mEventName;
543   return eventName.forget();
544 }
545 
546 already_AddRefed<nsIController>
GetController(EventTarget * aTarget)547 nsXBLPrototypeHandler::GetController(EventTarget* aTarget)
548 {
549   // XXX Fix this so there's a generic interface that describes controllers,
550   // This code should have no special knowledge of what objects might have controllers.
551   nsCOMPtr<nsIControllers> controllers;
552 
553   nsCOMPtr<nsIDOMXULElement> xulElement(do_QueryInterface(aTarget));
554   if (xulElement)
555     xulElement->GetControllers(getter_AddRefs(controllers));
556 
557   if (!controllers) {
558     nsCOMPtr<nsIDOMHTMLTextAreaElement> htmlTextArea(do_QueryInterface(aTarget));
559     if (htmlTextArea)
560       htmlTextArea->GetControllers(getter_AddRefs(controllers));
561   }
562 
563   if (!controllers) {
564     nsCOMPtr<nsIDOMHTMLInputElement> htmlInputElement(do_QueryInterface(aTarget));
565     if (htmlInputElement)
566       htmlInputElement->GetControllers(getter_AddRefs(controllers));
567   }
568 
569   if (!controllers) {
570     nsCOMPtr<nsPIDOMWindowOuter> domWindow(do_QueryInterface(aTarget));
571     if (domWindow) {
572       domWindow->GetControllers(getter_AddRefs(controllers));
573     }
574   }
575 
576   // Return the first controller.
577   // XXX This code should be checking the command name and using supportscommand and
578   // iscommandenabled.
579   nsCOMPtr<nsIController> controller;
580   if (controllers) {
581     controllers->GetControllerAt(0, getter_AddRefs(controller));
582   }
583 
584   return controller.forget();
585 }
586 
587 bool
KeyEventMatched(nsIDOMKeyEvent * aKeyEvent,uint32_t aCharCode,const IgnoreModifierState & aIgnoreModifierState)588 nsXBLPrototypeHandler::KeyEventMatched(
589                          nsIDOMKeyEvent* aKeyEvent,
590                          uint32_t aCharCode,
591                          const IgnoreModifierState& aIgnoreModifierState)
592 {
593   if (mDetail != -1) {
594     // Get the keycode or charcode of the key event.
595     uint32_t code;
596 
597     if (mMisc) {
598       if (aCharCode)
599         code = aCharCode;
600       else
601         aKeyEvent->GetCharCode(&code);
602       if (IS_IN_BMP(code))
603         code = ToLowerCase(char16_t(code));
604     }
605     else
606       aKeyEvent->GetKeyCode(&code);
607 
608     if (code != uint32_t(mDetail))
609       return false;
610   }
611 
612   return ModifiersMatchMask(aKeyEvent, aIgnoreModifierState);
613 }
614 
615 bool
MouseEventMatched(nsIDOMMouseEvent * aMouseEvent)616 nsXBLPrototypeHandler::MouseEventMatched(nsIDOMMouseEvent* aMouseEvent)
617 {
618   if (mDetail == -1 && mMisc == 0 && (mKeyMask & cAllModifiers) == 0)
619     return true; // No filters set up. It's generic.
620 
621   int16_t button;
622   aMouseEvent->GetButton(&button);
623   if (mDetail != -1 && (button != mDetail))
624     return false;
625 
626   int32_t clickcount;
627   aMouseEvent->GetDetail(&clickcount);
628   if (mMisc != 0 && (clickcount != mMisc))
629     return false;
630 
631   return ModifiersMatchMask(aMouseEvent, IgnoreModifierState());
632 }
633 
634 struct keyCodeData {
635   const char* str;
636   uint16_t strlength;
637   uint16_t keycode;
638 };
639 
640 // All of these must be uppercase, since the function below does
641 // case-insensitive comparison by converting to uppercase.
642 // XXX: be sure to check this periodically for new symbol additions!
643 static const keyCodeData gKeyCodes[] = {
644 
645 #define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \
646   { #aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode },
647 #include "mozilla/VirtualKeyCodeList.h"
648 #undef NS_DEFINE_VK
649 
650   { nullptr, 0, 0 }
651 };
652 
GetMatchingKeyCode(const nsAString & aKeyName)653 int32_t nsXBLPrototypeHandler::GetMatchingKeyCode(const nsAString& aKeyName)
654 {
655   nsAutoCString keyName;
656   keyName.AssignWithConversion(aKeyName);
657   ToUpperCase(keyName); // We want case-insensitive comparison with data
658                         // stored as uppercase.
659 
660   uint32_t keyNameLength = keyName.Length();
661   const char* keyNameStr = keyName.get();
662   for (uint16_t i = 0; i < ArrayLength(gKeyCodes) - 1; ++i) {
663     if (keyNameLength == gKeyCodes[i].strlength &&
664         !nsCRT::strcmp(gKeyCodes[i].str, keyNameStr)) {
665       return gKeyCodes[i].keycode;
666     }
667   }
668 
669   return 0;
670 }
671 
KeyToMask(int32_t key)672 int32_t nsXBLPrototypeHandler::KeyToMask(int32_t key)
673 {
674   switch (key)
675   {
676     case nsIDOMKeyEvent::DOM_VK_META:
677       return cMeta | cMetaMask;
678 
679     case nsIDOMKeyEvent::DOM_VK_WIN:
680       return cOS | cOSMask;
681 
682     case nsIDOMKeyEvent::DOM_VK_ALT:
683       return cAlt | cAltMask;
684 
685     case nsIDOMKeyEvent::DOM_VK_CONTROL:
686     default:
687       return cControl | cControlMask;
688   }
689   return cControl | cControlMask;  // for warning avoidance
690 }
691 
692 // static
693 int32_t
AccelKeyMask()694 nsXBLPrototypeHandler::AccelKeyMask()
695 {
696   switch (WidgetInputEvent::AccelModifier()) {
697     case MODIFIER_ALT:
698       return KeyToMask(nsIDOMKeyEvent::DOM_VK_ALT);
699     case MODIFIER_CONTROL:
700       return KeyToMask(nsIDOMKeyEvent::DOM_VK_CONTROL);
701     case MODIFIER_META:
702       return KeyToMask(nsIDOMKeyEvent::DOM_VK_META);
703     case MODIFIER_OS:
704       return KeyToMask(nsIDOMKeyEvent::DOM_VK_WIN);
705     default:
706       MOZ_CRASH("Handle the new result of WidgetInputEvent::AccelModifier()");
707       return 0;
708   }
709 }
710 
711 void
GetEventType(nsAString & aEvent)712 nsXBLPrototypeHandler::GetEventType(nsAString& aEvent)
713 {
714   nsCOMPtr<nsIContent> handlerElement = GetHandlerElement();
715   if (!handlerElement) {
716     aEvent.Truncate();
717     return;
718   }
719   handlerElement->GetAttr(kNameSpaceID_None, nsGkAtoms::event, aEvent);
720 
721   if (aEvent.IsEmpty() && (mType & NS_HANDLER_TYPE_XUL))
722     // If no type is specified for a XUL <key> element, let's assume that we're "keypress".
723     aEvent.AssignLiteral("keypress");
724 }
725 
726 void
ConstructPrototype(nsIContent * aKeyElement,const char16_t * aEvent,const char16_t * aPhase,const char16_t * aAction,const char16_t * aCommand,const char16_t * aKeyCode,const char16_t * aCharCode,const char16_t * aModifiers,const char16_t * aButton,const char16_t * aClickCount,const char16_t * aGroup,const char16_t * aPreventDefault,const char16_t * aAllowUntrusted)727 nsXBLPrototypeHandler::ConstructPrototype(nsIContent* aKeyElement,
728                                           const char16_t* aEvent,
729                                           const char16_t* aPhase,
730                                           const char16_t* aAction,
731                                           const char16_t* aCommand,
732                                           const char16_t* aKeyCode,
733                                           const char16_t* aCharCode,
734                                           const char16_t* aModifiers,
735                                           const char16_t* aButton,
736                                           const char16_t* aClickCount,
737                                           const char16_t* aGroup,
738                                           const char16_t* aPreventDefault,
739                                           const char16_t* aAllowUntrusted)
740 {
741   mType = 0;
742 
743   if (aKeyElement) {
744     mType |= NS_HANDLER_TYPE_XUL;
745     MOZ_ASSERT(!mPrototypeBinding);
746     nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(aKeyElement);
747     if (!weak) {
748       return;
749     }
750     weak.swap(mHandlerElement);
751   }
752   else {
753     mType |= aCommand ? NS_HANDLER_TYPE_XBL_COMMAND : NS_HANDLER_TYPE_XBL_JS;
754     mHandlerText = nullptr;
755   }
756 
757   mDetail = -1;
758   mMisc = 0;
759   mKeyMask = 0;
760   mPhase = NS_PHASE_BUBBLING;
761 
762   if (aAction)
763     mHandlerText = ToNewUnicode(nsDependentString(aAction));
764   else if (aCommand)
765     mHandlerText = ToNewUnicode(nsDependentString(aCommand));
766 
767   nsAutoString event(aEvent);
768   if (event.IsEmpty()) {
769     if (mType & NS_HANDLER_TYPE_XUL)
770       GetEventType(event);
771     if (event.IsEmpty())
772       return;
773   }
774 
775   mEventName = NS_Atomize(event);
776 
777   if (aPhase) {
778     const nsDependentString phase(aPhase);
779     if (phase.EqualsLiteral("capturing"))
780       mPhase = NS_PHASE_CAPTURING;
781     else if (phase.EqualsLiteral("target"))
782       mPhase = NS_PHASE_TARGET;
783   }
784 
785   // Button and clickcount apply only to XBL handlers and don't apply to XUL key
786   // handlers.
787   if (aButton && *aButton)
788     mDetail = *aButton - '0';
789 
790   if (aClickCount && *aClickCount)
791     mMisc = *aClickCount - '0';
792 
793   // Modifiers are supported by both types of handlers (XUL and XBL).
794   nsAutoString modifiers(aModifiers);
795   if (mType & NS_HANDLER_TYPE_XUL)
796     aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers);
797 
798   if (!modifiers.IsEmpty()) {
799     mKeyMask = cAllModifiers;
800     char* str = ToNewCString(modifiers);
801     char* newStr;
802     char* token = nsCRT::strtok( str, ", \t", &newStr );
803     while( token != nullptr ) {
804       if (PL_strcmp(token, "shift") == 0)
805         mKeyMask |= cShift | cShiftMask;
806       else if (PL_strcmp(token, "alt") == 0)
807         mKeyMask |= cAlt | cAltMask;
808       else if (PL_strcmp(token, "meta") == 0)
809         mKeyMask |= cMeta | cMetaMask;
810       else if (PL_strcmp(token, "os") == 0)
811         mKeyMask |= cOS | cOSMask;
812       else if (PL_strcmp(token, "control") == 0)
813         mKeyMask |= cControl | cControlMask;
814       else if (PL_strcmp(token, "accel") == 0)
815         mKeyMask |= AccelKeyMask();
816       else if (PL_strcmp(token, "access") == 0)
817         mKeyMask |= KeyToMask(kMenuAccessKey);
818       else if (PL_strcmp(token, "any") == 0)
819         mKeyMask &= ~(mKeyMask << 5);
820 
821       token = nsCRT::strtok( newStr, ", \t", &newStr );
822     }
823 
824     free(str);
825   }
826 
827   nsAutoString key(aCharCode);
828   if (key.IsEmpty()) {
829     if (mType & NS_HANDLER_TYPE_XUL) {
830       aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key);
831       if (key.IsEmpty())
832         aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::charcode, key);
833     }
834   }
835 
836   if (!key.IsEmpty()) {
837     if (mKeyMask == 0)
838       mKeyMask = cAllModifiers;
839     ToLowerCase(key);
840 
841     // We have a charcode.
842     mMisc = 1;
843     mDetail = key[0];
844     const uint8_t GTK2Modifiers = cShift | cControl | cShiftMask | cControlMask;
845     if ((mType & NS_HANDLER_TYPE_XUL) &&
846         (mKeyMask & GTK2Modifiers) == GTK2Modifiers &&
847         modifiers.First() != char16_t(',') &&
848         (mDetail == 'u' || mDetail == 'U'))
849       ReportKeyConflict(key.get(), modifiers.get(), aKeyElement, "GTK2Conflict2");
850     const uint8_t WinModifiers = cControl | cAlt | cControlMask | cAltMask;
851     if ((mType & NS_HANDLER_TYPE_XUL) &&
852         (mKeyMask & WinModifiers) == WinModifiers &&
853         modifiers.First() != char16_t(',') &&
854         (('A' <= mDetail && mDetail <= 'Z') ||
855          ('a' <= mDetail && mDetail <= 'z')))
856       ReportKeyConflict(key.get(), modifiers.get(), aKeyElement, "WinConflict2");
857   }
858   else {
859     key.Assign(aKeyCode);
860     if (mType & NS_HANDLER_TYPE_XUL)
861       aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, key);
862 
863     if (!key.IsEmpty()) {
864       if (mKeyMask == 0)
865         mKeyMask = cAllModifiers;
866       mDetail = GetMatchingKeyCode(key);
867     }
868   }
869 
870   if (aGroup && nsDependentString(aGroup).EqualsLiteral("system"))
871     mType |= NS_HANDLER_TYPE_SYSTEM;
872 
873   if (aPreventDefault &&
874       nsDependentString(aPreventDefault).EqualsLiteral("true"))
875     mType |= NS_HANDLER_TYPE_PREVENTDEFAULT;
876 
877   if (aAllowUntrusted) {
878     mType |= NS_HANDLER_HAS_ALLOW_UNTRUSTED_ATTR;
879     if (nsDependentString(aAllowUntrusted).EqualsLiteral("true")) {
880       mType |= NS_HANDLER_ALLOW_UNTRUSTED;
881     } else {
882       mType &= ~NS_HANDLER_ALLOW_UNTRUSTED;
883     }
884   }
885 }
886 
887 void
ReportKeyConflict(const char16_t * aKey,const char16_t * aModifiers,nsIContent * aKeyElement,const char * aMessageName)888 nsXBLPrototypeHandler::ReportKeyConflict(const char16_t* aKey, const char16_t* aModifiers, nsIContent* aKeyElement, const char *aMessageName)
889 {
890   nsCOMPtr<nsIDocument> doc;
891   if (mPrototypeBinding) {
892     nsXBLDocumentInfo* docInfo = mPrototypeBinding->XBLDocumentInfo();
893     if (docInfo) {
894       doc = docInfo->GetDocument();
895     }
896   } else {
897     doc = aKeyElement->OwnerDoc();
898   }
899 
900   nsAutoString id;
901   aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
902   const char16_t* params[] = { aKey, aModifiers, id.get() };
903   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
904                                   NS_LITERAL_CSTRING("XBL Prototype Handler"), doc,
905                                   nsContentUtils::eXBL_PROPERTIES,
906                                   aMessageName,
907                                   params, ArrayLength(params),
908                                   nullptr, EmptyString(), mLineNumber);
909 }
910 
911 bool
ModifiersMatchMask(nsIDOMUIEvent * aEvent,const IgnoreModifierState & aIgnoreModifierState)912 nsXBLPrototypeHandler::ModifiersMatchMask(
913                          nsIDOMUIEvent* aEvent,
914                          const IgnoreModifierState& aIgnoreModifierState)
915 {
916   WidgetInputEvent* inputEvent = aEvent->AsEvent()->WidgetEventPtr()->AsInputEvent();
917   NS_ENSURE_TRUE(inputEvent, false);
918 
919   if (mKeyMask & cMetaMask) {
920     if (inputEvent->IsMeta() != ((mKeyMask & cMeta) != 0)) {
921       return false;
922     }
923   }
924 
925   if ((mKeyMask & cOSMask) && !aIgnoreModifierState.mOS) {
926     if (inputEvent->IsOS() != ((mKeyMask & cOS) != 0)) {
927       return false;
928     }
929   }
930 
931   if (mKeyMask & cShiftMask && !aIgnoreModifierState.mShift) {
932     if (inputEvent->IsShift() != ((mKeyMask & cShift) != 0)) {
933       return false;
934     }
935   }
936 
937   if (mKeyMask & cAltMask) {
938     if (inputEvent->IsAlt() != ((mKeyMask & cAlt) != 0)) {
939       return false;
940     }
941   }
942 
943   if (mKeyMask & cControlMask) {
944     if (inputEvent->IsControl() != ((mKeyMask & cControl) != 0)) {
945       return false;
946     }
947   }
948 
949   return true;
950 }
951 
952 nsresult
Read(nsIObjectInputStream * aStream)953 nsXBLPrototypeHandler::Read(nsIObjectInputStream* aStream)
954 {
955   AssertInCompilationScope();
956   nsresult rv = aStream->Read8(&mPhase);
957   NS_ENSURE_SUCCESS(rv, rv);
958   rv = aStream->Read8(&mType);
959   NS_ENSURE_SUCCESS(rv, rv);
960   rv = aStream->Read8(&mMisc);
961   NS_ENSURE_SUCCESS(rv, rv);
962 
963   rv = aStream->Read32(reinterpret_cast<uint32_t*>(&mKeyMask));
964   NS_ENSURE_SUCCESS(rv, rv);
965   uint32_t detail;
966   rv = aStream->Read32(&detail);
967   NS_ENSURE_SUCCESS(rv, rv);
968   mDetail = detail;
969 
970   nsAutoString name;
971   rv = aStream->ReadString(name);
972   NS_ENSURE_SUCCESS(rv, rv);
973   mEventName = NS_Atomize(name);
974 
975   rv = aStream->Read32(&mLineNumber);
976   NS_ENSURE_SUCCESS(rv, rv);
977 
978   nsAutoString handlerText;
979   rv = aStream->ReadString(handlerText);
980   NS_ENSURE_SUCCESS(rv, rv);
981   if (!handlerText.IsEmpty())
982     mHandlerText = ToNewUnicode(handlerText);
983 
984   return NS_OK;
985 }
986 
987 nsresult
Write(nsIObjectOutputStream * aStream)988 nsXBLPrototypeHandler::Write(nsIObjectOutputStream* aStream)
989 {
990   AssertInCompilationScope();
991   // Make sure we don't write out NS_HANDLER_TYPE_XUL types, as they are used
992   // for <keyset> elements.
993   if ((mType & NS_HANDLER_TYPE_XUL) || !mEventName)
994     return NS_OK;
995 
996   XBLBindingSerializeDetails type = XBLBinding_Serialize_Handler;
997 
998   nsresult rv = aStream->Write8(type);
999   rv = aStream->Write8(mPhase);
1000   NS_ENSURE_SUCCESS(rv, rv);
1001   rv = aStream->Write8(mType);
1002   NS_ENSURE_SUCCESS(rv, rv);
1003   rv = aStream->Write8(mMisc);
1004   NS_ENSURE_SUCCESS(rv, rv);
1005   rv = aStream->Write32(static_cast<uint32_t>(mKeyMask));
1006   NS_ENSURE_SUCCESS(rv, rv);
1007   rv = aStream->Write32(mDetail);
1008   NS_ENSURE_SUCCESS(rv, rv);
1009 
1010   rv = aStream->WriteWStringZ(nsDependentAtomString(mEventName).get());
1011   NS_ENSURE_SUCCESS(rv, rv);
1012 
1013   rv = aStream->Write32(mLineNumber);
1014   NS_ENSURE_SUCCESS(rv, rv);
1015   return aStream->WriteWStringZ(mHandlerText ? mHandlerText : u"");
1016 }
1017