summaryrefslogtreecommitdiff
path: root/dom/inputmethod/HardwareKeyHandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/inputmethod/HardwareKeyHandler.cpp')
-rw-r--r--dom/inputmethod/HardwareKeyHandler.cpp562
1 files changed, 562 insertions, 0 deletions
diff --git a/dom/inputmethod/HardwareKeyHandler.cpp b/dom/inputmethod/HardwareKeyHandler.cpp
new file mode 100644
index 0000000000..737c30e5b5
--- /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