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

#include "mozilla/Logging.h"

#include "mozilla/IMEStateManager.h"

#include "mozilla/Attributes.h"
#include "mozilla/EditorBase.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStates.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/TextComposition.h"
#include "mozilla/TextEvents.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/HTMLFormElement.h"
#include "mozilla/dom/TabParent.h"

#include "HTMLInputElement.h"
#include "IMEContentObserver.h"

#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIDOMMouseEvent.h"
#include "nsIForm.h"
#include "nsIFormControl.h"
#include "nsINode.h"
#include "nsIObserverService.h"
#include "nsIPresShell.h"
#include "nsISelection.h"
#include "nsISupports.h"
#include "nsPresContext.h"

namespace mozilla {

using namespace dom;
using namespace widget;

/**
 * When a method is called, log its arguments and/or related static variables
 * with LogLevel::Info.  However, if it puts too many logs like
 * OnDestroyPresContext(), should long only when the method actually does
 * something. In this case, the log should start with "<method name>".
 *
 * When a method quits due to unexpected situation, log the reason with
 * LogLevel::Error.  In this case, the log should start with
 * "<method name>(), FAILED".  The indent makes the log look easier.
 *
 * When a method does something only in some situations and it may be important
 * for debug, log the information with LogLevel::Debug.  In this case, the log
 * should start with "  <method name>(),".
 */
LazyLogModule sISMLog("IMEStateManager");

static const char*
GetBoolName(bool aBool)
{
  return aBool ? "true" : "false";
}

static const char*
GetActionCauseName(InputContextAction::Cause aCause)
{
  switch (aCause) {
    case InputContextAction::CAUSE_UNKNOWN:
      return "CAUSE_UNKNOWN";
    case InputContextAction::CAUSE_UNKNOWN_CHROME:
      return "CAUSE_UNKNOWN_CHROME";
    case InputContextAction::CAUSE_KEY:
      return "CAUSE_KEY";
    case InputContextAction::CAUSE_MOUSE:
      return "CAUSE_MOUSE";
    case InputContextAction::CAUSE_TOUCH:
      return "CAUSE_TOUCH";
    default:
      return "illegal value";
  }
}

static const char*
GetActionFocusChangeName(InputContextAction::FocusChange aFocusChange)
{
  switch (aFocusChange) {
    case InputContextAction::FOCUS_NOT_CHANGED:
      return "FOCUS_NOT_CHANGED";
    case InputContextAction::GOT_FOCUS:
      return "GOT_FOCUS";
    case InputContextAction::LOST_FOCUS:
      return "LOST_FOCUS";
    case InputContextAction::MENU_GOT_PSEUDO_FOCUS:
      return "MENU_GOT_PSEUDO_FOCUS";
    case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
      return "MENU_LOST_PSEUDO_FOCUS";
    default:
      return "illegal value";
  }
}

static const char*
GetIMEStateEnabledName(IMEState::Enabled aEnabled)
{
  switch (aEnabled) {
    case IMEState::DISABLED:
      return "DISABLED";
    case IMEState::ENABLED:
      return "ENABLED";
    case IMEState::PASSWORD:
      return "PASSWORD";
    case IMEState::PLUGIN:
      return "PLUGIN";
    default:
      return "illegal value";
  }
}

static const char*
GetIMEStateSetOpenName(IMEState::Open aOpen)
{
  switch (aOpen) {
    case IMEState::DONT_CHANGE_OPEN_STATE:
      return "DONT_CHANGE_OPEN_STATE";
    case IMEState::OPEN:
      return "OPEN";
    case IMEState::CLOSED:
      return "CLOSED";
    default:
      return "illegal value";
  }
}

StaticRefPtr<nsIContent> IMEStateManager::sContent;
StaticRefPtr<nsPresContext> IMEStateManager::sPresContext;
nsIWidget* IMEStateManager::sWidget = nullptr;
nsIWidget* IMEStateManager::sFocusedIMEWidget = nullptr;
nsIWidget* IMEStateManager::sActiveInputContextWidget = nullptr;
StaticRefPtr<TabParent> IMEStateManager::sActiveTabParent;
StaticRefPtr<IMEContentObserver> IMEStateManager::sActiveIMEContentObserver;
TextCompositionArray* IMEStateManager::sTextCompositions = nullptr;
bool IMEStateManager::sInstalledMenuKeyboardListener = false;
bool IMEStateManager::sIsGettingNewIMEState = false;
bool IMEStateManager::sCheckForIMEUnawareWebApps = false;
bool IMEStateManager::sRemoteHasFocus = false;

// static
void
IMEStateManager::Init()
{
  Preferences::AddBoolVarCache(
    &sCheckForIMEUnawareWebApps,
    "intl.ime.hack.on_ime_unaware_apps.fire_key_events_for_composition",
    false);
}

// static
void
IMEStateManager::Shutdown()
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("Shutdown(), sTextCompositions=0x%p, sTextCompositions->Length()=%u",
     sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0));

  MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length());
  delete sTextCompositions;
  sTextCompositions = nullptr;
}

// static
void
IMEStateManager::OnTabParentDestroying(TabParent* aTabParent)
{
  if (sActiveTabParent != aTabParent) {
    return;
  }
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnTabParentDestroying(aTabParent=0x%p), "
     "The active TabParent is being destroyed", aTabParent));

  // The active remote process might have crashed.
  sActiveTabParent = nullptr;

  // TODO: Need to cancel composition without TextComposition and make
  //       disable IME.
}

// static
void
IMEStateManager::WidgetDestroyed(nsIWidget* aWidget)
{
  if (sWidget == aWidget) {
    sWidget = nullptr;
  }
  if (sFocusedIMEWidget == aWidget) {
    sFocusedIMEWidget = nullptr;
  }
  if (sActiveInputContextWidget == aWidget) {
    sActiveInputContextWidget = nullptr;
  }
}

// static
void
IMEStateManager::StopIMEStateManagement()
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("StopIMEStateManagement()"));

  // NOTE: Don't set input context from here since this has already lost
  //       the rights to change input context.

  if (sTextCompositions && sPresContext) {
    NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, sPresContext);
  }
  sActiveInputContextWidget = nullptr;
  sPresContext = nullptr;
  sContent = nullptr;
  sActiveTabParent = nullptr;
  DestroyIMEContentObserver();
}

// static
void
IMEStateManager::MaybeStartOffsetUpdatedInChild(nsIWidget* aWidget,
                                                uint32_t aStartOffset)
{
  if (NS_WARN_IF(!sTextCompositions)) {
    MOZ_LOG(sISMLog, LogLevel::Warning,
      ("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), "
       "called when there is no composition", aWidget, aStartOffset));
    return;
  }

  RefPtr<TextComposition> composition = GetTextCompositionFor(aWidget);
  if (NS_WARN_IF(!composition)) {
    MOZ_LOG(sISMLog, LogLevel::Warning,
      ("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), "
       "called when there is no composition", aWidget, aStartOffset));
    return;
  }

  if (composition->NativeOffsetOfStartComposition() == aStartOffset) {
    return;
  }

  MOZ_LOG(sISMLog, LogLevel::Info,
    ("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), "
     "old offset=%u",
     aWidget, aStartOffset, composition->NativeOffsetOfStartComposition()));
  composition->OnStartOffsetUpdatedInChild(aStartOffset);
}

