diff options
Diffstat (limited to 'dom/events/IMEStateManager.cpp')
-rw-r--r-- | dom/events/IMEStateManager.cpp | 1781 |
1 files changed, 1781 insertions, 0 deletions
diff --git a/dom/events/IMEStateManager.cpp b/dom/events/IMEStateManager.cpp new file mode 100644 index 000000000..1c8ed63f0 --- /dev/null +++ b/dom/events/IMEStateManager.cpp @@ -0,0 +1,1781 @@ +/* -*- 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"; + } +} + +StaticRefPtr<nsIContent> IMEStateManager::sContent; +StaticRefPtr<nsPresContext> IMEStateManager::sPresContext; +nsIWidget* IMEStateManager::sWidget = nullptr; +nsIWidget* IMEStateManager::sFocusedIMEWidget = nullptr; +nsIWidget* IMEStateManager::sActiveInputContextWidget = nullptr; +StaticRefPtr<TabParent> IMEStateManager::sActiveTabParent; +StaticRefPtr<IMEContentObserver> IMEStateManager::sActiveIMEContentObserver; +TextCompositionArray* IMEStateManager::sTextCompositions = nullptr; +bool IMEStateManager::sInstalledMenuKeyboardListener = false; +bool IMEStateManager::sIsGettingNewIMEState = false; +bool IMEStateManager::sCheckForIMEUnawareWebApps = false; +bool IMEStateManager::sRemoteHasFocus = false; + +// static +void +IMEStateManager::Init() +{ + Preferences::AddBoolVarCache( + &sCheckForIMEUnawareWebApps, + "intl.ime.hack.on_ime_unaware_apps.fire_key_events_for_composition", + false); +} + +// static +void +IMEStateManager::Shutdown() +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("Shutdown(), sTextCompositions=0x%p, sTextCompositions->Length()=%u", + sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0)); + + MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length()); + delete sTextCompositions; + sTextCompositions = nullptr; +} + +// static +void +IMEStateManager::OnTabParentDestroying(TabParent* aTabParent) +{ + 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; + + // TODO: Need to cancel composition without TextComposition and make + // disable IME. +} + +// static +void +IMEStateManager::WidgetDestroyed(nsIWidget* aWidget) +{ + if (sWidget == aWidget) { + sWidget = nullptr; + } + if (sFocusedIMEWidget == aWidget) { + 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); + } + sActiveInputContextWidget = nullptr; + sPresContext = nullptr; + sContent = nullptr; + sActiveTabParent = nullptr; + DestroyIMEContentObserver(); +} + +// 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=%u)", 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); + SetIMEState(newState, nullptr, sWidget, action); + } + 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); + SetIMEState(newState, nullptr, sWidget, action); + } + + 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 (oldWidget && focusActuallyChanging) { + // If we're deactivating, we shouldn't commit composition forcibly because + // the user may want to continue the composition. + if (aPresContext) { + NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget); + } + } + + if (sActiveIMEContentObserver && + (aPresContext || !sActiveIMEContentObserver->KeepAliveDuringDeactive()) && + !sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) { + DestroyIMEContentObserver(); + } + + if (!aPresContext) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnChangeFocusInternal(), " + "no nsPresContext is being activated")); + return NS_OK; + } + + nsIContentParent* currentContentParent = + sActiveTabParent ? sActiveTabParent->Manager() : nullptr; + nsIContentParent* newContentParent = + newTabParent ? newTabParent->Manager() : nullptr; + if (sActiveTabParent && currentContentParent != newContentParent) { + 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) { + if (aAction.mFocusChange == InputContextAction::MENU_GOT_PSEUDO_FOCUS || + aAction.mFocusChange == InputContextAction::MENU_LOST_PSEUDO_FOCUS) { + // XXX When menu keyboard listener is being uninstalled, IME state needs + // to be restored by the child process asynchronously. Therefore, + // some key events which are fired immediately after closing menu + // may not be handled by IME. + Unused << newTabParent-> + SendMenuKeyboardListenerInstalled(sInstalledMenuKeyboardListener); + setIMEState = sInstalledMenuKeyboardListener; + } else if (focusActuallyChanging) { + InputContext context = newWidget->GetInputContext(); + if (context.mIMEState.mEnabled == IMEState::DISABLED) { + 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")); + } else { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnChangeFocusInternal(), will disable IME " + "until new focused element (or document) in the child process " + "will get focus actually")); + } + } else { + // When focus is NOT changed actually, we shouldn't set IME state 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")); + } + } + + 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); + } + } 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; + } + + // Update IME state for new focus widget + SetIMEState(newState, aContent, newWidget, aAction); + } + + 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(); + } + } + + return NS_OK; +} + +// static +void +IMEStateManager::OnInstalledMenuKeyboardListener(bool aInstalling) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnInstalledMenuKeyboardListener(aInstalling=%s), " + "sInstalledMenuKeyboardListener=%s", + GetBoolName(aInstalling), GetBoolName(sInstalledMenuKeyboardListener))); + + 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, + nsIDOMMouseEvent* 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 (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; + } + + WidgetMouseEvent* internalEvent = + aMouseEvent->AsEvent()->WidgetEventPtr()->AsMouseEvent(); + if (NS_WARN_IF(!internalEvent)) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnMouseButtonEventInEditor(), " + "the internal event of aMouseEvent isn't WidgetMouseEvent")); + return false; + } + + bool consumed = + sActiveIMEContentObserver->OnMouseButtonEvent(aPresContext, internalEvent); + + if (MOZ_LOG_TEST(sISMLog, LogLevel::Info)) { + nsAutoString eventType; + aMouseEvent->AsEvent()->GetType(eventType); + MOZ_LOG(sISMLog, LogLevel::Info, + (" OnMouseButtonEventInEditor(), " + "mouse event (type=%s, button=%d) is %s", + NS_ConvertUTF16toUTF8(eventType).get(), internalEvent->button, + consumed ? "consumed" : "not consumed")); + } + + return consumed; +} + +// static +void +IMEStateManager::OnClickInEditor(nsPresContext* aPresContext, + nsIContent* aContent, + nsIDOMMouseEvent* 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 (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); + + bool isTrusted; + nsresult rv = aMouseEvent->AsEvent()->GetIsTrusted(&isTrusted); + NS_ENSURE_SUCCESS_VOID(rv); + if (!isTrusted) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnClickInEditor(), " + "the mouse event isn't a trusted event")); + return; // ignore untrusted event. + } + + int16_t button; + rv = aMouseEvent->GetButton(&button); + NS_ENSURE_SUCCESS_VOID(rv); + if (button != 0) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnClickInEditor(), " + "the mouse event isn't a left mouse button event")); + return; // not a left click event. + } + + int32_t clickCount; + rv = aMouseEvent->GetDetail(&clickCount); + NS_ENSURE_SUCCESS_VOID(rv); + if (clickCount != 1) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnClickInEditor(), " + "the mouse event isn't a single click event")); + return; // should notify only first click event. + } + + uint16_t inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; + aMouseEvent->GetMozInputSource(&inputSource); + InputContextAction::Cause cause = + 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, aContent, widget, action); +} + +// static +void +IMEStateManager::OnFocusInEditor(nsPresContext* aPresContext, + nsIContent* aContent, + nsIEditor* aEditor) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnFocusInEditor(aPresContext=0x%p, aContent=0x%p, aEditor=0x%p), " + "sPresContext=0x%p, sContent=0x%p, sActiveIMEContentObserver=0x%p", + aPresContext, aContent, aEditor, 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(aEditor); + + // 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(); + } +} + +// static +void +IMEStateManager::OnEditorInitialized(nsIEditor* aEditor) +{ + if (!sActiveIMEContentObserver || + sActiveIMEContentObserver->GetEditor() != aEditor) { + return; + } + + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnEditorInitialized(aEditor=0x%p)", + aEditor)); + + sActiveIMEContentObserver->UnsuppressNotifyingIME(); +} + +// static +void +IMEStateManager::OnEditorDestroying(nsIEditor* aEditor) +{ + if (!sActiveIMEContentObserver || + sActiveIMEContentObserver->GetEditor() != aEditor) { + return; + } + + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnEditorDestroying(aEditor=0x%p)", + aEditor)); + + // 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 = 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")); + if (!sActiveIMEContentObserver->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); + 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, aContent, widget, action); + 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::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\" }, " + "aAction={ mCause=%s, mAction=%s }), " + "sPresContext=0x%p (available: %s), sWidget=0x%p (available: %s), " + "sActiveTabParent=0x%p", + aTabParent, GetIMEStateEnabledName(aInputContext.mIMEState.mEnabled), + GetIMEStateSetOpenName(aInputContext.mIMEState.mOpen), + NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputType).get(), + NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputInputmode).get(), + NS_ConvertUTF16toUTF8(aInputContext.mActionHint).get(), + GetActionCauseName(aAction.mCause), + GetActionFocusChangeName(aAction.mFocusChange), + sPresContext.get(), GetBoolName(CanHandleWith(sPresContext)), + sWidget, GetBoolName(sWidget && !sWidget->Destroyed()), + sActiveTabParent.get())); + + 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); + + SetInputContext(widget, aInputContext, aAction); +} + +// static +void +IMEStateManager::SetIMEState(const IMEState& aState, + nsIContent* aContent, + nsIWidget* aWidget, + InputContextAction aAction) +{ + 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 })", + GetIMEStateEnabledName(aState.mEnabled), + GetIMEStateSetOpenName(aState.mOpen), aContent, + TabParent::GetFrom(aContent), aWidget, + GetActionCauseName(aAction.mCause), + GetActionFocusChangeName(aAction.mFocusChange))); + + NS_ENSURE_TRUE_VOID(aWidget); + + InputContext context; + context.mIMEState = aState; + context.mMayBeIMEUnaware = context.mIMEState.IsEditable() && + sCheckForIMEUnawareWebApps && MayBeIMEUnawareWebApp(aContent); + + 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 (Preferences::GetBool("dom.forms.inputmode", false) || + nsContentUtils::IsChromeDoc(aContent->OwnerDoc())) { + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::inputmode, + context.mHTMLInputInputmode); + } + + 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 and does it only have a single text input element? + } else if (formElement && formElement->IsHTMLElement(nsGkAtoms::form) && + !static_cast<dom::HTMLFormElement*>(formElement)-> + ImplicitSubmissionIsDisabled()) { + willSubmit = true; + } + } + context.mActionHint.Assign( + willSubmit ? (control->GetType() == 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; + } + + 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\" }, " + "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(), + 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%X, " + "mOriginProcessID=0x%X }, mWidget(0x%p)={ " + "GetNativeIMEContext()={ mRawNativeIMEContext=0x%X, " + "mOriginProcessID=0x%X }, 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%X, " + "mOriginProcessID=0x%X }, mWidget(0x%p)={ " + "GetNativeIMEContext()={ mRawNativeIMEContext=0x%X, " + "mOriginProcessID=0x%X }, 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, + bool aOriginIsRemote) +{ + return IMEStateManager::NotifyIME(IMENotification(aMessage), aWidget, + aOriginIsRemote); +} + +// static +nsresult +IMEStateManager::NotifyIME(const IMENotification& aNotification, + nsIWidget* aWidget, + bool aOriginIsRemote) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("NotifyIME(aNotification={ mMessage=%s }, " + "aWidget=0x%p, aOriginIsRemote=%s), sFocusedIMEWidget=0x%p, " + "sRemoteHasFocus=%s", + ToChar(aNotification.mMessage), aWidget, + GetBoolName(aOriginIsRemote), sFocusedIMEWidget, + GetBoolName(sRemoteHasFocus))); + + 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 (sFocusedIMEWidget) { + if (NS_WARN_IF(!sRemoteHasFocus && !aOriginIsRemote)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), although, this process is " + "getting IME focus but there was focused IME widget")); + } else { + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), tries to notify IME of " + "blur first because remote process's blur notification hasn't " + "been received yet...")); + } + nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget); + sFocusedIMEWidget = nullptr; + sRemoteHasFocus = false; + focusedIMEWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)); + } + sRemoteHasFocus = aOriginIsRemote; + sFocusedIMEWidget = aWidget; + nsCOMPtr<nsIWidget> widget(aWidget); + return widget->NotifyIME(aNotification); + } + case NOTIFY_IME_OF_BLUR: { + if (!sRemoteHasFocus && aOriginIsRemote) { + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), received blur notification " + "after another one has focus, nothing to do...")); + return NS_OK; + } + if (NS_WARN_IF(sRemoteHasFocus && !aOriginIsRemote)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED, received blur " + "notification from this process but the remote has focus")); + return NS_OK; + } + if (!sFocusedIMEWidget && aOriginIsRemote) { + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), received blur notification " + "but the remote has already lost focus")); + return NS_OK; + } + if (NS_WARN_IF(!sFocusedIMEWidget)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED, received blur " + "notification but there is no focused IME widget")); + return NS_OK; + } + if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED, received blur " + "notification but there is no focused IME widget")); + return NS_OK; + } + nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget); + sFocusedIMEWidget = nullptr; + sRemoteHasFocus = false; + 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 (!sRemoteHasFocus && aOriginIsRemote) { + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), received content change " + "notification from the remote but it's already lost focus")); + return NS_OK; + } + if (NS_WARN_IF(sRemoteHasFocus && !aOriginIsRemote)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED, received content " + "change notification from this process but the remote has already " + "gotten focus")); + return NS_OK; + } + if (!sFocusedIMEWidget) { + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), received content change " + "notification but there is no focused IME widget")); + return NS_OK; + } + if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED, received content " + "change notification for IME which has already lost focus, so, " + "nothing to do...")); + 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; + } + + RefPtr<TextComposition> composition; + if (sTextCompositions) { + composition = sTextCompositions->GetCompositionFor(aWidget); + } + + bool isSynthesizedForTests = + composition && composition->IsSynthesizedForTests(); + + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), composition=0x%p, " + "composition->IsSynthesizedForTests()=%s", + composition.get(), GetBoolName(isSynthesizedForTests))); + + switch (aNotification.mMessage) { + case REQUEST_TO_COMMIT_COMPOSITION: + return composition ? + composition->RequestToCommit(aWidget, false) : NS_OK; + case REQUEST_TO_CANCEL_COMPOSITION: + return composition ? + composition->RequestToCommit(aWidget, true) : NS_OK; + 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, + bool aOriginIsRemote) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("NotifyIME(aMessage=%s, aPresContext=0x%p, aOriginIsRemote=%s)", + ToChar(aMessage), aPresContext, GetBoolName(aOriginIsRemote))); + + 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, aOriginIsRemote); +} + +// 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(nsIEditor* aEditor) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("CreateIMEContentObserver(aEditor=0x%p), " + "sPresContext=0x%p, sContent=0x%p, sWidget=0x%p (available: %s), " + "sActiveIMEContentObserver=0x%p, " + "sActiveIMEContentObserver->IsManaging(sPresContext, sContent)=%s", + aEditor, 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, aEditor); +} + +// 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 |