Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "IMEStateManager.h"
#include "IMEContentObserver.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/EditorBase.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/Logging.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/PresShell.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_intl.h"
#include "mozilla/TextComposition.h"
#include "mozilla/TextEvents.h"
#include "mozilla/ToString.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/BrowserBridgeChild.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLFormElement.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLTextAreaElement.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/widget/IMEData.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsFocusManager.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsIFormControl.h"
#include "nsINode.h"
#include "nsISupports.h"
#include "nsIURI.h"
#include "nsIURIMutator.h"
#include "nsPresContext.h"
#include "nsThreadUtils.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"; }
StaticRefPtr<Element> IMEStateManager::sFocusedElement;
StaticRefPtr<nsPresContext> IMEStateManager::sFocusedPresContext;
nsIWidget* IMEStateManager::sTextInputHandlingWidget = nullptr;
nsIWidget* IMEStateManager::sFocusedIMEWidget = nullptr;
StaticRefPtr<BrowserParent> IMEStateManager::sFocusedIMEBrowserParent;
nsIWidget* IMEStateManager::sActiveInputContextWidget = nullptr;
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::sCleaningUpForStoppingIMEStateManagement = false;
bool IMEStateManager::sIsActive = false;
Maybe<IMEStateManager::PendingFocusedBrowserSwitchingData>
IMEStateManager::sPendingFocusedBrowserSwitchingData;
class PseudoFocusChangeRunnable : public Runnable {
public:
explicit PseudoFocusChangeRunnable(bool aInstallingMenuKeyboardListener)
: Runnable("PseudoFocusChangeRunnable"),
mFocusedPresContext(IMEStateManager::sFocusedPresContext),
mFocusedElement(IMEStateManager::sFocusedElement),
mInstallMenuKeyboardListener(aInstallingMenuKeyboardListener) {}
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
IMEStateManager::SetMenubarPseudoFocus(this, mInstallMenuKeyboardListener,
mFocusedPresContext);
return NS_OK;
}
private:
const RefPtr<nsPresContext> mFocusedPresContext;
const RefPtr<Element> mFocusedElement;
const bool mInstallMenuKeyboardListener;
};
StaticRefPtr<PseudoFocusChangeRunnable>
IMEStateManager::sPseudoFocusChangeRunnable;
// static
void IMEStateManager::Init() {
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, "
"sPendingFocusedBrowserSwitchingData.isSome()=%s",
sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0,
GetBoolName(sPendingFocusedBrowserSwitchingData.isSome())));
MOZ_LOG(sISMLog, LogLevel::Debug,
(" Shutdown(), sFocusedElement=0x%p, sFocusedPresContext=0x%p, "
"sTextInputHandlingWidget=0x%p, sFocusedIMEWidget=0x%p, "
"sFocusedIMEBrowserParent=0x%p, sActiveInputContextWidget=0x%p, "
"sActiveIMEContentObserver=0x%p",
sFocusedElement.get(), sFocusedPresContext.get(),
sTextInputHandlingWidget, sFocusedIMEWidget,
sFocusedIMEBrowserParent.get(), sActiveInputContextWidget,
sActiveIMEContentObserver.get()));
sPendingFocusedBrowserSwitchingData.reset();
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::OnFocusMovedBetweenBrowsers(BrowserParent* aBlur,
BrowserParent* aFocus) {
MOZ_ASSERT(aBlur != aFocus);
MOZ_ASSERT(XRE_IsParentProcess());
if (sPendingFocusedBrowserSwitchingData.isSome()) {
MOZ_ASSERT(aBlur ==
sPendingFocusedBrowserSwitchingData.ref().mBrowserParentFocused);
// If focus is not changed between browsers actually, we need to do
// nothing here. Let's cancel handling what this method does.
if (sPendingFocusedBrowserSwitchingData.ref().mBrowserParentBlurred ==
aFocus) {
sPendingFocusedBrowserSwitchingData.reset();
MOZ_LOG(sISMLog, LogLevel::Info,
(" OnFocusMovedBetweenBrowsers(), canceled all pending focus "
"moves between browsers"));
return;
}
aBlur = sPendingFocusedBrowserSwitchingData.ref().mBrowserParentBlurred;
sPendingFocusedBrowserSwitchingData.ref().mBrowserParentFocused = aFocus;
MOZ_ASSERT(aBlur != aFocus);
}
// If application was inactive, but is now activated, and the last focused
// this is called by BrowserParent::UnsetTopLevelWebFocusAll() from
// nsFocusManager::WindowRaised(). If a content has focus in a remote
// process and it has composition, it may get focus back later and the
// composition shouldn't be commited now. Therefore, we should put off to
// handle this until getting another call of this method or a call of
//`OnFocusChangeInternal()`.
if (aBlur && !aFocus && !sIsActive && sTextInputHandlingWidget &&
sTextCompositions &&
sTextCompositions->GetCompositionFor(sTextInputHandlingWidget)) {
if (sPendingFocusedBrowserSwitchingData.isNothing()) {
sPendingFocusedBrowserSwitchingData.emplace(aBlur, aFocus);
}
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnFocusMovedBetweenBrowsers(), put off to handle it until "
"next OnFocusChangeInternal() call"));
return;
}
sPendingFocusedBrowserSwitchingData.reset();
const nsCOMPtr<nsIWidget> oldWidget = sTextInputHandlingWidget;
// In the chrome-process case, we'll get sTextInputHandlingWidget from a
// PresShell later.
sTextInputHandlingWidget =
aFocus ? nsCOMPtr<nsIWidget>(aFocus->GetTextInputHandlingWidget()).get()
: nullptr;
if (oldWidget && sTextCompositions) {
RefPtr<TextComposition> composition =
sTextCompositions->GetCompositionFor(oldWidget);
if (composition) {
MOZ_LOG(
sISMLog, LogLevel::Debug,
(" OnFocusMovedBetweenBrowsers(), requesting to commit "
"composition to "
"the (previous) focused widget (would request=%s)",
GetBoolName(
!oldWidget->IMENotificationRequestsRef().WantDuringDeactive())));
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget,
composition->GetBrowserParent());
}
}
// The manager check is to avoid telling the content process to stop
// IME state management after focus has already moved there between
// two same-process-hosted out-of-process iframes.
if (aBlur && (!aFocus || (aBlur->Manager() != aFocus->Manager()))) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnFocusMovedBetweenBrowsers(), notifying previous "
"focused child process of parent process or another child process "
"getting focus"));
aBlur->StopIMEStateManagement();
}
if (sActiveIMEContentObserver) {
DestroyIMEContentObserver();
}
if (sFocusedIMEWidget) {
// sFocusedIMEBrowserParent can be null, if IME focus hasn't been
// taken before BrowserParent blur.
// aBlur can be null when keyboard focus moves not actually
// between tabs but an open menu is involved.
MOZ_ASSERT(!sFocusedIMEBrowserParent || !aBlur ||
(sFocusedIMEBrowserParent == aBlur));
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnFocusMovedBetweenBrowsers(), notifying IME of blur"));
NotifyIME(NOTIFY_IME_OF_BLUR, sFocusedIMEWidget, sFocusedIMEBrowserParent);
MOZ_ASSERT(!sFocusedIMEBrowserParent);
MOZ_ASSERT(!sFocusedIMEWidget);
} else {
MOZ_ASSERT(!sFocusedIMEBrowserParent);
}
// We deliberately don't null out sFocusedElement or sFocusedPresContext here.
// When focus is in remote content, as far as layout in the chrome process is
// concerned, the corresponding content is the top-level XUL browser. Changes
// among out-of-process iframes don't change that, so dropping the pointer to
// the XUL browser upon such a change would break IME handling.
}
// static
void IMEStateManager::WidgetDestroyed(nsIWidget* aWidget) {
MOZ_LOG(sISMLog, LogLevel::Debug,
("WidgetDestroyed(aWidget=0x%p), sFocusedIMEWidget=0x%p, "
"sActiveInputContextWidget=0x%p, sFocusedIMEBrowserParent=0x%p",
aWidget, sFocusedIMEWidget, sActiveInputContextWidget,
sFocusedIMEBrowserParent.get()));
if (sTextInputHandlingWidget == aWidget) {
sTextInputHandlingWidget = nullptr;
}
if (sFocusedIMEWidget == aWidget) {
if (sFocusedIMEBrowserParent) {
OnFocusMovedBetweenBrowsers(sFocusedIMEBrowserParent, nullptr);
MOZ_ASSERT(!sFocusedIMEBrowserParent);
}
sFocusedIMEWidget = nullptr;
}
if (sActiveInputContextWidget == aWidget) {
sActiveInputContextWidget = nullptr;
}
}
// static
void IMEStateManager::WidgetOnQuit(nsIWidget* aWidget) {
if (sFocusedIMEWidget == aWidget) {
MOZ_LOG(
sISMLog, LogLevel::Debug,
("WidgetOnQuit(aWidget=0x%p (available %s)), sFocusedIMEWidget=0x%p",
aWidget, GetBoolName(aWidget && !aWidget->Destroyed()),
sFocusedIMEWidget));
// Notify IME of blur (which is done by IMEContentObserver::Destroy
// automatically) when the widget still has IME focus before forgetting the
// focused widget because the focused widget is required to clean up native
// IME handler with sending blur notification. Fortunately, the widget
// has not been destroyed yet here since some methods to sending blur
// notification won't work with destroyed widget.
IMEStateManager::DestroyIMEContentObserver();
// Finally, clean up the widget and related objects for avoiding to leak.
IMEStateManager::WidgetDestroyed(aWidget);
}
}
// static
void IMEStateManager::StopIMEStateManagement() {
MOZ_ASSERT(XRE_IsContentProcess());
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.
// The requestee of this method in the main process must destroy its
// active IMEContentObserver for making existing composition end and
// make it be possible to start new composition in new focused process.
// Therefore, we shouldn't notify the main process of any changes which
// occurred after here.
AutoRestore<bool> restoreStoppingIMEStateManagementState(
sCleaningUpForStoppingIMEStateManagement);
sCleaningUpForStoppingIMEStateManagement = true;
if (sTextCompositions && sFocusedPresContext) {
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, sFocusedPresContext, nullptr);
}
sActiveInputContextWidget = nullptr;
sFocusedPresContext = nullptr;
sFocusedElement = nullptr;
sIsActive = false;
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;
}
TextComposition* const 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) {
// 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 != sFocusedPresContext) {
return NS_OK;
}
MOZ_LOG(
sISMLog, LogLevel::Info,
("OnDestroyPresContext(aPresContext=0x%p), "
"sFocusedPresContext=0x%p, sFocusedElement=0x%p, sTextCompositions=0x%p",
&aPresContext, sFocusedPresContext.get(), sFocusedElement.get(),
sTextCompositions));
DestroyIMEContentObserver();
if (sTextInputHandlingWidget) {
IMEState newState = GetNewIMEState(*sFocusedPresContext, nullptr);
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
InputContextAction::LOST_FOCUS);
InputContext::Origin origin =
BrowserParent::GetFocused() ? InputContext::ORIGIN_CONTENT : sOrigin;
OwningNonNull<nsIWidget> textInputHandlingWidget =
*sTextInputHandlingWidget;
SetIMEState(newState, nullptr, nullptr, textInputHandlingWidget, action,
origin);
}
sTextInputHandlingWidget = nullptr;
sFocusedElement = nullptr;
sFocusedPresContext = nullptr;
return NS_OK;
}
// static
nsresult IMEStateManager::OnRemoveContent(nsPresContext& aPresContext,
Element& aElement) {
// First, if there is a composition in the aElement, clean up it.
if (sTextCompositions) {
const RefPtr<TextComposition> compositionInContent =
sTextCompositions->GetCompositionInContent(&aPresContext, &aElement);
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 (!sFocusedPresContext || !sFocusedElement ||
!sFocusedElement->IsInclusiveDescendantOf(&aElement)) {
return NS_OK;
}
MOZ_ASSERT(sFocusedPresContext == &aPresContext);
MOZ_LOG(
sISMLog, LogLevel::Info,
("OnRemoveContent(aPresContext=0x%p, aElement=0x%p), "
"sFocusedPresContext=0x%p, sFocusedElement=0x%p, sTextCompositions=0x%p",
&aPresContext, &aElement, sFocusedPresContext.get(),
sFocusedElement.get(), sTextCompositions));
DestroyIMEContentObserver();
// FYI: Don't clear sTextInputHandlingWidget and sFocusedPresContext because
// the window/document keeps having focus.
sFocusedElement = nullptr;
// Current IME transaction should commit
if (!sTextInputHandlingWidget) {
return NS_OK;
}
IMEState newState = GetNewIMEState(*sFocusedPresContext, nullptr);
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
InputContextAction::LOST_FOCUS);
InputContext::Origin origin =
BrowserParent::GetFocused() ? InputContext::ORIGIN_CONTENT : sOrigin;
OwningNonNull<nsIWidget> textInputHandlingWidget = *sTextInputHandlingWidget;
SetIMEState(newState, &aPresContext, nullptr, textInputHandlingWidget, action,
origin);
if (sFocusedPresContext != &aPresContext || sFocusedElement) {
return NS_OK; // Some body must have set focus
}
if (IsIMEObserverNeeded(newState)) {
if (RefPtr<HTMLEditor> htmlEditor =
nsContentUtils::GetHTMLEditor(&aPresContext)) {
CreateIMEContentObserver(*htmlEditor, nullptr);
}
}
return NS_OK;
}
// static
bool IMEStateManager::CanHandleWith(const nsPresContext* aPresContext) {
return aPresContext && aPresContext->GetPresShell() &&
!aPresContext->PresShell()->IsDestroying();
}
// static
nsresult IMEStateManager::OnChangeFocus(nsPresContext* aPresContext,
Element* aElement,
InputContextAction::Cause aCause) {
MOZ_LOG(sISMLog, LogLevel::Info,
("OnChangeFocus(aPresContext=0x%p, aElement=0x%p, aCause=%s)",
aPresContext, aElement, ToString(aCause).c_str()));
InputContextAction action(aCause);
return OnChangeFocusInternal(aPresContext, aElement, action);
}
// static
nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
Element* aElement,
InputContextAction aAction) {
NS_ASSERTION(!aElement || aElement->GetPresContext(
Element::PresContextFor::eForComposedDoc) ==
aPresContext,
"aPresContext does not match with one of aElement");
bool remoteHasFocus = EventStateManager::IsRemoteTarget(aElement);
// If we've handled focused content, we were inactive but now active,
// a remote process has focus, and setting focus to same content in the main
// process, it means that we're restoring focus without changing DOM focus
// both in the main process and the remote process.
const bool restoringContextForRemoteContent =
XRE_IsParentProcess() && remoteHasFocus && !sIsActive && aPresContext &&
sFocusedPresContext && sFocusedElement &&
sFocusedPresContext.get() == aPresContext &&
sFocusedElement.get() == aElement &&
aAction.mFocusChange != InputContextAction::MENU_GOT_PSEUDO_FOCUS;
MOZ_LOG(
sISMLog, LogLevel::Info,
("OnChangeFocusInternal(aPresContext=0x%p (available: %s), "
"aElement=0x%p (remote: %s), aAction={ mCause=%s, "
"mFocusChange=%s }), sFocusedPresContext=0x%p (available: %s), "
"sFocusedElement=0x%p, sTextInputHandlingWidget=0x%p (available: %s), "
"BrowserParent::GetFocused()=0x%p, sActiveIMEContentObserver=0x%p, "
"sInstalledMenuKeyboardListener=%s, sIsActive=%s, "
"restoringContextForRemoteContent=%s",
aPresContext, GetBoolName(CanHandleWith(aPresContext)), aElement,
GetBoolName(remoteHasFocus), ToString(aAction.mCause).c_str(),
ToString(aAction.mFocusChange).c_str(), sFocusedPresContext.get(),
GetBoolName(CanHandleWith(sFocusedPresContext)), sFocusedElement.get(),
sTextInputHandlingWidget,
GetBoolName(sTextInputHandlingWidget &&
!sTextInputHandlingWidget->Destroyed()),
BrowserParent::GetFocused(), sActiveIMEContentObserver.get(),
GetBoolName(sInstalledMenuKeyboardListener), GetBoolName(sIsActive),
GetBoolName(restoringContextForRemoteContent)));
sIsActive = !!aPresContext;
if (sPendingFocusedBrowserSwitchingData.isSome()) {
MOZ_ASSERT(XRE_IsParentProcess());
RefPtr<Element> focusedElement = sFocusedElement;
RefPtr<nsPresContext> focusedPresContext = sFocusedPresContext;
RefPtr<BrowserParent> browserParentBlurred =
sPendingFocusedBrowserSwitchingData.ref().mBrowserParentBlurred;
RefPtr<BrowserParent> browserParentFocused =
sPendingFocusedBrowserSwitchingData.ref().mBrowserParentFocused;
OnFocusMovedBetweenBrowsers(browserParentBlurred, browserParentFocused);
// If another call of this method happens during the
// OnFocusMovedBetweenBrowsers call, we shouldn't take back focus to
// the old one.
if (focusedElement != sFocusedElement.get() ||
focusedPresContext != sFocusedPresContext.get()) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(aPresContext=0x%p, aElement=0x%p) "
"stoped handling it because the focused content was changed to "
"sFocusedPresContext=0x%p, sFocusedElement=0x%p by another call",
aPresContext, aElement, sFocusedPresContext.get(),
sFocusedElement.get()));
return NS_OK;
}
}
// If new aPresShell has been destroyed, this should handle the focus change
// as nobody is getting focus.
MOZ_ASSERT_IF(!aPresContext, !aElement);
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;
aElement = nullptr;
} else if (!aPresContext) {
aElement = nullptr;
}
const nsCOMPtr<nsIWidget> oldWidget = sTextInputHandlingWidget;
const nsCOMPtr<nsIWidget> newWidget =
aPresContext ? aPresContext->GetTextInputHandlingWidget() : nullptr;
const bool focusActuallyChanging =
(sFocusedElement != aElement || sFocusedPresContext != aPresContext ||
oldWidget != newWidget ||
(remoteHasFocus && !restoringContextForRemoteContent &&
(aAction.mFocusChange != InputContextAction::MENU_GOT_PSEUDO_FOCUS)));
// 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->GetBrowserParent());
}
}
}
if (sActiveIMEContentObserver) {
MOZ_ASSERT(!remoteHasFocus || XRE_IsContentProcess(),
"IMEContentObserver should have been destroyed by "
"OnFocusMovedBetweenBrowsers.");
if (!aPresContext) {
if (!sActiveIMEContentObserver->KeepAliveDuringDeactive()) {
DestroyIMEContentObserver();
}
}
// Otherwise, i.e., new focused content is in this process, let's check
// whether the editable content for aElement has already been being observed
// by the active IMEContentObserver. If not, let's stop observing the
// editable content which is being blurred.
else if (!sActiveIMEContentObserver->IsObserving(*aPresContext, aElement)) {
DestroyIMEContentObserver();
}
}
if (!aPresContext) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(), no nsPresContext is being activated"));
return NS_OK;
}
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.
sTextInputHandlingWidget = 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 = remoteHasFocus ? IMEState(IMEEnabled::Disabled)
: GetNewIMEState(*aPresContext, aElement);
bool setIMEState = true;
if (remoteHasFocus && 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 == IMEEnabled::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 (sFocusedPresContext && oldWidget && !focusActuallyChanging) {
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget,
sFocusedIMEBrowserParent);
}
} else if (aAction.mFocusChange == InputContextAction::FOCUS_NOT_CHANGED) {
// If aElement isn't null or aElement is null but editable, somebody gets
// focus.
bool gotFocus = aElement || (newState.mEnabled == IMEEnabled::Enabled);
aAction.mFocusChange = gotFocus ? InputContextAction::GOT_FOCUS
: InputContextAction::LOST_FOCUS;
}
if (remoteHasFocus && 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, aElement, *newWidget, aAction,
remoteHasFocus ? InputContext::ORIGIN_CONTENT : sOrigin);
}
}
sFocusedPresContext = aPresContext;
sFocusedElement = aElement;
// Don't call CreateIMEContentObserver() here because it will be called from
// the focus event handler of focused editor.
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(), modified IME state for "
"sFocusedPresContext=0x%p, sFocusedElement=0x%p",
sFocusedPresContext.get(), sFocusedElement.get()));
return NS_OK;
}
// static
void IMEStateManager::OnInstalledMenuKeyboardListener(bool aInstalling) {
MOZ_LOG(
sISMLog, LogLevel::Info,
("OnInstalledMenuKeyboardListener(aInstalling=%s), "
"nsContentUtils::IsSafeToRunScript()=%s, "
"sInstalledMenuKeyboardListener=%s, BrowserParent::GetFocused()=0x%p, "
"sActiveChildInputContext=%s, sFocusedPresContext=0x%p, "
"sFocusedElement=0x%p, sPseudoFocusChangeRunnable=0x%p",
GetBoolName(aInstalling),
GetBoolName(nsContentUtils::IsSafeToRunScript()),
GetBoolName(sInstalledMenuKeyboardListener), BrowserParent::GetFocused(),
ToString(sActiveChildInputContext).c_str(), sFocusedPresContext.get(),
sFocusedElement.get(), sPseudoFocusChangeRunnable.get()));
// Update the state whether the menubar has pseudo focus or not immediately.
// This will be referred by the runner which is created below.
sInstalledMenuKeyboardListener = aInstalling;
// However, this may be called when it's not safe to run script. Therefore,
// we need to create a runnable to notify IME of the pseudo focus change.
if (sPseudoFocusChangeRunnable) {
return;
}
sPseudoFocusChangeRunnable = new PseudoFocusChangeRunnable(aInstalling);
nsContentUtils::AddScriptRunner(sPseudoFocusChangeRunnable);
}
// static
void IMEStateManager::SetMenubarPseudoFocus(
PseudoFocusChangeRunnable* aCaller, bool aSetPseudoFocus,
nsPresContext* aFocusedPresContextAtRequested) {
MOZ_LOG(
sISMLog, LogLevel::Info,
("SetMenubarPseudoFocus(aCaller=0x%p, aSetPseudoFocus=%s, "
"aFocusedPresContextAtRequested=0x%p), "
"sInstalledMenuKeyboardListener=%s, sFocusedPresContext=0x%p, "
"sFocusedElement=0x%p, sPseudoFocusChangeRunnable=0x%p",
aCaller, GetBoolName(aSetPseudoFocus), aFocusedPresContextAtRequested,
GetBoolName(sInstalledMenuKeyboardListener), sFocusedPresContext.get(),
sFocusedElement.get(), sPseudoFocusChangeRunnable.get()));
MOZ_ASSERT(sPseudoFocusChangeRunnable.get() == aCaller);
// Clear the runnable first for nested call of
// OnInstalledMenuKeyboardListener().
RefPtr<PseudoFocusChangeRunnable> runningOne =
sPseudoFocusChangeRunnable.forget();
MOZ_ASSERT(!sPseudoFocusChangeRunnable);
// If the requested state is still same, let's make the menubar get
// pseudo focus with the latest focused PresContext and Element.
// Note that this is no problem even after the focused element is changed
// after aCaller is created because only sInstalledMenuKeyboardListener
// manages the state and current focused PresContext and element should be
// used only for restoring the focus from the menubar. So, restoring
// focus with the lasted one does make sense.
if (sInstalledMenuKeyboardListener == aSetPseudoFocus) {
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
aSetPseudoFocus
? InputContextAction::MENU_GOT_PSEUDO_FOCUS
: InputContextAction::MENU_LOST_PSEUDO_FOCUS);
RefPtr<nsPresContext> focusedPresContext = sFocusedPresContext;
RefPtr<Element> focusedElement = sFocusedElement;
OnChangeFocusInternal(focusedPresContext, focusedElement, action);
return;
}
// If the requested state is different from current state, we don't need to
// make the menubar get and lose pseudo focus because of redundant. However,
// if there is a composition on the original focused element, we need to
// commit it because pseudo focus move should've caused it.
if (!aFocusedPresContextAtRequested) {
return;
}
RefPtr<TextComposition> composition =
GetTextCompositionFor(aFocusedPresContextAtRequested);
if (!composition) {
return;
}
if (nsCOMPtr<nsIWidget> widget =
aFocusedPresContextAtRequested->GetTextInputHandlingWidget()) {
composition->RequestToCommit(widget, false);
}
}
// static
bool IMEStateManager::OnMouseButtonEventInEditor(
nsPresContext& aPresContext, Element* aElement,
WidgetMouseEvent& aMouseEvent) {
MOZ_LOG(sISMLog, LogLevel::Info,
("OnMouseButtonEventInEditor(aPresContext=0x%p (available: %s), "
"aElement=0x%p, aMouseEvent=0x%p), sFocusedPresContext=0x%p, "
"sFocusedElement=0x%p",
&aPresContext, GetBoolName(CanHandleWith(&aPresContext)), aElement,
&aMouseEvent, sFocusedPresContext.get(), sFocusedElement.get()));
if (sFocusedPresContext != &aPresContext || sFocusedElement != aElement) {
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->IsObserving(aPresContext, aElement)) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnMouseButtonEventInEditor(), "
"the active IMEContentObserver isn't managing the editor"));
return false;
}
OwningNonNull<IMEContentObserver> observer = *sActiveIMEContentObserver;
bool consumed = observer->OnMouseButtonEvent(aPresContext, aMouseEvent);
MOZ_LOG(sISMLog, LogLevel::Info,
(" OnMouseButtonEventInEditor(), "
"mouse event (mMessage=%s, mButton=%d) is %s",
ToChar(aMouseEvent.mMessage), aMouseEvent.mButton,
consumed ? "consumed" : "not consumed"));
return consumed;
}
// static
void IMEStateManager::OnClickInEditor(nsPresContext& aPresContext,
Element* aElement,
const WidgetMouseEvent& aMouseEvent) {
MOZ_LOG(sISMLog, LogLevel::Info,
("OnClickInEditor(aPresContext=0x%p (available: %s), aElement=0x%p, "
"aMouseEvent=0x%p), sFocusedPresContext=0x%p, sFocusedElement=0x%p, "
"sTextInputHandlingWidget=0x%p (available: %s)",
&aPresContext, GetBoolName(CanHandleWith(&aPresContext)), aElement,
&aMouseEvent, sFocusedPresContext.get(), sFocusedElement.get(),
sTextInputHandlingWidget,
GetBoolName(sTextInputHandlingWidget &&
!sTextInputHandlingWidget->Destroyed())));
if (sFocusedPresContext != &aPresContext || sFocusedElement != aElement ||
NS_WARN_IF(!sFocusedPresContext) ||
NS_WARN_IF(!sTextInputHandlingWidget) ||
NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnClickInEditor(), "
"the mouse event isn't fired on the editor managed by ISM"));
return;
}
const OwningNonNull<nsIWidget> textInputHandlingWidget =
*sTextInputHandlingWidget;
MOZ_ASSERT_IF(sFocusedPresContext->GetTextInputHandlingWidget(),
sFocusedPresContext->GetTextInputHandlingWidget() ==
textInputHandlingWidget.get());
if (!aMouseEvent.IsTrusted()) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnClickInEditor(), "
"the mouse event isn't a trusted event"));
return; // ignore untrusted event.
}
if (aMouseEvent.mButton) {
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.
}
MOZ_ASSERT_IF(aElement, !EventStateManager::IsRemoteTarget(aElement));
InputContextAction::Cause cause =
aMouseEvent.mInputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH
? InputContextAction::CAUSE_TOUCH
: InputContextAction::CAUSE_MOUSE;
InputContextAction action(cause, InputContextAction::FOCUS_NOT_CHANGED);
IMEState newState = GetNewIMEState(aPresContext, aElement);
// If the state is not editable, there should be no active IMEContentObserver.
// However, if this click sets focus to the editor, IMEContentObserver may
// have not been created yet. Instead, if there is active IMEContentObserver,
// it should be editable.
MOZ_ASSERT_IF(!newState.IsEditable(), !sActiveIMEContentObserver);
MOZ_ASSERT_IF(sActiveIMEContentObserver, newState.IsEditable());
SetIMEState(newState, &aPresContext, aElement, textInputHandlingWidget,
action, sOrigin);
}
// static
Element* IMEStateManager::GetFocusedElement() { return sFocusedElement; }
// static
bool IMEStateManager::IsFocusedElement(const nsPresContext& aPresContext,
const Element* aFocusedElement) {
if (!sFocusedPresContext || &aPresContext != sFocusedPresContext) {
return false;
}
if (sFocusedElement == aFocusedElement) {
return true;
}
// If sFocusedElement is not nullptr, but aFocusedElement is nullptr, it does
// not have focus from point of view of IMEStateManager.
if (sFocusedElement) {
return false;
}
// If the caller does not think that nobody has focus, but we know there is
// a focused content, the caller must be called with wrong content.
if (!aFocusedElement) {
return false;
}
// If the aFocusedElement is in design mode, sFocusedElement may be nullptr.
if (aFocusedElement->IsInDesignMode()) {
MOZ_ASSERT(&aPresContext == sFocusedPresContext && !sFocusedElement);
return true;
}
// Otherwise, only when aFocusedElement is the root element, it can have
// focus, but IMEStateManager::OnChangeFocus is called with nullptr for
// aFocusedElement if it was not editable.
// XXX In this case, should the caller update sFocusedElement?
return aFocusedElement->IsEditable() && sFocusedPresContext->Document() &&
sFocusedPresContext->Document()->GetRootElement() == aFocusedElement;
}
// static
void IMEStateManager::OnFocusInEditor(nsPresContext& aPresContext,
Element* aElement,
EditorBase& aEditorBase) {
MOZ_LOG(sISMLog, LogLevel::Info,
("OnFocusInEditor(aPresContext=0x%p (available: %s), aElement=0x%p, "
"aEditorBase=0x%p), sFocusedPresContext=0x%p, sFocusedElement=0x%p, "
"sActiveIMEContentObserver=0x%p",
&aPresContext, GetBoolName(CanHandleWith(&aPresContext)), aElement,
&aEditorBase, sFocusedPresContext.get(), sFocusedElement.get(),
sActiveIMEContentObserver.get()));
if (!IsFocusedElement(aPresContext, aElement)) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnFocusInEditor(), "
"an editor not managed by ISM gets focus"));
return;
}
MOZ_ASSERT(sTextInputHandlingWidget);
// If the IMEContentObserver instance isn't observing the editable content for
// aElement, we need to recreate the instance because the active observer is
// observing different content even if aElement keeps being focused. E.g.,
// it's an <input> and editing host but was not a text control, but now, it's
// a text control.
if (sActiveIMEContentObserver) {
if (sActiveIMEContentObserver->IsObserving(aPresContext, aElement)) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnFocusInEditor(), "
"the editable content for aEditorBase has already been being "
"observed by sActiveIMEContentObserver"));
return;
}
// If the IMEContentObserver has not finished initializing itself yet,
// we don't need to recreate it because the following
// TryToFlushPendingNotifications call must make it initialized.
const nsCOMPtr<nsIWidget> textInputHandlingWidget =
sTextInputHandlingWidget;
if (!sActiveIMEContentObserver->IsBeingInitializedFor(
aPresContext, aElement, aEditorBase)) {
DestroyIMEContentObserver();
}
if (NS_WARN_IF(!IsFocusedElement(aPresContext, aElement)) ||
NS_WARN_IF(!sTextInputHandlingWidget) ||
NS_WARN_IF(sTextInputHandlingWidget != textInputHandlingWidget)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" OnFocusInEditor(), detected unexpected focus change with "
"re-initializing active IMEContentObserver"));
return;
}
}
if (!sActiveIMEContentObserver && sTextInputHandlingWidget &&
IsIMEObserverNeeded(
sTextInputHandlingWidget->GetInputContext().mIMEState)) {
CreateIMEContentObserver(aEditorBase, aElement);
if (sActiveIMEContentObserver) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnFocusInEditor(), new IMEContentObserver is created (0x%p)",
sActiveIMEContentObserver.get()));
}
}
if (sActiveIMEContentObserver) {
sActiveIMEContentObserver->TryToFlushPendingNotifications(false);
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnFocusInEditor(), trying to send pending notifications in "
"the active IMEContentObserver (0x%p)...",
sActiveIMEContentObserver.get()));
}
}
// 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();
}
void IMEStateManager::OnReFocus(nsPresContext& aPresContext,
Element& aElement) {
MOZ_LOG(sISMLog, LogLevel::Info,
("OnReFocus(aPresContext=0x%p (available: %s), aElement=0x%p), "
"sActiveIMEContentObserver=0x%p, aElement=0x%p",
&aPresContext, GetBoolName(CanHandleWith(&aPresContext)), &aElement,
sActiveIMEContentObserver.get(), sFocusedElement.get()));
if (NS_WARN_IF(!sTextInputHandlingWidget) ||
NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) {
return;
}
// Check if IME has focus. If and only if IME has focus, we may need to
// update IME state of the widget to re-open VKB. Otherwise, IME will open
// VKB at getting focus.
if (!sActiveIMEContentObserver ||
!sActiveIMEContentObserver->IsObserving(aPresContext, &aElement)) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnReFocus(), editable content for aElement was not being "
"observed by the sActiveIMEContentObserver"));
return;
}
MOZ_ASSERT(&aElement == sFocusedElement.get());
if (!UserActivation::IsHandlingUserInput() ||
UserActivation::IsHandlingKeyboardInput()) {
return;
}
const OwningNonNull<nsIWidget> textInputHandlingWidget =
*sTextInputHandlingWidget;
// Don't update IME state during composition.
if (sTextCompositions) {
if (const TextComposition* composition =
sTextCompositions->GetCompositionFor(textInputHandlingWidget)) {
if (composition->IsComposing()) {
return;
}
}
}
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
InputContextAction::FOCUS_NOT_CHANGED);
IMEState newState = GetNewIMEState(aPresContext, &aElement);
MOZ_ASSERT(newState.IsEditable());
SetIMEState(newState, &aPresContext, &aElement, textInputHandlingWidget,
action, sOrigin);
}
// static
void IMEStateManager::MaybeOnEditableStateDisabled(nsPresContext& aPresContext,
dom::Element* aElement) {
MOZ_LOG(
sISMLog, LogLevel::Info,
("MaybeOnEditableStateDisabled(aPresContext=0x%p, aElement=0x%p), "
"sFocusedPresContext=0x%p (available: %s), "
"sFocusedElement=0x%p, sTextInputHandlingWidget=0x%p (available: %s), "
"sActiveIMEContentObserver=0x%p, sIsGettingNewIMEState=%s",
&aPresContext, aElement, sFocusedPresContext.get(),
GetBoolName(CanHandleWith(sFocusedPresContext)), sFocusedElement.get(),
sTextInputHandlingWidget,
GetBoolName(sTextInputHandlingWidget &&
!sTextInputHandlingWidget->Destroyed()),
sActiveIMEContentObserver.get(), GetBoolName(sIsGettingNewIMEState)));
if (sIsGettingNewIMEState) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" MaybeOnEditableStateDisabled(), "
"does nothing because of called while getting new IME state"));
return;
}
if (&aPresContext != sFocusedPresContext || aElement != sFocusedElement) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" MaybeOnEditableStateDisabled(), "
"does nothing because of another element already has focus"));
return;
}
if (NS_WARN_IF(!sTextInputHandlingWidget) ||
NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" MaybeOnEditableStateDisabled(), FAILED due to "
"the widget for the managing the nsPresContext has gone"));
return;
}
const OwningNonNull<nsIWidget> textInputHandlingWidget =
*sTextInputHandlingWidget;
MOZ_ASSERT_IF(sFocusedPresContext->GetTextInputHandlingWidget(),
sFocusedPresContext->GetTextInputHandlingWidget() ==
textInputHandlingWidget.get());
const IMEState newIMEState = GetNewIMEState(aPresContext, aElement);
// If IME state becomes editable, HTMLEditor should also be initialized with
// same path as it gets focus. Therefore, IMEStateManager::UpdateIMEState
// should be called by HTMLEditor instead.
if (newIMEState.IsEditable()) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" MaybeOnEditableStateDisabled(), "
"does nothing because IME state does not become disabled"));
return;
}
// Otherwise, disable IME on the widget and destroy active IMEContentObserver
// if there is.
const InputContext inputContext = textInputHandlingWidget->GetInputContext();
if (inputContext.mIMEState.mEnabled == newIMEState.mEnabled) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" MaybeOnEditableStateDisabled(), "
"does nothing because IME state is not changed"));
return;
}
if (sActiveIMEContentObserver) {
DestroyIMEContentObserver();
}
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
InputContextAction::FOCUS_NOT_CHANGED);
SetIMEState(newIMEState, &aPresContext, aElement, textInputHandlingWidget,
action, sOrigin);
}
// static
void IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState,
Element* aElement, EditorBase& aEditorBase,
const UpdateIMEStateOptions& aOptions) {
MOZ_LOG(
sISMLog, LogLevel::Info,
("UpdateIMEState(aNewIMEState=%s, aElement=0x%p, aEditorBase=0x%p, "
"aOptions=0x%0x), sFocusedPresContext=0x%p (available: %s), "
"sFocusedElement=0x%p, sTextInputHandlingWidget=0x%p (available: %s), "
"sActiveIMEContentObserver=0x%p, sIsGettingNewIMEState=%s",
ToString(aNewIMEState).c_str(), aElement, &aEditorBase,
aOptions.serialize(), sFocusedPresContext.get(),
GetBoolName(CanHandleWith(sFocusedPresContext)), sFocusedElement.get(),
sTextInputHandlingWidget,
GetBoolName(sTextInputHandlingWidget &&
!sTextInputHandlingWidget->Destroyed()),
sActiveIMEContentObserver.get(), GetBoolName(sIsGettingNewIMEState)));
if (sIsGettingNewIMEState) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" UpdateIMEState(), "
"does nothing because of called while getting new IME state"));
return;
}
RefPtr<PresShell> presShell(aEditorBase.GetPresShell());
if (NS_WARN_IF(!presShell)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), FAILED due to "
"editor doesn't have PresShell"));
return;
}
const RefPtr<nsPresContext> presContext =
aElement
? aElement->GetPresContext(Element::PresContextFor::eForComposedDoc)
: aEditorBase.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 aElement
// 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 (sFocusedPresContext != 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(!sFocusedPresContext)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), FAILED due to "
"no managing nsPresContext"));
return;
}
if (NS_WARN_IF(!sTextInputHandlingWidget) ||
NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), FAILED due to "
"the widget for the managing nsPresContext has gone"));
return;
}
const OwningNonNull<nsIWidget> textInputHandlingWidget =
*sTextInputHandlingWidget;
MOZ_ASSERT_IF(sFocusedPresContext->GetTextInputHandlingWidget(),
sFocusedPresContext->GetTextInputHandlingWidget() ==
textInputHandlingWidget.get());
// TODO: Investigate if we could put off to initialize IMEContentObserver
// later because a lot of callers need to be marked as
// MOZ_CAN_RUN_SCRIPT otherwise.
// If there is an active observer and we need an observer, we want to keep
// using the observer as far as possible because it's already notified IME of
// focus. However, between the call of OnChangeFocus() and UpdateIMEState(),
// focus and/or IME state may have been changed. If so, we need to update
// the observer for current situation.
const bool hasActiveObserverAndNeedObserver =
sActiveIMEContentObserver && IsIMEObserverNeeded(aNewIMEState);
// If the active observer was not initialized with aEditorBase, it means
// that the old editor lost focus but new editor gets focus **without**
// focus move in the DOM tree. This may happen with changing the type of
// <input> element, etc. In this case, we need to let IME know a focus move
// with recreating IMEContentObserver because editable target has been
// changed.
const bool needToRecreateObserver =
hasActiveObserverAndNeedObserver &&
!sActiveIMEContentObserver->WasInitializedWith(aEditorBase);
// If the active observer was initialized with same editor, we can/should
// keep using it for avoiding to notify IME of a focus change. However, we
// may need to re-initialize it because it may have temporarily stopped
// observing the content.
if (hasActiveObserverAndNeedObserver && !needToRecreateObserver) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" UpdateIMEState(), try to reinitialize the active "
"IMEContentObserver"));
OwningNonNull<IMEContentObserver> contentObserver =
*sActiveIMEContentObserver;
OwningNonNull<nsPresContext> presContext = *sFocusedPresContext;
if (!contentObserver->MaybeReinitialize(
textInputHandlingWidget, presContext, aElement, aEditorBase)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), failed to reinitialize the active "
"IMEContentObserver"));
}
if (NS_WARN_IF(textInputHandlingWidget->Destroyed())) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), widget has gone during re-initializing "
"the active IMEContentObserver"));
return;
}
}
// If there is no active IMEContentObserver or it isn't observing the
// editable content for aElement, we should recreate an observer. E.g.,
// aElement which is an editing host may have been changed from/to a text
// control.
const bool createNewObserver =
IsIMEObserverNeeded(aNewIMEState) &&
(!sActiveIMEContentObserver || needToRecreateObserver ||
!sActiveIMEContentObserver->IsObserving(*sFocusedPresContext, aElement));
// If we're recreating new IMEContentObserver or new state does not need
// IMEContentObserver, destroy the active one.
const bool destroyCurrentObserver =
sActiveIMEContentObserver &&
(createNewObserver || !IsIMEObserverNeeded(aNewIMEState));
// If IME state is changed, e.g., from "enabled" or "password" to "disabled",
// we cannot keep the composing state because the new "disabled" state does
// not support composition, and also from "enabled" to "password" and vice
// versa, IME may use different composing rules. Therefore, for avoiding
// IME to be confused, we should fix the composition first.
const bool updateIMEState =
aOptions.contains(UpdateIMEStateOption::ForceUpdate) ||
(textInputHandlingWidget->GetInputContext().mIMEState.mEnabled !=
aNewIMEState.mEnabled);
if (NS_WARN_IF(textInputHandlingWidget->Destroyed())) {
MOZ_LOG(
sISMLog, LogLevel::Error,
(" UpdateIMEState(), widget has gone during getting input context"));
return;
}
if (updateIMEState &&
!aOptions.contains(UpdateIMEStateOption::DontCommitComposition)) {
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, textInputHandlingWidget,
sFocusedIMEBrowserParent);
if (NS_WARN_IF(textInputHandlingWidget->Destroyed())) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), widget has gone during committing "
"composition"));
return;
}
// FIXME: If committing composition changes IME state recursively, we should
// not keep updating IME state here. However, how can we manage it?
// Is a generation of the state is required?
}
// Notify IME of blur first.
if (destroyCurrentObserver) {
DestroyIMEContentObserver();
if (NS_WARN_IF(textInputHandlingWidget->Destroyed()) ||
NS_WARN_IF(sTextInputHandlingWidget != textInputHandlingWidget)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), has set input context, but the widget is "
"not focused"));
return;
}
}
// Then, notify our IME handler of new IME state.
if (updateIMEState) {
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
InputContextAction::FOCUS_NOT_CHANGED);
RefPtr<nsPresContext> editorPresContext = aEditorBase.GetPresContext();
if (NS_WARN_IF(!editorPresContext)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), nsPresContext for editor has already been "
"lost"));
return;
}
SetIMEState(aNewIMEState, editorPresContext, aElement,
textInputHandlingWidget, action, sOrigin);
if (NS_WARN_IF(textInputHandlingWidget->Destroyed()) ||
NS_WARN_IF(sTextInputHandlingWidget != textInputHandlingWidget)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), has set input context, but the widget is "
"not focused"));
return;
}
if (NS_WARN_IF(
sTextInputHandlingWidget->GetInputContext().mIMEState.mEnabled !=
aNewIMEState.mEnabled)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), has set input context, but IME enabled "
"state was overridden by somebody else"));
return;
}
}
NS_ASSERTION(IsFocusedElement(*presContext, aElement),
"aElement does not match with sFocusedElement");
// Finally, create new observer if required.
if (createNewObserver) {
if (!sActiveIMEContentObserver && sFocusedPresContext &&
sTextInputHandlingWidget) {
// 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, aElement);
} else {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), wanted to create IMEContentObserver, but "
"lost focus"));
}
}
}
// static
IMEState IMEStateManager::GetNewIMEState(const nsPresContext& aPresContext,
Element* aElement) {
MOZ_LOG(
sISMLog, LogLevel::Info,
("GetNewIMEState(aPresContext=0x%p, aElement=0x%p), "
"sInstalledMenuKeyboardListener=%s",
&aPresContext, aElement, GetBoolName(sInstalledMenuKeyboardListener)));
if (!CanHandleWith(&aPresContext)) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" GetNewIMEState() returns IMEEnabled::Disabled because "
"the nsPresContext has been destroyed"));
return IMEState(IMEEnabled::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 IMEEnabled::Disabled because "
"the nsPresContext is for print or print preview"));
return IMEState(IMEEnabled::Disabled);
}
if (sInstalledMenuKeyboardListener) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" GetNewIMEState() returns IMEEnabled::Disabled because "
"menu keyboard listener was installed"));
return IMEState(IMEEnabled::Disabled);
}
if (!aElement) {
// Even if there are no focused content, the focused document might be
// editable, such case is design mode.
if (aPresContext.Document() && aPresContext.Document()->IsInDesignMode()) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" GetNewIMEState() returns IMEEnabled::Enabled because "
"design mode editor has focus"));
return IMEState(IMEEnabled::Enabled);
}
MOZ_LOG(sISMLog, LogLevel::Debug,
(" GetNewIMEState() returns IMEEnabled::Disabled because "
"no content has focus"));
return IMEState(IMEEnabled::Disabled);
}
// If aElement is in designMode, aElement should be the root node of the
// document.
if (aElement && aElement->IsInDesignMode()) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" GetNewIMEState() returns IMEEnabled::Enabled because "
"a content node in design mode editor has focus"));
return IMEState(IMEEnabled::Enabled);
}
// 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 = aElement->GetDesiredIMEState();
MOZ_LOG(sISMLog, LogLevel::Debug,
(" GetNewIMEState() returns %s", ToString(newIMEState).c_str()));
return newIMEState;
}
// static
void IMEStateManager::ResetActiveChildInputContext() {
sActiveChildInputContext.mIMEState.mEnabled = IMEEnabled::Unknown;
}
// static
bool IMEStateManager::HasActiveChildSetInputContext() {
return sActiveChildInputContext.mIMEState.mEnabled != IMEEnabled::Unknown;
}
// static
void IMEStateManager::SetInputContextForChildProcess(
BrowserParent* aBrowserParent, const InputContext& aInputContext,
const InputContextAction& aAction) {
MOZ_LOG(
sISMLog, LogLevel::Info,
("SetInputContextForChildProcess(aBrowserParent=0x%p, "
"aInputContext=%s , aAction={ mCause=%s, mAction=%s }), "
"sFocusedPresContext=0x%p (available: %s), "
"sTextInputHandlingWidget=0x%p (available: %s), "
"BrowserParent::GetFocused()=0x%p, sInstalledMenuKeyboardListener=%s",
aBrowserParent, ToString(aInputContext).c_str(),
ToString(aAction.mCause).c_str(), ToString(aAction.mFocusChange).c_str(),
sFocusedPresContext.get(),
GetBoolName(CanHandleWith(sFocusedPresContext)),
sTextInputHandlingWidget,
GetBoolName(sTextInputHandlingWidget &&
!sTextInputHandlingWidget->Destroyed()),
BrowserParent::GetFocused(),
GetBoolName(sInstalledMenuKeyboardListener)));
if (aBrowserParent != BrowserParent::GetFocused()) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" SetInputContextForChildProcess(), FAILED, "
"because non-focused tab parent tries to set input context"));
return;
}
if (NS_WARN_IF(!CanHandleWith(sFocusedPresContext))) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" SetInputContextForChildProcess(), FAILED, "
"due to no focused presContext"));
return;
}
if (NS_WARN_IF(!sTextInputHandlingWidget) ||
NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" SetInputContextForChildProcess(), FAILED, "
"due to the widget for the nsPresContext has gone"));
return;
}
const OwningNonNull<nsIWidget> textInputHandlingWidget =
*sTextInputHandlingWidget;
MOZ_ASSERT_IF(sFocusedPresContext->GetTextInputHandlingWidget(),
sFocusedPresContext->GetTextInputHandlingWidget() ==