// static
nsresult
IMEStateManager::OnDestroyPresContext(nsPresContext* aPresContext)
{
  NS_ENSURE_ARG_POINTER(aPresContext);

  // First, if there is a composition in the aPresContext, clean up it.
  if (sTextCompositions) {
    TextCompositionArray::index_type i =
      sTextCompositions->IndexOf(aPresContext);
    if (i != TextCompositionArray::NoIndex) {
      MOZ_LOG(sISMLog, LogLevel::Debug,
        ("  OnDestroyPresContext(), "
         "removing TextComposition instance from the array (index=%u)", i));
      // there should be only one composition per presContext object.
      sTextCompositions->ElementAt(i)->Destroy();
      sTextCompositions->RemoveElementAt(i);
      if (sTextCompositions->IndexOf(aPresContext) !=
            TextCompositionArray::NoIndex) {
        MOZ_LOG(sISMLog, LogLevel::Error,
          ("  OnDestroyPresContext(), FAILED to remove "
           "TextComposition instance from the array"));
        MOZ_CRASH("Failed to remove TextComposition instance from the array");
      }
    }
  }

  if (aPresContext != sPresContext) {
    return NS_OK;
  }

  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnDestroyPresContext(aPresContext=0x%p), "
     "sPresContext=0x%p, sContent=0x%p, sTextCompositions=0x%p",
     aPresContext, sPresContext.get(), sContent.get(), sTextCompositions));

  DestroyIMEContentObserver();

  if (sWidget) {
    IMEState newState = GetNewIMEState(sPresContext, nullptr);
    InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
                              InputContextAction::LOST_FOCUS);
    SetIMEState(newState, nullptr, sWidget, action);
  }
  sWidget = nullptr;
  sContent = nullptr;
  sPresContext = nullptr;
  sActiveTabParent = nullptr;
  return NS_OK;
}

// static
nsresult
IMEStateManager::OnRemoveContent(nsPresContext* aPresContext,
                                 nsIContent* aContent)
{
  NS_ENSURE_ARG_POINTER(aPresContext);

  // First, if there is a composition in the aContent, clean up it.
  if (sTextCompositions) {
    RefPtr<TextComposition> compositionInContent =
      sTextCompositions->GetCompositionInContent(aPresContext, aContent);

    if (compositionInContent) {
      MOZ_LOG(sISMLog, LogLevel::Debug,
        ("  OnRemoveContent(), "
         "composition is in the content"));

      // Try resetting the native IME state.  Be aware, typically, this method
      // is called during the content being removed.  Then, the native
      // composition events which are caused by following APIs are ignored due
      // to unsafe to run script (in PresShell::HandleEvent()).
      nsresult rv =
        compositionInContent->NotifyIME(REQUEST_TO_CANCEL_COMPOSITION);
      if (NS_FAILED(rv)) {
        compositionInContent->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
      }
    }
  }

  if (!sPresContext || !sContent ||
      !nsContentUtils::ContentIsDescendantOf(sContent, aContent)) {
    return NS_OK;
  }

  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnRemoveContent(aPresContext=0x%p, aContent=0x%p), "
     "sPresContext=0x%p, sContent=0x%p, sTextCompositions=0x%p",
     aPresContext, aContent, sPresContext.get(), sContent.get(), sTextCompositions));

  DestroyIMEContentObserver();

  // Current IME transaction should commit
  if (sWidget) {
    IMEState newState = GetNewIMEState(sPresContext, nullptr);
    InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
                              InputContextAction::LOST_FOCUS);
    SetIMEState(newState, nullptr, sWidget, action);
  }

  sWidget = nullptr;
  sContent = nullptr;
  sPresContext = nullptr;
  sActiveTabParent = nullptr;

  return NS_OK;
}

// static
bool
IMEStateManager::CanHandleWith(nsPresContext* aPresContext)
{
  return aPresContext &&
         aPresContext->GetPresShell() &&
         !aPresContext->PresShell()->IsDestroying();
}

// static
nsresult
IMEStateManager::OnChangeFocus(nsPresContext* aPresContext,
                               nsIContent* aContent,
                               InputContextAction::Cause aCause)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnChangeFocus(aPresContext=0x%p, aContent=0x%p, aCause=%s)",
     aPresContext, aContent, GetActionCauseName(aCause)));

  InputContextAction action(aCause);
  return OnChangeFocusInternal(aPresContext, aContent, action);
}

// static
nsresult
IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
                                       nsIContent* aContent,
                                       InputContextAction aAction)
{
  RefPtr<TabParent> newTabParent = TabParent::GetFrom(aContent);

  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnChangeFocusInternal(aPresContext=0x%p (available: %s), "
     "aContent=0x%p (TabParent=0x%p), aAction={ mCause=%s, mFocusChange=%s }), "
     "sPresContext=0x%p (available: %s), sContent=0x%p, "
     "sWidget=0x%p (available: %s), sActiveTabParent=0x%p, "
     "sActiveIMEContentObserver=0x%p, sInstalledMenuKeyboardListener=%s",
     aPresContext, GetBoolName(CanHandleWith(aPresContext)), aContent,
     newTabParent.get(), GetActionCauseName(aAction.mCause),
     GetActionFocusChangeName(aAction.mFocusChange),
     sPresContext.get(), GetBoolName(CanHandleWith(sPresContext)),
     sContent.get(), sWidget, GetBoolName(sWidget && !sWidget->Destroyed()),
     sActiveTabParent.get(), sActiveIMEContentObserver.get(),
     GetBoolName(sInstalledMenuKeyboardListener)));

  // If new aPresShell has been destroyed, this should handle the focus change
  // as nobody is getting focus.
  if (NS_WARN_IF(aPresContext && !CanHandleWith(aPresContext))) {
    MOZ_LOG(sISMLog, LogLevel::Warning,
      ("  OnChangeFocusInternal(), called with destroyed PresShell, "
       "handling this call as nobody getting focus"));
    aPresContext = nullptr;
    aContent = nullptr;
  }

  nsCOMPtr<nsIWidget> oldWidget = sWidget;
  nsCOMPtr<nsIWidget> newWidget =
    aPresContext ? aPresContext->GetRootWidget() : nullptr;
  bool focusActuallyChanging =
    (sContent != aContent || sPresContext != aPresContext ||
     oldWidget != newWidget || sActiveTabParent != newTabParent);

  if (oldWidget && focusActuallyChanging) {
    // If we're deactivating, we shouldn't commit composition forcibly because
    // the user may want to continue the composition.
    if (aPresContext) {
      NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget);
    }
  }

  if (sActiveIMEContentObserver &&
      (aPresContext || !sActiveIMEContentObserver->KeepAliveDuringDeactive()) &&
      !sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) {
    DestroyIMEContentObserver();
  }

  if (!aPresContext) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnChangeFocusInternal(), "
       "no nsPresContext is being activated"));
    return NS_OK;
  }

  nsIContentParent* currentContentParent =
    sActiveTabParent ? sActiveTabParent->Manager() : nullptr;
  nsIContentParent* newContentParent =
    newTabParent ? newTabParent->Manager() : nullptr;
  if (sActiveTabParent && currentContentParent != newContentParent) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnChangeFocusInternal(), notifying previous "
       "focused child process of parent process or another child process "
       "getting focus"));
    Unused << sActiveTabParent->SendStopIMEStateManagement();
  }

  if (NS_WARN_IF(!newWidget)) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  OnChangeFocusInternal(), FAILED due to "
       "no widget to manage its IME state"));
    return NS_OK;
  }

  // Update the cached widget since root view of the presContext may be
  // changed to different view.
  sWidget = newWidget;

  // If a child process has focus, we should disable IME state until the child
  // process actually gets focus because if user types keys before that they
  // are handled by IME.
  IMEState newState =
    newTabParent ? IMEState(IMEState::DISABLED) :
                   GetNewIMEState(aPresContext, aContent);
  bool setIMEState = true;

  if (newTabParent) {
    if (aAction.mFocusChange == InputContextAction::MENU_GOT_PSEUDO_FOCUS ||
        aAction.mFocusChange == InputContextAction::MENU_LOST_PSEUDO_FOCUS) {
      // XXX When menu keyboard listener is being uninstalled, IME state needs
      //     to be restored by the child process asynchronously.  Therefore,
      //     some key events which are fired immediately after closing menu
      //     may not be handled by IME.
      Unused << newTabParent->
        SendMenuKeyboardListenerInstalled(sInstalledMenuKeyboardListener);
      setIMEState = sInstalledMenuKeyboardListener;
    } else if (focusActuallyChanging) {
      InputContext context = newWidget->GetInputContext();
      if (context.mIMEState.mEnabled == IMEState::DISABLED) {
        setIMEState = false;
        MOZ_LOG(sISMLog, LogLevel::Debug,
          ("  OnChangeFocusInternal(), doesn't set IME "
           "state because focused element (or document) is in a child process "
           "and the IME state is already disabled"));
      } else {
        MOZ_LOG(sISMLog, LogLevel::Debug,
          ("  OnChangeFocusInternal(), will disable IME "
           "until new focused element (or document) in the child process "
           "will get focus actually"));
      }
    } else {
      // When focus is NOT changed actually, we shouldn't set IME state since
      // that means that the window is being activated and the child process
      // may have composition.  Then, we shouldn't commit the composition with
      // making IME state disabled.
      setIMEState = false; 
      MOZ_LOG(sISMLog, LogLevel::Debug,
        ("  OnChangeFocusInternal(), doesn't set IME "
         "state because focused element (or document) is already in the child "
         "process"));
    }
  }

  if (setIMEState) {
    if (!focusActuallyChanging) {
      // actual focus isn't changing, but if IME enabled state is changing,
      // we should do it.
      InputContext context = newWidget->GetInputContext();
      if (context.mIMEState.mEnabled == newState.mEnabled) {
        MOZ_LOG(sISMLog, LogLevel::Debug,
          ("  OnChangeFocusInternal(), "
           "neither focus nor IME state is changing"));
        return NS_OK;
      }
      aAction.mFocusChange = InputContextAction::FOCUS_NOT_CHANGED;

      // Even if focus isn't changing actually, we should commit current
      // composition here since the IME state is changing.
      if (sPresContext && oldWidget && !focusActuallyChanging) {
        NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget);
      }
    } else if (aAction.mFocusChange == InputContextAction::FOCUS_NOT_CHANGED) {
      // If aContent isn't null or aContent is null but editable, somebody gets
      // focus.
      bool gotFocus = aContent || (newState.mEnabled == IMEState::ENABLED);
      aAction.mFocusChange =
        gotFocus ? InputContextAction::GOT_FOCUS :
                   InputContextAction::LOST_FOCUS;
    }

    // Update IME state for new focus widget
    SetIMEState(newState, aContent, newWidget, aAction);
  }

  sActiveTabParent = newTabParent;
  sPresContext = aPresContext;
  sContent = aContent;

  // Don't call CreateIMEContentObserver() here except when a plugin gets
  // focus because it will be called from the focus event handler of focused
  // editor.
  if (newState.mEnabled == IMEState::PLUGIN) {
    CreateIMEContentObserver(nullptr);
    if (sActiveIMEContentObserver) {
      MOZ_LOG(sISMLog, LogLevel::Debug,
        ("  OnChangeFocusInternal(), an "
         "IMEContentObserver instance is created for plugin and trying to "
         "flush its pending notifications..."));
      sActiveIMEContentObserver->TryToFlushPendingNotifications();
    }
  }

  return NS_OK;
}

