Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  IMEStateManager.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "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;
MOZ_RUNINIT InputContext IMEStateManager::sActiveChildInputContext;
bool IMEStateManager::sInstalledMenuKeyboardListener = false;
bool IMEStateManager::sIsGettingNewIMEState = false;
bool IMEStateManager::sCleaningUpForStoppingIMEStateManagement = false;
bool IMEStateManager::sIsActive = false;
MOZ_RUNINIT 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 ||
      // If focused element is a text control or an editing host, we need to
      // emulate "blur" on it when it's removed.
      (sFocusedElement && sFocusedElement != &aElement) ||
      // If it is (or was) in design mode, we need to emulate "blur" on the
      // document when the observing element (typically, <body>) is removed.
      (!sFocusedElement &&
       (!sActiveIMEContentObserver ||
        sActiveIMEContentObserver->GetObservingElement() != &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;  // Somebody already has focus, don't steal it.
  }

  if (IsIMEObserverNeeded(newState)) {
    // Initializing IMEContentObserver instance requires Selection, but its
    // ranges have not been adjusted for this removal.  Therefore, we need to
    // wait a moment.
    nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
        "IMEStateManager::RecreateIMEContentObserverWhenContentRemoved",
        [presContext = OwningNonNull{aPresContext}]() {
          MOZ_ASSERT(sFocusedPresContext == presContext);
          MOZ_ASSERT(!sFocusedElement);
          if (RefPtr<HTMLEditor> htmlEditor =
                  nsContentUtils::GetHTMLEditor(presContext)) {
            CreateIMEContentObserver(*htmlEditor, nullptr);
          }
        }));
  }

  return NS_OK;
}

// static
void IMEStateManager::OnParentChainChangedOfObservingElement(
    IMEContentObserver& aObserver) {
  if (!sFocusedPresContext || sActiveIMEContentObserver != &aObserver) {
    return;
  }
  RefPtr<nsPresContext> presContext = aObserver.GetPresContext();
  RefPtr<Element> element = aObserver.GetObservingElement();
  if (NS_WARN_IF(!presContext) || NS_WARN_IF(!element)) {
    return;
  }
  MOZ_LOG(sISMLog, LogLevel::Info,
          ("OnParentChainChangedOfObservingElement(aObserver=0x%p), "
           "sFocusedPresContext=0x%p, sFocusedElement=0x%p, "
           "aObserver->GetPresContext()=0x%p, "
           "aObserver->GetObservingElement()=0x%p",
           &aObserver, sFocusedPresContext.get(), sFocusedElement.get(),
           presContext.get(), element.get()));
  OnRemoveContent(*presContext, *element);
}

// static
void IMEStateManager::OnUpdateHTMLEditorRootElement(HTMLEditor& aHTMLEditor,
                                                    Element* aNewRootElement) {
  MOZ_LOG(
      sISMLog, LogLevel::Info,
      ("OnUpdateHTMLEditorRootElement(aHTMLEditor=0x%p, aNewRootElement=%s), "
       "sFocusedPresContext=0x%p, sFocusedElement=%s, "
       "sActiveIMEContentObserver=0x%p (GetObservingElement()=%s), "
       "sTextInputHandlingWidget=0x%p, aHTMLEditor.GetPresContext()=0x%p",
       &aHTMLEditor,
       aNewRootElement ? ToString(*aNewRootElement).c_str() : "nullptr",
       sFocusedPresContext.get(),
       ToString(RefPtr<Element>(sFocusedElement)).c_str(),
       sActiveIMEContentObserver.get(),
       sActiveIMEContentObserver
           ? ToString(RefPtr<Element>(
                          sActiveIMEContentObserver->GetObservingElement()))
                 .c_str()
           : "N/A",
       sTextInputHandlingWidget, aHTMLEditor.GetPresContext()));

  if (
      // Nothing to do if nobody has focus.
      !sFocusedPresContext || !sTextInputHandlingWidget ||
      // Nothing to do if an element has focus because we need to handle this
      // case only when no element has focus in the design mode.
      sFocusedElement ||
      // Nothing to do if the editable document does not have focus.
      sFocusedPresContext != aHTMLEditor.GetPresContext() ||
      // If it's not in the design mode, any mutation should be handled with a
      // focus change.
      !aHTMLEditor.IsInDesignMode() ||
      // Nothing to do if the active IMEContentObserver has already been
      // observing the new root element.
      (aNewRootElement && sActiveIMEContentObserver &&
       sActiveIMEContentObserver->GetObservingElement() == aNewRootElement)) {
    return;
  }

  OwningNonNull<nsPresContext> presContext = *sFocusedPresContext;

  DestroyIMEContentObserver();

  if (!aNewRootElement) {
    // When there is no element in the document, let's disable IME.
    IMEState newState = GetNewIMEState(*presContext, nullptr);
    MOZ_ASSERT(newState.mEnabled == IMEEnabled::Disabled);
    InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
                              InputContextAction::LOST_FOCUS);
    InputContext::Origin origin =
        BrowserParent::GetFocused() ? InputContext::ORIGIN_CONTENT : sOrigin;
    OwningNonNull<nsIWidget> textInputHandlingWidget =
        *sTextInputHandlingWidget;
    SetIMEState(newState, presContext, nullptr, textInputHandlingWidget, action,
                origin);
    return;
  }

  MOZ_ASSERT(aNewRootElement);
  const IMEState newState = GetNewIMEState(*presContext, nullptr);
  // The caller wants to enable IME if there is at least one element in the most
  // cases.  However, IME may be disabled, e.g., the menubar key listener is now
  // installed, etc.  Therefore, if the new state is not "enabled", we should
  // not update the state in unexpected situations.
  if (MOZ_UNLIKELY(newState.mEnabled != IMEEnabled::Enabled)) {
    MOZ_LOG(sISMLog, LogLevel::Warning,
            (" OnUpdateHTMLEditorRootElement(): WARNING, Not updating IME "
             "state because of the new IME state is not \"enabled\""));
    return;
  }
  InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
                            InputContextAction::GOT_FOCUS);
  InputContext::Origin origin =
      BrowserParent::GetFocused() ? InputContext::ORIGIN_CONTENT : sOrigin;
  OwningNonNull<nsIWidget> textInputHandlingWidget = *sTextInputHandlingWidget;
  SetIMEState(newState, presContext, nullptr, textInputHandlingWidget, action,
              origin);
  // Somebody moved focus, don't keep handling this since we lost the rights
  // to touch IME state.
  if (sFocusedElement || sActiveIMEContentObserver) {
    MOZ_LOG(sISMLog, LogLevel::Warning,
            ("OnUpdateHTMLEditorRootElement(), WARNING: Somebody update focus "
             "during setting IME state, sFocusedElement=%s, "
             "sActiveIMEContentObserver=0x%p",
             ToString(RefPtr<Element>(sFocusedElement)).c_str(),
             sActiveIMEContentObserver.get()));
    return;
  }

  if (IsIMEObserverNeeded(newState)) {
    MOZ_ASSERT(sFocusedPresContext == presContext);
    MOZ_ASSERT(!sFocusedElement);
    CreateIMEContentObserver(aHTMLEditor, nullptr);
  }
}

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

--> maximum size reached

--> --------------------

Messung V0.5
C=89 H=98 G=93

¤ Dauer der Verarbeitung: 0.20 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge