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