// static
void
IMEStateManager::OnInstalledMenuKeyboardListener(bool aInstalling)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnInstalledMenuKeyboardListener(aInstalling=%s), "
     "sInstalledMenuKeyboardListener=%s",
     GetBoolName(aInstalling), GetBoolName(sInstalledMenuKeyboardListener)));

  sInstalledMenuKeyboardListener = aInstalling;

  InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
    aInstalling ? InputContextAction::MENU_GOT_PSEUDO_FOCUS :
                  InputContextAction::MENU_LOST_PSEUDO_FOCUS);
  OnChangeFocusInternal(sPresContext, sContent, action);
}

// static
bool
IMEStateManager::OnMouseButtonEventInEditor(nsPresContext* aPresContext,
                                            nsIContent* aContent,
                                            nsIDOMMouseEvent* aMouseEvent)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnMouseButtonEventInEditor(aPresContext=0x%p, "
     "aContent=0x%p, aMouseEvent=0x%p), sPresContext=0x%p, sContent=0x%p",
     aPresContext, aContent, aMouseEvent, sPresContext.get(), sContent.get()));

  if (sPresContext != aPresContext || sContent != aContent) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnMouseButtonEventInEditor(), "
       "the mouse event isn't fired on the editor managed by ISM"));
    return false;
  }

  if (!sActiveIMEContentObserver) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnMouseButtonEventInEditor(), "
       "there is no active IMEContentObserver"));
    return false;
  }

  if (!sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnMouseButtonEventInEditor(), "
       "the active IMEContentObserver isn't managing the editor"));
    return false;
  }

  WidgetMouseEvent* internalEvent =
    aMouseEvent->AsEvent()->WidgetEventPtr()->AsMouseEvent();
  if (NS_WARN_IF(!internalEvent)) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnMouseButtonEventInEditor(), "
       "the internal event of aMouseEvent isn't WidgetMouseEvent"));
    return false;
  }

  bool consumed =
    sActiveIMEContentObserver->OnMouseButtonEvent(aPresContext, internalEvent);

  if (MOZ_LOG_TEST(sISMLog, LogLevel::Info)) {
    nsAutoString eventType;
    aMouseEvent->AsEvent()->GetType(eventType);
    MOZ_LOG(sISMLog, LogLevel::Info,
      ("  OnMouseButtonEventInEditor(), "
       "mouse event (type=%s, button=%d) is %s",
       NS_ConvertUTF16toUTF8(eventType).get(), internalEvent->button,
       consumed ? "consumed" : "not consumed"));
  }

  return consumed;
}

// static
void
IMEStateManager::OnClickInEditor(nsPresContext* aPresContext,
                                 nsIContent* aContent,
                                 nsIDOMMouseEvent* aMouseEvent)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnClickInEditor(aPresContext=0x%p, aContent=0x%p, aMouseEvent=0x%p), "
     "sPresContext=0x%p, sContent=0x%p, sWidget=0x%p (available: %s)",
     aPresContext, aContent, aMouseEvent, sPresContext.get(), sContent.get(),
     sWidget, GetBoolName(sWidget && !sWidget->Destroyed())));

  if (sPresContext != aPresContext || sContent != aContent ||
      NS_WARN_IF(!sPresContext) || NS_WARN_IF(!sWidget) ||
      NS_WARN_IF(sWidget->Destroyed())) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnClickInEditor(), "
       "the mouse event isn't fired on the editor managed by ISM"));
    return;
  }

  nsCOMPtr<nsIWidget> widget(sWidget);

  MOZ_ASSERT(!sPresContext->GetRootWidget() ||
             sPresContext->GetRootWidget() == widget);

  bool isTrusted;
  nsresult rv = aMouseEvent->AsEvent()->GetIsTrusted(&isTrusted);
  NS_ENSURE_SUCCESS_VOID(rv);
  if (!isTrusted) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnClickInEditor(), "
       "the mouse event isn't a trusted event"));
    return; // ignore untrusted event.
  }

  int16_t button;
  rv = aMouseEvent->GetButton(&button);
  NS_ENSURE_SUCCESS_VOID(rv);
  if (button != 0) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnClickInEditor(), "
       "the mouse event isn't a left mouse button event"));
    return; // not a left click event.
  }

  int32_t clickCount;
  rv = aMouseEvent->GetDetail(&clickCount);
  NS_ENSURE_SUCCESS_VOID(rv);
  if (clickCount != 1) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnClickInEditor(), "
       "the mouse event isn't a single click event"));
    return; // should notify only first click event.
  }

  uint16_t inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
  aMouseEvent->GetMozInputSource(&inputSource);
  InputContextAction::Cause cause =
    inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH ?
      InputContextAction::CAUSE_TOUCH : InputContextAction::CAUSE_MOUSE;

  InputContextAction action(cause, InputContextAction::FOCUS_NOT_CHANGED);
  IMEState newState = GetNewIMEState(aPresContext, aContent);
  SetIMEState(newState, aContent, widget, action);
}

