gecko-dev/dom/inputmethod/HardwareKeyHandler.cpp

563 lines
17 KiB
C++

/* -*- 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