dom/events/IMEContentObserver.cpp
author Masayuki Nakano <masayuki@d-toybox.com>
Thu, 28 Jan 2016 13:28:53 +0900
changeset 282078 3442a539cc8b8f2a7ab504f1e3fad11541579652
parent 282077 66b71325dfbf5cf32f2eb20a724d0dfcd805ccd0
child 282079 04d8a95c6a5e19ca4debf62a052e8011cf6af478
permissions -rw-r--r--
Bug 1242331 part.3 Rename TextChangeDataBase::mOccurredDuringComposition to mIncludingChangesDuringComposition r=smaug

/* -*- 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 "ContentEventHandler.h"
#include "IMEContentObserver.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/TextComposition.h"
#include "mozilla/TextEvents.h"
#include "mozilla/dom/Element.h"
#include "nsAutoPtr.h"
#include "nsContentUtils.h"
#include "nsGkAtoms.h"
#include "nsIAtom.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIDOMDocument.h"
#include "nsIDOMRange.h"
#include "nsIEditorIMESupport.h"
#include "nsIFrame.h"
#include "nsINode.h"
#include "nsIPresShell.h"
#include "nsISelectionController.h"
#include "nsISelectionPrivate.h"
#include "nsISupports.h"
#include "nsIWidget.h"
#include "nsPresContext.h"
#include "nsWeakReference.h"
#include "WritingModes.h"

namespace mozilla {

using namespace widget;

LazyLogModule sIMECOLog("IMEContentObserver");

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

static const char*
ToChar(IMEMessage aIMEMessage)
{
  switch (aIMEMessage) {
    case NOTIFY_IME_OF_NOTHING:
      return "NOTIFY_IME_OF_NOTHING";
    case NOTIFY_IME_OF_FOCUS:
      return "NOTIFY_IME_OF_FOCUS";
    case NOTIFY_IME_OF_BLUR:
      return "NOTIFY_IME_OF_BLUR";
    case NOTIFY_IME_OF_SELECTION_CHANGE:
      return "NOTIFY_IME_OF_SELECTION_CHANGE";
    case NOTIFY_IME_OF_TEXT_CHANGE:
      return "NOTIFY_IME_OF_TEXT_CHANGE";
    case NOTIFY_IME_OF_COMPOSITION_UPDATE:
      return "NOTIFY_IME_OF_COMPOSITION_UPDATE";
    case NOTIFY_IME_OF_POSITION_CHANGE:
      return "NOTIFY_IME_OF_POSITION_CHANGE";
    case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
      return "NOTIFY_IME_OF_MOUSE_BUTTON_EVENT";
    case REQUEST_TO_COMMIT_COMPOSITION:
      return "REQUEST_TO_COMMIT_COMPOSITION";
    case REQUEST_TO_CANCEL_COMPOSITION:
      return "REQUEST_TO_CANCEL_COMPOSITION";
    default:
      return "Unexpected value";
  }
}

class WritingModeToString final : public nsAutoCString
{
public:
  explicit WritingModeToString(const WritingMode& aWritingMode)
  {
    if (!aWritingMode.IsVertical()) {
      AssignLiteral("Horizontal");
      return;
    }
    if (aWritingMode.IsVerticalLR()) {
      AssignLiteral("Vertical (LR)");
      return;
    }
    AssignLiteral("Vertical (RL)");
  }
  virtual ~WritingModeToString() {}
};

class SelectionChangeDataToString final : public nsAutoCString
{
public:
  explicit SelectionChangeDataToString(
             const IMENotification::SelectionChangeDataBase& aData)
  {
    if (!aData.IsValid()) {
      AppendLiteral("{ IsValid()=false }");
      return;
    }
    AppendPrintf("{ mOffset=%u, ", aData.mOffset);
    if (aData.mString->Length() > 20) {
      AppendPrintf("mString.Length()=%u, ", aData.mString->Length());
    } else {
      AppendPrintf("mString=\"%s\" (Length()=%u), ",
                   NS_ConvertUTF16toUTF8(*aData.mString).get(),
                   aData.mString->Length());
    }
    AppendPrintf("GetWritingMode()=%s, mReversed=%s, mCausedByComposition=%s, "
                 "mCausedBySelectionEvent=%s }",
                 WritingModeToString(aData.GetWritingMode()).get(),
                 ToChar(aData.mReversed),
                 ToChar(aData.mCausedByComposition),
                 ToChar(aData.mCausedBySelectionEvent));
  }
  virtual ~SelectionChangeDataToString() {}
};

class TextChangeDataToString final : public nsAutoCString
{
public:
  explicit TextChangeDataToString(
             const IMENotification::TextChangeDataBase& aData)
  {
    if (!aData.IsValid()) {
      AppendLiteral("{ IsValid()=false }");
      return;
    }
    AppendPrintf("{ mStartOffset=%u, mRemovedEndOffset=%u, mAddedEndOffset=%u, "
                 "mCausedOnlyByComposition=%s, "
                 "mIncludingChangesDuringComposition=%s }",
                 aData.mStartOffset, aData.mRemovedEndOffset,
                 aData.mAddedEndOffset,
                 ToChar(aData.mCausedOnlyByComposition),
                 ToChar(aData.mIncludingChangesDuringComposition));
  }
  virtual ~TextChangeDataToString() {}
};

/******************************************************************************
 * mozilla::IMEContentObserver
 ******************************************************************************/

NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver)
  nsAutoScriptBlocker scriptBlocker;

  tmp->NotifyIMEOfBlur();
  tmp->UnregisterObservers();

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelection)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootContent)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditableNode)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditor)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndOfAddedTextCache.mContainerNode)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartOfRemovingTextRangeCache.mContainerNode)

  tmp->mUpdatePreference.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING;
  tmp->mESM = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWidget)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedWidget)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelection)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootContent)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditableNode)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditor)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndOfAddedTextCache.mContainerNode)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
    mStartOfRemovingTextRangeCache.mContainerNode)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver)
 NS_INTERFACE_MAP_ENTRY(nsISelectionListener)
 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
 NS_INTERFACE_MAP_ENTRY(nsIReflowObserver)
 NS_INTERFACE_MAP_ENTRY(nsIScrollObserver)
 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_ENTRY(nsIEditorObserver)
 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelectionListener)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver)

IMEContentObserver::IMEContentObserver()
  : mESM(nullptr)
  , mSuppressNotifications(0)
  , mPreCharacterDataChangeLength(-1)
  , mSendingNotification(NOTIFY_IME_OF_NOTHING)
  , mIsObserving(false)
  , mIMEHasFocus(false)
  , mNeedsToNotifyIMEOfFocusSet(false)
  , mNeedsToNotifyIMEOfTextChange(false)
  , mNeedsToNotifyIMEOfSelectionChange(false)
  , mNeedsToNotifyIMEOfPositionChange(false)
  , mIsHandlingQueryContentEvent(false)
{
#ifdef DEBUG
  mTextChangeData.Test();
#endif
}