// static
void
IMEStateManager::OnFocusInEditor(nsPresContext* aPresContext,
                                 nsIContent* aContent,
                                 nsIEditor* aEditor)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnFocusInEditor(aPresContext=0x%p, aContent=0x%p, aEditor=0x%p), "
     "sPresContext=0x%p, sContent=0x%p, sActiveIMEContentObserver=0x%p",
     aPresContext, aContent, aEditor, sPresContext.get(), sContent.get(),
     sActiveIMEContentObserver.get()));

  if (sPresContext != aPresContext || sContent != aContent) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnFocusInEditor(), "
       "an editor not managed by ISM gets focus"));
    return;
  }

  // If the IMEContentObserver instance isn't managing the editor actually,
  // we need to recreate the instance.
  if (sActiveIMEContentObserver) {
    if (sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) {
      MOZ_LOG(sISMLog, LogLevel::Debug,
        ("  OnFocusInEditor(), "
         "the editor is already being managed by sActiveIMEContentObserver"));
      return;
    }
    DestroyIMEContentObserver();
  }

  CreateIMEContentObserver(aEditor);

  // Let's flush the focus notification now.
  if (sActiveIMEContentObserver) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  OnFocusInEditor(), new IMEContentObserver is "
       "created, trying to flush pending notifications..."));
    sActiveIMEContentObserver->TryToFlushPendingNotifications();
  }
}

// static
void
IMEStateManager::OnEditorInitialized(nsIEditor* aEditor)
{
  if (!sActiveIMEContentObserver ||
      sActiveIMEContentObserver->GetEditor() != aEditor) {
    return;
  }

  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnEditorInitialized(aEditor=0x%p)",
     aEditor));

  sActiveIMEContentObserver->UnsuppressNotifyingIME();
}

// static
void
IMEStateManager::OnEditorDestroying(nsIEditor* aEditor)
{
  if (!sActiveIMEContentObserver ||
      sActiveIMEContentObserver->GetEditor() != aEditor) {
    return;
  }

  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnEditorDestroying(aEditor=0x%p)",
     aEditor));

  // The IMEContentObserver shouldn't notify IME of anything until reframing
  // is finished.
  sActiveIMEContentObserver->SuppressNotifyingIME();
}

// static
void
IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState,
                                nsIContent* aContent,
                                EditorBase& aEditorBase)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("UpdateIMEState(aNewIMEState={ mEnabled=%s, "
     "mOpen=%s }, aContent=0x%p, aEditorBase=0x%p), "
     "sPresContext=0x%p, sContent=0x%p, sWidget=0x%p (available: %s), "
     "sActiveIMEContentObserver=0x%p, sIsGettingNewIMEState=%s",
     GetIMEStateEnabledName(aNewIMEState.mEnabled),
     GetIMEStateSetOpenName(aNewIMEState.mOpen), aContent, &aEditorBase,
     sPresContext.get(), sContent.get(),
     sWidget, GetBoolName(sWidget && !sWidget->Destroyed()),
     sActiveIMEContentObserver.get(),
     GetBoolName(sIsGettingNewIMEState)));

  if (sIsGettingNewIMEState) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  UpdateIMEState(), "
       "does nothing because of called while getting new IME state"));
    return;
  }

  nsCOMPtr<nsIPresShell> presShell = aEditorBase.GetPresShell();
  if (NS_WARN_IF(!presShell)) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  UpdateIMEState(), FAILED due to "
       "editor doesn't have PresShell"));
    return;
  }

  nsPresContext* presContext = presShell->GetPresContext();
  if (NS_WARN_IF(!presContext)) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  UpdateIMEState(), FAILED due to "
       "editor doesn't have PresContext"));
    return;
  }

  // IMEStateManager::UpdateIMEState() should be called after
  // IMEStateManager::OnChangeFocus() is called for setting focus to aContent
  // and aEditorBase.  However, when aEditorBase is an HTMLEditor, this may be
  // called by nsIEditor::PostCreate() before IMEStateManager::OnChangeFocus().
  // Similarly, when aEditorBase is a TextEditor, this may be called by
  // nsIEditor::SetFlags().  In such cases, this method should do nothing
  // because input context should be updated when
  // IMEStateManager::OnChangeFocus() is called later.
  if (sPresContext != presContext) {
    MOZ_LOG(sISMLog, LogLevel::Warning,
      ("  UpdateIMEState(), does nothing due to "
       "the editor hasn't managed by IMEStateManager yet"));
    return;
  }

  // If IMEStateManager doesn't manage any document, this cannot update IME
  // state of any widget.
  if (NS_WARN_IF(!sPresContext)) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  UpdateIMEState(), FAILED due to "
       "no managing nsPresContext"));
    return;
  }

  if (NS_WARN_IF(!sWidget) || NS_WARN_IF(sWidget->Destroyed())) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  UpdateIMEState(), FAILED due to "
       "the widget for the managing nsPresContext has gone"));
    return;
  }

  nsCOMPtr<nsIWidget> widget(sWidget);

  MOZ_ASSERT(!sPresContext->GetRootWidget() ||
             sPresContext->GetRootWidget() == widget);

  // Even if there is active IMEContentObserver, it may not be observing the
  // editor with current editable root content due to reframed.  In such case,
  // We should try to reinitialize the IMEContentObserver.
  if (sActiveIMEContentObserver && IsIMEObserverNeeded(aNewIMEState)) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  UpdateIMEState(), try to reinitialize the "
       "active IMEContentObserver"));
    if (!sActiveIMEContentObserver->MaybeReinitialize(widget, sPresContext,
                                                      aContent, &aEditorBase)) {
      MOZ_LOG(sISMLog, LogLevel::Error,
        ("  UpdateIMEState(), failed to reinitialize the "
         "active IMEContentObserver"));
    }
    if (NS_WARN_IF(widget->Destroyed())) {
      MOZ_LOG(sISMLog, LogLevel::Error,
        ("  UpdateIMEState(), widget has gone during reinitializing the "
         "active IMEContentObserver"));
      return;
    }
  }

  // If there is no active IMEContentObserver or it isn't observing the
  // editor correctly, we should recreate it.
  bool createTextStateManager =
    (!sActiveIMEContentObserver ||
     !sActiveIMEContentObserver->IsManaging(sPresContext, aContent));

  bool updateIMEState =
    (widget->GetInputContext().mIMEState.mEnabled != aNewIMEState.mEnabled);
  if (NS_WARN_IF(widget->Destroyed())) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  UpdateIMEState(), widget has gone during getting input context"));
    return;
  }

  if (updateIMEState) {
    // commit current composition before modifying IME state.
    NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, widget);
    if (NS_WARN_IF(widget->Destroyed())) {
      MOZ_LOG(sISMLog, LogLevel::Error,
        ("  UpdateIMEState(), widget has gone during committing composition"));
      return;
    }
  }

  if (createTextStateManager) {
    DestroyIMEContentObserver();
  }

  if (updateIMEState) {
    InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
                              InputContextAction::FOCUS_NOT_CHANGED);
    SetIMEState(aNewIMEState, aContent, widget, action);
    if (NS_WARN_IF(widget->Destroyed())) {
      MOZ_LOG(sISMLog, LogLevel::Error,
        ("  UpdateIMEState(), widget has gone during setting input context"));
      return;
    }
  }

  if (createTextStateManager) {
    // XXX In this case, it might not be enough safe to notify IME of anything.
    //     So, don't try to flush pending notifications of IMEContentObserver
    //     here.
    CreateIMEContentObserver(&aEditorBase);
  }
}

