diff options
Diffstat (limited to 'dom/inputmethod/HardwareKeyHandler.cpp')
-rw-r--r-- | dom/inputmethod/HardwareKeyHandler.cpp | 562 |
1 files changed, 562 insertions, 0 deletions
diff --git a/dom/inputmethod/HardwareKeyHandler.cpp b/dom/inputmethod/HardwareKeyHandler.cpp new file mode 100644 index 000000000..737c30e5b --- /dev/null +++ b/dom/inputmethod/HardwareKeyHandler.cpp @@ -0,0 +1,562 @@ +/* -*- 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> 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<nsIHardwareKeyEventListener> + 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> eventTarget = do_QueryInterface(aTarget); + nsPresContext* presContext = GetPresContext(aTarget); + RefPtr<KeyboardEvent> 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<KeyboardInfo> 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<nsIPresShell> 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<nsIContent> 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<nsPIDOMWindowOuter> originalRootWindow = GetRootWindow(aTarget); + nsCOMPtr<nsPIDOMWindowOuter> 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<nsIPresShell> 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<nsIFrameLoaderOwner> remoteLoaderOwner = do_QueryInterface(aTarget); + if (NS_WARN_IF(!remoteLoaderOwner)) { + return false; + } + + RefPtr<nsFrameLoader> 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<TabParent*>(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<mozilla::EventStateManager> 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<nsIPresShell> presShell = GetPresShell(aTarget); + if (NS_WARN_IF(!presShell)) { + return false; + } + return presShell->CanDispatchEvent(&aEvent); +} + +already_AddRefed<nsPIDOMWindowOuter> +HardwareKeyHandler::GetRootWindow(nsINode* aNode) +{ + // Get nsIPresShell's pointer first + nsCOMPtr<nsIPresShell> presShell = GetPresShell(aNode); + if (NS_WARN_IF(!presShell)) { + return nullptr; + } + nsCOMPtr<nsPIDOMWindowOuter> rootWindow = presShell->GetRootWindow(); + return rootWindow.forget(); +} + +already_AddRefed<nsIContent> +HardwareKeyHandler::GetCurrentTarget() +{ + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (NS_WARN_IF(!fm)) { + return nullptr; + } + + nsCOMPtr<mozIDOMWindowProxy> focusedWindow; + fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); + if (NS_WARN_IF(!focusedWindow)) { + return nullptr; + } + + auto* ourWindow = nsPIDOMWindowOuter::From(focusedWindow); + + nsCOMPtr<nsPIDOMWindowOuter> rootWindow = ourWindow->GetPrivateRoot(); + if (NS_WARN_IF(!rootWindow)) { + return nullptr; + } + + nsCOMPtr<nsPIDOMWindowOuter> focusedFrame; + nsCOMPtr<nsIContent> 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<nsIDOMHTMLDocument> htmlDocument = do_QueryInterface(document); + if (htmlDocument) { + nsCOMPtr<nsIDOMHTMLElement> body; + htmlDocument->GetBody(getter_AddRefs(body)); + nsCOMPtr<nsIContent> bodyContent = do_QueryInterface(body); + if (bodyContent) { + focusedContent = bodyContent; + } + } + } + + return focusedContent ? focusedContent.forget() : nullptr; +} + +nsPresContext* +HardwareKeyHandler::GetPresContext(nsINode* aNode) +{ + // Get nsIPresShell's pointer first + nsCOMPtr<nsIPresShell> presShell = GetPresShell(aNode); + if (NS_WARN_IF(!presShell)) { + return nullptr; + } + + // then use nsIPresShell to get nsPresContext's pointer + return presShell->GetPresContext(); +} + +already_AddRefed<nsIPresShell> +HardwareKeyHandler::GetPresShell(nsINode* aNode) +{ + nsIDocument* doc = aNode->OwnerDoc(); + if (NS_WARN_IF(!doc)) { + return nullptr; + } + + nsCOMPtr<nsIPresShell> presShell = doc->GetShell(); + if (NS_WARN_IF(!presShell)) { + return nullptr; + } + + return presShell.forget(); +} + +/* static */ +already_AddRefed<HardwareKeyHandler> +HardwareKeyHandler::GetInstance() +{ + if (!XRE_IsParentProcess()) { + return nullptr; + } + + if (!sInstance) { + sInstance = new HardwareKeyHandler(); + ClearOnShutdown(&sInstance); + } + + RefPtr<HardwareKeyHandler> service = sInstance.get(); + return service.forget(); +} + +} // namespace mozilla |