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 "nsCOMPtr.h" michael@0: #include "nsXBLPrototypeHandler.h" michael@0: #include "nsXBLWindowKeyHandler.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsXBLService.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsXBLDocumentInfo.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsFocusManager.h" michael@0: #include "nsIURI.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsXBLPrototypeBinding.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIPresShell.h" michael@0: #include "mozilla/EventStateManager.h" michael@0: #include "nsISelectionController.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/TextEvents.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/Event.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsIHTMLEditor.h" michael@0: #include "nsIDOMDocument.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: class nsXBLSpecialDocInfo : public nsIObserver michael@0: { michael@0: public: michael@0: nsRefPtr mHTMLBindings; michael@0: nsRefPtr mUserHTMLBindings; michael@0: michael@0: static const char sHTMLBindingStr[]; michael@0: static const char sUserHTMLBindingStr[]; michael@0: michael@0: bool mInitialized; michael@0: michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: michael@0: void LoadDocInfo(); michael@0: void GetAllHandlers(const char* aType, michael@0: nsXBLPrototypeHandler** handler, michael@0: nsXBLPrototypeHandler** userHandler); michael@0: void GetHandlers(nsXBLDocumentInfo* aInfo, michael@0: const nsACString& aRef, michael@0: nsXBLPrototypeHandler** aResult); michael@0: michael@0: nsXBLSpecialDocInfo() : mInitialized(false) {} michael@0: michael@0: virtual ~nsXBLSpecialDocInfo() {} michael@0: michael@0: }; michael@0: michael@0: const char nsXBLSpecialDocInfo::sHTMLBindingStr[] = michael@0: "chrome://global/content/platformHTMLBindings.xml"; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsXBLSpecialDocInfo, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: nsXBLSpecialDocInfo::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"), "wrong topic"); michael@0: michael@0: // On shutdown, clear our fields to avoid an extra cycle collection. michael@0: mHTMLBindings = nullptr; michael@0: mUserHTMLBindings = nullptr; michael@0: mInitialized = false; michael@0: nsContentUtils::UnregisterShutdownObserver(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsXBLSpecialDocInfo::LoadDocInfo() michael@0: { michael@0: if (mInitialized) michael@0: return; michael@0: mInitialized = true; michael@0: nsContentUtils::RegisterShutdownObserver(this); michael@0: michael@0: nsXBLService* xblService = nsXBLService::GetInstance(); michael@0: if (!xblService) michael@0: return; michael@0: michael@0: // Obtain the platform doc info michael@0: nsCOMPtr bindingURI; michael@0: NS_NewURI(getter_AddRefs(bindingURI), sHTMLBindingStr); michael@0: if (!bindingURI) { michael@0: return; michael@0: } michael@0: xblService->LoadBindingDocumentInfo(nullptr, nullptr, michael@0: bindingURI, michael@0: nullptr, michael@0: true, michael@0: getter_AddRefs(mHTMLBindings)); michael@0: michael@0: const nsAdoptingCString& userHTMLBindingStr = michael@0: Preferences::GetCString("dom.userHTMLBindings.uri"); michael@0: if (!userHTMLBindingStr.IsEmpty()) { michael@0: NS_NewURI(getter_AddRefs(bindingURI), userHTMLBindingStr); michael@0: if (!bindingURI) { michael@0: return; michael@0: } michael@0: michael@0: xblService->LoadBindingDocumentInfo(nullptr, nullptr, michael@0: bindingURI, michael@0: nullptr, michael@0: true, michael@0: getter_AddRefs(mUserHTMLBindings)); michael@0: } michael@0: } michael@0: michael@0: // michael@0: // GetHandlers michael@0: // michael@0: // michael@0: void michael@0: nsXBLSpecialDocInfo::GetHandlers(nsXBLDocumentInfo* aInfo, michael@0: const nsACString& aRef, michael@0: nsXBLPrototypeHandler** aResult) michael@0: { michael@0: nsXBLPrototypeBinding* binding = aInfo->GetPrototypeBinding(aRef); michael@0: michael@0: NS_ASSERTION(binding, "No binding found for the XBL window key handler."); michael@0: if (!binding) michael@0: return; michael@0: michael@0: *aResult = binding->GetPrototypeHandlers(); michael@0: } michael@0: michael@0: void michael@0: nsXBLSpecialDocInfo::GetAllHandlers(const char* aType, michael@0: nsXBLPrototypeHandler** aHandler, michael@0: nsXBLPrototypeHandler** aUserHandler) michael@0: { michael@0: if (mUserHTMLBindings) { michael@0: nsAutoCString type(aType); michael@0: type.Append("User"); michael@0: GetHandlers(mUserHTMLBindings, type, aUserHandler); michael@0: } michael@0: if (mHTMLBindings) { michael@0: GetHandlers(mHTMLBindings, nsDependentCString(aType), aHandler); michael@0: } michael@0: } michael@0: michael@0: // Init statics michael@0: nsXBLSpecialDocInfo* nsXBLWindowKeyHandler::sXBLSpecialDocInfo = nullptr; michael@0: uint32_t nsXBLWindowKeyHandler::sRefCnt = 0; michael@0: michael@0: nsXBLWindowKeyHandler::nsXBLWindowKeyHandler(nsIDOMElement* aElement, michael@0: EventTarget* aTarget) michael@0: : mTarget(aTarget), michael@0: mHandler(nullptr), michael@0: mUserHandler(nullptr) michael@0: { michael@0: mWeakPtrForElement = do_GetWeakReference(aElement); michael@0: ++sRefCnt; michael@0: } michael@0: michael@0: nsXBLWindowKeyHandler::~nsXBLWindowKeyHandler() michael@0: { michael@0: // If mWeakPtrForElement is non-null, we created a prototype handler. michael@0: if (mWeakPtrForElement) michael@0: delete mHandler; michael@0: michael@0: --sRefCnt; michael@0: if (!sRefCnt) { michael@0: NS_IF_RELEASE(sXBLSpecialDocInfo); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsXBLWindowKeyHandler, michael@0: nsIDOMEventListener) michael@0: michael@0: static void michael@0: BuildHandlerChain(nsIContent* aContent, nsXBLPrototypeHandler** aResult) michael@0: { michael@0: *aResult = nullptr; michael@0: michael@0: // Since we chain each handler onto the next handler, michael@0: // we'll enumerate them here in reverse so that when we michael@0: // walk the chain they'll come out in the original order michael@0: for (nsIContent* key = aContent->GetLastChild(); michael@0: key; michael@0: key = key->GetPreviousSibling()) { michael@0: michael@0: if (key->NodeInfo()->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) { michael@0: // Check whether the key element has empty value at key/char attribute. michael@0: // Such element is used by localizers for alternative shortcut key michael@0: // definition on the locale. See bug 426501. michael@0: nsAutoString valKey, valCharCode, valKeyCode; michael@0: bool attrExists = michael@0: key->GetAttr(kNameSpaceID_None, nsGkAtoms::key, valKey) || michael@0: key->GetAttr(kNameSpaceID_None, nsGkAtoms::charcode, valCharCode) || michael@0: key->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, valKeyCode); michael@0: if (attrExists && michael@0: valKey.IsEmpty() && valCharCode.IsEmpty() && valKeyCode.IsEmpty()) michael@0: continue; michael@0: michael@0: nsXBLPrototypeHandler* handler = new nsXBLPrototypeHandler(key); michael@0: michael@0: if (!handler) michael@0: return; michael@0: michael@0: handler->SetNextHandler(*aResult); michael@0: *aResult = handler; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // michael@0: // EnsureHandlers michael@0: // michael@0: // Lazily load the XBL handlers. Overridden to handle being attached michael@0: // to a particular element rather than the document michael@0: // michael@0: nsresult michael@0: nsXBLWindowKeyHandler::EnsureHandlers() michael@0: { michael@0: nsCOMPtr el = GetElement(); michael@0: NS_ENSURE_STATE(!mWeakPtrForElement || el); michael@0: if (el) { michael@0: // We are actually a XUL . michael@0: if (mHandler) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr content(do_QueryInterface(el)); michael@0: BuildHandlerChain(content, &mHandler); michael@0: } else { // We are an XBL file of handlers. michael@0: if (!sXBLSpecialDocInfo) { michael@0: sXBLSpecialDocInfo = new nsXBLSpecialDocInfo(); michael@0: NS_ADDREF(sXBLSpecialDocInfo); michael@0: } michael@0: sXBLSpecialDocInfo->LoadDocInfo(); michael@0: michael@0: // Now determine which handlers we should be using. michael@0: if (IsHTMLEditableFieldFocused()) { michael@0: sXBLSpecialDocInfo->GetAllHandlers("editor", &mHandler, &mUserHandler); michael@0: } michael@0: else { michael@0: sXBLSpecialDocInfo->GetAllHandlers("browser", &mHandler, &mUserHandler); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLWindowKeyHandler::WalkHandlers(nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventType) michael@0: { michael@0: bool prevent; michael@0: aKeyEvent->GetDefaultPrevented(&prevent); michael@0: if (prevent) michael@0: return NS_OK; michael@0: michael@0: bool trustedEvent = false; michael@0: // Don't process the event if it was not dispatched from a trusted source michael@0: aKeyEvent->GetIsTrusted(&trustedEvent); michael@0: michael@0: if (!trustedEvent) michael@0: return NS_OK; michael@0: michael@0: nsresult rv = EnsureHandlers(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool isDisabled; michael@0: nsCOMPtr el = GetElement(&isDisabled); michael@0: if (!el) { michael@0: if (mUserHandler) { michael@0: WalkHandlersInternal(aKeyEvent, aEventType, mUserHandler, true); michael@0: aKeyEvent->GetDefaultPrevented(&prevent); michael@0: if (prevent) michael@0: return NS_OK; // Handled by the user bindings. Our work here is done. michael@0: } michael@0: } michael@0: michael@0: // skip keysets that are disabled michael@0: if (el && isDisabled) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: WalkHandlersInternal(aKeyEvent, aEventType, mHandler, true); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXBLWindowKeyHandler::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsCOMPtr keyEvent(do_QueryInterface(aEvent)); michael@0: NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG); michael@0: michael@0: uint16_t eventPhase; michael@0: aEvent->GetEventPhase(&eventPhase); michael@0: if (eventPhase == nsIDOMEvent::CAPTURING_PHASE) { michael@0: HandleEventOnCapture(keyEvent); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString eventType; michael@0: aEvent->GetType(eventType); michael@0: nsCOMPtr eventTypeAtom = do_GetAtom(eventType); michael@0: NS_ENSURE_TRUE(eventTypeAtom, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: return WalkHandlers(keyEvent, eventTypeAtom); michael@0: } michael@0: michael@0: void michael@0: nsXBLWindowKeyHandler::HandleEventOnCapture(nsIDOMKeyEvent* aEvent) michael@0: { michael@0: WidgetKeyboardEvent* widgetEvent = michael@0: aEvent->GetInternalNSEvent()->AsKeyboardEvent(); michael@0: michael@0: if (widgetEvent->mFlags.mNoCrossProcessBoundaryForwarding) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr originalTarget = michael@0: do_QueryInterface(aEvent->GetInternalNSEvent()->originalTarget); michael@0: if (!EventStateManager::IsRemoteTarget(originalTarget)) { michael@0: return; michael@0: } michael@0: michael@0: if (!HasHandlerForEvent(aEvent)) { michael@0: return; michael@0: } michael@0: michael@0: // If this event hasn't been marked as mNoCrossProcessBoundaryForwarding michael@0: // yet, it means it wasn't processed by content. We'll not call any michael@0: // of the handlers at this moment, and will wait for the event to be michael@0: // redispatched with mNoCrossProcessBoundaryForwarding = 1 to process it. michael@0: michael@0: // Inform the child process that this is a event that we want a reply michael@0: // from. michael@0: widgetEvent->mFlags.mWantReplyFromContentProcess = 1; michael@0: aEvent->StopPropagation(); michael@0: } michael@0: michael@0: // michael@0: // EventMatched michael@0: // michael@0: // See if the given handler cares about this particular key event michael@0: // michael@0: bool michael@0: nsXBLWindowKeyHandler::EventMatched(nsXBLPrototypeHandler* inHandler, michael@0: nsIAtom* inEventType, michael@0: nsIDOMKeyEvent* inEvent, michael@0: uint32_t aCharCode, bool aIgnoreShiftKey) michael@0: { michael@0: return inHandler->KeyEventMatched(inEventType, inEvent, aCharCode, michael@0: aIgnoreShiftKey); michael@0: } michael@0: michael@0: bool michael@0: nsXBLWindowKeyHandler::IsHTMLEditableFieldFocused() michael@0: { michael@0: nsIFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: if (!fm) michael@0: return false; michael@0: michael@0: nsCOMPtr focusedWindow; michael@0: fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); michael@0: if (!focusedWindow) michael@0: return false; michael@0: michael@0: nsCOMPtr piwin(do_QueryInterface(focusedWindow)); michael@0: nsIDocShell *docShell = piwin->GetDocShell(); michael@0: if (!docShell) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr editor; michael@0: docShell->GetEditor(getter_AddRefs(editor)); michael@0: nsCOMPtr htmlEditor = do_QueryInterface(editor); michael@0: if (!htmlEditor) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr domDocument; michael@0: editor->GetDocument(getter_AddRefs(domDocument)); michael@0: nsCOMPtr doc = do_QueryInterface(domDocument); michael@0: if (doc->HasFlag(NODE_IS_EDITABLE)) { michael@0: // Don't need to perform any checks in designMode documents. michael@0: return true; michael@0: } michael@0: michael@0: nsCOMPtr focusedElement; michael@0: fm->GetFocusedElement(getter_AddRefs(focusedElement)); michael@0: nsCOMPtr focusedNode = do_QueryInterface(focusedElement); michael@0: if (focusedNode) { michael@0: // If there is a focused element, make sure it's in the active editing host. michael@0: // Note that GetActiveEditingHost finds the current editing host based on michael@0: // the document's selection. Even though the document selection is usually michael@0: // collapsed to where the focus is, but the page may modify the selection michael@0: // without our knowledge, in which case this check will do something useful. michael@0: nsCOMPtr activeEditingHost = htmlEditor->GetActiveEditingHost(); michael@0: if (!activeEditingHost) { michael@0: return false; michael@0: } michael@0: return nsContentUtils::ContentIsDescendantOf(focusedNode, activeEditingHost); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // michael@0: // WalkHandlersInternal and WalkHandlersAndExecute michael@0: // michael@0: // Given a particular DOM event and a pointer to the first handler in the list, michael@0: // scan through the list to find something to handle the event. If aExecute = true, michael@0: // the handler will be executed; otherwise just return an answer telling if a handler michael@0: // for that event was found. michael@0: // michael@0: bool michael@0: nsXBLWindowKeyHandler::WalkHandlersInternal(nsIDOMKeyEvent* aKeyEvent, michael@0: nsIAtom* aEventType, michael@0: nsXBLPrototypeHandler* aHandler, michael@0: bool aExecute) michael@0: { michael@0: nsAutoTArray accessKeys; michael@0: nsContentUtils::GetAccelKeyCandidates(aKeyEvent, accessKeys); michael@0: michael@0: if (accessKeys.IsEmpty()) { michael@0: return WalkHandlersAndExecute(aKeyEvent, aEventType, aHandler, michael@0: 0, false, aExecute); michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < accessKeys.Length(); ++i) { michael@0: nsShortcutCandidate &key = accessKeys[i]; michael@0: if (WalkHandlersAndExecute(aKeyEvent, aEventType, aHandler, michael@0: key.mCharCode, key.mIgnoreShift, aExecute)) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsXBLWindowKeyHandler::WalkHandlersAndExecute(nsIDOMKeyEvent* aKeyEvent, michael@0: nsIAtom* aEventType, michael@0: nsXBLPrototypeHandler* aHandler, michael@0: uint32_t aCharCode, michael@0: bool aIgnoreShiftKey, michael@0: bool aExecute) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Try all of the handlers until we find one that matches the event. michael@0: for (nsXBLPrototypeHandler *currHandler = aHandler; currHandler; michael@0: currHandler = currHandler->GetNextHandler()) { michael@0: bool stopped = aKeyEvent->IsDispatchStopped(); michael@0: if (stopped) { michael@0: // The event is finished, don't execute any more handlers michael@0: return false; michael@0: } michael@0: michael@0: if (!EventMatched(currHandler, aEventType, aKeyEvent, michael@0: aCharCode, aIgnoreShiftKey)) michael@0: continue; // try the next one michael@0: michael@0: // Before executing this handler, check that it's not disabled, michael@0: // and that it has something to do (oncommand of the or its michael@0: // is non-empty). michael@0: nsCOMPtr elt = currHandler->GetHandlerElement(); michael@0: nsCOMPtr commandElt; michael@0: michael@0: // See if we're in a XUL doc. michael@0: nsCOMPtr el = GetElement(); michael@0: if (el && elt) { michael@0: // We are. Obtain our command attribute. michael@0: nsAutoString command; michael@0: elt->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command); michael@0: if (!command.IsEmpty()) { michael@0: // Locate the command element in question. Note that we michael@0: // know "elt" is in a doc if we're dealing with it here. michael@0: NS_ASSERTION(elt->IsInDoc(), "elt must be in document"); michael@0: nsIDocument *doc = elt->GetCurrentDoc(); michael@0: if (doc) michael@0: commandElt = do_QueryInterface(doc->GetElementById(command)); michael@0: michael@0: if (!commandElt) { michael@0: NS_ERROR("A XUL is observing a command that doesn't exist. Unable to execute key binding!"); michael@0: continue; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!commandElt) { michael@0: commandElt = do_QueryInterface(elt); michael@0: } michael@0: michael@0: if (commandElt) { michael@0: nsAutoString value; michael@0: commandElt->GetAttribute(NS_LITERAL_STRING("disabled"), value); michael@0: if (value.EqualsLiteral("true")) { michael@0: continue; // this handler is disabled, try the next one michael@0: } michael@0: michael@0: // Check that there is an oncommand handler michael@0: commandElt->GetAttribute(NS_LITERAL_STRING("oncommand"), value); michael@0: if (value.IsEmpty()) { michael@0: continue; // nothing to do michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr piTarget; michael@0: nsCOMPtr element = GetElement(); michael@0: if (element) { michael@0: piTarget = commandElt; michael@0: } else { michael@0: piTarget = mTarget; michael@0: } michael@0: michael@0: if (!aExecute) { michael@0: return true; michael@0: } michael@0: michael@0: rv = currHandler->ExecuteHandler(piTarget, aKeyEvent); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsXBLWindowKeyHandler::HasHandlerForEvent(nsIDOMKeyEvent* aEvent) michael@0: { michael@0: if (!aEvent->InternalDOMEvent()->IsTrusted()) { michael@0: return false; michael@0: } michael@0: michael@0: nsresult rv = EnsureHandlers(); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: bool isDisabled; michael@0: nsCOMPtr el = GetElement(&isDisabled); michael@0: if (el && isDisabled) { michael@0: return false; michael@0: } michael@0: michael@0: nsAutoString eventType; michael@0: aEvent->GetType(eventType); michael@0: nsCOMPtr eventTypeAtom = do_GetAtom(eventType); michael@0: NS_ENSURE_TRUE(eventTypeAtom, false); michael@0: michael@0: return WalkHandlersInternal(aEvent, eventTypeAtom, mHandler, false); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsXBLWindowKeyHandler::GetElement(bool* aIsDisabled) michael@0: { michael@0: nsCOMPtr element = do_QueryReferent(mWeakPtrForElement); michael@0: if (element && aIsDisabled) { michael@0: *aIsDisabled = element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, michael@0: nsGkAtoms::_true, eCaseMatters); michael@0: } michael@0: return element.forget(); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: already_AddRefed michael@0: NS_NewXBLWindowKeyHandler(nsIDOMElement* aElement, EventTarget* aTarget) michael@0: { michael@0: nsRefPtr result = michael@0: new nsXBLWindowKeyHandler(aElement, aTarget); michael@0: return result.forget(); michael@0: }