From c602eedc7e9d38a83fccb5a75fb2ba2d9a5ea3aa Mon Sep 17 00:00:00 2001 From: chunminchang Date: Mon, 21 Mar 2016 17:10:09 +0800 Subject: [PATCH] Bug 1110030 - part4 - HardwareKeyHandler component. r=masayuki, r=smaug --- b2g/installer/package-manifest.in | 3 + dom/inputmethod/HardwareKeyHandler.cpp | 566 ++++++++++++++++++++++ dom/inputmethod/HardwareKeyHandler.h | 222 +++++++++ dom/inputmethod/moz.build | 23 + dom/inputmethod/nsIHardwareKeyHandler.idl | 142 ++++++ layout/build/nsLayoutModule.cpp | 20 + 6 files changed, 976 insertions(+) create mode 100644 dom/inputmethod/HardwareKeyHandler.cpp create mode 100644 dom/inputmethod/HardwareKeyHandler.h create mode 100644 dom/inputmethod/nsIHardwareKeyHandler.idl diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index c937cf025412..26ac263d4223 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -674,6 +674,9 @@ ; InputMethod API @RESPATH@/components/MozKeyboard.js @RESPATH@/components/InputMethod.manifest +#ifdef MOZ_B2G +@RESPATH@/components/inputmethod.xpt +#endif @RESPATH@/components/EngineeringMode.manifest @RESPATH@/components/EngineeringModeAPI.js diff --git a/dom/inputmethod/HardwareKeyHandler.cpp b/dom/inputmethod/HardwareKeyHandler.cpp new file mode 100644 index 000000000000..8e20fee22e0e --- /dev/null +++ b/dom/inputmethod/HardwareKeyHandler.cpp @@ -0,0 +1,566 @@ +/* -*- 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, "No event 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"); + MOZ_ASSERT(aEvent, "No event 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"); + MOZ_ASSERT(aEvent, "No event 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"); + MOZ_ASSERT(aEvent, "No event 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 diff --git a/dom/inputmethod/HardwareKeyHandler.h b/dom/inputmethod/HardwareKeyHandler.h new file mode 100644 index 000000000000..88b9a1cd39ad --- /dev/null +++ b/dom/inputmethod/HardwareKeyHandler.h @@ -0,0 +1,222 @@ +/* -*- 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/. */ + +#ifndef mozilla_HardwareKeyHandler_h_ +#define mozilla_HardwareKeyHandler_h_ + +#include "mozilla/EventForwards.h" // for nsEventStatus +#include "mozilla/StaticPtr.h" +#include "mozilla/TextEvents.h" +#include "nsCOMPtr.h" +#include "nsDeque.h" +#include "nsIHardwareKeyHandler.h" +#include "nsIWeakReferenceUtils.h" // for nsWeakPtr + +class nsIContent; +class nsINode; +class nsIPresShell; +class nsPIDOMWindowOuter; +class nsPresContext; + +namespace mozilla { + +// This module will copy the events' data into its event queue for reuse +// after receiving input-method-app's reply, so we use the following struct +// for storing these information. +// RefCounted is a helper class for adding reference counting mechanism. +struct KeyboardInfo : public RefCounted +{ + nsINode* mTarget; + WidgetKeyboardEvent mEvent; + nsEventStatus mStatus; + + KeyboardInfo(nsINode* aTarget, + WidgetKeyboardEvent& aEvent, + nsEventStatus aStatus) + : mTarget(aTarget) + , mEvent(aEvent) + , mStatus(aStatus) + { + } +}; + +// The following is the type-safe wrapper around nsDeque +// for storing events' data. +// The T must be one class that supports reference counting mechanism. +// The EventQueueDeallocator will be called in nsDeque::~nsDeque() or +// nsDeque::Erase() to deallocate the objects. nsDeque::Erase() will remove +// and delete all items in the queue. See more from nsDeque.h. +template +class EventQueueDeallocator : public nsDequeFunctor +{ + virtual void* operator() (void* aObject) + { + RefPtr releaseMe = dont_AddRef(static_cast(aObject)); + return nullptr; + } +}; + +// The type-safe queue to be used to store the KeyboardInfo data +template +class EventQueue : private nsDeque +{ +public: + EventQueue() + : nsDeque(new EventQueueDeallocator()) + { + }; + + ~EventQueue() + { + Clear(); + } + + inline size_t GetSize() + { + return nsDeque::GetSize(); + } + + bool IsEmpty() + { + return !nsDeque::GetSize(); + } + + inline bool Push(T* aItem) + { + MOZ_ASSERT(aItem); + NS_ADDREF(aItem); + size_t sizeBefore = GetSize(); + nsDeque::Push(aItem); + if (GetSize() != sizeBefore + 1) { + NS_RELEASE(aItem); + return false; + } + return true; + } + + inline already_AddRefed PopFront() + { + RefPtr rv = dont_AddRef(static_cast(nsDeque::PopFront())); + return rv.forget(); + } + + inline void RemoveFront() + { + RefPtr releaseMe = PopFront(); + } + + inline T* PeekFront() + { + return static_cast(nsDeque::PeekFront()); + } + + void Clear() + { + while (GetSize() > 0) { + RemoveFront(); + } + } +}; + +class HardwareKeyHandler : public nsIHardwareKeyHandler +{ +public: + HardwareKeyHandler(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIHARDWAREKEYHANDLER + + static already_AddRefed GetInstance(); + + virtual bool ForwardKeyToInputMethodApp(nsINode* aTarget, + WidgetKeyboardEvent* aEvent, + nsEventStatus* aEventStatus) override; + +private: + virtual ~HardwareKeyHandler(); + + // Return true if the keypress is successfully dispatched. + // Otherwise, return false. + bool DispatchKeyPress(nsINode* aTarget, + WidgetKeyboardEvent& aEvent, + nsEventStatus& aStatus); + + void DispatchAfterKeyEvent(nsINode* aTarget, WidgetKeyboardEvent& aEvent); + + void DispatchToCurrentProcess(nsIPresShell* aPresShell, + nsIContent* aTarget, + WidgetKeyboardEvent& aEvent, + nsEventStatus& aStatus); + + bool DispatchToCrossProcess(nsINode* aTarget, WidgetKeyboardEvent& aEvent); + + // This method will dispatch not only key* event to its event target, + // no mather it's in the current process or in its child process, + // but also mozbrowserafterkey* to the corresponding target if it needs. + // Return true if the key is successfully dispatched. + // Otherwise, return false. + bool DispatchToTargetApp(nsINode* aTarget, + WidgetKeyboardEvent& aEvent, + nsEventStatus& aStatus); + + // This method will be called after dispatching keypress to its target, + // if the input-method-app doesn't handle the key. + // In normal dispatching path, EventStateManager::PostHandleKeyboardEvent + // will be called when event is keypress. + // However, the ::PostHandleKeyboardEvent mentioned above will be aborted + // when we try to forward key event to the input-method-app. + // If the input-method-app consumes the key, then we don't need to do anything + // because the input-method-app will generate a new key event by itself. + // On the other hand, if the input-method-app doesn't consume the key, + // then we need to dispatch the key event by ourselves + // and call ::PostHandleKeyboardEvent again after the event is forwarded. + // Note that the EventStateManager::PreHandleEvent is already called before + // forwarding, so we don't need to call it in this module. + void PostHandleKeyboardEvent(nsINode* aTarget, + WidgetKeyboardEvent& aEvent, + nsEventStatus& aStatus); + + void SetDefaultPrevented(WidgetKeyboardEvent& aEvent, + uint16_t aDefaultPrevented); + + // Check whether the event is valid to be fired. + // This method should be called every time before dispatching next event. + bool CanDispatchEvent(nsINode* aTarget, + WidgetKeyboardEvent& aEvent); + + already_AddRefed GetRootWindow(nsINode* aNode); + + already_AddRefed GetCurrentTarget(); + + nsPresContext* GetPresContext(nsINode* aNode); + + already_AddRefed GetPresShell(nsINode* aNode); + + static StaticRefPtr sInstance; + + // The event queue is used to store the forwarded keyboard events. + // Those stored events will be dispatched if input-method-app doesn't + // consume them. + EventQueue mEventQueue; + + // Hold the pointer to the latest keydown's data + RefPtr mLatestKeyDownInfo; + + // input-method-app needs to register a listener by + // |nsIHardwareKeyHandler.registerListener| to receive + // the hardware keyboard event, and |nsIHardwareKeyHandler.registerListener| + // will set an nsIHardwareKeyEventListener to mHardwareKeyEventListener. + // Then, mHardwareKeyEventListener is used to forward the event + // to the input-method-app. + nsWeakPtr mHardwareKeyEventListener; + + // To keep tracking the input-method-app is active or disabled. + bool mInputMethodAppConnected; +}; + +} // namespace mozilla + +#endif // #ifndef mozilla_HardwareKeyHandler_h_ diff --git a/dom/inputmethod/moz.build b/dom/inputmethod/moz.build index b867207a83fb..4165d9e0d3b8 100644 --- a/dom/inputmethod/moz.build +++ b/dom/inputmethod/moz.build @@ -4,6 +4,29 @@ # 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/. +if CONFIG['MOZ_B2G']: + XPIDL_SOURCES += [ + 'nsIHardwareKeyHandler.idl', + ] + + XPIDL_MODULE = 'inputmethod' + + EXPORTS.mozilla += [ + 'HardwareKeyHandler.h', + ] + + SOURCES += [ + 'HardwareKeyHandler.cpp' + ] + + include('/ipc/chromium/chromium-config.mozbuild') + + FINAL_LIBRARY = 'xul' + LOCAL_INCLUDES += [ + '/dom/base', + '/layout/base', + ] + EXTRA_COMPONENTS += [ 'InputMethod.manifest', 'MozKeyboard.js', diff --git a/dom/inputmethod/nsIHardwareKeyHandler.idl b/dom/inputmethod/nsIHardwareKeyHandler.idl new file mode 100644 index 000000000000..5bce4d9805a8 --- /dev/null +++ b/dom/inputmethod/nsIHardwareKeyHandler.idl @@ -0,0 +1,142 @@ +/* -*- 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 "nsISupports.idl" + +interface nsIDOMKeyEvent; + +%{C++ +#define NS_HARDWARE_KEY_HANDLER_CID \ + { 0xfb45921b, 0xe0a5, 0x45c6, \ + { 0x90, 0xd0, 0xa6, 0x97, 0xa7, 0x72, 0xc4, 0x2a } } +#define NS_HARDWARE_KEY_HANDLER_CONTRACTID \ + "@mozilla.org/HardwareKeyHandler;1" + +#include "mozilla/EventForwards.h" /* For nsEventStatus */ + +namespace mozilla { +class WidgetKeyboardEvent; +} + +using mozilla::WidgetKeyboardEvent; + +class nsINode; +%} + +/** + * This interface is used to be registered to the nsIHardwareKeyHandler through + * |nsIHardwareKeyHandler.registerListener|. + */ +[scriptable, function, uuid(cd5aeee3-b4b9-459d-85e7-c0671c7a8a2e)] +interface nsIHardwareKeyEventListener : nsISupports +{ + /** + * This method will be invoked by nsIHardwareKeyHandler to forward the native + * keyboard event to the active input method + */ + bool onHardwareKey(in nsIDOMKeyEvent aEvent); +}; + +/** + * This interface has two main roles. One is to send a hardware keyboard event + * to the active input method app and the other is to receive its reply result. + * If a keyboard event is triggered from a hardware keyboard when an editor has + * focus, the event target should be the editor. However, the text input + * processor algorithm is implemented in an input method app and it should + * handle the event earlier than the real event target to do the mapping such + * as character conversion according to the language setting or the type of a + * hardware keyboard. + */ +[scriptable, builtinclass, uuid(25b34270-caad-4d18-a910-860351690639)] +interface nsIHardwareKeyHandler : nsISupports +{ + /** + * Flags used to set the defaultPrevented's result. The default result + * from input-method-app should be set to NO_DEFAULT_PREVENTED. + * (It means the forwarded event isn't consumed by input-method-app.) + * If the input-method-app consumes the forwarded event, + * then the result should be set by DEFAULT_PREVENTED* before reply. + */ + const unsigned short NO_DEFAULT_PREVENTED = 0x0000; + const unsigned short DEFAULT_PREVENTED = 0x0001; + const unsigned short DEFAULT_PREVENTED_BY_CHROME = 0x0002; + const unsigned short DEFAULT_PREVENTED_BY_CONTENT = 0x0004; + + /** + * Registers a listener in input-method-app to receive + * the forwarded hardware keyboard events + * + * @param aListener Listener object to be notified for receiving + * the keyboard event fired from hardware + * @note A listener object must implement + * nsIHardwareKeyEventListener and + * nsSupportsWeakReference + * @see nsIHardwareKeyEventListener + * @see nsSupportsWeakReference + */ + void registerListener(in nsIHardwareKeyEventListener aListener); + + /** + * Unregisters the current listener from input-method-app + */ + void unregisterListener(); + + /** + * Notifies nsIHardwareKeyHandler that input-method-app is active. + */ + void onInputMethodAppConnected(); + + /** + * Notifies nsIHardwareKeyHandler that input-method-app is disabled. + */ + void onInputMethodAppDisconnected(); + + /** + * Input-method-app will pass the processing result that the forwarded + * event is handled or not through this method, and the nsIHardwareKeyHandler + * can use this to receive the reply of |forwardKeyToInputMethodApp| + * from the active input method. + * + * The result should contain the original event type and the info whether + * the default is prevented, also, it is prevented by chrome or content. + * + * @param aEventType The type of an original event. + * @param aDefaultPrevented State that |evt.preventDefault| + * is called by content, chrome or not. + */ + void onHandledByInputMethodApp(in DOMString aType, + in unsigned short aDefaultPrevented); + + /** + * Sends the native keyboard events triggered from hardware to the + * active input method before dispatching to its event target. + * This method only forwards keydown and keyup events. + * If the event isn't allowed to be forwarded, we should continue the + * normal event processing. For those forwarded keydown and keyup events + * We will pause the further event processing to wait for the completion + * of the event handling in the active input method app. + * Once |onHandledByInputMethodApp| is called by the input method app, + * the pending event processing can be resumed according to its reply. + * On the other hand, the keypress will never be sent to the input-method-app. + * Depending on whether the keydown's reply arrives before the keypress event + * comes, the keypress event will be handled directly or pushed into + * the event queue to wait for its heading keydown's reply. + * + * This implementation will call |nsIHardwareKeyEventListener.onHardwareKey|, + * which is registered through |nsIHardwareKeyEventListener.registerListener|, + * to forward the events. + * + * Returns true, if the event is handled in this module. + * Returns false, otherwise. + * + * If it returns false, we should continue the normal event processing. + */ + %{C++ + virtual bool ForwardKeyToInputMethodApp(nsINode* aTarget, + WidgetKeyboardEvent* aEvent, + nsEventStatus* aEventStatus) = 0; + %} +}; diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index 0e21d645cedc..02b91db928e1 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -267,6 +267,11 @@ static void Shutdown(); #include "mozilla/TextInputProcessor.h" +#ifdef MOZ_B2G +#include "nsIHardwareKeyHandler.h" +#include "mozilla/HardwareKeyHandler.h" +#endif + using namespace mozilla; using namespace mozilla::dom; using mozilla::dom::alarm::AlarmHalService; @@ -666,6 +671,11 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(UDPSocketChild) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(GeckoMediaPluginService, GeckoMediaPluginService::GetGeckoMediaPluginService) +#ifdef MOZ_B2G +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIHardwareKeyHandler, + HardwareKeyHandler::GetInstance) +#endif + #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" @@ -866,6 +876,10 @@ NS_DEFINE_NAMED_CID(PRESENTATION_SESSION_TRANSPORT_CID); NS_DEFINE_NAMED_CID(TEXT_INPUT_PROCESSOR_CID); +#ifdef MOZ_B2G +NS_DEFINE_NAMED_CID(NS_HARDWARE_KEY_HANDLER_CID); +#endif + static nsresult CreateWindowCommandTableConstructor(nsISupports *aOuter, REFNSIID aIID, void **aResult) @@ -1161,6 +1175,9 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = { { &kTEXT_INPUT_PROCESSOR_CID, false, nullptr, TextInputProcessorConstructor }, { &kFAKE_INPUTPORT_SERVICE_CID, false, nullptr, FakeInputPortServiceConstructor }, { &kINPUTPORT_DATA_CID, false, nullptr, InputPortDataConstructor }, +#ifdef MOZ_B2G + { &kNS_HARDWARE_KEY_HANDLER_CID, false, nullptr, nsIHardwareKeyHandlerConstructor }, +#endif { nullptr } }; @@ -1327,6 +1344,9 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = { { "@mozilla.org/text-input-processor;1", &kTEXT_INPUT_PROCESSOR_CID }, { FAKE_INPUTPORT_SERVICE_CONTRACTID, &kFAKE_INPUTPORT_SERVICE_CID }, { INPUTPORT_DATA_CONTRACTID, &kINPUTPORT_DATA_CID }, +#ifdef MOZ_B2G + { NS_HARDWARE_KEY_HANDLER_CONTRACTID, &kNS_HARDWARE_KEY_HANDLER_CID }, +#endif { nullptr } };