void
IMEContentObserver::Init(nsIWidget* aWidget,
                         nsPresContext* aPresContext,
                         nsIContent* aContent,
                         nsIEditor* aEditor)
{
  State state = GetState();
  if (NS_WARN_IF(state == eState_Observing)) {
    return; // Nothing to do.
  }

  bool firstInitialization = state != eState_StoppedObserving;
  if (!firstInitialization) {
    // If this is now trying to initialize with new contents, all observers
    // should be registered again for simpler implementation.
    UnregisterObservers();
    // Clear members which may not be initialized again.
    Clear();
  }

  mESM = aPresContext->EventStateManager();
  mESM->OnStartToObserveContent(this);

  mWidget = aWidget;

  if (aWidget->GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) {
    if (!InitWithPlugin(aPresContext, aContent)) {
      Clear();
      return;
    }
  } else {
    if (!InitWithEditor(aPresContext, aContent, aEditor)) {
      Clear();
      return;
    }
  }

  if (firstInitialization) {
    // Now, try to send NOTIFY_IME_OF_FOCUS to IME via the widget.
    MaybeNotifyIMEOfFocusSet();
    // When this is called first time, IME has not received NOTIFY_IME_OF_FOCUS
    // yet since NOTIFY_IME_OF_FOCUS will be sent to widget asynchronously.
    // So, we need to do nothing here.  After NOTIFY_IME_OF_FOCUS has been
    // sent, OnIMEReceivedFocus() will be called and content, selection and/or
    // position changes will be observed
    return;
  }

  // When this is called after editor reframing (i.e., the root editable node
  // is also recreated), IME has usually received NOTIFY_IME_OF_FOCUS.  In this
  // case, we need to restart to observe content, selection and/or position
  // changes in new root editable node.
  ObserveEditableNode();

  if (!NeedsToNotifyIMEOfSomething()) {
    return;
  }

  // Some change events may wait to notify IME because this was being
  // initialized.  It is the time to flush them.
  FlushMergeableNotifications();
}

void
IMEContentObserver::OnIMEReceivedFocus()
{
  // While Init() notifies IME of focus, pending layout may be flushed
  // because the notification may cause querying content.  Then, recursive
  // call of Init() with the latest content may occur.  In such case, we
  // shouldn't keep first initialization which notified IME of focus.
  if (GetState() != eState_Initializing) {
    return;
  }

  // NOTIFY_IME_OF_FOCUS might cause recreating IMEContentObserver
  // instance via IMEStateManager::UpdateIMEState().  So, this
  // instance might already have been destroyed, check it.
  if (!mRootContent) {
    return;
  }

  // Start to observe which is needed by IME when IME actually has focus.
  ObserveEditableNode();

  if (!NeedsToNotifyIMEOfSomething()) {
    return;
  }

  // Some change events may wait to notify IME because this was being
  // initialized.  It is the time to flush them.
  FlushMergeableNotifications();
}

bool
IMEContentObserver::InitWithEditor(nsPresContext* aPresContext,
                                   nsIContent* aContent,
                                   nsIEditor* aEditor)
{
  MOZ_ASSERT(aEditor);

  mEditableNode =
    IMEStateManager::GetRootEditableNode(aPresContext, aContent);
  if (NS_WARN_IF(!mEditableNode)) {
    return false;
  }

  mEditor = aEditor;
  if (NS_WARN_IF(!mEditor)) {
    return false;
  }

  nsIPresShell* presShell = aPresContext->PresShell();

  // get selection and root content
  nsCOMPtr<nsISelectionController> selCon;
  if (mEditableNode->IsNodeOfType(nsINode::eCONTENT)) {
    nsIFrame* frame =
      static_cast<nsIContent*>(mEditableNode.get())->GetPrimaryFrame();
    if (NS_WARN_IF(!frame)) {
      return false;
    }

    frame->GetSelectionController(aPresContext,
                                  getter_AddRefs(selCon));
  } else {
    // mEditableNode is a document
    selCon = do_QueryInterface(presShell);
  }

  if (NS_WARN_IF(!selCon)) {
    return false;
  }

  selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
                       getter_AddRefs(mSelection));
  if (NS_WARN_IF(!mSelection)) {
    return false;
  }

  nsCOMPtr<nsIDOMRange> selDomRange;
  if (NS_SUCCEEDED(mSelection->GetRangeAt(0, getter_AddRefs(selDomRange)))) {
    nsRange* selRange = static_cast<nsRange*>(selDomRange.get());
    if (NS_WARN_IF(!selRange) || NS_WARN_IF(!selRange->GetStartParent())) {
      return false;
    }

    mRootContent = selRange->GetStartParent()->
                     GetSelectionRootContent(presShell);
  } else {
    mRootContent = mEditableNode->GetSelectionRootContent(presShell);
  }
  if (!mRootContent && mEditableNode->IsNodeOfType(nsINode::eDOCUMENT)) {
    // The document node is editable, but there are no contents, this document
    // is not editable.
    return false;
  }

  if (NS_WARN_IF(!mRootContent)) {
    return false;
  }

  mDocShell = aPresContext->GetDocShell();
  if (NS_WARN_IF(!mDocShell)) {
    return false;
  }

  return true;
}

bool
IMEContentObserver::InitWithPlugin(nsPresContext* aPresContext,
                                   nsIContent* aContent)
{
  if (NS_WARN_IF(!aContent) ||
      NS_WARN_IF(aContent->GetDesiredIMEState().mEnabled != IMEState::PLUGIN)) {
    return false;
  }
  nsIFrame* frame = aContent->GetPrimaryFrame();
  if (NS_WARN_IF(!frame)) {
    return false;
  }
  nsCOMPtr<nsISelectionController> selCon;
  frame->GetSelectionController(aPresContext, getter_AddRefs(selCon));
  if (NS_WARN_IF(!selCon)) {
    return false;
  }
  selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
                       getter_AddRefs(mSelection));
  if (NS_WARN_IF(!mSelection)) {
    return false;
  }

  mEditor = nullptr;
  mEditableNode = aContent;
  mRootContent = aContent;

  mDocShell = aPresContext->GetDocShell();
  if (NS_WARN_IF(!mDocShell)) {
    return false;
  }

  return true;
}

void
IMEContentObserver::Clear()
{
  mEditor = nullptr;
  mSelection = nullptr;
  mEditableNode = nullptr;
  mRootContent = nullptr;
  mDocShell = nullptr;
}

void
IMEContentObserver::ObserveEditableNode()
{
  MOZ_RELEASE_ASSERT(mSelection);
  MOZ_RELEASE_ASSERT(mRootContent);
  MOZ_RELEASE_ASSERT(GetState() != eState_Observing);

  // If this is called before sending NOTIFY_IME_OF_FOCUS (it's possible when
  // the editor is reframed before sending NOTIFY_IME_OF_FOCUS asynchronously),
  // the update preference of mWidget may be different from after the widget
  // receives NOTIFY_IME_OF_FOCUS.   So, this should be called again by
  // OnIMEReceivedFocus() which is called after sending NOTIFY_IME_OF_FOCUS.
  if (!mIMEHasFocus) {
    MOZ_ASSERT(!mWidget || mNeedsToNotifyIMEOfFocusSet ||
               mSendingNotification == NOTIFY_IME_OF_FOCUS,
               "Wow, OnIMEReceivedFocus() won't be called?");
    return;
  }

  mIsObserving = true;
  if (mEditor) {
    mEditor->AddEditorObserver(this);
  }

  mUpdatePreference = mWidget->GetIMEUpdatePreference();
  if (mUpdatePreference.WantSelectionChange()) {
    // add selection change listener
    nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection));
    NS_ENSURE_TRUE_VOID(selPrivate);
    nsresult rv = selPrivate->AddSelectionListener(this);
    NS_ENSURE_SUCCESS_VOID(rv);
  }

  if (mUpdatePreference.WantTextChange()) {
    // add text change observer
    mRootContent->AddMutationObserver(this);
  }

  if (mUpdatePreference.WantPositionChanged() && mDocShell) {
    // Add scroll position listener and reflow observer to detect position and
    // size changes
    mDocShell->AddWeakScrollObserver(this);
    mDocShell->AddWeakReflowObserver(this);
  }
}