// static
IMEState
IMEStateManager::GetNewIMEState(nsPresContext* aPresContext,
                                nsIContent*    aContent)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("GetNewIMEState(aPresContext=0x%p, aContent=0x%p), "
     "sInstalledMenuKeyboardListener=%s",
     aPresContext, aContent, GetBoolName(sInstalledMenuKeyboardListener)));

  if (!CanHandleWith(aPresContext)) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  GetNewIMEState() returns DISABLED because "
       "the nsPresContext has been destroyed"));
    return IMEState(IMEState::DISABLED);
  }

  // On Printing or Print Preview, we don't need IME.
  if (aPresContext->Type() == nsPresContext::eContext_PrintPreview ||
      aPresContext->Type() == nsPresContext::eContext_Print) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  GetNewIMEState() returns DISABLED because "
       "the nsPresContext is for print or print preview"));
    return IMEState(IMEState::DISABLED);
  }

  if (sInstalledMenuKeyboardListener) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  GetNewIMEState() returns DISABLED because "
       "menu keyboard listener was installed"));
    return IMEState(IMEState::DISABLED);
  }

  if (!aContent) {
    // Even if there are no focused content, the focused document might be
    // editable, such case is design mode.
    nsIDocument* doc = aPresContext->Document();
    if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
      MOZ_LOG(sISMLog, LogLevel::Debug,
        ("  GetNewIMEState() returns ENABLED because "
         "design mode editor has focus"));
      return IMEState(IMEState::ENABLED);
    }
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  GetNewIMEState() returns DISABLED because "
       "no content has focus"));
    return IMEState(IMEState::DISABLED);
  }

  // nsIContent::GetDesiredIMEState() may cause a call of UpdateIMEState()
  // from EditorBase::PostCreate() because GetDesiredIMEState() needs to
  // retrieve an editor instance for the element if it's editable element.
  // For avoiding such nested IME state updates, we should set
  // sIsGettingNewIMEState here and UpdateIMEState() should check it.
  GettingNewIMEStateBlocker blocker;

  IMEState newIMEState = aContent->GetDesiredIMEState();
  MOZ_LOG(sISMLog, LogLevel::Debug,
    ("  GetNewIMEState() returns { mEnabled=%s, "
     "mOpen=%s }",
     GetIMEStateEnabledName(newIMEState.mEnabled),
     GetIMEStateSetOpenName(newIMEState.mOpen)));
  return newIMEState;
}

static bool
MayBeIMEUnawareWebApp(nsINode* aNode)
{
  bool haveKeyEventsListener = false;

  while (aNode) {
    EventListenerManager* const mgr = aNode->GetExistingListenerManager();
    if (mgr) {
      if (mgr->MayHaveInputOrCompositionEventListener()) {
        return false;
      }
      haveKeyEventsListener |= mgr->MayHaveKeyEventListener();
    }
    aNode = aNode->GetParentNode();
  }

  return haveKeyEventsListener;
}

// static
void
IMEStateManager::SetInputContextForChildProcess(
                   TabParent* aTabParent,
                   const InputContext& aInputContext,
                   const InputContextAction& aAction)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("SetInputContextForChildProcess(aTabParent=0x%p, "
     "aInputContext={ mIMEState={ mEnabled=%s, mOpen=%s }, "
     "mHTMLInputType=\"%s\", mHTMLInputInputmode=\"%s\", mActionHint=\"%s\" }, "
     "aAction={ mCause=%s, mAction=%s }), "
     "sPresContext=0x%p (available: %s), sWidget=0x%p (available: %s), "
     "sActiveTabParent=0x%p",
     aTabParent, GetIMEStateEnabledName(aInputContext.mIMEState.mEnabled),
     GetIMEStateSetOpenName(aInputContext.mIMEState.mOpen),
     NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputType).get(),
     NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputInputmode).get(),
     NS_ConvertUTF16toUTF8(aInputContext.mActionHint).get(),
     GetActionCauseName(aAction.mCause),
     GetActionFocusChangeName(aAction.mFocusChange),
     sPresContext.get(), GetBoolName(CanHandleWith(sPresContext)),
     sWidget, GetBoolName(sWidget && !sWidget->Destroyed()),
     sActiveTabParent.get()));

  if (aTabParent != sActiveTabParent) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  SetInputContextForChildProcess(), FAILED, "
       "because non-focused tab parent tries to set input context"));
    return;
  }

  if (NS_WARN_IF(!CanHandleWith(sPresContext))) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  SetInputContextForChildProcess(), FAILED, "
       "due to no focused presContext"));
    return;
  }

  if (NS_WARN_IF(!sWidget) || NS_WARN_IF(sWidget->Destroyed())) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  SetInputContextForChildProcess(), FAILED, "
       "due to the widget for the nsPresContext has gone"));
    return;
  }

  nsCOMPtr<nsIWidget> widget(sWidget);

  MOZ_ASSERT(!sPresContext->GetRootWidget() ||
             sPresContext->GetRootWidget() == widget);
  MOZ_ASSERT(aInputContext.mOrigin == InputContext::ORIGIN_CONTENT);

  SetInputContext(widget, aInputContext, aAction);
}

// static
void
IMEStateManager::SetIMEState(const IMEState& aState,
                             nsIContent* aContent,
                             nsIWidget* aWidget,
                             InputContextAction aAction)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("SetIMEState(aState={ mEnabled=%s, mOpen=%s }, "
     "aContent=0x%p (TabParent=0x%p), aWidget=0x%p, aAction={ mCause=%s, "
     "mFocusChange=%s })",
     GetIMEStateEnabledName(aState.mEnabled),
     GetIMEStateSetOpenName(aState.mOpen), aContent,
     TabParent::GetFrom(aContent), aWidget,
     GetActionCauseName(aAction.mCause),
     GetActionFocusChangeName(aAction.mFocusChange)));

  NS_ENSURE_TRUE_VOID(aWidget);

  InputContext context;
  context.mIMEState = aState;
  context.mMayBeIMEUnaware = context.mIMEState.IsEditable() &&
    sCheckForIMEUnawareWebApps && MayBeIMEUnawareWebApp(aContent);

  if (aContent &&
      aContent->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) {
    if (!aContent->IsHTMLElement(nsGkAtoms::textarea)) {
      // <input type=number> has an anonymous <input type=text> descendant
      // that gets focus whenever anyone tries to focus the number control. We
      // need to check if aContent is one of those anonymous text controls and,
      // if so, use the number control instead:
      nsIContent* content = aContent;
      HTMLInputElement* inputElement =
        HTMLInputElement::FromContentOrNull(aContent);
      if (inputElement) {
        HTMLInputElement* ownerNumberControl =
          inputElement->GetOwnerNumberControl();
        if (ownerNumberControl) {
          content = ownerNumberControl; // an <input type=number>
        }
      }
      content->GetAttr(kNameSpaceID_None, nsGkAtoms::type,
                       context.mHTMLInputType);
    } else {
      context.mHTMLInputType.Assign(nsGkAtoms::textarea->GetUTF16String());
    }

    if (Preferences::GetBool("dom.forms.inputmode", false) ||
        nsContentUtils::IsChromeDoc(aContent->OwnerDoc())) {
      aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::inputmode,
                        context.mHTMLInputInputmode);
    }

    aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::moz_action_hint,
                      context.mActionHint);

    // Get the input content corresponding to the focused node,
    // which may be an anonymous child of the input content.
    nsIContent* inputContent = aContent->FindFirstNonChromeOnlyAccessContent();

    // If we don't have an action hint and
    // return won't submit the form, use "next".
    if (context.mActionHint.IsEmpty() &&
        inputContent->IsHTMLElement(nsGkAtoms::input)) {
      bool willSubmit = false;
      nsCOMPtr<nsIFormControl> control(do_QueryInterface(inputContent));
      mozilla::dom::Element* formElement = nullptr;
      nsCOMPtr<nsIForm> form;
      if (control) {
        formElement = control->GetFormElement();
        // is this a form and does it have a default submit element?
        if ((form = do_QueryInterface(formElement)) &&
            form->GetDefaultSubmitElement()) {
          willSubmit = true;
        // is this an html form and does it only have a single text input element?
        } else if (formElement && formElement->IsHTMLElement(nsGkAtoms::form) &&
                   !static_cast<dom::HTMLFormElement*>(formElement)->
                     ImplicitSubmissionIsDisabled()) {
          willSubmit = true;
        }
      }
      context.mActionHint.Assign(
        willSubmit ? (control->GetType() == NS_FORM_INPUT_SEARCH ?
                       NS_LITERAL_STRING("search") : NS_LITERAL_STRING("go")) :
                     (formElement ?
                       NS_LITERAL_STRING("next") : EmptyString()));
    }
  }

  // XXX I think that we should use nsContentUtils::IsCallerChrome() instead
  //     of the process type.
  if (aAction.mCause == InputContextAction::CAUSE_UNKNOWN &&
      !XRE_IsContentProcess()) {
    aAction.mCause = InputContextAction::CAUSE_UNKNOWN_CHROME;
  }

  SetInputContext(aWidget, context, aAction);
}

