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