void
IMEContentObserver::NotifyIMEOfBlur()
{
  // Prevent any notifications to be sent IME.
  nsCOMPtr<nsIWidget> widget;
  mWidget.swap(widget);

  // If we hasn't been set focus, we shouldn't send blur notification to IME.
  if (!mIMEHasFocus) {
    return;
  }

  // mWidget must have been non-nullptr if IME has focus.
  MOZ_RELEASE_ASSERT(widget);

  RefPtr<IMEContentObserver> kungFuDeathGrip(this);

  MOZ_LOG(sIMECOLog, LogLevel::Info,
    ("IMECO: 0x%p IMEContentObserver::NotifyIMEOfBlur(), "
     "sending NOTIFY_IME_OF_BLUR", this));

  // For now, we need to send blur notification in any condition because
  // we don't have any simple ways to send blur notification asynchronously.
  // After this call, Destroy() or Unlink() will stop observing the content
  // and forget everything.  Therefore, if it's not safe to send notification
  // when script blocker is unlocked, we cannot send blur notification after
  // that and before next focus notification.
  // Anyway, as far as we know, IME doesn't try to query content when it loses
  // focus.  So, this may not cause any problem.
  mIMEHasFocus = false;
  IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR), widget);

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::NotifyIMEOfBlur(), "
     "sent NOTIFY_IME_OF_BLUR", this));
}

void
IMEContentObserver::UnregisterObservers()
{
  if (!mIsObserving) {
    return;
  }
  mIsObserving = false;

  if (mEditor) {
    mEditor->RemoveEditorObserver(this);
  }

  if (mUpdatePreference.WantSelectionChange() && mSelection) {
    nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection));
    if (selPrivate) {
      selPrivate->RemoveSelectionListener(this);
    }
    mSelectionData.Clear();
    mFocusedWidget = nullptr;
  }

  if (mUpdatePreference.WantTextChange() && mRootContent) {
    mRootContent->RemoveMutationObserver(this);
  }

  if (mUpdatePreference.WantPositionChanged() && mDocShell) {
    mDocShell->RemoveWeakScrollObserver(this);
    mDocShell->RemoveWeakReflowObserver(this);
  }
}

nsPresContext*
IMEContentObserver::GetPresContext() const
{
  return mESM ? mESM->GetPresContext() : nullptr;
}

void
IMEContentObserver::Destroy()
{
  // WARNING: When you change this method, you have to check Unlink() too.

  NotifyIMEOfBlur();
  UnregisterObservers();
  Clear();

  mWidget = nullptr;
  mUpdatePreference.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING;

  if (mESM) {
    mESM->OnStopObservingContent(this);
    mESM = nullptr;
  }
}

void
IMEContentObserver::DisconnectFromEventStateManager()
{
  mESM = nullptr;
}

bool
IMEContentObserver::MaybeReinitialize(nsIWidget* aWidget,
                                      nsPresContext* aPresContext,
                                      nsIContent* aContent,
                                      nsIEditor* aEditor)
{
  if (!IsObservingContent(aPresContext, aContent)) {
    return false;
  }

  if (GetState() == eState_StoppedObserving) {
    Init(aWidget, aPresContext, aContent, aEditor);
  }
  return IsManaging(aPresContext, aContent);
}

bool
IMEContentObserver::IsManaging(nsPresContext* aPresContext,
                               nsIContent* aContent)
{
  return GetState() == eState_Observing &&
         IsObservingContent(aPresContext, aContent);
}

IMEContentObserver::State
IMEContentObserver::GetState() const
{
  if (!mSelection || !mRootContent || !mEditableNode) {
    return eState_NotObserving; // failed to initialize or finalized.
  }
  if (!mRootContent->IsInComposedDoc()) {
    // the focused editor has already been reframed.
    return eState_StoppedObserving;
  }
  return mIsObserving ? eState_Observing : eState_Initializing;
}

bool
IMEContentObserver::IsObservingContent(nsPresContext* aPresContext,
                                       nsIContent* aContent) const
{
  return IsInitializedWithPlugin() ?
    mRootContent == aContent && mRootContent != nullptr :
    mEditableNode == IMEStateManager::GetRootEditableNode(aPresContext,
                                                          aContent);
}

bool
IMEContentObserver::IsEditorHandlingEventForComposition() const
{
  if (!mWidget) {
    return false;
  }
  RefPtr<TextComposition> composition =
    IMEStateManager::GetTextCompositionFor(mWidget);
  if (!composition) {
    return false;
  }
  return composition->IsEditorHandlingEvent();
}

bool
IMEContentObserver::IsEditorComposing() const
{
  // Note that don't use TextComposition here. The important thing is,
  // whether the editor already started to handle composition because
  // web contents can change selection, text content and/or something from
  // compositionstart event listener which is run before nsEditor handles it.
  nsCOMPtr<nsIEditorIMESupport> editorIMESupport = do_QueryInterface(mEditor);
  if (NS_WARN_IF(!editorIMESupport)) {
    return false;
  }
  bool isComposing = false;
  nsresult rv = editorIMESupport->GetComposing(&isComposing);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }
  return isComposing;
}

nsresult
IMEContentObserver::GetSelectionAndRoot(nsISelection** aSelection,
                                        nsIContent** aRootContent) const
{
  if (!mEditableNode || !mSelection) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  NS_ASSERTION(mSelection && mRootContent, "uninitialized content observer");
  NS_ADDREF(*aSelection = mSelection);
  NS_ADDREF(*aRootContent = mRootContent);
  return NS_OK;
}

nsresult
IMEContentObserver::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
                                           nsISelection* aSelection,
                                           int16_t aReason)
{
  int32_t count = 0;
  nsresult rv = aSelection->GetRangeCount(&count);
  NS_ENSURE_SUCCESS(rv, rv);
  if (count > 0 && mWidget) {
    bool causedByComposition = IsEditorHandlingEventForComposition();
    bool causedBySelectionEvent = TextComposition::IsHandlingSelectionEvent();
    bool duringComposition = IsEditorComposing();
    MaybeNotifyIMEOfSelectionChange(causedByComposition,
                                    causedBySelectionEvent,
                                    duringComposition);
  }
  return NS_OK;
}

void
IMEContentObserver::ScrollPositionChanged()
{
  MaybeNotifyIMEOfPositionChange();
}

NS_IMETHODIMP
IMEContentObserver::Reflow(DOMHighResTimeStamp aStart,
                           DOMHighResTimeStamp aEnd)
{
  MaybeNotifyIMEOfPositionChange();
  return NS_OK;
}

