michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsXBLPrototypeHandler.h" michael@0: #include "nsXBLPrototypeBinding.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsIDOMMouseEvent.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIController.h" michael@0: #include "nsIControllers.h" michael@0: #include "nsIDOMXULElement.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIDOMHTMLTextAreaElement.h" michael@0: #include "nsIDOMHTMLInputElement.h" michael@0: #include "nsFocusManager.h" michael@0: #include "nsIDOMEventListener.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsPIWindowRoot.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsIDOMScriptObjectFactory.h" michael@0: #include "nsDOMCID.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsCRT.h" michael@0: #include "nsXBLEventHandler.h" michael@0: #include "nsXBLSerialize.h" michael@0: #include "nsJSUtils.h" michael@0: #include "mozilla/BasicEvents.h" michael@0: #include "mozilla/JSEventHandler.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/dom/EventHandlerBinding.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: uint32_t nsXBLPrototypeHandler::gRefCnt = 0; michael@0: michael@0: int32_t nsXBLPrototypeHandler::kMenuAccessKey = -1; michael@0: int32_t nsXBLPrototypeHandler::kAccelKey = -1; michael@0: michael@0: const int32_t nsXBLPrototypeHandler::cShift = (1<<0); michael@0: const int32_t nsXBLPrototypeHandler::cAlt = (1<<1); michael@0: const int32_t nsXBLPrototypeHandler::cControl = (1<<2); michael@0: const int32_t nsXBLPrototypeHandler::cMeta = (1<<3); michael@0: const int32_t nsXBLPrototypeHandler::cOS = (1<<4); michael@0: michael@0: const int32_t nsXBLPrototypeHandler::cShiftMask = (1<<5); michael@0: const int32_t nsXBLPrototypeHandler::cAltMask = (1<<6); michael@0: const int32_t nsXBLPrototypeHandler::cControlMask = (1<<7); michael@0: const int32_t nsXBLPrototypeHandler::cMetaMask = (1<<8); michael@0: const int32_t nsXBLPrototypeHandler::cOSMask = (1<<9); michael@0: michael@0: const int32_t nsXBLPrototypeHandler::cAllModifiers = michael@0: cShiftMask | cAltMask | cControlMask | cMetaMask | cOSMask; michael@0: michael@0: nsXBLPrototypeHandler::nsXBLPrototypeHandler(const char16_t* aEvent, michael@0: const char16_t* aPhase, michael@0: const char16_t* aAction, michael@0: const char16_t* aCommand, michael@0: const char16_t* aKeyCode, michael@0: const char16_t* aCharCode, michael@0: const char16_t* aModifiers, michael@0: const char16_t* aButton, michael@0: const char16_t* aClickCount, michael@0: const char16_t* aGroup, michael@0: const char16_t* aPreventDefault, michael@0: const char16_t* aAllowUntrusted, michael@0: nsXBLPrototypeBinding* aBinding, michael@0: uint32_t aLineNumber) michael@0: : mHandlerText(nullptr), michael@0: mLineNumber(aLineNumber), michael@0: mNextHandler(nullptr), michael@0: mPrototypeBinding(aBinding) michael@0: { michael@0: Init(); michael@0: michael@0: ConstructPrototype(nullptr, aEvent, aPhase, aAction, aCommand, aKeyCode, michael@0: aCharCode, aModifiers, aButton, aClickCount, michael@0: aGroup, aPreventDefault, aAllowUntrusted); michael@0: } michael@0: michael@0: nsXBLPrototypeHandler::nsXBLPrototypeHandler(nsIContent* aHandlerElement) michael@0: : mHandlerElement(nullptr), michael@0: mLineNumber(0), michael@0: mNextHandler(nullptr), michael@0: mPrototypeBinding(nullptr) michael@0: { michael@0: Init(); michael@0: michael@0: // Make sure our prototype is initialized. michael@0: ConstructPrototype(aHandlerElement); michael@0: } michael@0: michael@0: nsXBLPrototypeHandler::nsXBLPrototypeHandler(nsXBLPrototypeBinding* aBinding) michael@0: : mHandlerText(nullptr), michael@0: mLineNumber(0), michael@0: mNextHandler(nullptr), michael@0: mPrototypeBinding(aBinding) michael@0: { michael@0: Init(); michael@0: } michael@0: michael@0: nsXBLPrototypeHandler::~nsXBLPrototypeHandler() michael@0: { michael@0: --gRefCnt; michael@0: if (mType & NS_HANDLER_TYPE_XUL) { michael@0: NS_IF_RELEASE(mHandlerElement); michael@0: } else if (mHandlerText) { michael@0: nsMemory::Free(mHandlerText); michael@0: } michael@0: michael@0: // We own the next handler in the chain, so delete it now. michael@0: NS_CONTENT_DELETE_LIST_MEMBER(nsXBLPrototypeHandler, this, mNextHandler); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsXBLPrototypeHandler::GetHandlerElement() michael@0: { michael@0: if (mType & NS_HANDLER_TYPE_XUL) { michael@0: nsCOMPtr element = do_QueryReferent(mHandlerElement); michael@0: return element.forget(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeHandler::AppendHandlerText(const nsAString& aText) michael@0: { michael@0: if (mHandlerText) { michael@0: // Append our text to the existing text. michael@0: char16_t* temp = mHandlerText; michael@0: mHandlerText = ToNewUnicode(nsDependentString(temp) + aText); michael@0: nsMemory::Free(temp); michael@0: } michael@0: else { michael@0: mHandlerText = ToNewUnicode(aText); michael@0: } michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // Get the menu access key from prefs. michael@0: // XXX Eventually pick up using CSS3 key-equivalent property or somesuch michael@0: void michael@0: nsXBLPrototypeHandler::InitAccessKeys() michael@0: { michael@0: if (kAccelKey >= 0 && kMenuAccessKey >= 0) michael@0: return; michael@0: michael@0: // Compiled-in defaults, in case we can't get the pref -- michael@0: // mac doesn't have menu shortcuts, other platforms use alt. michael@0: #ifdef XP_MACOSX michael@0: kMenuAccessKey = 0; michael@0: kAccelKey = nsIDOMKeyEvent::DOM_VK_META; michael@0: #else michael@0: kMenuAccessKey = nsIDOMKeyEvent::DOM_VK_ALT; michael@0: kAccelKey = nsIDOMKeyEvent::DOM_VK_CONTROL; michael@0: #endif michael@0: michael@0: // Get the menu access key value from prefs, overriding the default: michael@0: kMenuAccessKey = michael@0: Preferences::GetInt("ui.key.menuAccessKey", kMenuAccessKey); michael@0: kAccelKey = Preferences::GetInt("ui.key.accelKey", kAccelKey); michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeHandler::ExecuteHandler(EventTarget* aTarget, michael@0: nsIDOMEvent* aEvent) michael@0: { michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: michael@0: // Prevent default action? michael@0: if (mType & NS_HANDLER_TYPE_PREVENTDEFAULT) { michael@0: aEvent->PreventDefault(); michael@0: // If we prevent default, then it's okay for michael@0: // mHandlerElement and mHandlerText to be null michael@0: rv = NS_OK; michael@0: } michael@0: michael@0: if (!mHandlerElement) // This works for both types of handlers. In both cases, the union's var should be defined. michael@0: return rv; michael@0: michael@0: // See if our event receiver is a content node (and not us). michael@0: bool isXULKey = !!(mType & NS_HANDLER_TYPE_XUL); michael@0: bool isXBLCommand = !!(mType & NS_HANDLER_TYPE_XBL_COMMAND); michael@0: NS_ASSERTION(!(isXULKey && isXBLCommand), michael@0: "can't be both a key and xbl command handler"); michael@0: michael@0: // XUL handlers and commands shouldn't be triggered by non-trusted michael@0: // events. michael@0: if (isXULKey || isXBLCommand) { michael@0: bool trustedEvent = false; michael@0: aEvent->GetIsTrusted(&trustedEvent); michael@0: michael@0: if (!trustedEvent) michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (isXBLCommand) { michael@0: return DispatchXBLCommand(aTarget, aEvent); michael@0: } michael@0: michael@0: // If we're executing on a XUL key element, just dispatch a command michael@0: // event at the element. It will take care of retargeting it to its michael@0: // command element, if applicable, and executing the event handler. michael@0: if (isXULKey) { michael@0: return DispatchXULKeyCommand(aEvent); michael@0: } michael@0: michael@0: // Look for a compiled handler on the element. michael@0: // Should be compiled and bound with "on" in front of the name. michael@0: nsCOMPtr onEventAtom = do_GetAtom(NS_LITERAL_STRING("onxbl") + michael@0: nsDependentAtomString(mEventName)); michael@0: michael@0: // Compile the handler and bind it to the element. michael@0: nsCOMPtr boundGlobal; michael@0: nsCOMPtr winRoot(do_QueryInterface(aTarget)); michael@0: nsCOMPtr window; michael@0: michael@0: if (winRoot) { michael@0: window = winRoot->GetWindow(); michael@0: } michael@0: michael@0: if (window) { michael@0: window = window->GetCurrentInnerWindow(); michael@0: NS_ENSURE_TRUE(window, NS_ERROR_UNEXPECTED); michael@0: michael@0: boundGlobal = do_QueryInterface(window->GetPrivateRoot()); michael@0: } michael@0: else boundGlobal = do_QueryInterface(aTarget); michael@0: michael@0: if (!boundGlobal) { michael@0: nsCOMPtr boundDocument(do_QueryInterface(aTarget)); michael@0: if (!boundDocument) { michael@0: // We must be an element. michael@0: nsCOMPtr content(do_QueryInterface(aTarget)); michael@0: if (!content) michael@0: return NS_OK; michael@0: boundDocument = content->OwnerDoc(); michael@0: } michael@0: michael@0: boundGlobal = do_QueryInterface(boundDocument->GetScopeObject()); michael@0: } michael@0: michael@0: if (!boundGlobal) michael@0: return NS_OK; michael@0: michael@0: nsIScriptContext *boundContext = boundGlobal->GetScriptContext(); michael@0: if (!boundContext) michael@0: return NS_OK; michael@0: michael@0: nsISupports *scriptTarget; michael@0: michael@0: if (winRoot) { michael@0: scriptTarget = boundGlobal; michael@0: } else { michael@0: scriptTarget = aTarget; michael@0: } michael@0: michael@0: // We're about to create a new JSEventHandler, which means that we're michael@0: // responsible for pushing the context of the event target. See the similar michael@0: // comment in nsEventManagerListener.cpp. michael@0: nsCxPusher pusher; michael@0: NS_ENSURE_STATE(pusher.Push(aTarget)); michael@0: michael@0: AutoPushJSContext cx(boundContext->GetNativeContext()); michael@0: JS::Rooted handler(cx); michael@0: michael@0: rv = EnsureEventHandler(boundGlobal, boundContext, onEventAtom, &handler); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: JS::Rooted globalObject(cx, boundGlobal->GetGlobalJSObject()); michael@0: JS::Rooted scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject)); michael@0: NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: // Bind it to the bound element. Note that if we're using a separate XBL scope, michael@0: // we'll actually be binding the event handler to a cross-compartment wrapper michael@0: // to the bound element's reflector. michael@0: michael@0: // First, enter our XBL scope. This is where the generic handler should have michael@0: // been compiled, above. michael@0: JSAutoCompartment ac(cx, scopeObject); michael@0: JS::Rooted genericHandler(cx, handler.get()); michael@0: bool ok = JS_WrapObject(cx, &genericHandler); michael@0: NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); michael@0: MOZ_ASSERT(!js::IsCrossCompartmentWrapper(genericHandler)); michael@0: michael@0: // Wrap the native into the XBL scope. This creates a reflector in the document michael@0: // scope if one doesn't already exist, and potentially wraps it cross- michael@0: // compartment into our scope (via aAllowWrapping=true). michael@0: JS::Rooted targetV(cx, JS::UndefinedValue()); michael@0: rv = nsContentUtils::WrapNative(cx, scriptTarget, &targetV); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Next, clone the generic handler to be parented to the target. michael@0: JS::Rooted target(cx, &targetV.toObject()); michael@0: JS::Rooted bound(cx, JS_CloneFunctionObject(cx, genericHandler, target)); michael@0: NS_ENSURE_TRUE(bound, NS_ERROR_FAILURE); michael@0: michael@0: // Now, wrap the bound handler into the content compartment and use it. michael@0: JSAutoCompartment ac2(cx, globalObject); michael@0: if (!JS_WrapObject(cx, &bound)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsRefPtr handlerCallback = michael@0: new EventHandlerNonNull(bound, /* aIncumbentGlobal = */ nullptr); michael@0: michael@0: TypedEventHandler typedHandler(handlerCallback); michael@0: michael@0: // Execute it. michael@0: nsCOMPtr jsEventHandler; michael@0: rv = NS_NewJSEventHandler(scriptTarget, onEventAtom, michael@0: typedHandler, michael@0: getter_AddRefs(jsEventHandler)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Handle the event. michael@0: jsEventHandler->HandleEvent(aEvent); michael@0: jsEventHandler->Disconnect(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeHandler::EnsureEventHandler(nsIScriptGlobalObject* aGlobal, michael@0: nsIScriptContext *aBoundContext, michael@0: nsIAtom *aName, michael@0: JS::MutableHandle aHandler) michael@0: { michael@0: AutoPushJSContext cx(aBoundContext->GetNativeContext()); michael@0: michael@0: // Check to see if we've already compiled this michael@0: nsCOMPtr pWindow = do_QueryInterface(aGlobal); michael@0: if (pWindow) { michael@0: JS::Rooted cachedHandler(cx, pWindow->GetCachedXBLPrototypeHandler(this)); michael@0: if (cachedHandler) { michael@0: JS::ExposeObjectToActiveJS(cachedHandler); michael@0: aHandler.set(cachedHandler); michael@0: NS_ENSURE_TRUE(aHandler, NS_ERROR_FAILURE); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // Ensure that we have something to compile michael@0: nsDependentString handlerText(mHandlerText); michael@0: NS_ENSURE_TRUE(!handlerText.IsEmpty(), NS_ERROR_FAILURE); michael@0: michael@0: JS::Rooted globalObject(cx, aGlobal->GetGlobalJSObject()); michael@0: JS::Rooted scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject)); michael@0: NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsAutoCString bindingURI; michael@0: mPrototypeBinding->DocURI()->GetSpec(bindingURI); michael@0: michael@0: uint32_t argCount; michael@0: const char **argNames; michael@0: nsContentUtils::GetEventArgNames(kNameSpaceID_XBL, aName, &argCount, michael@0: &argNames); michael@0: michael@0: // Compile the event handler in the xbl scope. michael@0: JSAutoCompartment ac(cx, scopeObject); michael@0: JS::CompileOptions options(cx); michael@0: options.setFileAndLine(bindingURI.get(), mLineNumber) michael@0: .setVersion(JSVERSION_LATEST); michael@0: michael@0: JS::Rooted handlerFun(cx); michael@0: nsresult rv = nsJSUtils::CompileFunction(cx, JS::NullPtr(), options, michael@0: nsAtomCString(aName), argCount, michael@0: argNames, handlerText, michael@0: handlerFun.address()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(handlerFun, NS_ERROR_FAILURE); michael@0: michael@0: // Wrap the handler into the content scope, since we're about to stash it michael@0: // on the DOM window and such. michael@0: JSAutoCompartment ac2(cx, globalObject); michael@0: bool ok = JS_WrapObject(cx, &handlerFun); michael@0: NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); michael@0: aHandler.set(handlerFun); michael@0: NS_ENSURE_TRUE(aHandler, NS_ERROR_FAILURE); michael@0: michael@0: if (pWindow) { michael@0: pWindow->CacheXBLPrototypeHandler(this, aHandler); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeHandler::DispatchXBLCommand(EventTarget* aTarget, nsIDOMEvent* aEvent) michael@0: { michael@0: // This is a special-case optimization to make command handling fast. michael@0: // It isn't really a part of XBL, but it helps speed things up. michael@0: michael@0: if (aEvent) { michael@0: // See if preventDefault has been set. If so, don't execute. michael@0: bool preventDefault = false; michael@0: aEvent->GetDefaultPrevented(&preventDefault); michael@0: if (preventDefault) { michael@0: return NS_OK; michael@0: } michael@0: bool dispatchStopped = aEvent->IsDispatchStopped(); michael@0: if (dispatchStopped) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // Instead of executing JS, let's get the controller for the bound michael@0: // element and call doCommand on it. michael@0: nsCOMPtr controller; michael@0: michael@0: nsCOMPtr privateWindow; michael@0: nsCOMPtr windowRoot(do_QueryInterface(aTarget)); michael@0: if (windowRoot) { michael@0: privateWindow = windowRoot->GetWindow(); michael@0: } michael@0: else { michael@0: privateWindow = do_QueryInterface(aTarget); michael@0: if (!privateWindow) { michael@0: nsCOMPtr elt(do_QueryInterface(aTarget)); michael@0: nsCOMPtr doc; michael@0: // XXXbz sXBL/XBL2 issue -- this should be the "scope doc" or michael@0: // something... whatever we use when wrapping DOM nodes michael@0: // normally. It's not clear that the owner doc is the right michael@0: // thing. michael@0: if (elt) michael@0: doc = elt->OwnerDoc(); michael@0: michael@0: if (!doc) michael@0: doc = do_QueryInterface(aTarget); michael@0: michael@0: if (!doc) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: privateWindow = doc->GetWindow(); michael@0: if (!privateWindow) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: windowRoot = privateWindow->GetTopWindowRoot(); michael@0: } michael@0: michael@0: NS_LossyConvertUTF16toASCII command(mHandlerText); michael@0: if (windowRoot) michael@0: windowRoot->GetControllerForCommand(command.get(), getter_AddRefs(controller)); michael@0: else michael@0: controller = GetController(aTarget); // We're attached to the receiver possibly. michael@0: michael@0: if (mEventName == nsGkAtoms::keypress && michael@0: mDetail == nsIDOMKeyEvent::DOM_VK_SPACE && michael@0: mMisc == 1) { michael@0: // get the focused element so that we can pageDown only at michael@0: // certain times. michael@0: michael@0: nsCOMPtr windowToCheck; michael@0: if (windowRoot) michael@0: windowToCheck = windowRoot->GetWindow(); michael@0: else michael@0: windowToCheck = privateWindow->GetPrivateRoot(); michael@0: michael@0: nsCOMPtr focusedContent; michael@0: if (windowToCheck) { michael@0: nsCOMPtr focusedWindow; michael@0: focusedContent = michael@0: nsFocusManager::GetFocusedDescendant(windowToCheck, true, getter_AddRefs(focusedWindow)); michael@0: } michael@0: michael@0: bool isLink = false; michael@0: nsIContent *content = focusedContent; michael@0: michael@0: // if the focused element is a link then we do want space to michael@0: // scroll down. The focused element may be an element in a link, michael@0: // we need to check the parent node too. Only do this check if an michael@0: // element is focused and has a parent. michael@0: if (focusedContent && focusedContent->GetParent()) { michael@0: while (content) { michael@0: if (content->Tag() == nsGkAtoms::a && content->IsHTML()) { michael@0: isLink = true; michael@0: break; michael@0: } michael@0: michael@0: if (content->HasAttr(kNameSpaceID_XLink, nsGkAtoms::type)) { michael@0: isLink = content->AttrValueIs(kNameSpaceID_XLink, nsGkAtoms::type, michael@0: nsGkAtoms::simple, eCaseMatters); michael@0: michael@0: if (isLink) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: content = content->GetParent(); michael@0: } michael@0: michael@0: if (!isLink) michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // We are the default action for this command. michael@0: // Stop any other default action from executing. michael@0: aEvent->PreventDefault(); michael@0: michael@0: if (controller) michael@0: controller->DoCommand(command.get()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeHandler::DispatchXULKeyCommand(nsIDOMEvent* aEvent) michael@0: { michael@0: nsCOMPtr handlerElement = GetHandlerElement(); michael@0: NS_ENSURE_STATE(handlerElement); michael@0: if (handlerElement->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::disabled, michael@0: nsGkAtoms::_true, michael@0: eCaseMatters)) { michael@0: // Don't dispatch command events for disabled keys. michael@0: return NS_OK; michael@0: } michael@0: michael@0: aEvent->PreventDefault(); michael@0: michael@0: // Copy the modifiers from the key event. michael@0: nsCOMPtr keyEvent = do_QueryInterface(aEvent); michael@0: if (!keyEvent) { michael@0: NS_ERROR("Trying to execute a key handler for a non-key event!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // XXX We should use mozilla::Modifiers for supporting all modifiers. michael@0: michael@0: bool isAlt = false; michael@0: bool isControl = false; michael@0: bool isShift = false; michael@0: bool isMeta = false; michael@0: keyEvent->GetAltKey(&isAlt); michael@0: keyEvent->GetCtrlKey(&isControl); michael@0: keyEvent->GetShiftKey(&isShift); michael@0: keyEvent->GetMetaKey(&isMeta); michael@0: michael@0: nsContentUtils::DispatchXULCommand(handlerElement, true, michael@0: nullptr, nullptr, michael@0: isControl, isAlt, isShift, isMeta); michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsXBLPrototypeHandler::GetEventName() michael@0: { michael@0: nsCOMPtr eventName = mEventName; michael@0: return eventName.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsXBLPrototypeHandler::GetController(EventTarget* aTarget) michael@0: { michael@0: // XXX Fix this so there's a generic interface that describes controllers, michael@0: // This code should have no special knowledge of what objects might have controllers. michael@0: nsCOMPtr controllers; michael@0: michael@0: nsCOMPtr xulElement(do_QueryInterface(aTarget)); michael@0: if (xulElement) michael@0: xulElement->GetControllers(getter_AddRefs(controllers)); michael@0: michael@0: if (!controllers) { michael@0: nsCOMPtr htmlTextArea(do_QueryInterface(aTarget)); michael@0: if (htmlTextArea) michael@0: htmlTextArea->GetControllers(getter_AddRefs(controllers)); michael@0: } michael@0: michael@0: if (!controllers) { michael@0: nsCOMPtr htmlInputElement(do_QueryInterface(aTarget)); michael@0: if (htmlInputElement) michael@0: htmlInputElement->GetControllers(getter_AddRefs(controllers)); michael@0: } michael@0: michael@0: if (!controllers) { michael@0: nsCOMPtr domWindow(do_QueryInterface(aTarget)); michael@0: if (domWindow) michael@0: domWindow->GetControllers(getter_AddRefs(controllers)); michael@0: } michael@0: michael@0: // Return the first controller. michael@0: // XXX This code should be checking the command name and using supportscommand and michael@0: // iscommandenabled. michael@0: nsCOMPtr controller; michael@0: if (controllers) { michael@0: controllers->GetControllerAt(0, getter_AddRefs(controller)); michael@0: } michael@0: michael@0: return controller.forget(); michael@0: } michael@0: michael@0: bool michael@0: nsXBLPrototypeHandler::KeyEventMatched(nsIDOMKeyEvent* aKeyEvent, michael@0: uint32_t aCharCode, michael@0: bool aIgnoreShiftKey) michael@0: { michael@0: if (mDetail != -1) { michael@0: // Get the keycode or charcode of the key event. michael@0: uint32_t code; michael@0: michael@0: if (mMisc) { michael@0: if (aCharCode) michael@0: code = aCharCode; michael@0: else michael@0: aKeyEvent->GetCharCode(&code); michael@0: if (IS_IN_BMP(code)) michael@0: code = ToLowerCase(char16_t(code)); michael@0: } michael@0: else michael@0: aKeyEvent->GetKeyCode(&code); michael@0: michael@0: if (code != uint32_t(mDetail)) michael@0: return false; michael@0: } michael@0: michael@0: return ModifiersMatchMask(aKeyEvent, aIgnoreShiftKey); michael@0: } michael@0: michael@0: bool michael@0: nsXBLPrototypeHandler::MouseEventMatched(nsIDOMMouseEvent* aMouseEvent) michael@0: { michael@0: if (mDetail == -1 && mMisc == 0 && (mKeyMask & cAllModifiers) == 0) michael@0: return true; // No filters set up. It's generic. michael@0: michael@0: int16_t button; michael@0: aMouseEvent->GetButton(&button); michael@0: if (mDetail != -1 && (button != mDetail)) michael@0: return false; michael@0: michael@0: int32_t clickcount; michael@0: aMouseEvent->GetDetail(&clickcount); michael@0: if (mMisc != 0 && (clickcount != mMisc)) michael@0: return false; michael@0: michael@0: return ModifiersMatchMask(aMouseEvent); michael@0: } michael@0: michael@0: struct keyCodeData { michael@0: const char* str; michael@0: size_t strlength; michael@0: uint32_t keycode; michael@0: }; michael@0: michael@0: // All of these must be uppercase, since the function below does michael@0: // case-insensitive comparison by converting to uppercase. michael@0: // XXX: be sure to check this periodically for new symbol additions! michael@0: static const keyCodeData gKeyCodes[] = { michael@0: michael@0: #define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \ michael@0: { #aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode } michael@0: #include "mozilla/VirtualKeyCodeList.h" michael@0: #undef NS_DEFINE_VK michael@0: }; michael@0: michael@0: int32_t nsXBLPrototypeHandler::GetMatchingKeyCode(const nsAString& aKeyName) michael@0: { michael@0: nsAutoCString keyName; michael@0: keyName.AssignWithConversion(aKeyName); michael@0: ToUpperCase(keyName); // We want case-insensitive comparison with data michael@0: // stored as uppercase. michael@0: michael@0: uint32_t keyNameLength = keyName.Length(); michael@0: const char* keyNameStr = keyName.get(); michael@0: for (uint16_t i = 0; i < (sizeof(gKeyCodes) / sizeof(gKeyCodes[0])); ++i) michael@0: if (keyNameLength == gKeyCodes[i].strlength && michael@0: !nsCRT::strcmp(gKeyCodes[i].str, keyNameStr)) michael@0: return gKeyCodes[i].keycode; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int32_t nsXBLPrototypeHandler::KeyToMask(int32_t key) michael@0: { michael@0: switch (key) michael@0: { michael@0: case nsIDOMKeyEvent::DOM_VK_META: michael@0: return cMeta | cMetaMask; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_WIN: michael@0: return cOS | cOSMask; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_ALT: michael@0: return cAlt | cAltMask; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_CONTROL: michael@0: default: michael@0: return cControl | cControlMask; michael@0: } michael@0: return cControl | cControlMask; // for warning avoidance michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeHandler::GetEventType(nsAString& aEvent) michael@0: { michael@0: nsCOMPtr handlerElement = GetHandlerElement(); michael@0: if (!handlerElement) { michael@0: aEvent.Truncate(); michael@0: return; michael@0: } michael@0: handlerElement->GetAttr(kNameSpaceID_None, nsGkAtoms::event, aEvent); michael@0: michael@0: if (aEvent.IsEmpty() && (mType & NS_HANDLER_TYPE_XUL)) michael@0: // If no type is specified for a XUL element, let's assume that we're "keypress". michael@0: aEvent.AssignLiteral("keypress"); michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeHandler::ConstructPrototype(nsIContent* aKeyElement, michael@0: const char16_t* aEvent, michael@0: const char16_t* aPhase, michael@0: const char16_t* aAction, michael@0: const char16_t* aCommand, michael@0: const char16_t* aKeyCode, michael@0: const char16_t* aCharCode, michael@0: const char16_t* aModifiers, michael@0: const char16_t* aButton, michael@0: const char16_t* aClickCount, michael@0: const char16_t* aGroup, michael@0: const char16_t* aPreventDefault, michael@0: const char16_t* aAllowUntrusted) michael@0: { michael@0: mType = 0; michael@0: michael@0: if (aKeyElement) { michael@0: mType |= NS_HANDLER_TYPE_XUL; michael@0: nsCOMPtr weak = do_GetWeakReference(aKeyElement); michael@0: if (!weak) { michael@0: return; michael@0: } michael@0: weak.swap(mHandlerElement); michael@0: } michael@0: else { michael@0: mType |= aCommand ? NS_HANDLER_TYPE_XBL_COMMAND : NS_HANDLER_TYPE_XBL_JS; michael@0: mHandlerText = nullptr; michael@0: } michael@0: michael@0: mDetail = -1; michael@0: mMisc = 0; michael@0: mKeyMask = 0; michael@0: mPhase = NS_PHASE_BUBBLING; michael@0: michael@0: if (aAction) michael@0: mHandlerText = ToNewUnicode(nsDependentString(aAction)); michael@0: else if (aCommand) michael@0: mHandlerText = ToNewUnicode(nsDependentString(aCommand)); michael@0: michael@0: nsAutoString event(aEvent); michael@0: if (event.IsEmpty()) { michael@0: if (mType & NS_HANDLER_TYPE_XUL) michael@0: GetEventType(event); michael@0: if (event.IsEmpty()) michael@0: return; michael@0: } michael@0: michael@0: mEventName = do_GetAtom(event); michael@0: michael@0: if (aPhase) { michael@0: const nsDependentString phase(aPhase); michael@0: if (phase.EqualsLiteral("capturing")) michael@0: mPhase = NS_PHASE_CAPTURING; michael@0: else if (phase.EqualsLiteral("target")) michael@0: mPhase = NS_PHASE_TARGET; michael@0: } michael@0: michael@0: // Button and clickcount apply only to XBL handlers and don't apply to XUL key michael@0: // handlers. michael@0: if (aButton && *aButton) michael@0: mDetail = *aButton - '0'; michael@0: michael@0: if (aClickCount && *aClickCount) michael@0: mMisc = *aClickCount - '0'; michael@0: michael@0: // Modifiers are supported by both types of handlers (XUL and XBL). michael@0: nsAutoString modifiers(aModifiers); michael@0: if (mType & NS_HANDLER_TYPE_XUL) michael@0: aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers); michael@0: michael@0: if (!modifiers.IsEmpty()) { michael@0: mKeyMask = cAllModifiers; michael@0: char* str = ToNewCString(modifiers); michael@0: char* newStr; michael@0: char* token = nsCRT::strtok( str, ", \t", &newStr ); michael@0: while( token != nullptr ) { michael@0: if (PL_strcmp(token, "shift") == 0) michael@0: mKeyMask |= cShift | cShiftMask; michael@0: else if (PL_strcmp(token, "alt") == 0) michael@0: mKeyMask |= cAlt | cAltMask; michael@0: else if (PL_strcmp(token, "meta") == 0) michael@0: mKeyMask |= cMeta | cMetaMask; michael@0: else if (PL_strcmp(token, "os") == 0) michael@0: mKeyMask |= cOS | cOSMask; michael@0: else if (PL_strcmp(token, "control") == 0) michael@0: mKeyMask |= cControl | cControlMask; michael@0: else if (PL_strcmp(token, "accel") == 0) michael@0: mKeyMask |= KeyToMask(kAccelKey); michael@0: else if (PL_strcmp(token, "access") == 0) michael@0: mKeyMask |= KeyToMask(kMenuAccessKey); michael@0: else if (PL_strcmp(token, "any") == 0) michael@0: mKeyMask &= ~(mKeyMask << 5); michael@0: michael@0: token = nsCRT::strtok( newStr, ", \t", &newStr ); michael@0: } michael@0: michael@0: nsMemory::Free(str); michael@0: } michael@0: michael@0: nsAutoString key(aCharCode); michael@0: if (key.IsEmpty()) { michael@0: if (mType & NS_HANDLER_TYPE_XUL) { michael@0: aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key); michael@0: if (key.IsEmpty()) michael@0: aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::charcode, key); michael@0: } michael@0: } michael@0: michael@0: if (!key.IsEmpty()) { michael@0: if (mKeyMask == 0) michael@0: mKeyMask = cAllModifiers; michael@0: ToLowerCase(key); michael@0: michael@0: // We have a charcode. michael@0: mMisc = 1; michael@0: mDetail = key[0]; michael@0: const uint8_t GTK2Modifiers = cShift | cControl | cShiftMask | cControlMask; michael@0: if ((mKeyMask & GTK2Modifiers) == GTK2Modifiers && michael@0: modifiers.First() != char16_t(',') && michael@0: (mDetail == 'u' || mDetail == 'U')) michael@0: ReportKeyConflict(key.get(), modifiers.get(), aKeyElement, "GTK2Conflict"); michael@0: const uint8_t WinModifiers = cControl | cAlt | cControlMask | cAltMask; michael@0: if ((mKeyMask & WinModifiers) == WinModifiers && michael@0: modifiers.First() != char16_t(',') && michael@0: (('A' <= mDetail && mDetail <= 'Z') || michael@0: ('a' <= mDetail && mDetail <= 'z'))) michael@0: ReportKeyConflict(key.get(), modifiers.get(), aKeyElement, "WinConflict"); michael@0: } michael@0: else { michael@0: key.Assign(aKeyCode); michael@0: if (mType & NS_HANDLER_TYPE_XUL) michael@0: aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, key); michael@0: michael@0: if (!key.IsEmpty()) { michael@0: if (mKeyMask == 0) michael@0: mKeyMask = cAllModifiers; michael@0: mDetail = GetMatchingKeyCode(key); michael@0: } michael@0: } michael@0: michael@0: if (aGroup && nsDependentString(aGroup).EqualsLiteral("system")) michael@0: mType |= NS_HANDLER_TYPE_SYSTEM; michael@0: michael@0: if (aPreventDefault && michael@0: nsDependentString(aPreventDefault).EqualsLiteral("true")) michael@0: mType |= NS_HANDLER_TYPE_PREVENTDEFAULT; michael@0: michael@0: if (aAllowUntrusted) { michael@0: mType |= NS_HANDLER_HAS_ALLOW_UNTRUSTED_ATTR; michael@0: if (nsDependentString(aAllowUntrusted).EqualsLiteral("true")) { michael@0: mType |= NS_HANDLER_ALLOW_UNTRUSTED; michael@0: } else { michael@0: mType &= ~NS_HANDLER_ALLOW_UNTRUSTED; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeHandler::ReportKeyConflict(const char16_t* aKey, const char16_t* aModifiers, nsIContent* aKeyElement, const char *aMessageName) michael@0: { michael@0: nsCOMPtr doc; michael@0: if (mPrototypeBinding) { michael@0: nsXBLDocumentInfo* docInfo = mPrototypeBinding->XBLDocumentInfo(); michael@0: if (docInfo) { michael@0: doc = docInfo->GetDocument(); michael@0: } michael@0: } else if (aKeyElement) { michael@0: doc = aKeyElement->OwnerDoc(); michael@0: } michael@0: michael@0: const char16_t* params[] = { aKey, aModifiers }; michael@0: nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, michael@0: NS_LITERAL_CSTRING("XBL Prototype Handler"), doc, michael@0: nsContentUtils::eXBL_PROPERTIES, michael@0: aMessageName, michael@0: params, ArrayLength(params), michael@0: nullptr, EmptyString(), mLineNumber); michael@0: } michael@0: michael@0: bool michael@0: nsXBLPrototypeHandler::ModifiersMatchMask(nsIDOMUIEvent* aEvent, michael@0: bool aIgnoreShiftKey) michael@0: { michael@0: WidgetInputEvent* inputEvent = aEvent->GetInternalNSEvent()->AsInputEvent(); michael@0: NS_ENSURE_TRUE(inputEvent, false); michael@0: michael@0: if (mKeyMask & cMetaMask) { michael@0: if (inputEvent->IsMeta() != ((mKeyMask & cMeta) != 0)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (mKeyMask & cOSMask) { michael@0: if (inputEvent->IsOS() != ((mKeyMask & cOS) != 0)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (mKeyMask & cShiftMask && !aIgnoreShiftKey) { michael@0: if (inputEvent->IsShift() != ((mKeyMask & cShift) != 0)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (mKeyMask & cAltMask) { michael@0: if (inputEvent->IsAlt() != ((mKeyMask & cAlt) != 0)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (mKeyMask & cControlMask) { michael@0: if (inputEvent->IsControl() != ((mKeyMask & cControl) != 0)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeHandler::Read(nsIObjectInputStream* aStream) michael@0: { michael@0: AssertInCompilationScope(); michael@0: nsresult rv = aStream->Read8(&mPhase); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aStream->Read8(&mType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aStream->Read8(&mMisc); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aStream->Read32(reinterpret_cast(&mKeyMask)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: uint32_t detail; michael@0: rv = aStream->Read32(&detail); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mDetail = detail; michael@0: michael@0: nsAutoString name; michael@0: rv = aStream->ReadString(name); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mEventName = do_GetAtom(name); michael@0: michael@0: rv = aStream->Read32(&mLineNumber); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString handlerText; michael@0: rv = aStream->ReadString(handlerText); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!handlerText.IsEmpty()) michael@0: mHandlerText = ToNewUnicode(handlerText); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeHandler::Write(nsIObjectOutputStream* aStream) michael@0: { michael@0: AssertInCompilationScope(); michael@0: // Make sure we don't write out NS_HANDLER_TYPE_XUL types, as they are used michael@0: // for elements. michael@0: if ((mType & NS_HANDLER_TYPE_XUL) || !mEventName) michael@0: return NS_OK; michael@0: michael@0: XBLBindingSerializeDetails type = XBLBinding_Serialize_Handler; michael@0: michael@0: nsresult rv = aStream->Write8(type); michael@0: rv = aStream->Write8(mPhase); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aStream->Write8(mType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aStream->Write8(mMisc); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aStream->Write32(static_cast(mKeyMask)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aStream->Write32(mDetail); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aStream->WriteWStringZ(nsDependentAtomString(mEventName).get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aStream->Write32(mLineNumber); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return aStream->WriteWStringZ(mHandlerText ? mHandlerText : MOZ_UTF16("")); michael@0: }