/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "HardwareKeyHandler.h" #include "mozilla/BasicEvents.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/dom/KeyboardEvent.h" #include "mozilla/dom/TabParent.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStateManager.h" #include "mozilla/TextEvents.h" #include "nsDeque.h" #include "nsFocusManager.h" #include "nsFrameLoader.h" #include "nsIContent.h" #include "nsIDOMHTMLDocument.h" #include "nsIDOMHTMLElement.h" #include "nsPIDOMWindow.h" #include "nsPresContext.h" #include "nsPresShell.h" namespace mozilla { using namespace dom; NS_IMPL_ISUPPORTS(HardwareKeyHandler, nsIHardwareKeyHandler) StaticRefPtr HardwareKeyHandler::sInstance; HardwareKeyHandler::HardwareKeyHandler() : mInputMethodAppConnected(false) { } HardwareKeyHandler::~HardwareKeyHandler() { } NS_IMETHODIMP HardwareKeyHandler::OnInputMethodAppConnected() { if (NS_WARN_IF(mInputMethodAppConnected)) { return NS_ERROR_UNEXPECTED; } mInputMethodAppConnected = true; return NS_OK; } NS_IMETHODIMP HardwareKeyHandler::OnInputMethodAppDisconnected() { if (NS_WARN_IF(!mInputMethodAppConnected)) { return NS_ERROR_UNEXPECTED; } mInputMethodAppConnected = false; return NS_OK; } NS_IMETHODIMP HardwareKeyHandler::RegisterListener(nsIHardwareKeyEventListener* aListener) { // Make sure the listener is not nullptr and there is no available // hardwareKeyEventListener now if (NS_WARN_IF(!aListener)) { return NS_ERROR_NULL_POINTER; } if (NS_WARN_IF(mHardwareKeyEventListener)) { return NS_ERROR_ALREADY_INITIALIZED; } mHardwareKeyEventListener = do_GetWeakReference(aListener); if (NS_WARN_IF(!mHardwareKeyEventListener)) { return NS_ERROR_NULL_POINTER; } return NS_OK; } NS_IMETHODIMP HardwareKeyHandler::UnregisterListener() { // Clear the HardwareKeyEventListener mHardwareKeyEventListener = nullptr; return NS_OK; } bool HardwareKeyHandler::ForwardKeyToInputMethodApp(nsINode* aTarget, WidgetKeyboardEvent* aEvent, nsEventStatus* aEventStatus) { MOZ_ASSERT(aTarget, "No target provided"); MOZ_ASSERT(aEvent, "No event provided"); // No need to forward hardware key event to IME // if key's defaultPrevented is true if (aEvent->mFlags.mDefaultPrevented) { return false; } // No need to forward hardware key event to IME if IME is disabled if (!mInputMethodAppConnected) { return false; } // No need to forward hardware key event to IME // if this key event is generated by IME itself(from nsITextInputProcessor) if (aEvent->mIsSynthesizedByTIP) { return false; } // No need to forward hardware key event to IME // if the key event is handling or already handled if (aEvent->mInputMethodAppState != WidgetKeyboardEvent::eNotHandled) { return false; } // No need to forward hardware key event to IME // if there is no nsIHardwareKeyEventListener in use nsCOMPtr keyHandler(do_QueryReferent(mHardwareKeyEventListener)); if (!keyHandler) { return false; } // Set the flags to specify the keyboard event is in forwarding phase. aEvent->mInputMethodAppState = WidgetKeyboardEvent::eHandling; // For those keypress events coming after their heading keydown's reply // already arrives, they should be dispatched directly instead of // being stored into the event queue. Otherwise, without the heading keydown // in the event queue, the stored keypress will never be withdrawn to be fired. if (aEvent->mMessage == eKeyPress && mEventQueue.IsEmpty()) { DispatchKeyPress(aTarget, *aEvent, *aEventStatus); return true; } // Push the key event into queue for reuse when its reply arrives. KeyboardInfo* copiedInfo = new KeyboardInfo(aTarget, *aEvent, aEventStatus ? *aEventStatus : nsEventStatus_eIgnore); // No need to forward hardware key event to IME if the event queue is full if (!mEventQueue.Push(copiedInfo)) { delete copiedInfo; return false; } // We only forward keydown and keyup event to input-method-app // because input-method-app will generate keypress by itself. if (aEvent->mMessage == eKeyPress) { return true; } // Create a keyboard event to pass into // nsIHardwareKeyEventListener.onHardwareKey nsCOMPtr eventTarget = do_QueryInterface(aTarget); nsPresContext* presContext = GetPresContext(aTarget); RefPtr keyboardEvent = NS_NewDOMKeyboardEvent(eventTarget, presContext, aEvent->AsKeyboardEvent()); // Duplicate the internal event data in the heap for the keyboardEvent, // or the internal data from |aEvent| in the stack may be destroyed by others. keyboardEvent->DuplicatePrivateData(); // Forward the created keyboard event to input-method-app bool isSent = false; keyHandler->OnHardwareKey(keyboardEvent, &isSent); // Pop the pending key event if it can't be forwarded if (!isSent) { mEventQueue.RemoveFront(); } return isSent; } NS_IMETHODIMP HardwareKeyHandler::OnHandledByInputMethodApp(const nsAString& aType, uint16_t aDefaultPrevented) { // We can not handle this reply because the pending events had been already // removed from the forwarding queue before this reply arrives. if (mEventQueue.IsEmpty()) { return NS_OK; } RefPtr keyInfo = mEventQueue.PopFront(); // Only allow keydown and keyup to call this method if (NS_WARN_IF(aType.EqualsLiteral("keydown") && keyInfo->mEvent.mMessage != eKeyDown) || NS_WARN_IF(aType.EqualsLiteral("keyup") && keyInfo->mEvent.mMessage != eKeyUp)) { return NS_ERROR_INVALID_ARG; } // The value of defaultPrevented depends on whether or not // the key is consumed by input-method-app SetDefaultPrevented(keyInfo->mEvent, aDefaultPrevented); // Set the flag to specify the reply phase keyInfo->mEvent.mInputMethodAppState = WidgetKeyboardEvent::eHandled; // Check whether the event is still valid to be fired if (CanDispatchEvent(keyInfo->mTarget, keyInfo->mEvent)) { // If the key's defaultPrevented is true, it means that the // input-method-app has already consumed this key, // so we can dispatch |mozbrowserafterkey*| directly if // preference "dom.beforeAfterKeyboardEvent.enabled" is enabled. if (keyInfo->mEvent.mFlags.mDefaultPrevented) { DispatchAfterKeyEvent(keyInfo->mTarget, keyInfo->mEvent); // Otherwise, it means that input-method-app doesn't handle this key, // so we need to dispatch it to its current event target. } else { DispatchToTargetApp(keyInfo->mTarget, keyInfo->mEvent, keyInfo->mStatus); } } // No need to do further processing if the event is not keydown if (keyInfo->mEvent.mMessage != eKeyDown) { return NS_OK; } // Update the latest keydown data: // Release the holding reference to the previous keydown's data and // add a reference count to the current keydown's data. mLatestKeyDownInfo = keyInfo; // Handle the pending keypress event once keydown's reply arrives: // It may have many keypress events per keydown on some platforms, // so we use loop to dispatch keypress events. // (But Gonk dispatch only one keypress per keydown) // However, if there is no keypress after this keydown, // then those following keypress will be handled in // ForwardKeyToInputMethodApp directly. for (KeyboardInfo* keypressInfo; !mEventQueue.IsEmpty() && (keypressInfo = mEventQueue.PeekFront()) && keypressInfo->mEvent.mMessage == eKeyPress; mEventQueue.RemoveFront()) { DispatchKeyPress(keypressInfo->mTarget, keypressInfo->mEvent, keypressInfo->mStatus); } return NS_OK; } bool HardwareKeyHandler::DispatchKeyPress(nsINode* aTarget, WidgetKeyboardEvent& aEvent, nsEventStatus& aStatus) { MOZ_ASSERT(aTarget, "No target provided"); MOZ_ASSERT(aEvent.mMessage == eKeyPress, "Event is not keypress"); // No need to dispatch keypress to the event target // if the keydown event is consumed by the input-method-app. if (mLatestKeyDownInfo && mLatestKeyDownInfo->mEvent.mFlags.mDefaultPrevented) { return false; } // No need to dispatch keypress to the event target // if the previous keydown event is modifier key's if (mLatestKeyDownInfo && mLatestKeyDownInfo->mEvent.IsModifierKeyEvent()) { return false; } // No need to dispatch keypress to the event target // if it's invalid to be dispatched if (!CanDispatchEvent(aTarget, aEvent)) { return false; } // Set the flag to specify the reply phase. aEvent.mInputMethodAppState = WidgetKeyboardEvent::eHandled; // Dispatch the pending keypress event bool ret = DispatchToTargetApp(aTarget, aEvent, aStatus); // Re-trigger EventStateManager::PostHandleKeyboardEvent for keypress PostHandleKeyboardEvent(aTarget, aEvent, aStatus); return ret; } void HardwareKeyHandler::DispatchAfterKeyEvent(nsINode* aTarget, WidgetKeyboardEvent& aEvent) { MOZ_ASSERT(aTarget, "No target provided"); if (!PresShell::BeforeAfterKeyboardEventEnabled() || aEvent.mMessage == eKeyPress) { return; } nsCOMPtr presShell = GetPresShell(aTarget); if (NS_WARN_IF(presShell)) { presShell->DispatchAfterKeyboardEvent(aTarget, aEvent, aEvent.mFlags.mDefaultPrevented); } } bool HardwareKeyHandler::DispatchToTargetApp(nsINode* aTarget, WidgetKeyboardEvent& aEvent, nsEventStatus& aStatus) { MOZ_ASSERT(aTarget, "No target provided"); // Get current focused element as the event target nsCOMPtr currentTarget = GetCurrentTarget(); if (NS_WARN_IF(!currentTarget)) { return false; } // The event target should be set to the current focused element. // However, it might have security issue if the event is dispatched to // the unexpected application, and it might cause unexpected operation // in the new app. nsCOMPtr originalRootWindow = GetRootWindow(aTarget); nsCOMPtr currentRootWindow = GetRootWindow(currentTarget); if (currentRootWindow != originalRootWindow) { NS_WARNING("The root window is changed during the event is dispatching"); return false; } // If the current focused element is still in the same app, // then we can use it as the current target to dispatch event. nsCOMPtr presShell = GetPresShell(currentTarget); if (!presShell) { return false; } if (!presShell->CanDispatchEvent(&aEvent)) { return false; } // In-process case: the event target is in the current process if (!PresShell::IsTargetIframe(currentTarget)) { DispatchToCurrentProcess(presShell, currentTarget, aEvent, aStatus); if (presShell->CanDispatchEvent(&aEvent)) { DispatchAfterKeyEvent(aTarget, aEvent); } return true; } // OOP case: the event target is in the child process return DispatchToCrossProcess(aTarget, aEvent); // After the oop target receives the event from TabChild::RecvRealKeyEvent // and return the result through TabChild::SendDispatchAfterKeyboardEvent, // the |mozbrowserafterkey*| will be fired from // TabParent::RecvDispatchAfterKeyboardEvent, so we don't need to dispatch // |mozbrowserafterkey*| by ourselves in this module. } void HardwareKeyHandler::DispatchToCurrentProcess(nsIPresShell* presShell, nsIContent* aTarget, WidgetKeyboardEvent& aEvent, nsEventStatus& aStatus) { EventDispatcher::Dispatch(aTarget, presShell->GetPresContext(), &aEvent, nullptr, &aStatus, nullptr); } bool HardwareKeyHandler::DispatchToCrossProcess(nsINode* aTarget, WidgetKeyboardEvent& aEvent) { nsCOMPtr remoteLoaderOwner = do_QueryInterface(aTarget); if (NS_WARN_IF(!remoteLoaderOwner)) { return false; } RefPtr remoteFrameLoader = remoteLoaderOwner->GetFrameLoader(); if (NS_WARN_IF(!remoteFrameLoader)) { return false; } uint32_t eventMode; remoteFrameLoader->GetEventMode(&eventMode); if (eventMode == nsIFrameLoader::EVENT_MODE_DONT_FORWARD_TO_CHILD) { return false; } PBrowserParent* remoteBrowser = remoteFrameLoader->GetRemoteBrowser(); TabParent* remote = static_cast(remoteBrowser); if (NS_WARN_IF(!remote)) { return false; } return remote->SendRealKeyEvent(aEvent); } void HardwareKeyHandler::PostHandleKeyboardEvent(nsINode* aTarget, WidgetKeyboardEvent& aEvent, nsEventStatus& aStatus) { MOZ_ASSERT(aTarget, "No target provided"); nsPresContext* presContext = GetPresContext(aTarget); RefPtr esm = presContext->EventStateManager(); bool dispatchedToChildProcess = PresShell::IsTargetIframe(aTarget); esm->PostHandleKeyboardEvent(&aEvent, aStatus, dispatchedToChildProcess); } void HardwareKeyHandler::SetDefaultPrevented(WidgetKeyboardEvent& aEvent, uint16_t aDefaultPrevented) { if (aDefaultPrevented & DEFAULT_PREVENTED) { aEvent.mFlags.mDefaultPrevented = true; } if (aDefaultPrevented & DEFAULT_PREVENTED_BY_CHROME) { aEvent.mFlags.mDefaultPreventedByChrome = true; } if (aDefaultPrevented & DEFAULT_PREVENTED_BY_CONTENT) { aEvent.mFlags.mDefaultPreventedByContent = true; } } bool HardwareKeyHandler::CanDispatchEvent(nsINode* aTarget, WidgetKeyboardEvent& aEvent) { nsCOMPtr presShell = GetPresShell(aTarget); if (NS_WARN_IF(!presShell)) { return false; } return presShell->CanDispatchEvent(&aEvent); } already_AddRefed HardwareKeyHandler::GetRootWindow(nsINode* aNode) { // Get nsIPresShell's pointer first nsCOMPtr presShell = GetPresShell(aNode); if (NS_WARN_IF(!presShell)) { return nullptr; } nsCOMPtr rootWindow = presShell->GetRootWindow(); return rootWindow.forget(); } already_AddRefed HardwareKeyHandler::GetCurrentTarget() { nsFocusManager* fm = nsFocusManager::GetFocusManager(); if (NS_WARN_IF(!fm)) { return nullptr; } nsCOMPtr focusedWindow; fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); if (NS_WARN_IF(!focusedWindow)) { return nullptr; } auto* ourWindow = nsPIDOMWindowOuter::From(focusedWindow); nsCOMPtr rootWindow = ourWindow->GetPrivateRoot(); if (NS_WARN_IF(!rootWindow)) { return nullptr; } nsCOMPtr focusedFrame; nsCOMPtr focusedContent = fm->GetFocusedDescendant(rootWindow, true, getter_AddRefs(focusedFrame)); // If there is no focus, then we use document body instead if (NS_WARN_IF(!focusedContent || !focusedContent->GetPrimaryFrame())) { nsIDocument* document = ourWindow->GetExtantDoc(); if (NS_WARN_IF(!document)) { return nullptr; } focusedContent = document->GetRootElement(); nsCOMPtr htmlDocument = do_QueryInterface(document); if (htmlDocument) { nsCOMPtr body; htmlDocument->GetBody(getter_AddRefs(body)); nsCOMPtr bodyContent = do_QueryInterface(body); if (bodyContent) { focusedContent = bodyContent; } } } return focusedContent ? focusedContent.forget() : nullptr; } nsPresContext* HardwareKeyHandler::GetPresContext(nsINode* aNode) { // Get nsIPresShell's pointer first nsCOMPtr presShell = GetPresShell(aNode); if (NS_WARN_IF(!presShell)) { return nullptr; } // then use nsIPresShell to get nsPresContext's pointer return presShell->GetPresContext(); } already_AddRefed HardwareKeyHandler::GetPresShell(nsINode* aNode) { nsIDocument* doc = aNode->OwnerDoc(); if (NS_WARN_IF(!doc)) { return nullptr; } nsCOMPtr presShell = doc->GetShell(); if (NS_WARN_IF(!presShell)) { return nullptr; } return presShell.forget(); } /* static */ already_AddRefed HardwareKeyHandler::GetInstance() { if (!XRE_IsParentProcess()) { return nullptr; } if (!sInstance) { sInstance = new HardwareKeyHandler(); ClearOnShutdown(&sInstance); } RefPtr service = sInstance.get(); return service.forget(); } } // namespace mozilla