NS_IMETHODIMP
IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart,
                                        DOMHighResTimeStamp aEnd)
{
  MaybeNotifyIMEOfPositionChange();
  return NS_OK;
}

nsresult
IMEContentObserver::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
{
  // If the instance has cache, it should use the cached selection which was
  // sent to the widget.  However, if this instance has already received new
  // selection change notification but hasn't updated the cache yet (i.e.,
  // not sending selection change notification to IME, don't use the cached
  // value.  Note that don't update selection cache here since if you update
  // selection cache here, IMENotificationSender won't notify IME of selection
  // change because it looks like that the selection isn't actually changed.
  if (aEvent->mMessage == eQuerySelectedText && aEvent->mUseNativeLineBreak &&
      mSelectionData.IsValid() && !mNeedsToNotifyIMEOfSelectionChange) {
    aEvent->mReply.mContentsRoot = mRootContent;
    aEvent->mReply.mHasSelection = !mSelectionData.IsCollapsed();
    aEvent->mReply.mOffset = mSelectionData.mOffset;
    aEvent->mReply.mString = mSelectionData.String();
    aEvent->mReply.mWritingMode = mSelectionData.GetWritingMode();
    aEvent->mReply.mReversed = mSelectionData.mReversed;
    aEvent->mSucceeded = true;
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p IMEContentObserver::HandleQueryContentEvent(aEvent={ "
       "mMessage=%s })", this, ToChar(aEvent->mMessage)));
    return NS_OK;
  }

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::HandleQueryContentEvent(aEvent={ "
     "mMessage=%s })", this, ToChar(aEvent->mMessage)));

  AutoRestore<bool> handling(mIsHandlingQueryContentEvent);
  mIsHandlingQueryContentEvent = true;
  ContentEventHandler handler(GetPresContext());
  nsresult rv = handler.HandleQueryContentEvent(aEvent);
  if (aEvent->mSucceeded) {
    // We need to guarantee that mRootContent should be always same value for
    // the observing editor.
    aEvent->mReply.mContentsRoot = mRootContent;
  }
  return rv;
}

bool
IMEContentObserver::OnMouseButtonEvent(nsPresContext* aPresContext,
                                       WidgetMouseEvent* aMouseEvent)
{
  if (!mUpdatePreference.WantMouseButtonEventOnChar()) {
    return false;
  }
  if (!aMouseEvent->mFlags.mIsTrusted ||
      aMouseEvent->mFlags.mDefaultPrevented ||
      !aMouseEvent->widget) {
    return false;
  }
  // Now, we need to notify only mouse down and mouse up event.
  switch (aMouseEvent->mMessage) {
    case eMouseUp:
    case eMouseDown:
      break;
    default:
      return false;
  }
  if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
    return false;
  }

  RefPtr<IMEContentObserver> kungFuDeathGrip(this);

  WidgetQueryContentEvent charAtPt(true, eQueryCharacterAtPoint,
                                   aMouseEvent->widget);
  charAtPt.refPoint = aMouseEvent->refPoint;
  ContentEventHandler handler(aPresContext);
  handler.OnQueryCharacterAtPoint(&charAtPt);
  if (NS_WARN_IF(!charAtPt.mSucceeded) ||
      charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND) {
    return false;
  }

  // The widget might be destroyed during querying the content since it
  // causes flushing layout.
  if (!mWidget || NS_WARN_IF(mWidget->Destroyed())) {
    return false;
  }

  // The result character rect is relative to the top level widget.
  // We should notify it with offset in the widget.
  nsIWidget* topLevelWidget = mWidget->GetTopLevelWidget();
  if (topLevelWidget && topLevelWidget != mWidget) {
    charAtPt.mReply.mRect.MoveBy(
      topLevelWidget->WidgetToScreenOffset() -
        mWidget->WidgetToScreenOffset());
  }
  // The refPt is relative to its widget.
  // We should notify it with offset in the widget.
  if (aMouseEvent->widget != mWidget) {
    charAtPt.refPoint += aMouseEvent->widget->WidgetToScreenOffset() -
      mWidget->WidgetToScreenOffset();
  }

  IMENotification notification(NOTIFY_IME_OF_MOUSE_BUTTON_EVENT);
  notification.mMouseButtonEventData.mEventMessage = aMouseEvent->mMessage;
  notification.mMouseButtonEventData.mOffset = charAtPt.mReply.mOffset;
  notification.mMouseButtonEventData.mCursorPos.Set(
    charAtPt.refPoint.ToUnknownPoint());
  notification.mMouseButtonEventData.mCharRect.Set(
    charAtPt.mReply.mRect.ToUnknownRect());
  notification.mMouseButtonEventData.mButton = aMouseEvent->button;
  notification.mMouseButtonEventData.mButtons = aMouseEvent->buttons;
  notification.mMouseButtonEventData.mModifiers = aMouseEvent->modifiers;

  nsresult rv = IMEStateManager::NotifyIME(notification, mWidget);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }

  bool consumed = (rv == NS_SUCCESS_EVENT_CONSUMED);
  aMouseEvent->mFlags.mDefaultPrevented = consumed;
  return consumed;
}

void
IMEContentObserver::CharacterDataWillChange(nsIDocument* aDocument,
                                            nsIContent* aContent,
                                            CharacterDataChangeInfo* aInfo)
{
  NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
               "character data changed for non-text node");
  MOZ_ASSERT(mPreCharacterDataChangeLength < 0,
             "CharacterDataChanged() should've reset "
             "mPreCharacterDataChangeLength");

  mEndOfAddedTextCache.Clear();
  mStartOfRemovingTextRangeCache.Clear();

  bool causedByComposition = IsEditorHandlingEventForComposition();
  if (!mTextChangeData.IsValid() && causedByComposition &&
      !mUpdatePreference.WantChangesCausedByComposition()) {
    return;
  }

  mPreCharacterDataChangeLength =
    ContentEventHandler::GetNativeTextLength(aContent, aInfo->mChangeStart,
                                             aInfo->mChangeEnd);
  MOZ_ASSERT(mPreCharacterDataChangeLength >=
               aInfo->mChangeEnd - aInfo->mChangeStart,
             "The computed length must be same as or larger than XP length");
}

void
IMEContentObserver::CharacterDataChanged(nsIDocument* aDocument,
                                         nsIContent* aContent,
                                         CharacterDataChangeInfo* aInfo)
{
  NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
               "character data changed for non-text node");

  mEndOfAddedTextCache.Clear();
  mStartOfRemovingTextRangeCache.Clear();

  int64_t removedLength = mPreCharacterDataChangeLength;
  mPreCharacterDataChangeLength = -1;

  bool causedByComposition = IsEditorHandlingEventForComposition();
  if (!mTextChangeData.IsValid() && causedByComposition &&
      !mUpdatePreference.WantChangesCausedByComposition()) {
    return;
  }

  MOZ_ASSERT(removedLength >= 0,
             "mPreCharacterDataChangeLength should've been set by "
             "CharacterDataWillChange()");

  uint32_t offset = 0;
  // get offsets of change and fire notification
  nsresult rv =
    ContentEventHandler::GetFlatTextLengthInRange(
                           NodePosition(mRootContent, 0),
                           NodePosition(aContent, aInfo->mChangeStart),
                           mRootContent, &offset, LINE_BREAK_TYPE_NATIVE);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  uint32_t newLength =
    ContentEventHandler::GetNativeTextLength(aContent, aInfo->mChangeStart,
                                             aInfo->mChangeStart +
                                               aInfo->mReplaceLength);

  uint32_t oldEnd = offset + static_cast<uint32_t>(removedLength);
  uint32_t newEnd = offset + newLength;

  TextChangeData data(offset, oldEnd, newEnd, causedByComposition,
                      IsEditorComposing());
  MaybeNotifyIMEOfTextChange(data);
}

