зеркало из https://github.com/mozilla/gecko-dev.git
1999 строки
70 KiB
C++
1999 строки
70 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 "mozilla/Logging.h"
|
|
|
|
#include "mozilla/IMEStateManager.h"
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/EditorBase.h"
|
|
#include "mozilla/EventListenerManager.h"
|
|
#include "mozilla/EventStates.h"
|
|
#include "mozilla/MouseEvents.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/TextComposition.h"
|
|
#include "mozilla/TextEvents.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/dom/HTMLFormElement.h"
|
|
#include "mozilla/dom/TabParent.h"
|
|
|
|
#include "HTMLInputElement.h"
|
|
#include "IMEContentObserver.h"
|
|
|
|
#include "nsCOMPtr.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIDocument.h"
|
|
#include "nsIDOMMouseEvent.h"
|
|
#include "nsIForm.h"
|
|
#include "nsIFormControl.h"
|
|
#include "nsINode.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsISelection.h"
|
|
#include "nsISupports.h"
|
|
#include "nsPresContext.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace dom;
|
|
using namespace widget;
|
|
|
|
/**
|
|
* When a method is called, log its arguments and/or related static variables
|
|
* with LogLevel::Info. However, if it puts too many logs like
|
|
* OnDestroyPresContext(), should long only when the method actually does
|
|
* something. In this case, the log should start with "<method name>".
|
|
*
|
|
* When a method quits due to unexpected situation, log the reason with
|
|
* LogLevel::Error. In this case, the log should start with
|
|
* "<method name>(), FAILED". The indent makes the log look easier.
|
|
*
|
|
* When a method does something only in some situations and it may be important
|
|
* for debug, log the information with LogLevel::Debug. In this case, the log
|
|
* should start with " <method name>(),".
|
|
*/
|
|
LazyLogModule sISMLog("IMEStateManager");
|
|
|
|
static const char*
|
|
GetBoolName(bool aBool)
|
|
{
|
|
return aBool ? "true" : "false";
|
|
}
|
|
|
|
static const char*
|
|
GetActionCauseName(InputContextAction::Cause aCause)
|
|
{
|
|
switch (aCause) {
|
|
case InputContextAction::CAUSE_UNKNOWN:
|
|
return "CAUSE_UNKNOWN";
|
|
case InputContextAction::CAUSE_UNKNOWN_CHROME:
|
|
return "CAUSE_UNKNOWN_CHROME";
|
|
case InputContextAction::CAUSE_KEY:
|
|
return "CAUSE_KEY";
|
|
case InputContextAction::CAUSE_MOUSE:
|
|
return "CAUSE_MOUSE";
|
|
case InputContextAction::CAUSE_TOUCH:
|
|
return "CAUSE_TOUCH";
|
|
default:
|
|
return "illegal value";
|
|
}
|
|
}
|
|
|
|
static const char*
|
|
GetActionFocusChangeName(InputContextAction::FocusChange aFocusChange)
|
|
{
|
|
switch (aFocusChange) {
|
|
case InputContextAction::FOCUS_NOT_CHANGED:
|
|
return "FOCUS_NOT_CHANGED";
|
|
case InputContextAction::GOT_FOCUS:
|
|
return "GOT_FOCUS";
|
|
case InputContextAction::LOST_FOCUS:
|
|
return "LOST_FOCUS";
|
|
case InputContextAction::MENU_GOT_PSEUDO_FOCUS:
|
|
return "MENU_GOT_PSEUDO_FOCUS";
|
|
case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
|
|
return "MENU_LOST_PSEUDO_FOCUS";
|
|
default:
|
|
return "illegal value";
|
|
}
|
|
}
|
|
|
|
static const char*
|
|
GetIMEStateEnabledName(IMEState::Enabled aEnabled)
|
|
{
|
|
switch (aEnabled) {
|
|
case IMEState::DISABLED:
|
|
return "DISABLED";
|
|
case IMEState::ENABLED:
|
|
return "ENABLED";
|
|
case IMEState::PASSWORD:
|
|
return "PASSWORD";
|
|
case IMEState::PLUGIN:
|
|
return "PLUGIN";
|
|
default:
|
|
return "illegal value";
|
|
}
|
|
}
|
|
|
|
static const char*
|
|
GetIMEStateSetOpenName(IMEState::Open aOpen)
|
|
{
|
|
switch (aOpen) {
|
|
case IMEState::DONT_CHANGE_OPEN_STATE:
|
|
return "DONT_CHANGE_OPEN_STATE";
|
|
case IMEState::OPEN:
|
|
return "OPEN";
|
|
case IMEState::CLOSED:
|
|
return "CLOSED";
|
|
default:
|
|
return "illegal value";
|
|
}
|
|
}
|
|
|
|
static bool
|
|
IsSameProcess(const TabParent* aTabParent1, const TabParent* aTabParent2)
|
|
{
|
|
if (aTabParent1 == aTabParent2) {
|
|
return true;
|
|
}
|
|
if (!aTabParent1 != !aTabParent2) {
|
|
return false;
|
|
}
|
|
return aTabParent1->Manager() == aTabParent2->Manager();
|
|
}
|
|
|
|
StaticRefPtr<nsIContent> IMEStateManager::sContent;
|
|
StaticRefPtr<nsPresContext> IMEStateManager::sPresContext;
|
|
nsIWidget* IMEStateManager::sWidget = nullptr;
|
|
nsIWidget* IMEStateManager::sFocusedIMEWidget = nullptr;
|
|
StaticRefPtr<TabParent> IMEStateManager::sFocusedIMETabParent;
|
|
nsIWidget* IMEStateManager::sActiveInputContextWidget = nullptr;
|
|
StaticRefPtr<TabParent> IMEStateManager::sActiveTabParent;
|
|
StaticRefPtr<IMEContentObserver> IMEStateManager::sActiveIMEContentObserver;
|
|
TextCompositionArray* IMEStateManager::sTextCompositions = nullptr;
|
|
InputContext::Origin IMEStateManager::sOrigin = InputContext::ORIGIN_MAIN;
|
|
InputContext IMEStateManager::sActiveChildInputContext;
|
|
bool IMEStateManager::sInstalledMenuKeyboardListener = false;
|
|
bool IMEStateManager::sIsGettingNewIMEState = false;
|
|
bool IMEStateManager::sCheckForIMEUnawareWebApps = false;
|
|
bool IMEStateManager::sInputModeSupported = false;
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::Init()
|
|
{
|
|
Preferences::AddBoolVarCache(
|
|
&sCheckForIMEUnawareWebApps,
|
|
"intl.ime.hack.on_ime_unaware_apps.fire_key_events_for_composition",
|
|
false);
|
|
|
|
Preferences::AddBoolVarCache(
|
|
&sInputModeSupported,
|
|
"dom.forms.inputmode",
|
|
false);
|
|
|
|
sOrigin = XRE_IsParentProcess() ? InputContext::ORIGIN_MAIN :
|
|
InputContext::ORIGIN_CONTENT;
|
|
ResetActiveChildInputContext();
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::Shutdown()
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("Shutdown(), sTextCompositions=0x%p, sTextCompositions->Length()=%zu",
|
|
sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0));
|
|
|
|
MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length());
|
|
delete sTextCompositions;
|
|
sTextCompositions = nullptr;
|
|
// All string instances in the global space need to be empty after XPCOM
|
|
// shutdown.
|
|
sActiveChildInputContext.ShutDown();
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::OnTabParentDestroying(TabParent* aTabParent)
|
|
{
|
|
if (sFocusedIMETabParent == aTabParent) {
|
|
NotifyIMEOfBlurForChildProcess();
|
|
}
|
|
|
|
if (sActiveTabParent != aTabParent) {
|
|
return;
|
|
}
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnTabParentDestroying(aTabParent=0x%p), "
|
|
"The active TabParent is being destroyed", aTabParent));
|
|
|
|
// The active remote process might have crashed.
|
|
sActiveTabParent = nullptr;
|
|
|
|
// XXX: Need to disable IME?
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::WidgetDestroyed(nsIWidget* aWidget)
|
|
{
|
|
if (sWidget == aWidget) {
|
|
sWidget = nullptr;
|
|
}
|
|
if (sFocusedIMEWidget == aWidget) {
|
|
NotifyIMEOfBlurForChildProcess();
|
|
sFocusedIMEWidget = nullptr;
|
|
}
|
|
if (sActiveInputContextWidget == aWidget) {
|
|
sActiveInputContextWidget = nullptr;
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::StopIMEStateManagement()
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("StopIMEStateManagement()"));
|
|
|
|
// NOTE: Don't set input context from here since this has already lost
|
|
// the rights to change input context.
|
|
|
|
if (sTextCompositions && sPresContext) {
|
|
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, sPresContext, sActiveTabParent);
|
|
}
|
|
sActiveInputContextWidget = nullptr;
|
|
sPresContext = nullptr;
|
|
sContent = nullptr;
|
|
sActiveTabParent = nullptr;
|
|
DestroyIMEContentObserver();
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::NotifyIMEOfBlurForChildProcess()
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
("NotifyIMEOfBlurForChildProcess(), sFocusedIMETabParent=0x%p, "
|
|
"sFocusedIMEWidget=0x%p",
|
|
sFocusedIMETabParent.get(), sFocusedIMEWidget));
|
|
|
|
if (!sFocusedIMETabParent) {
|
|
MOZ_ASSERT(!sFocusedIMEWidget);
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(sFocusedIMEWidget);
|
|
|
|
if (MOZ_LOG_TEST(sISMLog, LogLevel::Debug) && sTextCompositions) {
|
|
RefPtr<TextComposition> composition =
|
|
sTextCompositions->GetCompositionFor(sFocusedIMEWidget);
|
|
if (composition) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" NotifyIMEOfBlurForChildProcess(), sFocusedIMEWidget still has "
|
|
"composition"));
|
|
}
|
|
}
|
|
|
|
NotifyIME(NOTIFY_IME_OF_BLUR, sFocusedIMEWidget, sFocusedIMETabParent);
|
|
|
|
MOZ_ASSERT(!sFocusedIMETabParent);
|
|
MOZ_ASSERT(!sFocusedIMEWidget);
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::MaybeStartOffsetUpdatedInChild(nsIWidget* aWidget,
|
|
uint32_t aStartOffset)
|
|
{
|
|
if (NS_WARN_IF(!sTextCompositions)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), "
|
|
"called when there is no composition", aWidget, aStartOffset));
|
|
return;
|
|
}
|
|
|
|
RefPtr<TextComposition> composition = GetTextCompositionFor(aWidget);
|
|
if (NS_WARN_IF(!composition)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), "
|
|
"called when there is no composition", aWidget, aStartOffset));
|
|
return;
|
|
}
|
|
|
|
if (composition->NativeOffsetOfStartComposition() == aStartOffset) {
|
|
return;
|
|
}
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), "
|
|
"old offset=%u",
|
|
aWidget, aStartOffset, composition->NativeOffsetOfStartComposition()));
|
|
composition->OnStartOffsetUpdatedInChild(aStartOffset);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
IMEStateManager::OnDestroyPresContext(nsPresContext* aPresContext)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aPresContext);
|
|
|
|
// First, if there is a composition in the aPresContext, clean up it.
|
|
if (sTextCompositions) {
|
|
TextCompositionArray::index_type i =
|
|
sTextCompositions->IndexOf(aPresContext);
|
|
if (i != TextCompositionArray::NoIndex) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnDestroyPresContext(), "
|
|
"removing TextComposition instance from the array (index=%zu)", i));
|
|
// there should be only one composition per presContext object.
|
|
sTextCompositions->ElementAt(i)->Destroy();
|
|
sTextCompositions->RemoveElementAt(i);
|
|
if (sTextCompositions->IndexOf(aPresContext) !=
|
|
TextCompositionArray::NoIndex) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" OnDestroyPresContext(), FAILED to remove "
|
|
"TextComposition instance from the array"));
|
|
MOZ_CRASH("Failed to remove TextComposition instance from the array");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aPresContext != sPresContext) {
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnDestroyPresContext(aPresContext=0x%p), "
|
|
"sPresContext=0x%p, sContent=0x%p, sTextCompositions=0x%p",
|
|
aPresContext, sPresContext.get(), sContent.get(), sTextCompositions));
|
|
|
|
DestroyIMEContentObserver();
|
|
|
|
if (sWidget) {
|
|
IMEState newState = GetNewIMEState(sPresContext, nullptr);
|
|
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
|
InputContextAction::LOST_FOCUS);
|
|
InputContext::Origin origin =
|
|
sActiveTabParent ? InputContext::ORIGIN_CONTENT : sOrigin;
|
|
SetIMEState(newState, nullptr, nullptr, sWidget, action, origin);
|
|
}
|
|
sWidget = nullptr;
|
|
sContent = nullptr;
|
|
sPresContext = nullptr;
|
|
sActiveTabParent = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
IMEStateManager::OnRemoveContent(nsPresContext* aPresContext,
|
|
nsIContent* aContent)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aPresContext);
|
|
|
|
// First, if there is a composition in the aContent, clean up it.
|
|
if (sTextCompositions) {
|
|
RefPtr<TextComposition> compositionInContent =
|
|
sTextCompositions->GetCompositionInContent(aPresContext, aContent);
|
|
|
|
if (compositionInContent) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnRemoveContent(), "
|
|
"composition is in the content"));
|
|
|
|
// Try resetting the native IME state. Be aware, typically, this method
|
|
// is called during the content being removed. Then, the native
|
|
// composition events which are caused by following APIs are ignored due
|
|
// to unsafe to run script (in PresShell::HandleEvent()).
|
|
nsresult rv =
|
|
compositionInContent->NotifyIME(REQUEST_TO_CANCEL_COMPOSITION);
|
|
if (NS_FAILED(rv)) {
|
|
compositionInContent->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!sPresContext || !sContent ||
|
|
!nsContentUtils::ContentIsDescendantOf(sContent, aContent)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnRemoveContent(aPresContext=0x%p, aContent=0x%p), "
|
|
"sPresContext=0x%p, sContent=0x%p, sTextCompositions=0x%p",
|
|
aPresContext, aContent, sPresContext.get(), sContent.get(), sTextCompositions));
|
|
|
|
DestroyIMEContentObserver();
|
|
|
|
// Current IME transaction should commit
|
|
if (sWidget) {
|
|
IMEState newState = GetNewIMEState(sPresContext, nullptr);
|
|
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
|
InputContextAction::LOST_FOCUS);
|
|
InputContext::Origin origin =
|
|
sActiveTabParent ? InputContext::ORIGIN_CONTENT : sOrigin;
|
|
SetIMEState(newState, aPresContext, nullptr, sWidget, action, origin);
|
|
}
|
|
|
|
sWidget = nullptr;
|
|
sContent = nullptr;
|
|
sPresContext = nullptr;
|
|
sActiveTabParent = nullptr;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMEStateManager::CanHandleWith(nsPresContext* aPresContext)
|
|
{
|
|
return aPresContext &&
|
|
aPresContext->GetPresShell() &&
|
|
!aPresContext->PresShell()->IsDestroying();
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
IMEStateManager::OnChangeFocus(nsPresContext* aPresContext,
|
|
nsIContent* aContent,
|
|
InputContextAction::Cause aCause)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnChangeFocus(aPresContext=0x%p, aContent=0x%p, aCause=%s)",
|
|
aPresContext, aContent, GetActionCauseName(aCause)));
|
|
|
|
InputContextAction action(aCause);
|
|
return OnChangeFocusInternal(aPresContext, aContent, action);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
|
|
nsIContent* aContent,
|
|
InputContextAction aAction)
|
|
{
|
|
RefPtr<TabParent> newTabParent = TabParent::GetFrom(aContent);
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnChangeFocusInternal(aPresContext=0x%p (available: %s), "
|
|
"aContent=0x%p (TabParent=0x%p), aAction={ mCause=%s, mFocusChange=%s }), "
|
|
"sPresContext=0x%p (available: %s), sContent=0x%p, "
|
|
"sWidget=0x%p (available: %s), sActiveTabParent=0x%p, "
|
|
"sActiveIMEContentObserver=0x%p, sInstalledMenuKeyboardListener=%s",
|
|
aPresContext, GetBoolName(CanHandleWith(aPresContext)), aContent,
|
|
newTabParent.get(), GetActionCauseName(aAction.mCause),
|
|
GetActionFocusChangeName(aAction.mFocusChange),
|
|
sPresContext.get(), GetBoolName(CanHandleWith(sPresContext)),
|
|
sContent.get(), sWidget, GetBoolName(sWidget && !sWidget->Destroyed()),
|
|
sActiveTabParent.get(), sActiveIMEContentObserver.get(),
|
|
GetBoolName(sInstalledMenuKeyboardListener)));
|
|
|
|
// If new aPresShell has been destroyed, this should handle the focus change
|
|
// as nobody is getting focus.
|
|
if (NS_WARN_IF(aPresContext && !CanHandleWith(aPresContext))) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" OnChangeFocusInternal(), called with destroyed PresShell, "
|
|
"handling this call as nobody getting focus"));
|
|
aPresContext = nullptr;
|
|
aContent = nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIWidget> oldWidget = sWidget;
|
|
nsCOMPtr<nsIWidget> newWidget =
|
|
aPresContext ? aPresContext->GetRootWidget() : nullptr;
|
|
bool focusActuallyChanging =
|
|
(sContent != aContent || sPresContext != aPresContext ||
|
|
oldWidget != newWidget || sActiveTabParent != newTabParent);
|
|
|
|
// If old widget has composition, we may need to commit composition since
|
|
// a native IME context is shared on all editors on some widgets or all
|
|
// widgets (it depends on platforms).
|
|
if (oldWidget && focusActuallyChanging && sTextCompositions) {
|
|
RefPtr<TextComposition> composition =
|
|
sTextCompositions->GetCompositionFor(oldWidget);
|
|
if (composition) {
|
|
// However, don't commit the composition if we're being inactivated
|
|
// but the composition should be kept even during deactive.
|
|
// Note that oldWidget and sFocusedIMEWidget may be different here (in
|
|
// such case, sFocusedIMEWidget is perhaps nullptr). For example, IME
|
|
// may receive only blur notification but still has composition.
|
|
// We need to clean up only the oldWidget's composition state here.
|
|
if (aPresContext ||
|
|
!oldWidget->IMENotificationRequestsRef().WantDuringDeactive()) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" OnChangeFocusInternal(), requesting to commit composition to "
|
|
"the (previous) focused widget"));
|
|
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget,
|
|
composition->GetTabParent());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sActiveIMEContentObserver) {
|
|
// If there is active IMEContentObserver, it means that focused content was
|
|
// in this process. So, if a tab parent gets focus, it means that the
|
|
// focused editor in this process is being blurred.
|
|
if (newTabParent) {
|
|
DestroyIMEContentObserver();
|
|
}
|
|
// If the process is being inactivated, then, IMEContentObserver should
|
|
// stop observing the contents unless native IME requests to keep
|
|
// composition even during deactivated.
|
|
else if (!aPresContext) {
|
|
if (!sActiveIMEContentObserver->KeepAliveDuringDeactive()) {
|
|
DestroyIMEContentObserver();
|
|
}
|
|
}
|
|
// Otherwise, i.e., new focused content is in this process, let's check
|
|
// whether the new focused content is already being managed by the
|
|
// active IME content observer.
|
|
else if (!sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) {
|
|
DestroyIMEContentObserver();
|
|
}
|
|
} else {
|
|
// If there is no active IMEContentObserver, it means that focused content
|
|
// may be in another process.
|
|
|
|
// If focus is moving from current focused remote process to different
|
|
// process while the process has IME focus too, we need to notify IME of
|
|
// blur here because it may be too late the blur notification to reach
|
|
// this process especially when closing active window.
|
|
// However, don't send blur if we're being deactivated and IME wants to
|
|
// keep composition during deactive because notifying blur will commit
|
|
// or cancel composition.
|
|
if (sFocusedIMETabParent && sFocusedIMEWidget &&
|
|
(aPresContext ||
|
|
!sFocusedIMEWidget->IMENotificationRequestsRef().
|
|
WantDuringDeactive()) &&
|
|
!IsSameProcess(sFocusedIMETabParent, newTabParent)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" OnChangeFocusInternal(), notifying IME of blur of previous focused "
|
|
"remote process because it may be too late actual notification to "
|
|
"reach this process"));
|
|
NotifyIMEOfBlurForChildProcess();
|
|
}
|
|
}
|
|
|
|
if (!aPresContext) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnChangeFocusInternal(), "
|
|
"no nsPresContext is being activated"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (sActiveTabParent && !IsSameProcess(sActiveTabParent, newTabParent)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnChangeFocusInternal(), notifying previous "
|
|
"focused child process of parent process or another child process "
|
|
"getting focus"));
|
|
Unused << sActiveTabParent->SendStopIMEStateManagement();
|
|
}
|
|
|
|
if (NS_WARN_IF(!newWidget)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" OnChangeFocusInternal(), FAILED due to "
|
|
"no widget to manage its IME state"));
|
|
return NS_OK;
|
|
}
|
|
|
|
// Update the cached widget since root view of the presContext may be
|
|
// changed to different view.
|
|
sWidget = newWidget;
|
|
|
|
// If a child process has focus, we should disable IME state until the child
|
|
// process actually gets focus because if user types keys before that they
|
|
// are handled by IME.
|
|
IMEState newState =
|
|
newTabParent ? IMEState(IMEState::DISABLED) :
|
|
GetNewIMEState(aPresContext, aContent);
|
|
bool setIMEState = true;
|
|
|
|
if (newTabParent) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
if (aAction.mFocusChange == InputContextAction::MENU_GOT_PSEUDO_FOCUS) {
|
|
// If menu keyboard listener is installed, we need to disable IME now.
|
|
setIMEState = true;
|
|
} else if (aAction.mFocusChange ==
|
|
InputContextAction::MENU_LOST_PSEUDO_FOCUS) {
|
|
// If menu keyboard listener is uninstalled, we need to restore
|
|
// input context which was set by the remote process. However, if
|
|
// the remote process hasn't been set input context yet, we need to
|
|
// wait next SetInputContextForChildProcess() call.
|
|
if (HasActiveChildSetInputContext()) {
|
|
setIMEState = true;
|
|
newState = sActiveChildInputContext.mIMEState;
|
|
} else {
|
|
setIMEState = false;
|
|
}
|
|
} else if (focusActuallyChanging) {
|
|
InputContext context = newWidget->GetInputContext();
|
|
if (context.mIMEState.mEnabled == IMEState::DISABLED &&
|
|
context.mOrigin == InputContext::ORIGIN_CONTENT) {
|
|
setIMEState = false;
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnChangeFocusInternal(), doesn't set IME "
|
|
"state because focused element (or document) is in a child process "
|
|
"and the IME state is already disabled by a remote process"));
|
|
} else {
|
|
// When new remote process gets focus, we should forget input context
|
|
// coming from old focused remote process.
|
|
ResetActiveChildInputContext();
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnChangeFocusInternal(), will disable IME "
|
|
"until new focused element (or document) in the child process "
|
|
"will get focus actually"));
|
|
}
|
|
} else if (newWidget->GetInputContext().mOrigin !=
|
|
InputContext::ORIGIN_MAIN) {
|
|
// When focus is NOT changed actually, we shouldn't set IME state if
|
|
// current input context was set by a remote process since that means
|
|
// that the window is being activated and the child process may have
|
|
// composition. Then, we shouldn't commit the composition with making
|
|
// IME state disabled.
|
|
setIMEState = false;
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnChangeFocusInternal(), doesn't set IME "
|
|
"state because focused element (or document) is already in the child "
|
|
"process"));
|
|
}
|
|
} else {
|
|
// When this process gets focus, we should forget input context coming
|
|
// from remote process.
|
|
ResetActiveChildInputContext();
|
|
}
|
|
|
|
if (setIMEState) {
|
|
if (!focusActuallyChanging) {
|
|
// actual focus isn't changing, but if IME enabled state is changing,
|
|
// we should do it.
|
|
InputContext context = newWidget->GetInputContext();
|
|
if (context.mIMEState.mEnabled == newState.mEnabled) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnChangeFocusInternal(), "
|
|
"neither focus nor IME state is changing"));
|
|
return NS_OK;
|
|
}
|
|
aAction.mFocusChange = InputContextAction::FOCUS_NOT_CHANGED;
|
|
|
|
// Even if focus isn't changing actually, we should commit current
|
|
// composition here since the IME state is changing.
|
|
if (sPresContext && oldWidget && !focusActuallyChanging) {
|
|
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget,
|
|
sFocusedIMETabParent);
|
|
}
|
|
} else if (aAction.mFocusChange == InputContextAction::FOCUS_NOT_CHANGED) {
|
|
// If aContent isn't null or aContent is null but editable, somebody gets
|
|
// focus.
|
|
bool gotFocus = aContent || (newState.mEnabled == IMEState::ENABLED);
|
|
aAction.mFocusChange =
|
|
gotFocus ? InputContextAction::GOT_FOCUS :
|
|
InputContextAction::LOST_FOCUS;
|
|
}
|
|
|
|
if (newTabParent && HasActiveChildSetInputContext() &&
|
|
aAction.mFocusChange == InputContextAction::MENU_LOST_PSEUDO_FOCUS) {
|
|
// Restore the input context in the active remote process when
|
|
// menu keyboard listener is uninstalled and active remote tab has
|
|
// focus.
|
|
SetInputContext(newWidget, sActiveChildInputContext, aAction);
|
|
} else {
|
|
// Update IME state for new focus widget
|
|
SetIMEState(newState, aPresContext, aContent, newWidget, aAction,
|
|
newTabParent ? InputContext::ORIGIN_CONTENT : sOrigin);
|
|
}
|
|
}
|
|
|
|
sActiveTabParent = newTabParent;
|
|
sPresContext = aPresContext;
|
|
sContent = aContent;
|
|
|
|
// Don't call CreateIMEContentObserver() here except when a plugin gets
|
|
// focus because it will be called from the focus event handler of focused
|
|
// editor.
|
|
if (newState.mEnabled == IMEState::PLUGIN) {
|
|
CreateIMEContentObserver(nullptr);
|
|
if (sActiveIMEContentObserver) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnChangeFocusInternal(), an "
|
|
"IMEContentObserver instance is created for plugin and trying to "
|
|
"flush its pending notifications..."));
|
|
sActiveIMEContentObserver->TryToFlushPendingNotifications(false);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::OnInstalledMenuKeyboardListener(bool aInstalling)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnInstalledMenuKeyboardListener(aInstalling=%s), "
|
|
"sInstalledMenuKeyboardListener=%s, sActiveTabParent=0x%p, "
|
|
"sActiveChildInputContext={ mIMEState={ mEnabled=%s, mOpen=%s }, "
|
|
"mHTMLInputType=\"%s\", mHTMLInputInputmode=\"%s\", mActionHint=\"%s\", "
|
|
"mInPrivateBrowsing=%s }",
|
|
GetBoolName(aInstalling),
|
|
GetBoolName(sInstalledMenuKeyboardListener),
|
|
sActiveTabParent.get(),
|
|
GetIMEStateEnabledName(sActiveChildInputContext.mIMEState.mEnabled),
|
|
GetIMEStateSetOpenName(sActiveChildInputContext.mIMEState.mOpen),
|
|
NS_ConvertUTF16toUTF8(sActiveChildInputContext.mHTMLInputType).get(),
|
|
NS_ConvertUTF16toUTF8(sActiveChildInputContext.mHTMLInputInputmode).get(),
|
|
NS_ConvertUTF16toUTF8(sActiveChildInputContext.mActionHint).get(),
|
|
GetBoolName(sActiveChildInputContext.mInPrivateBrowsing)));
|
|
|
|
sInstalledMenuKeyboardListener = aInstalling;
|
|
|
|
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
|
aInstalling ? InputContextAction::MENU_GOT_PSEUDO_FOCUS :
|
|
InputContextAction::MENU_LOST_PSEUDO_FOCUS);
|
|
OnChangeFocusInternal(sPresContext, sContent, action);
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMEStateManager::OnMouseButtonEventInEditor(nsPresContext* aPresContext,
|
|
nsIContent* aContent,
|
|
WidgetMouseEvent* aMouseEvent)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnMouseButtonEventInEditor(aPresContext=0x%p, "
|
|
"aContent=0x%p, aMouseEvent=0x%p), sPresContext=0x%p, sContent=0x%p",
|
|
aPresContext, aContent, aMouseEvent, sPresContext.get(), sContent.get()));
|
|
|
|
if (NS_WARN_IF(!aMouseEvent)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnMouseButtonEventInEditor(), aMouseEvent is nullptr"));
|
|
return false;
|
|
}
|
|
|
|
if (sPresContext != aPresContext || sContent != aContent) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnMouseButtonEventInEditor(), "
|
|
"the mouse event isn't fired on the editor managed by ISM"));
|
|
return false;
|
|
}
|
|
|
|
if (!sActiveIMEContentObserver) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnMouseButtonEventInEditor(), "
|
|
"there is no active IMEContentObserver"));
|
|
return false;
|
|
}
|
|
|
|
if (!sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnMouseButtonEventInEditor(), "
|
|
"the active IMEContentObserver isn't managing the editor"));
|
|
return false;
|
|
}
|
|
|
|
bool consumed =
|
|
sActiveIMEContentObserver->OnMouseButtonEvent(aPresContext, aMouseEvent);
|
|
|
|
if (MOZ_LOG_TEST(sISMLog, LogLevel::Info)) {
|
|
nsAutoString eventType;
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" OnMouseButtonEventInEditor(), "
|
|
"mouse event (mMessage=%s, button=%d) is %s",
|
|
ToChar(aMouseEvent->mMessage), aMouseEvent->button,
|
|
consumed ? "consumed" : "not consumed"));
|
|
}
|
|
|
|
return consumed;
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::OnClickInEditor(nsPresContext* aPresContext,
|
|
nsIContent* aContent,
|
|
const WidgetMouseEvent* aMouseEvent)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnClickInEditor(aPresContext=0x%p, aContent=0x%p, aMouseEvent=0x%p), "
|
|
"sPresContext=0x%p, sContent=0x%p, sWidget=0x%p (available: %s)",
|
|
aPresContext, aContent, aMouseEvent, sPresContext.get(), sContent.get(),
|
|
sWidget, GetBoolName(sWidget && !sWidget->Destroyed())));
|
|
|
|
if (NS_WARN_IF(!aMouseEvent)) {
|
|
return;
|
|
}
|
|
|
|
if (sPresContext != aPresContext || sContent != aContent ||
|
|
NS_WARN_IF(!sPresContext) || NS_WARN_IF(!sWidget) ||
|
|
NS_WARN_IF(sWidget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnClickInEditor(), "
|
|
"the mouse event isn't fired on the editor managed by ISM"));
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIWidget> widget(sWidget);
|
|
|
|
MOZ_ASSERT(!sPresContext->GetRootWidget() ||
|
|
sPresContext->GetRootWidget() == widget);
|
|
|
|
if (!aMouseEvent->IsTrusted()) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnClickInEditor(), "
|
|
"the mouse event isn't a trusted event"));
|
|
return; // ignore untrusted event.
|
|
}
|
|
|
|
if (aMouseEvent->button) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnClickInEditor(), "
|
|
"the mouse event isn't a left mouse button event"));
|
|
return; // not a left click event.
|
|
}
|
|
|
|
if (aMouseEvent->mClickCount != 1) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnClickInEditor(), "
|
|
"the mouse event isn't a single click event"));
|
|
return; // should notify only first click event.
|
|
}
|
|
|
|
InputContextAction::Cause cause =
|
|
aMouseEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH ?
|
|
InputContextAction::CAUSE_TOUCH : InputContextAction::CAUSE_MOUSE;
|
|
|
|
InputContextAction action(cause, InputContextAction::FOCUS_NOT_CHANGED);
|
|
IMEState newState = GetNewIMEState(aPresContext, aContent);
|
|
SetIMEState(newState, aPresContext, aContent, widget, action, sOrigin);
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::OnFocusInEditor(nsPresContext* aPresContext,
|
|
nsIContent* aContent,
|
|
EditorBase& aEditorBase)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnFocusInEditor(aPresContext=0x%p, aContent=0x%p, aEditorBase=0x%p), "
|
|
"sPresContext=0x%p, sContent=0x%p, sActiveIMEContentObserver=0x%p",
|
|
aPresContext, aContent, &aEditorBase, sPresContext.get(), sContent.get(),
|
|
sActiveIMEContentObserver.get()));
|
|
|
|
if (sPresContext != aPresContext || sContent != aContent) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnFocusInEditor(), "
|
|
"an editor not managed by ISM gets focus"));
|
|
return;
|
|
}
|
|
|
|
// If the IMEContentObserver instance isn't managing the editor actually,
|
|
// we need to recreate the instance.
|
|
if (sActiveIMEContentObserver) {
|
|
if (sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnFocusInEditor(), "
|
|
"the editor is already being managed by sActiveIMEContentObserver"));
|
|
return;
|
|
}
|
|
DestroyIMEContentObserver();
|
|
}
|
|
|
|
CreateIMEContentObserver(&aEditorBase);
|
|
|
|
// Let's flush the focus notification now.
|
|
if (sActiveIMEContentObserver) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnFocusInEditor(), new IMEContentObserver is "
|
|
"created, trying to flush pending notifications..."));
|
|
sActiveIMEContentObserver->TryToFlushPendingNotifications(false);
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::OnEditorInitialized(EditorBase& aEditorBase)
|
|
{
|
|
if (!sActiveIMEContentObserver ||
|
|
!sActiveIMEContentObserver->WasInitializedWith(aEditorBase)) {
|
|
return;
|
|
}
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnEditorInitialized(aEditorBase=0x%p)",
|
|
&aEditorBase));
|
|
|
|
sActiveIMEContentObserver->UnsuppressNotifyingIME();
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::OnEditorDestroying(EditorBase& aEditorBase)
|
|
{
|
|
if (!sActiveIMEContentObserver ||
|
|
!sActiveIMEContentObserver->WasInitializedWith(aEditorBase)) {
|
|
return;
|
|
}
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnEditorDestroying(aEditorBase=0x%p)",
|
|
&aEditorBase));
|
|
|
|
// The IMEContentObserver shouldn't notify IME of anything until reframing
|
|
// is finished.
|
|
sActiveIMEContentObserver->SuppressNotifyingIME();
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState,
|
|
nsIContent* aContent,
|
|
EditorBase* aEditorBase)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("UpdateIMEState(aNewIMEState={ mEnabled=%s, "
|
|
"mOpen=%s }, aContent=0x%p, aEditorBase=0x%p), "
|
|
"sPresContext=0x%p, sContent=0x%p, sWidget=0x%p (available: %s), "
|
|
"sActiveIMEContentObserver=0x%p, sIsGettingNewIMEState=%s",
|
|
GetIMEStateEnabledName(aNewIMEState.mEnabled),
|
|
GetIMEStateSetOpenName(aNewIMEState.mOpen), aContent, aEditorBase,
|
|
sPresContext.get(), sContent.get(),
|
|
sWidget, GetBoolName(sWidget && !sWidget->Destroyed()),
|
|
sActiveIMEContentObserver.get(),
|
|
GetBoolName(sIsGettingNewIMEState)));
|
|
|
|
if (sIsGettingNewIMEState) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" UpdateIMEState(), "
|
|
"does nothing because of called while getting new IME state"));
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPresShell> presShell;
|
|
if (!aEditorBase) {
|
|
MOZ_ASSERT(aContent, "we must have content");
|
|
nsIDocument* doc = aContent->OwnerDoc();
|
|
presShell = doc->GetShell();
|
|
} else {
|
|
presShell = aEditorBase->GetPresShell();
|
|
}
|
|
|
|
if (NS_WARN_IF(!presShell)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), FAILED due to "
|
|
"editor doesn't have PresShell"));
|
|
return;
|
|
}
|
|
|
|
nsPresContext* presContext = presShell->GetPresContext();
|
|
if (NS_WARN_IF(!presContext)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), FAILED due to "
|
|
"editor doesn't have PresContext"));
|
|
return;
|
|
}
|
|
|
|
// IMEStateManager::UpdateIMEState() should be called after
|
|
// IMEStateManager::OnChangeFocus() is called for setting focus to aContent
|
|
// and aEditorBase. However, when aEditorBase is an HTMLEditor, this may be
|
|
// called by nsIEditor::PostCreate() before IMEStateManager::OnChangeFocus().
|
|
// Similarly, when aEditorBase is a TextEditor, this may be called by
|
|
// nsIEditor::SetFlags(). In such cases, this method should do nothing
|
|
// because input context should be updated when
|
|
// IMEStateManager::OnChangeFocus() is called later.
|
|
if (sPresContext != presContext) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" UpdateIMEState(), does nothing due to "
|
|
"the editor hasn't managed by IMEStateManager yet"));
|
|
return;
|
|
}
|
|
|
|
// If IMEStateManager doesn't manage any document, this cannot update IME
|
|
// state of any widget.
|
|
if (NS_WARN_IF(!sPresContext)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), FAILED due to "
|
|
"no managing nsPresContext"));
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!sWidget) || NS_WARN_IF(sWidget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), FAILED due to "
|
|
"the widget for the managing nsPresContext has gone"));
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIWidget> widget(sWidget);
|
|
|
|
MOZ_ASSERT(!sPresContext->GetRootWidget() ||
|
|
sPresContext->GetRootWidget() == widget);
|
|
|
|
// Even if there is active IMEContentObserver, it may not be observing the
|
|
// editor with current editable root content due to reframed. In such case,
|
|
// We should try to reinitialize the IMEContentObserver.
|
|
if (sActiveIMEContentObserver && IsIMEObserverNeeded(aNewIMEState)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" UpdateIMEState(), try to reinitialize the "
|
|
"active IMEContentObserver"));
|
|
RefPtr<IMEContentObserver> contentObserver = sActiveIMEContentObserver;
|
|
if (!contentObserver->MaybeReinitialize(widget, sPresContext,
|
|
aContent, aEditorBase)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), failed to reinitialize the "
|
|
"active IMEContentObserver"));
|
|
}
|
|
if (NS_WARN_IF(widget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), widget has gone during reinitializing the "
|
|
"active IMEContentObserver"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If there is no active IMEContentObserver or it isn't observing the
|
|
// editor correctly, we should recreate it.
|
|
bool createTextStateManager =
|
|
(!sActiveIMEContentObserver ||
|
|
!sActiveIMEContentObserver->IsManaging(sPresContext, aContent));
|
|
|
|
bool updateIMEState =
|
|
(widget->GetInputContext().mIMEState.mEnabled != aNewIMEState.mEnabled);
|
|
if (NS_WARN_IF(widget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), widget has gone during getting input context"));
|
|
return;
|
|
}
|
|
|
|
if (updateIMEState) {
|
|
// commit current composition before modifying IME state.
|
|
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, widget, sFocusedIMETabParent);
|
|
if (NS_WARN_IF(widget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), widget has gone during committing composition"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (createTextStateManager) {
|
|
DestroyIMEContentObserver();
|
|
}
|
|
|
|
if (updateIMEState) {
|
|
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
|
InputContextAction::FOCUS_NOT_CHANGED);
|
|
SetIMEState(aNewIMEState, presContext, aContent, widget, action, sOrigin);
|
|
if (NS_WARN_IF(widget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), widget has gone during setting input context"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (createTextStateManager) {
|
|
// XXX In this case, it might not be enough safe to notify IME of anything.
|
|
// So, don't try to flush pending notifications of IMEContentObserver
|
|
// here.
|
|
CreateIMEContentObserver(aEditorBase);
|
|
}
|
|
}
|
|
|
|
// static
|
|
IMEState
|
|
IMEStateManager::GetNewIMEState(nsPresContext* aPresContext,
|
|
nsIContent* aContent)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("GetNewIMEState(aPresContext=0x%p, aContent=0x%p), "
|
|
"sInstalledMenuKeyboardListener=%s",
|
|
aPresContext, aContent, GetBoolName(sInstalledMenuKeyboardListener)));
|
|
|
|
if (!CanHandleWith(aPresContext)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns DISABLED because "
|
|
"the nsPresContext has been destroyed"));
|
|
return IMEState(IMEState::DISABLED);
|
|
}
|
|
|
|
// On Printing or Print Preview, we don't need IME.
|
|
if (aPresContext->Type() == nsPresContext::eContext_PrintPreview ||
|
|
aPresContext->Type() == nsPresContext::eContext_Print) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns DISABLED because "
|
|
"the nsPresContext is for print or print preview"));
|
|
return IMEState(IMEState::DISABLED);
|
|
}
|
|
|
|
if (sInstalledMenuKeyboardListener) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns DISABLED because "
|
|
"menu keyboard listener was installed"));
|
|
return IMEState(IMEState::DISABLED);
|
|
}
|
|
|
|
if (!aContent) {
|
|
// Even if there are no focused content, the focused document might be
|
|
// editable, such case is design mode.
|
|
nsIDocument* doc = aPresContext->Document();
|
|
if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns ENABLED because "
|
|
"design mode editor has focus"));
|
|
return IMEState(IMEState::ENABLED);
|
|
}
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns DISABLED because "
|
|
"no content has focus"));
|
|
return IMEState(IMEState::DISABLED);
|
|
}
|
|
|
|
// nsIContent::GetDesiredIMEState() may cause a call of UpdateIMEState()
|
|
// from EditorBase::PostCreate() because GetDesiredIMEState() needs to
|
|
// retrieve an editor instance for the element if it's editable element.
|
|
// For avoiding such nested IME state updates, we should set
|
|
// sIsGettingNewIMEState here and UpdateIMEState() should check it.
|
|
GettingNewIMEStateBlocker blocker;
|
|
|
|
IMEState newIMEState = aContent->GetDesiredIMEState();
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns { mEnabled=%s, "
|
|
"mOpen=%s }",
|
|
GetIMEStateEnabledName(newIMEState.mEnabled),
|
|
GetIMEStateSetOpenName(newIMEState.mOpen)));
|
|
return newIMEState;
|
|
}
|
|
|
|
static bool
|
|
MayBeIMEUnawareWebApp(nsINode* aNode)
|
|
{
|
|
bool haveKeyEventsListener = false;
|
|
|
|
while (aNode) {
|
|
EventListenerManager* const mgr = aNode->GetExistingListenerManager();
|
|
if (mgr) {
|
|
if (mgr->MayHaveInputOrCompositionEventListener()) {
|
|
return false;
|
|
}
|
|
haveKeyEventsListener |= mgr->MayHaveKeyEventListener();
|
|
}
|
|
aNode = aNode->GetParentNode();
|
|
}
|
|
|
|
return haveKeyEventsListener;
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::ResetActiveChildInputContext()
|
|
{
|
|
sActiveChildInputContext.mIMEState.mEnabled = IMEState::UNKNOWN;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMEStateManager::HasActiveChildSetInputContext()
|
|
{
|
|
return sActiveChildInputContext.mIMEState.mEnabled != IMEState::UNKNOWN;
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::SetInputContextForChildProcess(
|
|
TabParent* aTabParent,
|
|
const InputContext& aInputContext,
|
|
const InputContextAction& aAction)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("SetInputContextForChildProcess(aTabParent=0x%p, "
|
|
"aInputContext={ mIMEState={ mEnabled=%s, mOpen=%s }, "
|
|
"mHTMLInputType=\"%s\", mHTMLInputInputmode=\"%s\", mActionHint=\"%s\", "
|
|
"mInPrivateBrowsing=%s }, aAction={ mCause=%s, mAction=%s }), "
|
|
"sPresContext=0x%p (available: %s), sWidget=0x%p (available: %s), "
|
|
"sActiveTabParent=0x%p, sInstalledMenuKeyboardListener=%s",
|
|
aTabParent, GetIMEStateEnabledName(aInputContext.mIMEState.mEnabled),
|
|
GetIMEStateSetOpenName(aInputContext.mIMEState.mOpen),
|
|
NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputType).get(),
|
|
NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputInputmode).get(),
|
|
NS_ConvertUTF16toUTF8(aInputContext.mActionHint).get(),
|
|
GetBoolName(aInputContext.mInPrivateBrowsing),
|
|
GetActionCauseName(aAction.mCause),
|
|
GetActionFocusChangeName(aAction.mFocusChange),
|
|
sPresContext.get(), GetBoolName(CanHandleWith(sPresContext)),
|
|
sWidget, GetBoolName(sWidget && !sWidget->Destroyed()),
|
|
sActiveTabParent.get(), GetBoolName(sInstalledMenuKeyboardListener)));
|
|
|
|
if (aTabParent != sActiveTabParent) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" SetInputContextForChildProcess(), FAILED, "
|
|
"because non-focused tab parent tries to set input context"));
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!CanHandleWith(sPresContext))) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" SetInputContextForChildProcess(), FAILED, "
|
|
"due to no focused presContext"));
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!sWidget) || NS_WARN_IF(sWidget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" SetInputContextForChildProcess(), FAILED, "
|
|
"due to the widget for the nsPresContext has gone"));
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIWidget> widget(sWidget);
|
|
|
|
MOZ_ASSERT(!sPresContext->GetRootWidget() ||
|
|
sPresContext->GetRootWidget() == widget);
|
|
MOZ_ASSERT(aInputContext.mOrigin == InputContext::ORIGIN_CONTENT);
|
|
|
|
sActiveChildInputContext = aInputContext;
|
|
MOZ_ASSERT(HasActiveChildSetInputContext());
|
|
|
|
// If input context is changed in remote process while menu keyboard listener
|
|
// is installed, this process shouldn't set input context now. When it's
|
|
// uninstalled, input context should be restored from
|
|
// sActiveChildInputContext.
|
|
if (sInstalledMenuKeyboardListener) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" SetInputContextForChildProcess(), waiting to set input context "
|
|
"until menu keyboard listener is uninstalled"));
|
|
return;
|
|
}
|
|
|
|
SetInputContext(widget, aInputContext, aAction);
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::SetIMEState(const IMEState& aState,
|
|
nsPresContext* aPresContext,
|
|
nsIContent* aContent,
|
|
nsIWidget* aWidget,
|
|
InputContextAction aAction,
|
|
InputContext::Origin aOrigin)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("SetIMEState(aState={ mEnabled=%s, mOpen=%s }, "
|
|
"aContent=0x%p (TabParent=0x%p), aWidget=0x%p, aAction={ mCause=%s, "
|
|
"mFocusChange=%s }, aOrigin=%s)",
|
|
GetIMEStateEnabledName(aState.mEnabled),
|
|
GetIMEStateSetOpenName(aState.mOpen), aContent,
|
|
TabParent::GetFrom(aContent), aWidget,
|
|
GetActionCauseName(aAction.mCause),
|
|
GetActionFocusChangeName(aAction.mFocusChange),
|
|
ToChar(aOrigin)));
|
|
|
|
NS_ENSURE_TRUE_VOID(aWidget);
|
|
|
|
InputContext context;
|
|
context.mIMEState = aState;
|
|
context.mOrigin = aOrigin;
|
|
context.mMayBeIMEUnaware = context.mIMEState.IsEditable() &&
|
|
sCheckForIMEUnawareWebApps && MayBeIMEUnawareWebApp(aContent);
|
|
|
|
context.mHasHandledUserInput =
|
|
aPresContext && aPresContext->PresShell()->HasHandledUserInput();
|
|
|
|
context.mInPrivateBrowsing =
|
|
aPresContext &&
|
|
nsContentUtils::IsInPrivateBrowsing(aPresContext->Document());
|
|
|
|
if (aContent &&
|
|
aContent->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) {
|
|
if (!aContent->IsHTMLElement(nsGkAtoms::textarea)) {
|
|
// <input type=number> has an anonymous <input type=text> descendant
|
|
// that gets focus whenever anyone tries to focus the number control. We
|
|
// need to check if aContent is one of those anonymous text controls and,
|
|
// if so, use the number control instead:
|
|
nsIContent* content = aContent;
|
|
HTMLInputElement* inputElement =
|
|
HTMLInputElement::FromContentOrNull(aContent);
|
|
if (inputElement) {
|
|
HTMLInputElement* ownerNumberControl =
|
|
inputElement->GetOwnerNumberControl();
|
|
if (ownerNumberControl) {
|
|
content = ownerNumberControl; // an <input type=number>
|
|
}
|
|
}
|
|
content->GetAttr(kNameSpaceID_None, nsGkAtoms::type,
|
|
context.mHTMLInputType);
|
|
} else {
|
|
context.mHTMLInputType.Assign(nsGkAtoms::textarea->GetUTF16String());
|
|
}
|
|
|
|
if (sInputModeSupported ||
|
|
nsContentUtils::IsChromeDoc(aContent->OwnerDoc())) {
|
|
aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::inputmode,
|
|
context.mHTMLInputInputmode);
|
|
if (context.mHTMLInputInputmode.EqualsLiteral("mozAwesomebar") &&
|
|
!nsContentUtils::IsChromeDoc(aContent->OwnerDoc())) {
|
|
// mozAwesomebar should be allowed only in chrome
|
|
context.mHTMLInputInputmode.Truncate();
|
|
}
|
|
}
|
|
|
|
aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::moz_action_hint,
|
|
context.mActionHint);
|
|
|
|
// Get the input content corresponding to the focused node,
|
|
// which may be an anonymous child of the input content.
|
|
nsIContent* inputContent = aContent->FindFirstNonChromeOnlyAccessContent();
|
|
|
|
// If we don't have an action hint and
|
|
// return won't submit the form, use "next".
|
|
if (context.mActionHint.IsEmpty() &&
|
|
inputContent->IsHTMLElement(nsGkAtoms::input)) {
|
|
bool willSubmit = false;
|
|
nsCOMPtr<nsIFormControl> control(do_QueryInterface(inputContent));
|
|
mozilla::dom::Element* formElement = nullptr;
|
|
nsCOMPtr<nsIForm> form;
|
|
if (control) {
|
|
formElement = control->GetFormElement();
|
|
// is this a form and does it have a default submit element?
|
|
if ((form = do_QueryInterface(formElement)) &&
|
|
form->GetDefaultSubmitElement()) {
|
|
willSubmit = true;
|
|
// is this an html form...
|
|
} else if (formElement && formElement->IsHTMLElement(nsGkAtoms::form)) {
|
|
dom::HTMLFormElement* htmlForm =
|
|
static_cast<dom::HTMLFormElement*>(formElement);
|
|
// ... and does it only have a single text input element ?
|
|
if (!htmlForm->ImplicitSubmissionIsDisabled() ||
|
|
// ... or is this the last non-disabled element?
|
|
htmlForm->IsLastActiveElement(control)) {
|
|
willSubmit = true;
|
|
}
|
|
}
|
|
}
|
|
context.mActionHint.Assign(
|
|
willSubmit ? (control->ControlType() == NS_FORM_INPUT_SEARCH ?
|
|
NS_LITERAL_STRING("search") : NS_LITERAL_STRING("go")) :
|
|
(formElement ?
|
|
NS_LITERAL_STRING("next") : EmptyString()));
|
|
}
|
|
}
|
|
|
|
// XXX I think that we should use nsContentUtils::IsCallerChrome() instead
|
|
// of the process type.
|
|
if (aAction.mCause == InputContextAction::CAUSE_UNKNOWN &&
|
|
!XRE_IsContentProcess()) {
|
|
aAction.mCause = InputContextAction::CAUSE_UNKNOWN_CHROME;
|
|
}
|
|
|
|
if ((aAction.mCause == InputContextAction::CAUSE_UNKNOWN ||
|
|
aAction.mCause == InputContextAction::CAUSE_UNKNOWN_CHROME) &&
|
|
EventStateManager::IsHandlingUserInput()) {
|
|
aAction.mCause = EventStateManager::IsHandlingKeyboardInput() ?
|
|
InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT :
|
|
InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT;
|
|
}
|
|
|
|
SetInputContext(aWidget, context, aAction);
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::SetInputContext(nsIWidget* aWidget,
|
|
const InputContext& aInputContext,
|
|
const InputContextAction& aAction)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("SetInputContext(aWidget=0x%p, aInputContext={ "
|
|
"mIMEState={ mEnabled=%s, mOpen=%s }, mHTMLInputType=\"%s\", "
|
|
"mHTMLInputInputmode=\"%s\", mActionHint=\"%s\", "
|
|
"mInPrivateBrowsing=%s }, "
|
|
"aAction={ mCause=%s, mAction=%s }), sActiveTabParent=0x%p",
|
|
aWidget,
|
|
GetIMEStateEnabledName(aInputContext.mIMEState.mEnabled),
|
|
GetIMEStateSetOpenName(aInputContext.mIMEState.mOpen),
|
|
NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputType).get(),
|
|
NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputInputmode).get(),
|
|
NS_ConvertUTF16toUTF8(aInputContext.mActionHint).get(),
|
|
GetBoolName(aInputContext.mInPrivateBrowsing),
|
|
GetActionCauseName(aAction.mCause),
|
|
GetActionFocusChangeName(aAction.mFocusChange),
|
|
sActiveTabParent.get()));
|
|
|
|
MOZ_RELEASE_ASSERT(aWidget);
|
|
|
|
nsCOMPtr<nsIWidget> widget(aWidget);
|
|
widget->SetInputContext(aInputContext, aAction);
|
|
sActiveInputContextWidget = widget;
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::EnsureTextCompositionArray()
|
|
{
|
|
if (sTextCompositions) {
|
|
return;
|
|
}
|
|
sTextCompositions = new TextCompositionArray();
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::DispatchCompositionEvent(
|
|
nsINode* aEventTargetNode,
|
|
nsPresContext* aPresContext,
|
|
WidgetCompositionEvent* aCompositionEvent,
|
|
nsEventStatus* aStatus,
|
|
EventDispatchingCallback* aCallBack,
|
|
bool aIsSynthesized)
|
|
{
|
|
RefPtr<TabParent> tabParent =
|
|
aEventTargetNode->IsContent() ?
|
|
TabParent::GetFrom(aEventTargetNode->AsContent()) : nullptr;
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("DispatchCompositionEvent(aNode=0x%p, "
|
|
"aPresContext=0x%p, aCompositionEvent={ mMessage=%s, "
|
|
"mNativeIMEContext={ mRawNativeIMEContext=0x%" PRIXPTR ", "
|
|
"mOriginProcessID=0x%" PRIX64 " }, mWidget(0x%p)={ "
|
|
"GetNativeIMEContext()={ mRawNativeIMEContext=0x%" PRIXPTR ", "
|
|
"mOriginProcessID=0x%" PRIX64 " }, Destroyed()=%s }, "
|
|
"mFlags={ mIsTrusted=%s, mPropagationStopped=%s } }, "
|
|
"aIsSynthesized=%s), tabParent=%p",
|
|
aEventTargetNode, aPresContext,
|
|
ToChar(aCompositionEvent->mMessage),
|
|
aCompositionEvent->mNativeIMEContext.mRawNativeIMEContext,
|
|
aCompositionEvent->mNativeIMEContext.mOriginProcessID,
|
|
aCompositionEvent->mWidget.get(),
|
|
aCompositionEvent->mWidget->GetNativeIMEContext().mRawNativeIMEContext,
|
|
aCompositionEvent->mWidget->GetNativeIMEContext().mOriginProcessID,
|
|
GetBoolName(aCompositionEvent->mWidget->Destroyed()),
|
|
GetBoolName(aCompositionEvent->mFlags.mIsTrusted),
|
|
GetBoolName(aCompositionEvent->mFlags.mPropagationStopped),
|
|
GetBoolName(aIsSynthesized), tabParent.get()));
|
|
|
|
if (!aCompositionEvent->IsTrusted() ||
|
|
aCompositionEvent->PropagationStopped()) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionUpdate,
|
|
"compositionupdate event shouldn't be dispatched manually");
|
|
|
|
EnsureTextCompositionArray();
|
|
|
|
RefPtr<TextComposition> composition =
|
|
sTextCompositions->GetCompositionFor(aCompositionEvent);
|
|
if (!composition) {
|
|
// If synthesized event comes after delayed native composition events
|
|
// for request of commit or cancel, we should ignore it.
|
|
if (NS_WARN_IF(aIsSynthesized)) {
|
|
return;
|
|
}
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" DispatchCompositionEvent(), "
|
|
"adding new TextComposition to the array"));
|
|
MOZ_ASSERT(aCompositionEvent->mMessage == eCompositionStart);
|
|
composition =
|
|
new TextComposition(aPresContext, aEventTargetNode, tabParent,
|
|
aCompositionEvent);
|
|
sTextCompositions->AppendElement(composition);
|
|
}
|
|
#ifdef DEBUG
|
|
else {
|
|
MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionStart);
|
|
}
|
|
#endif // #ifdef DEBUG
|
|
|
|
// Dispatch the event on composing target.
|
|
composition->DispatchCompositionEvent(aCompositionEvent, aStatus, aCallBack,
|
|
aIsSynthesized);
|
|
|
|
// WARNING: the |composition| might have been destroyed already.
|
|
|
|
// Remove the ended composition from the array.
|
|
// NOTE: When TextComposition is synthesizing compositionend event for
|
|
// emulating a commit, the instance shouldn't be removed from the array
|
|
// because IME may perform it later. Then, we need to ignore the
|
|
// following commit events in TextComposition::DispatchEvent().
|
|
// However, if commit or cancel for a request is performed synchronously
|
|
// during not safe to dispatch events, PresShell must have discarded
|
|
// compositionend event. Then, the synthesized compositionend event is
|
|
// the last event for the composition. In this case, we need to
|
|
// destroy the TextComposition with synthesized compositionend event.
|
|
if ((!aIsSynthesized ||
|
|
composition->WasNativeCompositionEndEventDiscarded()) &&
|
|
aCompositionEvent->CausesDOMCompositionEndEvent()) {
|
|
TextCompositionArray::index_type i =
|
|
sTextCompositions->IndexOf(aCompositionEvent->mWidget);
|
|
if (i != TextCompositionArray::NoIndex) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" DispatchCompositionEvent(), "
|
|
"removing TextComposition from the array since NS_COMPOSTION_END "
|
|
"was dispatched"));
|
|
sTextCompositions->ElementAt(i)->Destroy();
|
|
sTextCompositions->RemoveElementAt(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// static
|
|
IMEContentObserver*
|
|
IMEStateManager::GetActiveContentObserver()
|
|
{
|
|
return sActiveIMEContentObserver;
|
|
}
|
|
|
|
// static
|
|
nsIContent*
|
|
IMEStateManager::GetRootContent(nsPresContext* aPresContext)
|
|
{
|
|
nsIDocument* doc = aPresContext->Document();
|
|
if (NS_WARN_IF(!doc)) {
|
|
return nullptr;
|
|
}
|
|
return doc->GetRootElement();
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::HandleSelectionEvent(nsPresContext* aPresContext,
|
|
nsIContent* aEventTargetContent,
|
|
WidgetSelectionEvent* aSelectionEvent)
|
|
{
|
|
nsIContent* eventTargetContent =
|
|
aEventTargetContent ? aEventTargetContent :
|
|
GetRootContent(aPresContext);
|
|
RefPtr<TabParent> tabParent =
|
|
eventTargetContent ? TabParent::GetFrom(eventTargetContent) : nullptr;
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("HandleSelectionEvent(aPresContext=0x%p, "
|
|
"aEventTargetContent=0x%p, aSelectionEvent={ mMessage=%s, "
|
|
"mFlags={ mIsTrusted=%s } }), tabParent=%p",
|
|
aPresContext, aEventTargetContent,
|
|
ToChar(aSelectionEvent->mMessage),
|
|
GetBoolName(aSelectionEvent->mFlags.mIsTrusted),
|
|
tabParent.get()));
|
|
|
|
if (!aSelectionEvent->IsTrusted()) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<TextComposition> composition = sTextCompositions ?
|
|
sTextCompositions->GetCompositionFor(aSelectionEvent->mWidget) : nullptr;
|
|
if (composition) {
|
|
// When there is a composition, TextComposition should guarantee that the
|
|
// selection event will be handled in same target as composition events.
|
|
composition->HandleSelectionEvent(aSelectionEvent);
|
|
} else {
|
|
// When there is no composition, the selection event should be handled
|
|
// in the aPresContext or tabParent.
|
|
TextComposition::HandleSelectionEvent(aPresContext, tabParent,
|
|
aSelectionEvent);
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::OnCompositionEventDiscarded(
|
|
WidgetCompositionEvent* aCompositionEvent)
|
|
{
|
|
// Note that this method is never called for synthesized events for emulating
|
|
// commit or cancel composition.
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnCompositionEventDiscarded(aCompositionEvent={ "
|
|
"mMessage=%s, mNativeIMEContext={ mRawNativeIMEContext=0x%" PRIXPTR ", "
|
|
"mOriginProcessID=0x%" PRIX64 " }, mWidget(0x%p)={ "
|
|
"GetNativeIMEContext()={ mRawNativeIMEContext=0x%" PRIXPTR ", "
|
|
"mOriginProcessID=0x%" PRIX64 " }, Destroyed()=%s }, "
|
|
"mFlags={ mIsTrusted=%s } })",
|
|
ToChar(aCompositionEvent->mMessage),
|
|
aCompositionEvent->mNativeIMEContext.mRawNativeIMEContext,
|
|
aCompositionEvent->mNativeIMEContext.mOriginProcessID,
|
|
aCompositionEvent->mWidget.get(),
|
|
aCompositionEvent->mWidget->GetNativeIMEContext().mRawNativeIMEContext,
|
|
aCompositionEvent->mWidget->GetNativeIMEContext().mOriginProcessID,
|
|
GetBoolName(aCompositionEvent->mWidget->Destroyed()),
|
|
GetBoolName(aCompositionEvent->mFlags.mIsTrusted)));
|
|
|
|
if (!aCompositionEvent->IsTrusted()) {
|
|
return;
|
|
}
|
|
|
|
// Ignore compositionstart for now because sTextCompositions may not have
|
|
// been created yet.
|
|
if (aCompositionEvent->mMessage == eCompositionStart) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<TextComposition> composition =
|
|
sTextCompositions->GetCompositionFor(aCompositionEvent->mWidget);
|
|
if (!composition) {
|
|
// If the PresShell has been being destroyed during composition,
|
|
// a TextComposition instance for the composition was already removed from
|
|
// the array and destroyed in OnDestroyPresContext(). Therefore, we may
|
|
// fail to retrieve a TextComposition instance here.
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" OnCompositionEventDiscarded(), "
|
|
"TextComposition instance for the widget has already gone"));
|
|
return;
|
|
}
|
|
composition->OnCompositionEventDiscarded(aCompositionEvent);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
IMEStateManager::NotifyIME(IMEMessage aMessage,
|
|
nsIWidget* aWidget,
|
|
TabParent* aTabParent)
|
|
{
|
|
return IMEStateManager::NotifyIME(IMENotification(aMessage), aWidget,
|
|
aTabParent);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
IMEStateManager::NotifyIME(const IMENotification& aNotification,
|
|
nsIWidget* aWidget,
|
|
TabParent* aTabParent)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("NotifyIME(aNotification={ mMessage=%s }, "
|
|
"aWidget=0x%p, aTabParent=0x%p), sFocusedIMEWidget=0x%p, "
|
|
"sActiveTabParent=0x%p, sFocusedIMETabParent=0x%p, "
|
|
"IsSameProcess(aTabParent, sActiveTabParent)=%s, "
|
|
"IsSameProcess(aTabParent, sFocusedIMETabParent)=%s",
|
|
ToChar(aNotification.mMessage), aWidget,
|
|
aTabParent, sFocusedIMEWidget, sActiveTabParent.get(),
|
|
sFocusedIMETabParent.get(),
|
|
GetBoolName(IsSameProcess(aTabParent, sActiveTabParent)),
|
|
GetBoolName(IsSameProcess(aTabParent, sFocusedIMETabParent))));
|
|
|
|
if (NS_WARN_IF(!aWidget)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" NotifyIME(), FAILED due to no widget"));
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
switch (aNotification.mMessage) {
|
|
case NOTIFY_IME_OF_FOCUS: {
|
|
// If focus notification comes from a remote process which already lost
|
|
// focus, we shouldn't accept the focus notification. Then, following
|
|
// notifications from the process will be ignored.
|
|
if (NS_WARN_IF(!IsSameProcess(aTabParent, sActiveTabParent))) {
|
|
MOZ_ASSERT(aTabParent,
|
|
"Why was the input context initialized for a remote process but "
|
|
"does this process get IME focus?");
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received focus notification is ignored "
|
|
"because input context was initialized for %s, perhaps, it came "
|
|
"from a busy remote process",
|
|
sActiveTabParent ? "another remote process" : "current process"));
|
|
return NS_OK;
|
|
}
|
|
// If there is pending blur notification for current focused IME,
|
|
// we should notify IME of blur by ourselves. Then, we should ignore
|
|
// following notifications coming from the process.
|
|
if (sFocusedIMEWidget) {
|
|
MOZ_ASSERT(sFocusedIMETabParent || aTabParent,
|
|
"This case shouldn't be caused by focus move in this process");
|
|
nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget);
|
|
sFocusedIMEWidget = nullptr;
|
|
sFocusedIMETabParent = nullptr;
|
|
focusedIMEWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR));
|
|
}
|
|
sFocusedIMETabParent = aTabParent;
|
|
sFocusedIMEWidget = aWidget;
|
|
nsCOMPtr<nsIWidget> widget(aWidget);
|
|
return widget->NotifyIME(aNotification);
|
|
}
|
|
case NOTIFY_IME_OF_BLUR: {
|
|
if (!IsSameProcess(aTabParent, sFocusedIMETabParent)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received blur notification is ignored "
|
|
"because it's not from current focused IME process"));
|
|
return NS_OK;
|
|
}
|
|
if (!sFocusedIMEWidget) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" NotifyIME(), WARNING, received blur notification but there is "
|
|
"no focused IME widget"));
|
|
return NS_OK;
|
|
}
|
|
if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received blur notification is ignored "
|
|
"because it's not for current focused IME widget"));
|
|
return NS_OK;
|
|
}
|
|
nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget);
|
|
sFocusedIMEWidget = nullptr;
|
|
sFocusedIMETabParent = nullptr;
|
|
return focusedIMEWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR));
|
|
}
|
|
case NOTIFY_IME_OF_SELECTION_CHANGE:
|
|
case NOTIFY_IME_OF_TEXT_CHANGE:
|
|
case NOTIFY_IME_OF_POSITION_CHANGE:
|
|
case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
|
|
case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: {
|
|
if (!IsSameProcess(aTabParent, sFocusedIMETabParent)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received content change notification "
|
|
"is ignored because it's not from current focused IME process"));
|
|
return NS_OK;
|
|
}
|
|
if (!sFocusedIMEWidget) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received content change notification "
|
|
"is ignored because there is no focused IME widget"));
|
|
return NS_OK;
|
|
}
|
|
if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received content change notification "
|
|
"is ignored because it's not for current focused IME widget"));
|
|
return NS_OK;
|
|
}
|
|
nsCOMPtr<nsIWidget> widget(aWidget);
|
|
return widget->NotifyIME(aNotification);
|
|
}
|
|
default:
|
|
// Other notifications should be sent only when there is composition.
|
|
// So, we need to handle the others below.
|
|
break;
|
|
}
|
|
|
|
if (!sTextCompositions) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" NotifyIME(), the request to IME is ignored because "
|
|
"there have been no compositions yet"));
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<TextComposition> composition =
|
|
sTextCompositions->GetCompositionFor(aWidget);
|
|
if (!composition) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" NotifyIME(), the request to IME is ignored because "
|
|
"there is no active composition"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!IsSameProcess(aTabParent, composition->GetTabParent())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the request to IME is ignored because "
|
|
"it does not come from the remote process which has the composition "
|
|
"on aWidget"));
|
|
return NS_OK;
|
|
}
|
|
|
|
switch (aNotification.mMessage) {
|
|
case REQUEST_TO_COMMIT_COMPOSITION:
|
|
return composition->RequestToCommit(aWidget, false);
|
|
case REQUEST_TO_CANCEL_COMPOSITION:
|
|
return composition->RequestToCommit(aWidget, true);
|
|
default:
|
|
MOZ_CRASH("Unsupported notification");
|
|
}
|
|
MOZ_CRASH(
|
|
"Failed to handle the notification for non-synthesized composition");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
IMEStateManager::NotifyIME(IMEMessage aMessage,
|
|
nsPresContext* aPresContext,
|
|
TabParent* aTabParent)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("NotifyIME(aMessage=%s, aPresContext=0x%p, aTabParent=0x%p)",
|
|
ToChar(aMessage), aPresContext, aTabParent));
|
|
|
|
if (NS_WARN_IF(!CanHandleWith(aPresContext))) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsIWidget* widget = aPresContext->GetRootWidget();
|
|
if (NS_WARN_IF(!widget)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" NotifyIME(), FAILED due to no widget for the "
|
|
"nsPresContext"));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
return NotifyIME(aMessage, widget, aTabParent);
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMEStateManager::IsEditable(nsINode* node)
|
|
{
|
|
if (node->IsEditable()) {
|
|
return true;
|
|
}
|
|
// |node| might be readwrite (for example, a text control)
|
|
if (node->IsElement() &&
|
|
node->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// static
|
|
nsINode*
|
|
IMEStateManager::GetRootEditableNode(nsPresContext* aPresContext,
|
|
nsIContent* aContent)
|
|
{
|
|
if (aContent) {
|
|
nsINode* root = nullptr;
|
|
nsINode* node = aContent;
|
|
while (node && IsEditable(node)) {
|
|
// If the node has independent selection like <input type="text"> or
|
|
// <textarea>, the node should be the root editable node for aContent.
|
|
// FYI: <select> element also has independent selection but IsEditable()
|
|
// returns false.
|
|
// XXX: If somebody adds new editable element which has independent
|
|
// selection but doesn't own editor, we'll need more checks here.
|
|
if (node->IsContent() &&
|
|
node->AsContent()->HasIndependentSelection()) {
|
|
return node;
|
|
}
|
|
root = node;
|
|
node = node->GetParentNode();
|
|
}
|
|
return root;
|
|
}
|
|
if (aPresContext) {
|
|
nsIDocument* document = aPresContext->Document();
|
|
if (document && document->IsEditable()) {
|
|
return document;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMEStateManager::IsIMEObserverNeeded(const IMEState& aState)
|
|
{
|
|
return aState.MaybeEditable();
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::DestroyIMEContentObserver()
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("DestroyIMEContentObserver(), sActiveIMEContentObserver=0x%p",
|
|
sActiveIMEContentObserver.get()));
|
|
|
|
if (!sActiveIMEContentObserver) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" DestroyIMEContentObserver() does nothing"));
|
|
return;
|
|
}
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" DestroyIMEContentObserver(), destroying "
|
|
"the active IMEContentObserver..."));
|
|
RefPtr<IMEContentObserver> tsm = sActiveIMEContentObserver.get();
|
|
sActiveIMEContentObserver = nullptr;
|
|
tsm->Destroy();
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEStateManager::CreateIMEContentObserver(EditorBase* aEditorBase)
|
|
{
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("CreateIMEContentObserver(aEditorBase=0x%p), "
|
|
"sPresContext=0x%p, sContent=0x%p, sWidget=0x%p (available: %s), "
|
|
"sActiveIMEContentObserver=0x%p, "
|
|
"sActiveIMEContentObserver->IsManaging(sPresContext, sContent)=%s",
|
|
aEditorBase, sPresContext.get(), sContent.get(),
|
|
sWidget, GetBoolName(sWidget && !sWidget->Destroyed()),
|
|
sActiveIMEContentObserver.get(),
|
|
GetBoolName(sActiveIMEContentObserver ?
|
|
sActiveIMEContentObserver->IsManaging(sPresContext, sContent) : false)));
|
|
|
|
if (NS_WARN_IF(sActiveIMEContentObserver)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" CreateIMEContentObserver(), FAILED due to "
|
|
"there is already an active IMEContentObserver"));
|
|
MOZ_ASSERT(sActiveIMEContentObserver->IsManaging(sPresContext, sContent));
|
|
return;
|
|
}
|
|
|
|
if (!sWidget || NS_WARN_IF(sWidget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" CreateIMEContentObserver(), FAILED due to "
|
|
"the widget for the nsPresContext has gone"));
|
|
return; // Sometimes, there are no widgets.
|
|
}
|
|
|
|
nsCOMPtr<nsIWidget> widget(sWidget);
|
|
|
|
// If it's not text editable, we don't need to create IMEContentObserver.
|
|
if (!IsIMEObserverNeeded(widget->GetInputContext().mIMEState)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" CreateIMEContentObserver() doesn't create "
|
|
"IMEContentObserver because of non-editable IME state"));
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(widget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" CreateIMEContentObserver(), FAILED due to "
|
|
"the widget for the nsPresContext has gone"));
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(sPresContext->GetRootWidget() == widget);
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" CreateIMEContentObserver() is creating an "
|
|
"IMEContentObserver instance..."));
|
|
sActiveIMEContentObserver = new IMEContentObserver();
|
|
|
|
// IMEContentObserver::Init() might create another IMEContentObserver
|
|
// instance. So, sActiveIMEContentObserver would be replaced with new one.
|
|
// We should hold the current instance here.
|
|
RefPtr<IMEContentObserver> activeIMEContentObserver(sActiveIMEContentObserver);
|
|
activeIMEContentObserver->Init(widget, sPresContext, sContent, aEditorBase);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
IMEStateManager::GetFocusSelectionAndRoot(nsISelection** aSelection,
|
|
nsIContent** aRootContent)
|
|
{
|
|
if (!sActiveIMEContentObserver) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
return sActiveIMEContentObserver->GetSelectionAndRoot(aSelection,
|
|
aRootContent);
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<TextComposition>
|
|
IMEStateManager::GetTextCompositionFor(nsIWidget* aWidget)
|
|
{
|
|
if (!sTextCompositions) {
|
|
return nullptr;
|
|
}
|
|
RefPtr<TextComposition> textComposition =
|
|
sTextCompositions->GetCompositionFor(aWidget);
|
|
return textComposition.forget();
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<TextComposition>
|
|
IMEStateManager::GetTextCompositionFor(
|
|
const WidgetCompositionEvent* aCompositionEvent)
|
|
{
|
|
if (!sTextCompositions) {
|
|
return nullptr;
|
|
}
|
|
RefPtr<TextComposition> textComposition =
|
|
sTextCompositions->GetCompositionFor(aCompositionEvent);
|
|
return textComposition.forget();
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<TextComposition>
|
|
IMEStateManager::GetTextCompositionFor(nsPresContext* aPresContext)
|
|
{
|
|
if (!sTextCompositions) {
|
|
return nullptr;
|
|
}
|
|
RefPtr<TextComposition> textComposition =
|
|
sTextCompositions->GetCompositionFor(aPresContext);
|
|
return textComposition.forget();
|
|
}
|
|
|
|
} // namespace mozilla
|