// static
void
IMEStateManager::SetInputContext(nsIWidget* aWidget,
                                 const InputContext& aInputContext,
                                 const InputContextAction& aAction)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("SetInputContext(aWidget=0x%p, aInputContext={ "
     "mIMEState={ mEnabled=%s, mOpen=%s }, mHTMLInputType=\"%s\", "
     "mHTMLInputInputmode=\"%s\", mActionHint=\"%s\" }, "
     "aAction={ mCause=%s, mAction=%s }), sActiveTabParent=0x%p",
     aWidget,
     GetIMEStateEnabledName(aInputContext.mIMEState.mEnabled),
     GetIMEStateSetOpenName(aInputContext.mIMEState.mOpen),
     NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputType).get(),
     NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputInputmode).get(),
     NS_ConvertUTF16toUTF8(aInputContext.mActionHint).get(),
     GetActionCauseName(aAction.mCause),
     GetActionFocusChangeName(aAction.mFocusChange),
     sActiveTabParent.get()));

  MOZ_RELEASE_ASSERT(aWidget);

  nsCOMPtr<nsIWidget> widget(aWidget);
  widget->SetInputContext(aInputContext, aAction);
  sActiveInputContextWidget = widget;
}

// static
void
IMEStateManager::EnsureTextCompositionArray()
{
  if (sTextCompositions) {
    return;
  }
  sTextCompositions = new TextCompositionArray();
}

// static
void
IMEStateManager::DispatchCompositionEvent(
                   nsINode* aEventTargetNode,
                   nsPresContext* aPresContext,
                   WidgetCompositionEvent* aCompositionEvent,
                   nsEventStatus* aStatus,
                   EventDispatchingCallback* aCallBack,
                   bool aIsSynthesized)
{
  RefPtr<TabParent> tabParent =
    aEventTargetNode->IsContent() ?
      TabParent::GetFrom(aEventTargetNode->AsContent()) : nullptr;

  MOZ_LOG(sISMLog, LogLevel::Info,
    ("DispatchCompositionEvent(aNode=0x%p, "
     "aPresContext=0x%p, aCompositionEvent={ mMessage=%s, "
     "mNativeIMEContext={ mRawNativeIMEContext=0x%X, "
     "mOriginProcessID=0x%X }, mWidget(0x%p)={ "
     "GetNativeIMEContext()={ mRawNativeIMEContext=0x%X, "
     "mOriginProcessID=0x%X }, Destroyed()=%s }, "
     "mFlags={ mIsTrusted=%s, mPropagationStopped=%s } }, "
     "aIsSynthesized=%s), tabParent=%p",
     aEventTargetNode, aPresContext,
     ToChar(aCompositionEvent->mMessage),
     aCompositionEvent->mNativeIMEContext.mRawNativeIMEContext,
     aCompositionEvent->mNativeIMEContext.mOriginProcessID,
     aCompositionEvent->mWidget.get(),
     aCompositionEvent->mWidget->GetNativeIMEContext().mRawNativeIMEContext,
     aCompositionEvent->mWidget->GetNativeIMEContext().mOriginProcessID,
     GetBoolName(aCompositionEvent->mWidget->Destroyed()),
     GetBoolName(aCompositionEvent->mFlags.mIsTrusted),
     GetBoolName(aCompositionEvent->mFlags.mPropagationStopped),
     GetBoolName(aIsSynthesized), tabParent.get()));

  if (!aCompositionEvent->IsTrusted() ||
      aCompositionEvent->PropagationStopped()) {
    return;
  }

  MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionUpdate,
             "compositionupdate event shouldn't be dispatched manually");

  EnsureTextCompositionArray();

  RefPtr<TextComposition> composition =
    sTextCompositions->GetCompositionFor(aCompositionEvent);
  if (!composition) {
    // If synthesized event comes after delayed native composition events
    // for request of commit or cancel, we should ignore it.
    if (NS_WARN_IF(aIsSynthesized)) {
      return;
    }
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  DispatchCompositionEvent(), "
       "adding new TextComposition to the array"));
    MOZ_ASSERT(aCompositionEvent->mMessage == eCompositionStart);
    composition =
      new TextComposition(aPresContext, aEventTargetNode, tabParent,
                          aCompositionEvent);
    sTextCompositions->AppendElement(composition);
  }
#ifdef DEBUG
  else {
    MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionStart);
  }
#endif // #ifdef DEBUG

  // Dispatch the event on composing target.
  composition->DispatchCompositionEvent(aCompositionEvent, aStatus, aCallBack,
                                        aIsSynthesized);

  // WARNING: the |composition| might have been destroyed already.

  // Remove the ended composition from the array.
  // NOTE: When TextComposition is synthesizing compositionend event for
  //       emulating a commit, the instance shouldn't be removed from the array
  //       because IME may perform it later.  Then, we need to ignore the
  //       following commit events in TextComposition::DispatchEvent().
  //       However, if commit or cancel for a request is performed synchronously
  //       during not safe to dispatch events, PresShell must have discarded
  //       compositionend event.  Then, the synthesized compositionend event is
  //       the last event for the composition.  In this case, we need to
  //       destroy the TextComposition with synthesized compositionend event.
  if ((!aIsSynthesized ||
       composition->WasNativeCompositionEndEventDiscarded()) &&
      aCompositionEvent->CausesDOMCompositionEndEvent()) {
    TextCompositionArray::index_type i =
      sTextCompositions->IndexOf(aCompositionEvent->mWidget);
    if (i != TextCompositionArray::NoIndex) {
      MOZ_LOG(sISMLog, LogLevel::Debug,
        ("  DispatchCompositionEvent(), "
         "removing TextComposition from the array since NS_COMPOSTION_END "
         "was dispatched"));
      sTextCompositions->ElementAt(i)->Destroy();
      sTextCompositions->RemoveElementAt(i);
    }
  }
}

// static
IMEContentObserver*
IMEStateManager::GetActiveContentObserver()
{
  return sActiveIMEContentObserver;
}

// static
nsIContent*
IMEStateManager::GetRootContent(nsPresContext* aPresContext)
{
  nsIDocument* doc = aPresContext->Document();
  if (NS_WARN_IF(!doc)) {
    return nullptr;
  }
  return doc->GetRootElement();
}