void
IMEContentObserver::NotifyContentAdded(nsINode* aContainer,
                                       int32_t aStartIndex,
                                       int32_t aEndIndex)
{
  mStartOfRemovingTextRangeCache.Clear();

  bool causedByComposition = IsEditorHandlingEventForComposition();
  if (!mTextChangeData.IsValid() && causedByComposition &&
      !mUpdatePreference.WantChangesCausedByComposition()) {
    return;
  }

  uint32_t offset = 0;
  nsresult rv = NS_OK;
  if (!mEndOfAddedTextCache.Match(aContainer, aStartIndex)) {
    mEndOfAddedTextCache.Clear();
    rv = ContentEventHandler::GetFlatTextLengthInRange(
                                NodePosition(mRootContent, 0),
                                NodePositionBefore(aContainer, aStartIndex),
                                mRootContent, &offset, LINE_BREAK_TYPE_NATIVE);
    if (NS_WARN_IF(NS_FAILED((rv)))) {
      return;
    }
  } else {
    offset = mEndOfAddedTextCache.mFlatTextLength;
  }

  // get offset at the end of the last added node
  uint32_t addingLength = 0;
  rv = ContentEventHandler::GetFlatTextLengthInRange(
                              NodePositionBefore(aContainer, aStartIndex),
                              NodePosition(aContainer, aEndIndex),
                              mRootContent, &addingLength,
                              LINE_BREAK_TYPE_NATIVE);
  if (NS_WARN_IF(NS_FAILED((rv)))) {
    mEndOfAddedTextCache.Clear();
    return;
  }

  // If multiple lines are being inserted in an HTML editor, next call of
  // NotifyContentAdded() is for adding next node.  Therefore, caching the text
  // length can skip to compute the text length before the adding node and
  // before of it.
  mEndOfAddedTextCache.Cache(aContainer, aEndIndex, offset + addingLength);

  if (!addingLength) {
    return;
  }

  TextChangeData data(offset, offset, offset + addingLength,
                      causedByComposition, IsEditorComposing());
  MaybeNotifyIMEOfTextChange(data);
}

void
IMEContentObserver::ContentAppended(nsIDocument* aDocument,
                                    nsIContent* aContainer,
                                    nsIContent* aFirstNewContent,
                                    int32_t aNewIndexInContainer)
{
  NotifyContentAdded(aContainer, aNewIndexInContainer,
                     aContainer->GetChildCount());
}

void
IMEContentObserver::ContentInserted(nsIDocument* aDocument,
                                    nsIContent* aContainer,
                                    nsIContent* aChild,
                                    int32_t aIndexInContainer)
{
  NotifyContentAdded(NODE_FROM(aContainer, aDocument),
                     aIndexInContainer, aIndexInContainer + 1);
}

void
IMEContentObserver::ContentRemoved(nsIDocument* aDocument,
                                   nsIContent* aContainer,
                                   nsIContent* aChild,
                                   int32_t aIndexInContainer,
                                   nsIContent* aPreviousSibling)
{
  mEndOfAddedTextCache.Clear();

  bool causedByComposition = IsEditorHandlingEventForComposition();
  if (!mTextChangeData.IsValid() && causedByComposition &&
      !mUpdatePreference.WantChangesCausedByComposition()) {
    return;
  }

  nsINode* containerNode = NODE_FROM(aContainer, aDocument);

  uint32_t offset = 0;
  nsresult rv = NS_OK;
  if (!mStartOfRemovingTextRangeCache.Match(containerNode, aIndexInContainer)) {
    // At removing a child node of aContainer, we need the line break caused
    // by open tag of aContainer.  Be careful when aIndexInContainer is 0.
    rv = ContentEventHandler::GetFlatTextLengthInRange(
                                NodePosition(mRootContent, 0),
                                NodePosition(containerNode, aIndexInContainer),
                                mRootContent, &offset, LINE_BREAK_TYPE_NATIVE);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      mStartOfRemovingTextRangeCache.Clear();
      return;
    }
    mStartOfRemovingTextRangeCache.Cache(containerNode, aIndexInContainer,
                                         offset);
  } else {
    offset = mStartOfRemovingTextRangeCache.mFlatTextLength;
  }

  // get offset at the end of the deleted node
  uint32_t textLength = 0;
  if (aChild->IsNodeOfType(nsINode::eTEXT)) {
    textLength = ContentEventHandler::GetNativeTextLength(aChild);
  } else {
    uint32_t nodeLength = static_cast<int32_t>(aChild->GetChildCount());
    rv = ContentEventHandler::GetFlatTextLengthInRange(
                                NodePositionBefore(aChild, 0),
                                NodePosition(aChild, nodeLength),
                                mRootContent, &textLength,
                                LINE_BREAK_TYPE_NATIVE, true);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      mStartOfRemovingTextRangeCache.Clear();
      return;
    }
  }

  if (!textLength) {
    return;
  }

  TextChangeData data(offset, offset + textLength, offset,
                      causedByComposition, IsEditorComposing());
  MaybeNotifyIMEOfTextChange(data);
}

void
IMEContentObserver::AttributeWillChange(nsIDocument* aDocument,
                                        dom::Element* aElement,
                                        int32_t aNameSpaceID,
                                        nsIAtom* aAttribute,
                                        int32_t aModType,
                                        const nsAttrValue* aNewValue)
{
  mPreAttrChangeLength =
    ContentEventHandler::GetNativeTextLengthBefore(aElement, mRootContent);
}

void
IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
                                     dom::Element* aElement,
                                     int32_t aNameSpaceID,
                                     nsIAtom* aAttribute,
                                     int32_t aModType,
                                     const nsAttrValue* aOldValue)
{
  mEndOfAddedTextCache.Clear();
  mStartOfRemovingTextRangeCache.Clear();

  bool causedByComposition = IsEditorHandlingEventForComposition();
  if (!mTextChangeData.IsValid() && causedByComposition &&
      !mUpdatePreference.WantChangesCausedByComposition()) {
    return;
  }

  uint32_t postAttrChangeLength =
    ContentEventHandler::GetNativeTextLengthBefore(aElement, mRootContent);
  if (postAttrChangeLength == mPreAttrChangeLength) {
    return;
  }
  uint32_t start;
  nsresult rv =
    ContentEventHandler::GetFlatTextLengthInRange(
                           NodePosition(mRootContent, 0),
                           NodePositionBefore(aElement, 0),
                           mRootContent, &start, LINE_BREAK_TYPE_NATIVE);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  TextChangeData data(start, start + mPreAttrChangeLength,
                      start + postAttrChangeLength, causedByComposition,
                      IsEditorComposing());
  MaybeNotifyIMEOfTextChange(data);
}

