Bug 1110030 - part4 - HardwareKeyHandler component. r=masayuki, r=smaug
authorchunminchang <cchang@mozilla.com>
Mon, 21 Mar 2016 17:10:09 +0800
changeset 291023 d16d8002bf472b54be1993ca030e52139c588afa
parent 291022 22f2994b6c2d91c59806337454381b7ef396648b
child 291024 2f19708ce61927455c743b30d3389602919c7e7c
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki, smaug
bugs1110030
milestone48.0a1
Bug 1110030 - part4 - HardwareKeyHandler component. r=masayuki, r=smaug
b2g/installer/package-manifest.in
dom/inputmethod/HardwareKeyHandler.cpp
dom/inputmethod/HardwareKeyHandler.h
dom/inputmethod/moz.build
dom/inputmethod/nsIHardwareKeyHandler.idl
layout/build/nsLayoutModule.cpp
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -669,16 +669,19 @@
 @RESPATH@/components/Payment.manifest
 
 @RESPATH@/components/DownloadsAPI.js
 @RESPATH@/components/DownloadsAPI.manifest
 
 ; 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
 @RESPATH@/components/EngineeringModeService.js
 
 @RESPATH@/components/SystemUpdate.manifest
 @RESPATH@/components/SystemUpdateManager.js
 
new file mode 100644
--- /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> 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, "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<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");
+  MOZ_ASSERT(aEvent, "No event 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");
+  MOZ_ASSERT(aEvent, "No event 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
new file mode 100644
--- /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<T> is a helper class for adding reference counting mechanism.
+struct KeyboardInfo : public RefCounted<KeyboardInfo>
+{
+  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 T>
+class EventQueueDeallocator : public nsDequeFunctor
+{
+  virtual void* operator() (void* aObject)
+  {
+    RefPtr<T> releaseMe = dont_AddRef(static_cast<T*>(aObject));
+    return nullptr;
+  }
+};
+
+// The type-safe queue to be used to store the KeyboardInfo data
+template <class T>
+class EventQueue : private nsDeque
+{
+public:
+  EventQueue()
+    : nsDeque(new EventQueueDeallocator<T>())
+  {
+  };
+
+  ~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<T> PopFront()
+  {
+    RefPtr<T> rv = dont_AddRef(static_cast<T*>(nsDeque::PopFront()));
+    return rv.forget();
+  }
+
+  inline void RemoveFront()
+  {
+    RefPtr<T> releaseMe = PopFront();
+  }
+
+  inline T* PeekFront()
+  {
+    return static_cast<T*>(nsDeque::PeekFront());
+  }
+
+  void Clear()
+  {
+    while (GetSize() > 0) {
+      RemoveFront();
+    }
+  }
+};
+
+class HardwareKeyHandler : public nsIHardwareKeyHandler
+{
+public:
+  HardwareKeyHandler();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIHARDWAREKEYHANDLER
+
+  static already_AddRefed<HardwareKeyHandler> 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<nsPIDOMWindowOuter> GetRootWindow(nsINode* aNode);
+
+  already_AddRefed<nsIContent> GetCurrentTarget();
+
+  nsPresContext* GetPresContext(nsINode* aNode);
+
+  already_AddRefed<nsIPresShell> GetPresShell(nsINode* aNode);
+
+  static StaticRefPtr<HardwareKeyHandler> 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<KeyboardInfo> mEventQueue;
+
+  // Hold the pointer to the latest keydown's data
+  RefPtr<KeyboardInfo> 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_
--- a/dom/inputmethod/moz.build
+++ b/dom/inputmethod/moz.build
@@ -1,14 +1,37 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+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',
 ]
 
 EXTRA_JS_MODULES += [
     'Keyboard.jsm',
 ]
new file mode 100644
--- /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;
+  %}
+};
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -262,16 +262,21 @@ static void Shutdown();
 
 #include "GMPService.h"
 
 #include "mozilla/dom/PresentationDeviceManager.h"
 #include "mozilla/dom/PresentationSessionTransport.h"
 
 #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;
 using mozilla::dom::power::PowerManagerService;
 using mozilla::dom::quota::QuotaManagerService;
 using mozilla::dom::workers::ServiceWorkerManager;
 using mozilla::dom::workers::WorkerDebuggerManager;
 using mozilla::dom::UDPSocketChild;
@@ -661,16 +666,21 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNullPrincipal, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsStructuredCloneContainer)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(OSFileConstantsService)
 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"
 
   MAKE_CTOR(CreateA11yService, nsIAccessibilityService, NS_GetAccessibilityService)
 #endif
 
 static nsresult
 Construct_nsIScriptSecurityManager(nsISupports *aOuter, REFNSIID aIID,
@@ -861,16 +871,20 @@ NS_DEFINE_NAMED_CID(INPUTPORT_DATA_CID);
 NS_DEFINE_NAMED_CID(GECKO_MEDIA_PLUGIN_SERVICE_CID);
 
 NS_DEFINE_NAMED_CID(PRESENTATION_SERVICE_CID);
 NS_DEFINE_NAMED_CID(PRESENTATION_DEVICE_MANAGER_CID);
 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)
 {
   nsresult rv;
   nsCOMPtr<nsIControllerCommandTable> commandTable =
       do_CreateInstance(NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &rv);
   if (NS_FAILED(rv)) return rv;
@@ -1156,16 +1170,19 @@ static const mozilla::Module::CIDEntry k
   { &kTV_CHANNEL_DATA_CID, false, nullptr, TVChannelDataConstructor },
   { &kTV_PROGRAM_DATA_CID, false, nullptr, TVProgramDataConstructor },
   { &kPRESENTATION_SERVICE_CID, false, nullptr, nsIPresentationServiceConstructor },
   { &kPRESENTATION_DEVICE_MANAGER_CID, false, nullptr, PresentationDeviceManagerConstructor },
   { &kPRESENTATION_SESSION_TRANSPORT_CID, false, nullptr, PresentationSessionTransportConstructor },
   { &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 }
 };
 
 static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
   XPCONNECT_CONTRACTS
   { "@mozilla.org/layout/xul-boxobject;1", &kNS_BOXOBJECT_CID },
 #ifdef MOZ_XUL
   { "@mozilla.org/layout/xul-boxobject-listbox;1", &kNS_LISTBOXOBJECT_CID },
@@ -1322,16 +1339,19 @@ static const mozilla::Module::ContractID
   { NS_MOBILE_CONNECTION_SERVICE_CONTRACTID, &kNS_MOBILE_CONNECTION_SERVICE_CID },
   { NS_VOICEMAIL_SERVICE_CONTRACTID, &kNS_VOICEMAIL_SERVICE_CID },
   { PRESENTATION_SERVICE_CONTRACTID, &kPRESENTATION_SERVICE_CID },
   { PRESENTATION_DEVICE_MANAGER_CONTRACTID, &kPRESENTATION_DEVICE_MANAGER_CID },
   { PRESENTATION_SESSION_TRANSPORT_CONTRACTID, &kPRESENTATION_SESSION_TRANSPORT_CID },
   { "@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 }
 };
 
 static const mozilla::Module::CategoryEntry kLayoutCategories[] = {
   XPCONNECT_CATEGORIES
   { "content-policy", NS_DATADOCUMENTCONTENTPOLICY_CONTRACTID, NS_DATADOCUMENTCONTENTPOLICY_CONTRACTID },
   { "content-policy", NS_NODATAPROTOCOLCONTENTPOLICY_CONTRACTID, NS_NODATAPROTOCOLCONTENTPOLICY_CONTRACTID },
   { "content-policy", "CSPService", CSPSERVICE_CONTRACTID },