// static
void
IMEStateManager::HandleSelectionEvent(nsPresContext* aPresContext,
                                      nsIContent* aEventTargetContent,
                                      WidgetSelectionEvent* aSelectionEvent)
{
  nsIContent* eventTargetContent =
    aEventTargetContent ? aEventTargetContent :
                          GetRootContent(aPresContext);
  RefPtr<TabParent> tabParent =
    eventTargetContent ? TabParent::GetFrom(eventTargetContent) : nullptr;

  MOZ_LOG(sISMLog, LogLevel::Info,
    ("HandleSelectionEvent(aPresContext=0x%p, "
     "aEventTargetContent=0x%p, aSelectionEvent={ mMessage=%s, "
     "mFlags={ mIsTrusted=%s } }), tabParent=%p",
     aPresContext, aEventTargetContent,
     ToChar(aSelectionEvent->mMessage),
     GetBoolName(aSelectionEvent->mFlags.mIsTrusted),
     tabParent.get()));

  if (!aSelectionEvent->IsTrusted()) {
    return;
  }

  RefPtr<TextComposition> composition = sTextCompositions ?
    sTextCompositions->GetCompositionFor(aSelectionEvent->mWidget) : nullptr;
  if (composition) {
    // When there is a composition, TextComposition should guarantee that the
    // selection event will be handled in same target as composition events.
    composition->HandleSelectionEvent(aSelectionEvent);
  } else {
    // When there is no composition, the selection event should be handled
    // in the aPresContext or tabParent.
    TextComposition::HandleSelectionEvent(aPresContext, tabParent,
                                          aSelectionEvent);
  }
}

// static
void
IMEStateManager::OnCompositionEventDiscarded(
                   WidgetCompositionEvent* aCompositionEvent)
{
  // Note that this method is never called for synthesized events for emulating
  // commit or cancel composition.

  MOZ_LOG(sISMLog, LogLevel::Info,
    ("OnCompositionEventDiscarded(aCompositionEvent={ "
     "mMessage=%s, mNativeIMEContext={ mRawNativeIMEContext=0x%X, "
     "mOriginProcessID=0x%X }, mWidget(0x%p)={ "
     "GetNativeIMEContext()={ mRawNativeIMEContext=0x%X, "
     "mOriginProcessID=0x%X }, Destroyed()=%s }, "
     "mFlags={ mIsTrusted=%s } })",
     ToChar(aCompositionEvent->mMessage),
     aCompositionEvent->mNativeIMEContext.mRawNativeIMEContext,
     aCompositionEvent->mNativeIMEContext.mOriginProcessID,
     aCompositionEvent->mWidget.get(),
     aCompositionEvent->mWidget->GetNativeIMEContext().mRawNativeIMEContext,
     aCompositionEvent->mWidget->GetNativeIMEContext().mOriginProcessID,
     GetBoolName(aCompositionEvent->mWidget->Destroyed()),
     GetBoolName(aCompositionEvent->mFlags.mIsTrusted)));

  if (!aCompositionEvent->IsTrusted()) {
    return;
  }

  // Ignore compositionstart for now because sTextCompositions may not have
  // been created yet.
  if (aCompositionEvent->mMessage == eCompositionStart) {
    return;
  }

  RefPtr<TextComposition> composition =
    sTextCompositions->GetCompositionFor(aCompositionEvent->mWidget);
  if (!composition) {
    // If the PresShell has been being destroyed during composition,
    // a TextComposition instance for the composition was already removed from
    // the array and destroyed in OnDestroyPresContext().  Therefore, we may
    // fail to retrieve a TextComposition instance here.
    MOZ_LOG(sISMLog, LogLevel::Info,
      ("  OnCompositionEventDiscarded(), "
       "TextComposition instance for the widget has already gone"));
    return;
  }
  composition->OnCompositionEventDiscarded(aCompositionEvent);
}

// static
nsresult
IMEStateManager::NotifyIME(IMEMessage aMessage,
                           nsIWidget* aWidget,
                           bool aOriginIsRemote)
{
  return IMEStateManager::NotifyIME(IMENotification(aMessage), aWidget,
                                    aOriginIsRemote);
}

// static
nsresult
IMEStateManager::NotifyIME(const IMENotification& aNotification,
                           nsIWidget* aWidget,
                           bool aOriginIsRemote)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("NotifyIME(aNotification={ mMessage=%s }, "
     "aWidget=0x%p, aOriginIsRemote=%s), sFocusedIMEWidget=0x%p, "
     "sRemoteHasFocus=%s",
     ToChar(aNotification.mMessage), aWidget,
     GetBoolName(aOriginIsRemote), sFocusedIMEWidget,
     GetBoolName(sRemoteHasFocus)));

  if (NS_WARN_IF(!aWidget)) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  NotifyIME(), FAILED due to no widget"));
    return NS_ERROR_INVALID_ARG;
  }

  switch (aNotification.mMessage) {
    case NOTIFY_IME_OF_FOCUS: {
      if (sFocusedIMEWidget) {
        if (NS_WARN_IF(!sRemoteHasFocus && !aOriginIsRemote)) {
          MOZ_LOG(sISMLog, LogLevel::Error,
            ("  NotifyIME(), although, this process is "
             "getting IME focus but there was focused IME widget"));
        } else {
          MOZ_LOG(sISMLog, LogLevel::Info,
            ("  NotifyIME(), tries to notify IME of "
             "blur first because remote process's blur notification hasn't "
             "been received yet..."));
        }
        nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget);
        sFocusedIMEWidget = nullptr;
        sRemoteHasFocus = false;
        focusedIMEWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR));
      }
      sRemoteHasFocus = aOriginIsRemote;
      sFocusedIMEWidget = aWidget;
      nsCOMPtr<nsIWidget> widget(aWidget);
      return widget->NotifyIME(aNotification);
    }
    case NOTIFY_IME_OF_BLUR: {
      if (!sRemoteHasFocus && aOriginIsRemote) {
        MOZ_LOG(sISMLog, LogLevel::Info,
          ("  NotifyIME(), received blur notification "
           "after another one has focus, nothing to do..."));
        return NS_OK;
      }
      if (NS_WARN_IF(sRemoteHasFocus && !aOriginIsRemote)) {
        MOZ_LOG(sISMLog, LogLevel::Error,
          ("  NotifyIME(), FAILED, received blur "
           "notification from this process but the remote has focus"));
        return NS_OK;
      }
      if (!sFocusedIMEWidget && aOriginIsRemote) {
        MOZ_LOG(sISMLog, LogLevel::Info,
          ("  NotifyIME(), received blur notification "
           "but the remote has already lost focus"));
        return NS_OK;
      }
      if (NS_WARN_IF(!sFocusedIMEWidget)) {
        MOZ_LOG(sISMLog, LogLevel::Error,
          ("  NotifyIME(), FAILED, received blur "
           "notification but there is no focused IME widget"));
        return NS_OK;
      }
      if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) {
        MOZ_LOG(sISMLog, LogLevel::Error,
          ("  NotifyIME(), FAILED, received blur "
           "notification but there is no focused IME widget"));
        return NS_OK;
      }
      nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget);
      sFocusedIMEWidget = nullptr;
      sRemoteHasFocus = false;
      return focusedIMEWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR));
    }
    case NOTIFY_IME_OF_SELECTION_CHANGE:
    case NOTIFY_IME_OF_TEXT_CHANGE:
    case NOTIFY_IME_OF_POSITION_CHANGE:
    case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
    case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: {
      if (!sRemoteHasFocus && aOriginIsRemote) {
        MOZ_LOG(sISMLog, LogLevel::Info,
          ("  NotifyIME(), received content change "
           "notification from the remote but it's already lost focus"));
        return NS_OK;
      }
      if (NS_WARN_IF(sRemoteHasFocus && !aOriginIsRemote)) {
        MOZ_LOG(sISMLog, LogLevel::Error,
          ("  NotifyIME(), FAILED, received content "
           "change notification from this process but the remote has already "
           "gotten focus"));
        return NS_OK;
      }
      if (!sFocusedIMEWidget) {
        MOZ_LOG(sISMLog, LogLevel::Info,
          ("  NotifyIME(), received content change "
           "notification but there is no focused IME widget"));
        return NS_OK;
      }
      if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) {
        MOZ_LOG(sISMLog, LogLevel::Error,
          ("  NotifyIME(), FAILED, received content "
           "change notification for IME which has already lost focus, so, "
           "nothing to do..."));
        return NS_OK;
      }
      nsCOMPtr<nsIWidget> widget(aWidget);
      return widget->NotifyIME(aNotification);
    }
    default:
      // Other notifications should be sent only when there is composition.
      // So, we need to handle the others below.
      break;
  }

  RefPtr<TextComposition> composition;
  if (sTextCompositions) {
    composition = sTextCompositions->GetCompositionFor(aWidget);
  }

  bool isSynthesizedForTests =
    composition && composition->IsSynthesizedForTests();

  MOZ_LOG(sISMLog, LogLevel::Info,
    ("  NotifyIME(), composition=0x%p, "
     "composition->IsSynthesizedForTests()=%s",
     composition.get(), GetBoolName(isSynthesizedForTests)));

  switch (aNotification.mMessage) {
    case REQUEST_TO_COMMIT_COMPOSITION:
      return composition ?
        composition->RequestToCommit(aWidget, false) : NS_OK;
    case REQUEST_TO_CANCEL_COMPOSITION:
      return composition ?
        composition->RequestToCommit(aWidget, true) : NS_OK;
    default:
      MOZ_CRASH("Unsupported notification");
  }
  MOZ_CRASH(
    "Failed to handle the notification for non-synthesized composition");
  return NS_ERROR_FAILURE;
}