void
IMEContentObserver::SuppressNotifyingIME()
{
  mSuppressNotifications++;

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::SuppressNotifyingIME(), "
     "mSuppressNotifications=%u", this, mSuppressNotifications));
}

void
IMEContentObserver::UnsuppressNotifyingIME()
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::UnsuppressNotifyingIME(), "
     "mSuppressNotifications=%u", this, mSuppressNotifications));

  if (!mSuppressNotifications || --mSuppressNotifications) {
    return;
  }
  FlushMergeableNotifications();
}

NS_IMETHODIMP
IMEContentObserver::EditAction()
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::EditAction()", this));

  mEndOfAddedTextCache.Clear();
  mStartOfRemovingTextRangeCache.Clear();
  FlushMergeableNotifications();
  return NS_OK;
}

NS_IMETHODIMP
IMEContentObserver::BeforeEditAction()
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::BeforeEditAction()", this));

  mEndOfAddedTextCache.Clear();
  mStartOfRemovingTextRangeCache.Clear();
  return NS_OK;
}

NS_IMETHODIMP
IMEContentObserver::CancelEditAction()
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::CancelEditAction()", this));

  mEndOfAddedTextCache.Clear();
  mStartOfRemovingTextRangeCache.Clear();
  FlushMergeableNotifications();
  return NS_OK;
}

void
IMEContentObserver::PostFocusSetNotification()
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::PostFocusSetNotification()", this));

  mNeedsToNotifyIMEOfFocusSet = true;
}

void
IMEContentObserver::PostTextChangeNotification()
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::PostTextChangeNotification("
     "mTextChangeData=%s)",
     this, TextChangeDataToString(mTextChangeData).get()));

  MOZ_ASSERT(mTextChangeData.IsValid(),
             "mTextChangeData must have text change data");
  mNeedsToNotifyIMEOfTextChange = true;
}

void
IMEContentObserver::PostSelectionChangeNotification()
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::PostSelectionChangeNotification(), "
     "mSelectionData={ mCausedByComposition=%s, mCausedBySelectionEvent=%s }",
     this, ToChar(mSelectionData.mCausedByComposition),
     ToChar(mSelectionData.mCausedBySelectionEvent)));

  mNeedsToNotifyIMEOfSelectionChange = true;
}

void
IMEContentObserver::MaybeNotifyIMEOfFocusSet()
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfFocusSet()", this));

  PostFocusSetNotification();
  FlushMergeableNotifications();
}

void
IMEContentObserver::MaybeNotifyIMEOfTextChange(
                      const TextChangeDataBase& aTextChangeData)
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfTextChange("
     "aTextChangeData=%s)",
     this, TextChangeDataToString(aTextChangeData).get()));

  mTextChangeData += aTextChangeData;
  PostTextChangeNotification();
  FlushMergeableNotifications();
}

void
IMEContentObserver::MaybeNotifyIMEOfSelectionChange(
                      bool aCausedByComposition,
                      bool aCausedBySelectionEvent,
                      bool aOccurredDuringComposition)
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfSelectionChange("
     "aCausedByComposition=%s, aCausedBySelectionEvent=%s, "
     "aOccurredDuringComposition)",
     this, ToChar(aCausedByComposition), ToChar(aCausedBySelectionEvent)));

  mSelectionData.AssignReason(aCausedByComposition,
                              aCausedBySelectionEvent,
                              aOccurredDuringComposition);
  PostSelectionChangeNotification();
  FlushMergeableNotifications();
}

void
IMEContentObserver::MaybeNotifyIMEOfPositionChange()
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfPositionChange()", this));
  // If reflow is caused by ContentEventHandler during PositionChangeEvent
  // sending NOTIFY_IME_OF_POSITION_CHANGE, we don't need to notify IME of it
  // again since ContentEventHandler returns the result including this reflow's
  // result.
  if (mIsHandlingQueryContentEvent &&
      mSendingNotification == NOTIFY_IME_OF_POSITION_CHANGE) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p   IMEContentObserver::MaybeNotifyIMEOfPositionChange(), "
       "ignored since caused by ContentEventHandler during sending "
       "NOTIY_IME_OF_POSITION_CHANGE", this));
    return;
  }
  PostPositionChangeNotification();
  FlushMergeableNotifications();
}

bool
IMEContentObserver::UpdateSelectionCache()
{
  MOZ_ASSERT(IsSafeToNotifyIME());

  if (!mUpdatePreference.WantSelectionChange()) {
    return false;
  }

  mSelectionData.ClearSelectionData();

  // XXX Cannot we cache some information for reducing the cost to compute
  //     selection offset and writing mode?
  WidgetQueryContentEvent selection(true, eQuerySelectedText, mWidget);
  ContentEventHandler handler(GetPresContext());
  handler.OnQuerySelectedText(&selection);
  if (NS_WARN_IF(!selection.mSucceeded)) {
    return false;
  }

  mFocusedWidget = selection.mReply.mFocusedWidget;
  mSelectionData.mOffset = selection.mReply.mOffset;
  *mSelectionData.mString = selection.mReply.mString;
  mSelectionData.SetWritingMode(selection.GetWritingMode());
  mSelectionData.mReversed = selection.mReply.mReversed;

  // WARNING: Don't modify the reason of selection change here.

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::UpdateSelectionCache(), "
     "mSelectionData=%s",
     this, SelectionChangeDataToString(mSelectionData).get()));

  return mSelectionData.IsValid();
}

void
IMEContentObserver::PostPositionChangeNotification()
{
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::PostPositionChangeNotification()", this));

  mNeedsToNotifyIMEOfPositionChange = true;
}

bool
IMEContentObserver::IsReflowLocked() const
{
  nsPresContext* presContext = GetPresContext();
  if (NS_WARN_IF(!presContext)) {
    return false;
  }
  nsIPresShell* presShell = presContext->GetPresShell();
  if (NS_WARN_IF(!presShell)) {
    return false;
  }
  // During reflow, we shouldn't notify IME because IME may query content
  // synchronously.  Then, it causes ContentEventHandler will try to flush
  // pending notifications during reflow.
  return presShell->IsReflowLocked();
}

bool
IMEContentObserver::IsSafeToNotifyIME() const
{
  // If this is already detached from the widget, this doesn't need to notify
  // anything.
  if (!mWidget) {
    return false;
  }

  // Don't notify IME of anything if it's not good time to do it.
  if (mSuppressNotifications) {
    return false;
  }

  if (!mESM || NS_WARN_IF(!GetPresContext())) {
    return false;
  }

  // If it's in reflow, we should wait to finish the reflow.
  // FYI: This should be called again from Reflow() or ReflowInterruptible().
  if (IsReflowLocked()) {
    return false;
  }

  // If we're in handling an edit action, this method will be called later.
  bool isInEditAction = false;
  if (mEditor && NS_SUCCEEDED(mEditor->GetIsInEditAction(&isInEditAction)) &&
      isInEditAction) {
    return false;
  }

  return true;
}

void
IMEContentObserver::FlushMergeableNotifications()
{
  if (!IsSafeToNotifyIME()) {
    // So, if this is already called, this should do nothing.
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
       "FAILED, due to unsafe to notify IME", this));
    return;
  }

  // Notifying something may cause nested call of this method.  For example,
  // when somebody notified one of the notifications may dispatch query content
  // event. Then, it causes flushing layout which may cause another layout
  // change notification.

  if (mQueuedSender) {
    // So, if this is already called, this should do nothing.
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p   IMEContentObserver::FlushMergeableNotifications(), "
       "FAILED, due to already flushing pending notifications", this));
    return;
  }

  if (!NeedsToNotifyIMEOfSomething()) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p   IMEContentObserver::FlushMergeableNotifications(), "
       "FAILED, due to no pending notifications", this));
    return;
  }

  // NOTE: Reset each pending flag because sending notification may cause
  //       another change.

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
     "creating IMENotificationSender...", this));

  // If contents in selection range is modified, the selection range still
  // has removed node from the tree.  In such case, nsContentIterator won't
  // work well.  Therefore, we shouldn't use AddScriptRunnder() here since
  // it may kick runnable event immediately after DOM tree is changed but
  // the selection range isn't modified yet.
  mQueuedSender = new IMENotificationSender(this);
  NS_DispatchToCurrentThread(mQueuedSender);

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
     "finished", this));
}

void
IMEContentObserver::TryToFlushPendingNotifications()
{
  if (!mQueuedSender || mSendingNotification != NOTIFY_IME_OF_NOTHING) {
    return;
  }

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::TryToFlushPendingNotifications(), "
     "performing queued IMENotificationSender forcibly", this));
  RefPtr<IMENotificationSender> queuedSender = mQueuedSender;
  queuedSender->Run();
}

/******************************************************************************
 * mozilla::IMEContentObserver::AChangeEvent
 ******************************************************************************/

bool
IMEContentObserver::AChangeEvent::CanNotifyIME(
                                    ChangeEventType aChangeEventType) const
{
  if (NS_WARN_IF(!mIMEContentObserver)) {
    return false;
  }
  State state = mIMEContentObserver->GetState();
  // If it's not initialized, we should do nothing.
  if (state == eState_NotObserving) {
    return false;
  }
  // If setting focus, just check the state.
  if (aChangeEventType == eChangeEventType_Focus) {
    return !NS_WARN_IF(mIMEContentObserver->mIMEHasFocus);
  }
  // If we've not notified IME of focus yet, we shouldn't notify anything.
  if (!mIMEContentObserver->mIMEHasFocus) {
    return false;
  }

  // If IME has focus, IMEContentObserver must hold the widget.
  MOZ_ASSERT(mIMEContentObserver->mWidget);

  return true;
}

bool
IMEContentObserver::AChangeEvent::IsSafeToNotifyIME(
                                    ChangeEventType aChangeEventType) const
{
  if (NS_WARN_IF(!nsContentUtils::IsSafeToRunScript())) {
    return false;
  }
  // While we're sending a notification, we shouldn't send another notification
  // recursively.
  if (mIMEContentObserver->mSendingNotification != NOTIFY_IME_OF_NOTHING) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p   IMEContentObserver::AChangeEvent::IsSafeToNotifyIME(), "
       "putting off sending notification due to detecting recursive call, "
       "mIMEContentObserver={ mSendingNotification=%s }",
       this, ToChar(mIMEContentObserver->mSendingNotification)));
    return false;
  }
  State state = mIMEContentObserver->GetState();
  if (aChangeEventType == eChangeEventType_Focus) {
    if (NS_WARN_IF(state != eState_Initializing && state != eState_Observing)) {
      return false;
    }
  } else if (state != eState_Observing) {
    return false;
  }
  return mIMEContentObserver->IsSafeToNotifyIME();
}

/******************************************************************************
 * mozilla::IMEContentObserver::IMENotificationSender
 ******************************************************************************/
 
NS_IMETHODIMP
IMEContentObserver::IMENotificationSender::Run()
{
  if (NS_WARN_IF(mIsRunning)) {
    MOZ_LOG(sIMECOLog, LogLevel::Error,
      ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::Run(), FAILED, "
       "called recursively", this));
    return NS_OK;
  }

  AutoRestore<bool> running(mIsRunning);
  mIsRunning = true;

  // This instance was already performed forcibly.
  if (mIMEContentObserver->mQueuedSender != this) {
    return NS_OK;
  }

  // NOTE: Reset each pending flag because sending notification may cause
  //       another change.

  if (mIMEContentObserver->mNeedsToNotifyIMEOfFocusSet) {
    mIMEContentObserver->mNeedsToNotifyIMEOfFocusSet = false;
    SendFocusSet();
    mIMEContentObserver->mQueuedSender = nullptr;
    // If it's not safe to notify IME of focus, SendFocusSet() sets
    // mNeedsToNotifyIMEOfFocusSet true again.  For guaranteeing to send the
    // focus notification later,  we should put a new sender into the queue but
    // this case must be rare.  Note that if mIMEContentObserver is already
    // destroyed, mNeedsToNotifyIMEOfFocusSet is never set true again.
    if (mIMEContentObserver->mNeedsToNotifyIMEOfFocusSet) {
      MOZ_ASSERT(!mIMEContentObserver->mIMEHasFocus);
      MOZ_LOG(sIMECOLog, LogLevel::Debug,
        ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::Run(), "
         "posting IMENotificationSender to current thread", this));
      mIMEContentObserver->mQueuedSender =
        new IMENotificationSender(mIMEContentObserver);
      NS_DispatchToCurrentThread(mIMEContentObserver->mQueuedSender);
      return NS_OK;
    }
    // This is the first notification to IME. So, we don't need to notify
    // anymore since IME starts to query content after it gets focus.
    mIMEContentObserver->ClearPendingNotifications();
    return NS_OK;
  }

  if (mIMEContentObserver->mNeedsToNotifyIMEOfTextChange) {
    mIMEContentObserver->mNeedsToNotifyIMEOfTextChange = false;
    SendTextChange();
  }

  // If a text change notification causes another text change again, we should
  // notify IME of that before sending a selection change notification.
  if (!mIMEContentObserver->mNeedsToNotifyIMEOfTextChange) {
    // Be aware, PuppetWidget depends on the order of this. A selection change
    // notification should not be sent before a text change notification because
    // PuppetWidget shouldn't query new text content every selection change.
    if (mIMEContentObserver->mNeedsToNotifyIMEOfSelectionChange) {
      mIMEContentObserver->mNeedsToNotifyIMEOfSelectionChange = false;
      SendSelectionChange();
    }
  }

  // If a text change notification causes another text change again or a
  // selection change notification causes either a text change or another
  // selection change, we should notify IME of those before sending a position
  // change notification.
  if (!mIMEContentObserver->mNeedsToNotifyIMEOfTextChange &&
      !mIMEContentObserver->mNeedsToNotifyIMEOfSelectionChange) {
    if (mIMEContentObserver->mNeedsToNotifyIMEOfPositionChange) {
      mIMEContentObserver->mNeedsToNotifyIMEOfPositionChange = false;
      SendPositionChange();
    }
  }

  mIMEContentObserver->mQueuedSender = nullptr;

  // If notifications caused some new change, we should notify them now.
  if (mIMEContentObserver->NeedsToNotifyIMEOfSomething()) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::Run(), "
       "posting IMENotificationSender to current thread", this));
    mIMEContentObserver->mQueuedSender =
      new IMENotificationSender(mIMEContentObserver);
    NS_DispatchToCurrentThread(mIMEContentObserver->mQueuedSender);
  }
  return NS_OK;
}