// static
nsresult
IMEStateManager::NotifyIME(IMEMessage aMessage,
                           nsPresContext* aPresContext,
                           bool aOriginIsRemote)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("NotifyIME(aMessage=%s, aPresContext=0x%p, aOriginIsRemote=%s)",
     ToChar(aMessage), aPresContext, GetBoolName(aOriginIsRemote)));

  if (NS_WARN_IF(!CanHandleWith(aPresContext))) {
    return NS_ERROR_INVALID_ARG;
  }

  nsIWidget* widget = aPresContext->GetRootWidget();
  if (NS_WARN_IF(!widget)) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  NotifyIME(), FAILED due to no widget for the "
       "nsPresContext"));
    return NS_ERROR_NOT_AVAILABLE;
  }
  return NotifyIME(aMessage, widget, aOriginIsRemote);
}

// static
bool
IMEStateManager::IsEditable(nsINode* node)
{
  if (node->IsEditable()) {
    return true;
  }
  // |node| might be readwrite (for example, a text control)
  if (node->IsElement() &&
      node->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) {
    return true;
  }
  return false;
}

// static
nsINode*
IMEStateManager::GetRootEditableNode(nsPresContext* aPresContext,
                                     nsIContent* aContent)
{
  if (aContent) {
    nsINode* root = nullptr;
    nsINode* node = aContent;
    while (node && IsEditable(node)) {
      // If the node has independent selection like <input type="text"> or
      // <textarea>, the node should be the root editable node for aContent.
      // FYI: <select> element also has independent selection but IsEditable()
      //      returns false.
      // XXX: If somebody adds new editable element which has independent
      //      selection but doesn't own editor, we'll need more checks here.
      if (node->IsContent() &&
          node->AsContent()->HasIndependentSelection()) {
        return node;
      }
      root = node;
      node = node->GetParentNode();
    }
    return root;
  }
  if (aPresContext) {
    nsIDocument* document = aPresContext->Document();
    if (document && document->IsEditable()) {
      return document;
    }
  }
  return nullptr;
}

// static
bool
IMEStateManager::IsIMEObserverNeeded(const IMEState& aState)
{
  return aState.MaybeEditable();
}

// static
void
IMEStateManager::DestroyIMEContentObserver()
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("DestroyIMEContentObserver(), sActiveIMEContentObserver=0x%p",
     sActiveIMEContentObserver.get()));

  if (!sActiveIMEContentObserver) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  DestroyIMEContentObserver() does nothing"));
    return;
  }

  MOZ_LOG(sISMLog, LogLevel::Debug,
    ("  DestroyIMEContentObserver(), destroying "
     "the active IMEContentObserver..."));
  RefPtr<IMEContentObserver> tsm = sActiveIMEContentObserver.get();
  sActiveIMEContentObserver = nullptr;
  tsm->Destroy();
}

// static
void
IMEStateManager::CreateIMEContentObserver(nsIEditor* aEditor)
{
  MOZ_LOG(sISMLog, LogLevel::Info,
    ("CreateIMEContentObserver(aEditor=0x%p), "
     "sPresContext=0x%p, sContent=0x%p, sWidget=0x%p (available: %s), "
     "sActiveIMEContentObserver=0x%p, "
     "sActiveIMEContentObserver->IsManaging(sPresContext, sContent)=%s",
     aEditor, sPresContext.get(), sContent.get(),
     sWidget, GetBoolName(sWidget && !sWidget->Destroyed()),
     sActiveIMEContentObserver.get(),
     GetBoolName(sActiveIMEContentObserver ?
       sActiveIMEContentObserver->IsManaging(sPresContext, sContent) : false)));

  if (NS_WARN_IF(sActiveIMEContentObserver)) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  CreateIMEContentObserver(), FAILED due to "
       "there is already an active IMEContentObserver"));
    MOZ_ASSERT(sActiveIMEContentObserver->IsManaging(sPresContext, sContent));
    return;
  }

  if (!sWidget || NS_WARN_IF(sWidget->Destroyed())) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  CreateIMEContentObserver(), FAILED due to "
       "the widget for the nsPresContext has gone"));
    return; // Sometimes, there are no widgets.
  }

  nsCOMPtr<nsIWidget> widget(sWidget);

  // If it's not text editable, we don't need to create IMEContentObserver.
  if (!IsIMEObserverNeeded(widget->GetInputContext().mIMEState)) {
    MOZ_LOG(sISMLog, LogLevel::Debug,
      ("  CreateIMEContentObserver() doesn't create "
       "IMEContentObserver because of non-editable IME state"));
    return;
  }

  if (NS_WARN_IF(widget->Destroyed())) {
    MOZ_LOG(sISMLog, LogLevel::Error,
      ("  CreateIMEContentObserver(), FAILED due to "
       "the widget for the nsPresContext has gone"));
    return;
  }

  MOZ_ASSERT(sPresContext->GetRootWidget() == widget);

  MOZ_LOG(sISMLog, LogLevel::Debug,
    ("  CreateIMEContentObserver() is creating an "
     "IMEContentObserver instance..."));
  sActiveIMEContentObserver = new IMEContentObserver();

  // IMEContentObserver::Init() might create another IMEContentObserver
  // instance.  So, sActiveIMEContentObserver would be replaced with new one.
  // We should hold the current instance here.
  RefPtr<IMEContentObserver> activeIMEContentObserver(sActiveIMEContentObserver);
  activeIMEContentObserver->Init(widget, sPresContext, sContent, aEditor);
}

// static
nsresult
IMEStateManager::GetFocusSelectionAndRoot(nsISelection** aSelection,
                                          nsIContent** aRootContent)
{
  if (!sActiveIMEContentObserver) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  return sActiveIMEContentObserver->GetSelectionAndRoot(aSelection,
                                                        aRootContent);
}

// static
already_AddRefed<TextComposition>
IMEStateManager::GetTextCompositionFor(nsIWidget* aWidget)
{
  if (!sTextCompositions) {
    return nullptr;
  }
  RefPtr<TextComposition> textComposition =
    sTextCompositions->GetCompositionFor(aWidget);
  return textComposition.forget();
}

// static
already_AddRefed<TextComposition>
IMEStateManager::GetTextCompositionFor(
                   const WidgetCompositionEvent* aCompositionEvent)
{
  if (!sTextCompositions) {
    return nullptr;
  }
  RefPtr<TextComposition> textComposition =
    sTextCompositions->GetCompositionFor(aCompositionEvent);
  return textComposition.forget();
}

// static
already_AddRefed<TextComposition>
IMEStateManager::GetTextCompositionFor(nsPresContext* aPresContext)
{
  if (!sTextCompositions) {
    return nullptr;
  }
  RefPtr<TextComposition> textComposition =
    sTextCompositions->GetCompositionFor(aPresContext);
  return textComposition.forget();
}

} // namespace mozilla