void
IMEContentObserver::IMENotificationSender::SendFocusSet()
{
  if (!CanNotifyIME(eChangeEventType_Focus)) {
    // If IMEContentObserver has already gone, we don't need to notify IME of
    // focus.
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
       "SendFocusSet(), FAILED, due to impossible to notify IME of focus",
       this));
    mIMEContentObserver->ClearPendingNotifications();
    return;
  }

  if (!IsSafeToNotifyIME(eChangeEventType_Focus)) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p   IMEContentObserver::IMENotificationSender::"
       "SendFocusSet(), retrying to send NOTIFY_IME_OF_FOCUS...", this));
    mIMEContentObserver->PostFocusSetNotification();
    return;
  }

  mIMEContentObserver->mIMEHasFocus = true;
  // Initialize selection cache with the first selection data.
  mIMEContentObserver->UpdateSelectionCache();

  MOZ_LOG(sIMECOLog, LogLevel::Info,
    ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
     "SendFocusSet(), sending NOTIFY_IME_OF_FOCUS...", this));

  MOZ_RELEASE_ASSERT(mIMEContentObserver->mSendingNotification ==
                       NOTIFY_IME_OF_NOTHING);
  mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_FOCUS;
  IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS),
                             mIMEContentObserver->mWidget);
  mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING;

  // nsIMEUpdatePreference referred by ObserveEditableNode() may be different
  // before or after widget receives NOTIFY_IME_OF_FOCUS.  Therefore, we need
  // to guarantee to call ObserveEditableNode() after sending
  // NOTIFY_IME_OF_FOCUS.
  mIMEContentObserver->OnIMEReceivedFocus();

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
     "SendFocusSet(), sent NOTIFY_IME_OF_FOCUS", this));
}

void
IMEContentObserver::IMENotificationSender::SendSelectionChange()
{
  if (!CanNotifyIME(eChangeEventType_Selection)) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
       "SendSelectionChange(), FAILED, due to impossible to notify IME of "
       "selection change", this));
    return;
  }

  if (!IsSafeToNotifyIME(eChangeEventType_Selection)) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p   IMEContentObserver::IMENotificationSender::"
       "SendSelectionChange(), retrying to send "
       "NOTIFY_IME_OF_SELECTION_CHANGE...", this));
    mIMEContentObserver->PostSelectionChangeNotification();
    return;
  }

  SelectionChangeData lastSelChangeData = mIMEContentObserver->mSelectionData;
  if (NS_WARN_IF(!mIMEContentObserver->UpdateSelectionCache())) {
    MOZ_LOG(sIMECOLog, LogLevel::Error,
      ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
       "SendSelectionChange(), FAILED, due to UpdateSelectionCache() failure",
       this));
    return;
  }

  // If the IME doesn't want selection change notifications caused by
  // composition, we should do nothing anymore.
  SelectionChangeData& newSelChangeData = mIMEContentObserver->mSelectionData;
  if (newSelChangeData.mCausedByComposition &&
      !mIMEContentObserver->
        mUpdatePreference.WantChangesCausedByComposition()) {
    return;
  }

  // The state may be changed since querying content causes flushing layout.
  if (!CanNotifyIME(eChangeEventType_Selection)) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
       "SendSelectionChange(), FAILED, due to flushing layout having changed "
       "something", this));
    return;
  }

  // If the selection isn't changed actually, we shouldn't notify IME of
  // selection change.
  if (lastSelChangeData.IsValid() &&
      lastSelChangeData.mOffset == newSelChangeData.mOffset &&
      lastSelChangeData.String() == newSelChangeData.String() &&
      lastSelChangeData.GetWritingMode() == newSelChangeData.GetWritingMode() &&
      lastSelChangeData.mReversed == newSelChangeData.mReversed) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
       "SendSelectionChange(), not notifying IME of "
       "NOTIFY_IME_OF_SELECTION_CHANGE due to not changed actually", this));
    return;
  }

  MOZ_LOG(sIMECOLog, LogLevel::Info,
    ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
     "SendSelectionChange(), sending NOTIFY_IME_OF_SELECTION_CHANGE... "
     "newSelChangeData=%s",
     this, SelectionChangeDataToString(newSelChangeData).get()));

  IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
  notification.SetData(mIMEContentObserver->mSelectionData);

  MOZ_RELEASE_ASSERT(mIMEContentObserver->mSendingNotification ==
                       NOTIFY_IME_OF_NOTHING);
  mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_SELECTION_CHANGE;
  IMEStateManager::NotifyIME(notification, mIMEContentObserver->mWidget);
  mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING;

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
     "SendSelectionChange(), sent NOTIFY_IME_OF_SELECTION_CHANGE", this));
}

void
IMEContentObserver::IMENotificationSender::SendTextChange()
{
  if (!CanNotifyIME(eChangeEventType_Text)) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
       "SendTextChange(), FAILED, due to impossible to notify IME of text "
       "change", this));
    return;
  }

  if (!IsSafeToNotifyIME(eChangeEventType_Text)) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p   IMEContentObserver::IMENotificationSender::"
       "SendTextChange(), retrying to send NOTIFY_IME_OF_TEXT_CHANGE...",
       this));
    mIMEContentObserver->PostTextChangeNotification();
    return;
  }

  MOZ_LOG(sIMECOLog, LogLevel::Info,
    ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
     "SendTextChange(), sending NOTIFY_IME_OF_TEXT_CHANGE... "
     "mIMEContentObserver={ mTextChangeData=%s }",
     this, TextChangeDataToString(mIMEContentObserver->mTextChangeData).get()));

  IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
  notification.SetData(mIMEContentObserver->mTextChangeData);
  mIMEContentObserver->mTextChangeData.Clear();

  MOZ_RELEASE_ASSERT(mIMEContentObserver->mSendingNotification ==
                       NOTIFY_IME_OF_NOTHING);
  mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_TEXT_CHANGE;
  IMEStateManager::NotifyIME(notification, mIMEContentObserver->mWidget);
  mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING;

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
     "SendTextChange(), sent NOTIFY_IME_OF_TEXT_CHANGE", this));
}

void
IMEContentObserver::IMENotificationSender::SendPositionChange()
{
  if (!CanNotifyIME(eChangeEventType_Position)) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
       "SendPositionChange(), FAILED, due to impossible to notify IME of "
       "position change", this));
    return;
  }

  if (!IsSafeToNotifyIME(eChangeEventType_Position)) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
      ("IMECO: 0x%p   IMEContentObserver::IMENotificationSender::"
       "SendPositionChange(), retrying to send "
       "NOTIFY_IME_OF_POSITION_CHANGE...", this));
    mIMEContentObserver->PostPositionChangeNotification();
    return;
  }

  MOZ_LOG(sIMECOLog, LogLevel::Info,
    ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
     "SendPositionChange(), sending NOTIFY_IME_OF_POSITION_CHANGE...", this));

  MOZ_RELEASE_ASSERT(mIMEContentObserver->mSendingNotification ==
                       NOTIFY_IME_OF_NOTHING);
  mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_POSITION_CHANGE;
  IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE),
                             mIMEContentObserver->mWidget);
  mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING;

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
    ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
     "SendPositionChange(), sent NOTIFY_IME_OF_POSITION_CHANGE", this));
}

} // namespace mozilla