/* -*- 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/PresShell.h"
#include "mozilla/TextComposition.h"
#include "mozilla/TextEvents.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Selection.h"
#include "nsContentUtils.h"
#include "nsGkAtoms.h"
#include "nsAtom.h"
#include "nsDocShell.h"
#include "nsIContent.h"
#include "mozilla/dom/Document.h"
#include "nsIFrame.h"
#include "nsINode.h"
#include "nsISelectionController.h"
#include "nsISupports.h"
#include "nsIWeakReferenceUtils.h"
#include "nsIWidget.h"
#include "nsPresContext.h"
#include "nsRange.h"
#include "nsRefreshDriver.h"
#include "WritingModes.h"

namespace mozilla {

using NodePosition = ContentEventHandler::NodePosition;
using NodePositionBefore = ContentEventHandler::NodePositionBefore;

using namespace dom;
using namespace widget;

LazyLogModule sIMECOLog("IMEContentObserver");

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

// This method determines the node to use for the point before the current node.
// If you have the following aContent and aContainer, and want to represent the
// following point for `NodePosition` or `RangeBoundary`:
//
// <parent> {node} {node} | {node} </parent>
//  ^                     ^     ^
// aContainer           point  aContent
//
// This function will shift `aContent` to the left into the format which
// `NodePosition` and `RangeBoundary` use:
//
// <parent> {node} {node} | {node} </parent>
//  ^               ^     ^
// aContainer    result  point
static nsIContent* PointBefore(nsINode* aContainer, nsIContent* aContent) {
  if (aContent) {
    return aContent->GetPreviousSibling();
  }
  return aContainer->GetLastChild();
}

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

NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver)

// Note that we don't need to add mFirstAddedContainer nor
// mLastAddedContainer to cycle collection because they are non-null only
// during short time and shouldn't be touched while they are non-null.

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver)
  nsAutoScriptBlocker scriptBlocker;

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

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelection)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootElement)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditableNode)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorBase)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentObserver)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndOfAddedTextCache.mContainerNode)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartOfRemovingTextRangeCache.mContainerNode)
  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE

  tmp->mIMENotificationRequests = nullptr;
  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(mRootElement)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditableNode)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorBase)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentObserver)
  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(nsIMutationObserver)
  NS_INTERFACE_MAP_ENTRY(nsIReflowObserver)
  NS_INTERFACE_MAP_ENTRY(nsIScrollObserver)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIReflowObserver)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver)

IMEContentObserver::IMEContentObserver()
    : mESM(nullptr),
      mIMENotificationRequests(nullptr),
      mSendingNotification(NOTIFY_IME_OF_NOTHING),
      mIsObserving(false),
      mIMEHasFocus(false),
      mNeedsToNotifyIMEOfFocusSet(false),
      mNeedsToNotifyIMEOfTextChange(false),
      mNeedsToNotifyIMEOfSelectionChange(false),
      mNeedsToNotifyIMEOfPositionChange(false),
      mNeedsToNotifyIMEOfCompositionEventHandled(false),
      mIsHandlingQueryContentEvent(false) {
#ifdef DEBUG
  mTextChangeData.Test();
#endif
}

void IMEContentObserver::Init(nsIWidget& aWidget, nsPresContext& aPresContext,
                              Element* aElement, EditorBase& aEditorBase) {
  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();
  }

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

  mWidget = &aWidget;
  mIMENotificationRequests = &mWidget->IMENotificationRequestsRef();

  if (!InitWithEditor(aPresContext, aElement, aEditorBase)) {
    MOZ_LOG(sIMECOLog, LogLevel::Error,
            ("0x%p   Init() FAILED, due to InitWithEditor() "
             "failure",
             this));
    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) {
    MOZ_LOG(sIMECOLog, LogLevel::Warning,
            ("0x%p   OnIMEReceivedFocus(), "
             "but the state is not \"initializing\", so does nothing",
             this));
    return;
  }

  // NOTIFY_IME_OF_FOCUS might cause recreating IMEContentObserver
  // instance via IMEStateManager::UpdateIMEState().  So, this
  // instance might already have been destroyed, check it.
  if (!mRootElement) {
    MOZ_LOG(sIMECOLog, LogLevel::Warning,
            ("0x%p   OnIMEReceivedFocus(), "
             "but mRootElement has already been cleared, so does nothing",
             this));
    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,
                                        Element* aElement,
                                        EditorBase& aEditorBase) {
  mEditableNode = IMEStateManager::GetRootEditableNode(aPresContext, aElement);
  if (NS_WARN_IF(!mEditableNode)) {
    return false;
  }

  mEditorBase = &aEditorBase;

  RefPtr<PresShell> presShell = aPresContext.GetPresShell();

  // get selection and root content
  nsCOMPtr<nsISelectionController> selCon;
  if (mEditableNode->IsContent()) {
    nsIFrame* frame = mEditableNode->AsContent()->GetPrimaryFrame();
    if (NS_WARN_IF(!frame)) {
      return false;
    }

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

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

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

  if (mEditorBase->IsTextEditor()) {
    mRootElement = mEditorBase->GetRoot();  // The anonymous <div>
    MOZ_ASSERT(mRootElement);
    MOZ_ASSERT(mRootElement->GetFirstChild());
    if (auto* text = Text::FromNodeOrNull(
            mRootElement ? mRootElement->GetFirstChild() : nullptr)) {
      mTextControlValueLength = ContentEventHandler::GetNativeTextLength(*text);
    }
  } else if (const nsRange* selRange = mSelection->GetRangeAt(0)) {
    if (NS_WARN_IF(!selRange->GetStartContainer())) {
      return false;
    }

    nsCOMPtr<nsINode> startContainer = selRange->GetStartContainer();
    mRootElement = Element::FromNodeOrNull(
        startContainer->GetSelectionRootContent(presShell));
  } else {
    nsCOMPtr<nsINode> editableNode = mEditableNode;
    mRootElement = Element::FromNodeOrNull(
        editableNode->GetSelectionRootContent(presShell));
  }
  if (!mRootElement && mEditableNode->IsDocument()) {
    // The document node is editable, but there are no contents, this document
    // is not editable.
    return false;
  }

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

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

  mDocumentObserver = new DocumentObserver(*this);

  return true;
}

void IMEContentObserver::Clear() {
  mEditorBase = nullptr;
  mSelection = nullptr;
  mEditableNode = nullptr;
  mRootElement = nullptr;
  mDocShell = nullptr;
  // Should be safe to clear mDocumentObserver here even though it grabs
  // this instance in most cases because this is called by Init() or Destroy().
  // The callers of Init() grab this instance with local RefPtr.
  // The caller of Destroy() also grabs this instance with local RefPtr.
  // So, this won't cause refcount of this instance become 0.
  mDocumentObserver = nullptr;
}

void IMEContentObserver::ObserveEditableNode() {
  MOZ_RELEASE_ASSERT(mSelection);
  MOZ_RELEASE_ASSERT(mRootElement);
  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 notification requests 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 (mEditorBase) {
    mEditorBase->SetIMEContentObserver(this);
  }

  mRootElement->AddMutationObserver(this);
  // If it's in a document (should be so), we can use document observer to
  // reduce redundant computation of text change offsets.
  Document* doc = mRootElement->GetComposedDoc();
  if (doc) {
    RefPtr<DocumentObserver> documentObserver = mDocumentObserver;
    documentObserver->Observe(doc);
  }

  if (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);
  mIMENotificationRequests = nullptr;

  // 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,
          ("0x%p 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,
          ("0x%p   NotifyIMEOfBlur(), sent NOTIFY_IME_OF_BLUR", this));
}

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

  if (mEditorBase) {
    mEditorBase->SetIMEContentObserver(nullptr);
  }

  if (mSelection) {
    mSelectionData.Clear();
    mFocusedWidget = nullptr;
  }

  if (mRootElement) {
    mRootElement->RemoveMutationObserver(this);
  }

  if (mDocumentObserver) {
    RefPtr<DocumentObserver> documentObserver = mDocumentObserver;
    documentObserver->StopObserving();
  }

  if (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.

  // Note that don't send any notifications later from here.  I.e., notify
  // IMEStateManager of the blur synchronously because IMEStateManager needs to
  // stop notifying the main process if this is requested by the main process.
  NotifyIMEOfBlur();
  UnregisterObservers();
  Clear();

  mWidget = nullptr;
  mIMENotificationRequests = nullptr;

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

bool IMEContentObserver::Destroyed() const { return !mWidget; }

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

bool IMEContentObserver::MaybeReinitialize(nsIWidget& aWidget,
                                           nsPresContext& aPresContext,
                                           Element* aElement,
                                           EditorBase& aEditorBase) {
  if (!IsObservingContent(aPresContext, aElement)) {
    return false;
  }

  if (GetState() == eState_StoppedObserving) {
    Init(aWidget, aPresContext, aElement, aEditorBase);
  }
  return IsManaging(aPresContext, aElement);
}

bool IMEContentObserver::IsManaging(const nsPresContext& aPresContext,
                                    const Element* aElement) const {
  return GetState() == eState_Observing &&
         IsObservingContent(aPresContext, aElement);
}

bool IMEContentObserver::IsBeingInitializedFor(
    const nsPresContext& aPresContext, const Element* aElement) const {
  return GetState() == eState_Initializing &&
         IsObservingContent(aPresContext, aElement);
}

bool IMEContentObserver::IsManaging(
    const TextComposition& aTextComposition) const {
  if (GetState() != eState_Observing) {
    return false;
  }
  nsPresContext* const presContext = aTextComposition.GetPresContext();
  if (NS_WARN_IF(!presContext)) {
    return false;
  }
  if (presContext != GetPresContext()) {
    return false;  // observing different document
  }
  return IsObservingContent(
      *presContext,
      Element::FromNodeOrNull(aTextComposition.GetEventTargetNode()));
}

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

bool IMEContentObserver::IsObservingContent(const nsPresContext& aPresContext,
                                            const Element* aElement) const {
  return mEditableNode ==
         IMEStateManager::GetRootEditableNode(aPresContext, aElement);
}

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 EditorBase handles it.
  if (NS_WARN_IF(!mEditorBase)) {
    return false;
  }
  return mEditorBase->IsIMEComposing();
}

nsresult IMEContentObserver::GetSelectionAndRoot(Selection** aSelection,
                                                 Element** aRootElement) const {
  if (!mEditableNode || !mSelection) {
    return NS_ERROR_NOT_AVAILABLE;
  }

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

void IMEContentObserver::OnSelectionChange(Selection& aSelection) {
  if (!mIsObserving) {
    return;
  }

  if (mWidget) {
    bool causedByComposition = IsEditorHandlingEventForComposition();
    bool causedBySelectionEvent = TextComposition::IsHandlingSelectionEvent();
    bool duringComposition = IsEditorComposing();
    MaybeNotifyIMEOfSelectionChange(causedByComposition, causedBySelectionEvent,
                                    duringComposition);
  }
}

void IMEContentObserver::ScrollPositionChanged() {
  if (!NeedsPositionChangeNotification()) {
    return;
  }

  MaybeNotifyIMEOfPositionChange();
}

NS_IMETHODIMP
IMEContentObserver::Reflow(DOMHighResTimeStamp aStart,
                           DOMHighResTimeStamp aEnd) {
  if (!NeedsPositionChangeNotification()) {
    return NS_OK;
  }

  MaybeNotifyIMEOfPositionChange();
  return NS_OK;
}

NS_IMETHODIMP
IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart,
                                        DOMHighResTimeStamp aEnd) {
  if (!NeedsPositionChangeNotification()) {
    return NS_OK;
  }

  MaybeNotifyIMEOfPositionChange();
  return NS_OK;
}

nsresult IMEContentObserver::HandleQueryContentEvent(
    WidgetQueryContentEvent* aEvent) {
  // If the instance has normal selection cache and the query event queries
  // normal selection's range, 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.
  const bool isSelectionCacheAvailable = aEvent->mUseNativeLineBreak &&
                                         mSelectionData.IsInitialized() &&
                                         !mNeedsToNotifyIMEOfSelectionChange;
  if (isSelectionCacheAvailable && aEvent->mMessage == eQuerySelectedText &&
      aEvent->mInput.mSelectionType == SelectionType::eNormal) {
    aEvent->EmplaceReply();
    if (mSelectionData.HasRange()) {
      aEvent->mReply->mOffsetAndData.emplace(mSelectionData.mOffset,
                                             mSelectionData.String(),
                                             OffsetAndDataFor::SelectedString);
      aEvent->mReply->mReversed = mSelectionData.mReversed;
    }
    aEvent->mReply->mContentsRoot = mRootElement;
    aEvent->mReply->mWritingMode = mSelectionData.GetWritingMode();
    // The selection cache in IMEContentObserver must always have been in
    // an editing host (or an editable anonymous <div> element).  Therefore,
    // we set mIsEditableContent to true here even though it's already been
    // blurred or changed its editable state but the selection cache has not
    // been invalidated yet.
    aEvent->mReply->mIsEditableContent = true;
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
            ("0x%p HandleQueryContentEvent(aEvent={ "
             "mMessage=%s, mReply=%s })",
             this, ToChar(aEvent->mMessage), ToString(aEvent->mReply).c_str()));
    return NS_OK;
  }

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

  // If we can make the event's input offset absolute with TextComposition or
  // mSelection, we should set it here for reducing the cost of computing
  // selection start offset.  If ContentEventHandler receives a
  // WidgetQueryContentEvent whose input offset is relative to insertion point,
  // it computes current selection start offset (this may be expensive) and
  // make the offset absolute value itself.
  // Note that calling MakeOffsetAbsolute() makes the event a query event with
  // absolute offset.  So, ContentEventHandler doesn't pay any additional cost
  // after calling MakeOffsetAbsolute() here.
  if (aEvent->mInput.mRelativeToInsertionPoint &&
      aEvent->mInput.IsValidEventMessage(aEvent->mMessage)) {
    RefPtr<TextComposition> composition =
        IMEStateManager::GetTextCompositionFor(aEvent->mWidget);
    if (composition) {
      uint32_t compositionStart = composition->NativeOffsetOfStartComposition();
      if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(compositionStart))) {
        return NS_ERROR_FAILURE;
      }
    } else if (isSelectionCacheAvailable && mSelectionData.HasRange()) {
      const uint32_t selectionStart = mSelectionData.mOffset;
      if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(selectionStart))) {
        return NS_ERROR_FAILURE;
      }
    }
  }

  AutoRestore<bool> handling(mIsHandlingQueryContentEvent);
  mIsHandlingQueryContentEvent = true;
  ContentEventHandler handler(GetPresContext());
  nsresult rv = handler.HandleQueryContentEvent(aEvent);
  if (NS_WARN_IF(Destroyed())) {
    // If this has already destroyed during querying the content, the query
    // is outdated even if it's succeeded.  So, make the query fail.
    aEvent->mReply.reset();
    MOZ_LOG(sIMECOLog, LogLevel::Warning,
            ("0x%p   HandleQueryContentEvent(), WARNING, "
             "IMEContentObserver has been destroyed during the query, "
             "making the query fail",
             this));
    return rv;
  }

  if (aEvent->Succeeded() &&
      NS_WARN_IF(aEvent->mReply->mContentsRoot != mRootElement)) {
    // Focus has changed unexpectedly, so make the query fail.
    aEvent->mReply.reset();
  }
  return rv;
}

nsresult IMEContentObserver::MaybeHandleSelectionEvent(
    nsPresContext* aPresContext, WidgetSelectionEvent* aEvent) {
  MOZ_ASSERT(aEvent);
  MOZ_ASSERT(aEvent->mMessage == eSetSelection);
  NS_ASSERTION(!mNeedsToNotifyIMEOfSelectionChange,
               "Selection cache has not been updated yet");

  MOZ_LOG(
      sIMECOLog, LogLevel::Debug,
      ("0x%p MaybeHandleSelectionEvent(aEvent={ "
       "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
       "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
       "mSelectionData=%s",
       this, ToChar(aEvent->mMessage), aEvent->mOffset, aEvent->mLength,
       ToChar(aEvent->mReversed), ToChar(aEvent->mExpandToClusterBoundary),
       ToChar(aEvent->mUseNativeLineBreak), ToString(mSelectionData).c_str()));

  // When we have Selection cache, and the caller wants to set same selection
  // range, we shouldn't try to compute same range because it may be impossible
  // if the range boundary is around element boundaries which won't be
  // serialized with line breaks like close tags of inline elements.  In that
  // case, inserting new text at different point may be different from intention
  // of users or web apps which set current selection.
  // FIXME: We cache only selection data computed with native line breaker
  // lengths.  Perhaps, we should improve the struct to have both data of
  // offset and length.  E.g., adding line break counts for both offset and
  // length.
  if (!mNeedsToNotifyIMEOfSelectionChange && aEvent->mUseNativeLineBreak &&
      mSelectionData.IsInitialized() && mSelectionData.HasRange() &&
      mSelectionData.StartOffset() == aEvent->mOffset &&
      mSelectionData.Length() == aEvent->mLength) {
    if (RefPtr<Selection> selection = mSelection) {
      selection->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
                                ScrollAxis(), ScrollAxis(), 0);
    }
    aEvent->mSucceeded = true;
    return NS_OK;
  }

  ContentEventHandler handler(aPresContext);
  return handler.OnSelectionEvent(aEvent);
}

bool IMEContentObserver::OnMouseButtonEvent(nsPresContext& aPresContext,
                                            WidgetMouseEvent& aMouseEvent) {
  if (!mIMENotificationRequests ||
      !mIMENotificationRequests->WantMouseButtonEventOnChar()) {
    return false;
  }
  if (!aMouseEvent.IsTrusted() || aMouseEvent.DefaultPrevented() ||
      !aMouseEvent.mWidget) {
    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;
  }

  WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint,
                                                aMouseEvent.mWidget);
  queryCharAtPointEvent.mRefPoint = aMouseEvent.mRefPoint;
  ContentEventHandler handler(&aPresContext);
  handler.OnQueryCharacterAtPoint(&queryCharAtPointEvent);
  if (NS_WARN_IF(queryCharAtPointEvent.Failed()) ||
      queryCharAtPointEvent.DidNotFindChar()) {
    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) {
    queryCharAtPointEvent.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.mWidget != mWidget) {
    queryCharAtPointEvent.mRefPoint +=
        aMouseEvent.mWidget->WidgetToScreenOffset() -
        mWidget->WidgetToScreenOffset();
  }

  IMENotification notification(NOTIFY_IME_OF_MOUSE_BUTTON_EVENT);
  notification.mMouseButtonEventData.mEventMessage = aMouseEvent.mMessage;
  notification.mMouseButtonEventData.mOffset =
      queryCharAtPointEvent.mReply->StartOffset();
  notification.mMouseButtonEventData.mCursorPos =
      queryCharAtPointEvent.mRefPoint;
  notification.mMouseButtonEventData.mCharRect =
      queryCharAtPointEvent.mReply->mRect;
  notification.mMouseButtonEventData.mButton = aMouseEvent.mButton;
  notification.mMouseButtonEventData.mButtons = aMouseEvent.mButtons;
  notification.mMouseButtonEventData.mModifiers = aMouseEvent.mModifiers;

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

  bool consumed = (rv == NS_SUCCESS_EVENT_CONSUMED);
  if (consumed) {
    aMouseEvent.PreventDefault();
  }
  return consumed;
}

void IMEContentObserver::CharacterDataWillChange(
    nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
  if (!aContent->IsText()) {
    return;  // Ignore if it's a comment node or something other invisible data
             // node.
  }
  MOZ_ASSERT(mPreCharacterDataChangeLength < 0,
             "CharacterDataChanged() should've reset "
             "mPreCharacterDataChangeLength");

  if (!NeedsTextChangeNotification() ||
      !nsContentUtils::IsInSameAnonymousTree(mRootElement, aContent)) {
    return;
  }

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

  // Although we don't assume this change occurs while this is storing
  // the range of added consecutive nodes, if it actually happens, we need to
  // flush them since this change may occur before or in the range.  So, it's
  // safe to flush pending computation of mTextChangeData before handling this.
  MaybeNotifyIMEOfAddedTextDuringDocumentChange();

  mPreCharacterDataChangeLength = ContentEventHandler::GetNativeTextLength(
      *aContent->AsText(), 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(
    nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
  if (!aContent->IsText()) {
    return;  // Ignore if it's a comment node or something other invisible data
             // node.
  }

  // Let TextComposition have a change to update composition string range in
  // the text node if the change is caused by the web apps.
  if (mWidget && !IsEditorHandlingEventForComposition()) {
    if (RefPtr<TextComposition> composition =
            IMEStateManager::GetTextCompositionFor(mWidget)) {
      composition->OnCharacterDataChanged(*aContent->AsText(), aInfo);
    }
  }

  if (!NeedsTextChangeNotification() ||
      !nsContentUtils::IsInSameAnonymousTree(mRootElement, aContent)) {
    return;
  }

  mEndOfAddedTextCache.Clear();
  mStartOfRemovingTextRangeCache.Clear();
  MOZ_ASSERT(
      !HasAddedNodesDuringDocumentChange(),
      "The stored range should be flushed before actually the data is changed");

  int64_t removedLength = mPreCharacterDataChangeLength;
  mPreCharacterDataChangeLength = -1;

  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(mRootElement, 0u),
      NodePosition(aContent, aInfo.mChangeStart), mRootElement, &offset,
      LINE_BREAK_TYPE_NATIVE);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  uint32_t newLength = ContentEventHandler::GetNativeTextLength(
      *aContent->AsText(), 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,
                      IsEditorHandlingEventForComposition(),
                      IsEditorComposing());
  MaybeNotifyIMEOfTextChange(data);
}

void IMEContentObserver::NotifyContentAdded(nsINode* aContainer,
                                            nsIContent* aFirstContent,
                                            nsIContent* aLastContent) {
  if (!NeedsTextChangeNotification() ||
      !nsContentUtils::IsInSameAnonymousTree(mRootElement, aFirstContent)) {
    return;
  }

  MOZ_ASSERT_IF(aFirstContent, aFirstContent->GetParentNode() == aContainer);
  MOZ_ASSERT_IF(aLastContent, aLastContent->GetParentNode() == aContainer);

  mStartOfRemovingTextRangeCache.Clear();

  // If it's in a document change, nodes are added consecutively.  Therefore,
  // if we cache the first node and the last node, we need to compute the
  // range once.
  // FYI: This is not true if the change caused by an operation in the editor.
  if (IsInDocumentChange()) {
    // Now, mEndOfAddedTextCache may be invalid if node is added before
    // the last node in mEndOfAddedTextCache.  Clear it.
    mEndOfAddedTextCache.Clear();
    if (!HasAddedNodesDuringDocumentChange()) {
      mFirstAddedContainer = mLastAddedContainer = aContainer;
      mFirstAddedContent = aFirstContent;
      mLastAddedContent = aLastContent;
      MOZ_LOG(sIMECOLog, LogLevel::Debug,
              ("0x%p   NotifyContentAdded(), starts to store consecutive added "
               "nodes",
               this));
      return;
    }
    // If first node being added is not next node of the last node,
    // notify IME of the previous range first, then, restart to cache the
    // range.
    if (NS_WARN_IF(!IsNextNodeOfLastAddedNode(aContainer, aFirstContent))) {
      // Flush the old range first.
      MaybeNotifyIMEOfAddedTextDuringDocumentChange();
      mFirstAddedContainer = aContainer;
      mFirstAddedContent = aFirstContent;
      MOZ_LOG(sIMECOLog, LogLevel::Debug,
              ("0x%p   NotifyContentAdded(), starts to store consecutive added "
               "nodes",
               this));
    }
    mLastAddedContainer = aContainer;
    mLastAddedContent = aLastContent;
    return;
  }
  MOZ_ASSERT(!HasAddedNodesDuringDocumentChange(),
             "The cache should be cleared when document change finished");

  uint32_t offset = 0;
  nsresult rv = NS_OK;
  if (!mEndOfAddedTextCache.Match(aContainer,
                                  aFirstContent->GetPreviousSibling())) {
    mEndOfAddedTextCache.Clear();
    rv = ContentEventHandler::GetFlatTextLengthInRange(
        NodePosition(mRootElement, 0u),
        NodePositionBefore(aContainer, PointBefore(aContainer, aFirstContent)),
        mRootElement, &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, PointBefore(aContainer, aFirstContent)),
      NodePosition(aContainer, aLastContent), mRootElement, &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, aLastContent, offset + addingLength);

  if (!addingLength) {
    return;
  }

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

void IMEContentObserver::ContentAppended(nsIContent* aFirstNewContent) {
  nsIContent* parent = aFirstNewContent->GetParent();
  MOZ_ASSERT(parent);
  NotifyContentAdded(parent, aFirstNewContent, parent->GetLastChild());
}

void IMEContentObserver::ContentInserted(nsIContent* aChild) {
  MOZ_ASSERT(aChild);
  NotifyContentAdded(aChild->GetParentNode(), aChild, aChild);
}

void IMEContentObserver::ContentRemoved(nsIContent* aChild,
                                        nsIContent* aPreviousSibling) {
  if (!NeedsTextChangeNotification() ||
      !nsContentUtils::IsInSameAnonymousTree(mRootElement, aChild)) {
    return;
  }

  mEndOfAddedTextCache.Clear();
  MaybeNotifyIMEOfAddedTextDuringDocumentChange();

  nsINode* containerNode = aChild->GetParentNode();
  MOZ_ASSERT(containerNode);

  uint32_t offset = 0;
  nsresult rv = NS_OK;
  if (!mStartOfRemovingTextRangeCache.Match(containerNode, aPreviousSibling)) {
    // At removing a child node of aContainer, we need the line break caused
    // by open tag of aContainer.  Be careful when aPreviousSibling is nullptr.

    rv = ContentEventHandler::GetFlatTextLengthInRange(
        NodePosition(mRootElement, 0u),
        NodePosition(containerNode, aPreviousSibling), mRootElement, &offset,
        LINE_BREAK_TYPE_NATIVE);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      mStartOfRemovingTextRangeCache.Clear();
      return;
    }
    mStartOfRemovingTextRangeCache.Cache(containerNode, aPreviousSibling,
                                         offset);
  } else {
    offset = mStartOfRemovingTextRangeCache.mFlatTextLength;
  }

  // get offset at the end of the deleted node
  uint32_t textLength = 0;
  if (const Text* textNode = Text::FromNode(aChild)) {
    textLength = ContentEventHandler::GetNativeTextLength(*textNode);
  } else {
    nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
        NodePositionBefore(aChild, 0u),
        NodePosition(aChild, aChild->GetChildCount()), mRootElement,
        &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,
                      IsEditorHandlingEventForComposition(),
                      IsEditorComposing());
  MaybeNotifyIMEOfTextChange(data);
}

void IMEContentObserver::ClearAddedNodesDuringDocumentChange() {
  mFirstAddedContainer = mLastAddedContainer = nullptr;
  mFirstAddedContent = mLastAddedContent = nullptr;
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
          ("0x%p ClearAddedNodesDuringDocumentChange(), finished storing "
           "consecutive nodes",
           this));
}

bool IMEContentObserver::IsNextNodeOfLastAddedNode(nsINode* aParent,
                                                   nsIContent* aChild) const {
  MOZ_ASSERT(aParent);
  MOZ_ASSERT(aChild && aChild->GetParentNode() == aParent);
  MOZ_ASSERT(mRootElement);
  MOZ_ASSERT(HasAddedNodesDuringDocumentChange());

  // If the parent node isn't changed, we can check that mLastAddedContent has
  // aChild as its next sibling.
  if (aParent == mLastAddedContainer) {
    return !NS_WARN_IF(mLastAddedContent->GetNextSibling() != aChild);
  }

  // If the parent node is changed, that means that the recorded last added node
  // shouldn't have a sibling.
  if (NS_WARN_IF(mLastAddedContent->GetNextSibling())) {
    return false;
  }

  // If the node is aParent is a descendant of mLastAddedContainer,
  // aChild should be the first child in the new container.
  if (mLastAddedContainer == aParent->GetParent()) {
    return !NS_WARN_IF(aChild->GetPreviousSibling());
  }

  // Otherwise, we need to check it even with slow path.
  nsIContent* nextContentOfLastAddedContent =
      mLastAddedContent->GetNextNode(mRootElement->GetParentNode());
  if (NS_WARN_IF(!nextContentOfLastAddedContent)) {
    return false;
  }
  if (NS_WARN_IF(nextContentOfLastAddedContent != aChild)) {
    return false;
  }
  return true;
}

void IMEContentObserver::MaybeNotifyIMEOfAddedTextDuringDocumentChange() {
  if (!HasAddedNodesDuringDocumentChange()) {
    return;
  }

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
          ("0x%p "
           "IMEContentObserver::MaybeNotifyIMEOfAddedTextDuringDocumentChange()"
           ", flushing stored consecutive nodes",
           this));

  // Notify IME of text change which is caused by added nodes now.

  // First, compute offset of start of first added node from start of the
  // editor.
  uint32_t offset;
  nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
      NodePosition(mRootElement, 0u),
      NodePosition(mFirstAddedContainer,
                   PointBefore(mFirstAddedContainer, mFirstAddedContent)),
      mRootElement, &offset, LINE_BREAK_TYPE_NATIVE);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    ClearAddedNodesDuringDocumentChange();
    return;
  }

  // Next, compute the text length of added nodes.
  uint32_t length;
  rv = ContentEventHandler::GetFlatTextLengthInRange(
      NodePosition(mFirstAddedContainer,
                   PointBefore(mFirstAddedContainer, mFirstAddedContent)),
      NodePosition(mLastAddedContainer, mLastAddedContent), mRootElement,
      &length, LINE_BREAK_TYPE_NATIVE);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    ClearAddedNodesDuringDocumentChange();
    return;
  }

  // Finally, try to notify IME of the range.
  TextChangeData data(offset, offset, offset + length,
                      IsEditorHandlingEventForComposition(),
                      IsEditorComposing());
  MaybeNotifyIMEOfTextChange(data);
  ClearAddedNodesDuringDocumentChange();
}

void IMEContentObserver::OnTextControlValueChangedWhileNotObservable(
    const nsAString& aNewValue) {
  MOZ_ASSERT(mEditorBase);
  MOZ_ASSERT(mEditorBase->IsTextEditor());
  uint32_t newLength = ContentEventHandler::GetNativeTextLength(aNewValue);
  TextChangeData data(0, mTextControlValueLength, newLength, false, false);
  MaybeNotifyIMEOfTextChange(data);
}

void IMEContentObserver::BeginDocumentUpdate() {
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
          ("0x%p BeginDocumentUpdate(), HasAddedNodesDuringDocumentChange()=%s",
           this, ToChar(HasAddedNodesDuringDocumentChange())));

  // If we're not in a nested document update, this will return early,
  // otherwise, it will handle flusing any changes currently pending before
  // entering a nested document update.
  MaybeNotifyIMEOfAddedTextDuringDocumentChange();
}

void IMEContentObserver::EndDocumentUpdate() {
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
          ("0x%p EndDocumentUpdate(), HasAddedNodesDuringDocumentChange()=%s",
           this, ToChar(HasAddedNodesDuringDocumentChange())));

  MaybeNotifyIMEOfAddedTextDuringDocumentChange();
}

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

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

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

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

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

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

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

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

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

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

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

  mNeedsToNotifyIMEOfFocusSet = true;
}

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

  MOZ_ASSERT(mTextChangeData.IsValid(),
             "mTextChangeData must have text change data");
  mNeedsToNotifyIMEOfTextChange = true;
  // Even if the observer hasn't received selection change, selection in the
  // flat text may have already been changed.  For example, when previous `<p>`
  // element of another `<p>` element which contains caret is removed by a DOM
  // mutation, selection change event won't be fired, but selection start offset
  // should be decreased by the length of removed `<p>` element.
  // In such case, HandleQueryContentEvent shouldn't use the selection cache
  // anymore.  Therefore, we also need to post selection change notification
  // too.  eQuerySelectedText event may be dispatched at sending a text change
  // notification.
  mNeedsToNotifyIMEOfSelectionChange = true;
}

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

  mNeedsToNotifyIMEOfSelectionChange = true;
}

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

  PostFocusSetNotification();
  FlushMergeableNotifications();
}

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

  if (mEditorBase && mEditorBase->IsTextEditor()) {
    MOZ_DIAGNOSTIC_ASSERT(static_cast<int64_t>(mTextControlValueLength) +
                              aTextChangeData.Difference() >=
                          0);
    mTextControlValueLength += aTextChangeData.Difference();
  }

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

void IMEContentObserver::CancelNotifyingIMEOfTextChange() {
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
          ("0x%p CancelNotifyingIMEOfTextChange()", this));
  mTextChangeData.Clear();
  mNeedsToNotifyIMEOfTextChange = false;
}

void IMEContentObserver::MaybeNotifyIMEOfSelectionChange(
    bool aCausedByComposition, bool aCausedBySelectionEvent,
    bool aOccurredDuringComposition) {
  MOZ_LOG(
      sIMECOLog, LogLevel::Debug,
      ("0x%p 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::Verbose,
          ("0x%p 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::Verbose,
            ("0x%p   MaybeNotifyIMEOfPositionChange(), ignored since caused by "
             "ContentEventHandler during sending NOTIFY_IME_OF_POSITION_CHANGE",
             this));
    return;
  }
  PostPositionChangeNotification();
  FlushMergeableNotifications();
}

void IMEContentObserver::CancelNotifyingIMEOfPositionChange() {
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
          ("0x%p CancelNotifyIMEOfPositionChange()", this));
  mNeedsToNotifyIMEOfPositionChange = false;
}

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

  PostCompositionEventHandledNotification();
  FlushMergeableNotifications();
}

bool IMEContentObserver::UpdateSelectionCache(bool aRequireFlush /* = true */) {
  MOZ_ASSERT(IsSafeToNotifyIME());

  mSelectionData.ClearSelectionData();

  // XXX Cannot we cache some information for reducing the cost to compute
  //     selection offset and writing mode?
  WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
                                                 mWidget);
  querySelectedTextEvent.mNeedsToFlushLayout = aRequireFlush;
  ContentEventHandler handler(GetPresContext());
  handler.OnQuerySelectedText(&querySelectedTextEvent);
  if (NS_WARN_IF(querySelectedTextEvent.Failed()) ||
      NS_WARN_IF(querySelectedTextEvent.mReply->mContentsRoot !=
                 mRootElement)) {
    return false;
  }

  mFocusedWidget = querySelectedTextEvent.mReply->mFocusedWidget;
  mSelectionData.Assign(querySelectedTextEvent);

  // WARNING: Don't set the reason of selection change here because it should be
  //          set the reason at sending the notification.

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
          ("0x%p UpdateSelectionCache(), mSelectionData=%s", this,
           ToString(mSelectionData).c_str()));

  return true;
}

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

  mNeedsToNotifyIMEOfPositionChange = true;
}

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

  mNeedsToNotifyIMEOfCompositionEventHandled = true;
}

bool IMEContentObserver::IsReflowLocked() const {
  nsPresContext* presContext = GetPresContext();
  if (NS_WARN_IF(!presContext)) {
    return false;
  }
  PresShell* 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) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
            ("0x%p   IsSafeToNotifyIME(), it's not safe because of no widget",
             this));
    return false;
  }

  // Don't notify IME of anything if it's not good time to do it.
  if (mSuppressNotifications) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
            ("0x%p   IsSafeToNotifyIME(), it's not safe because of no widget",
             this));
    return false;
  }

  if (!mESM || NS_WARN_IF(!GetPresContext())) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
            ("0x%p   IsSafeToNotifyIME(), it's not safe because of no "
             "EventStateManager and/or PresContext",
             this));
    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()) {
    MOZ_LOG(
        sIMECOLog, LogLevel::Debug,
        ("0x%p   IsSafeToNotifyIME(), it's not safe because of reflow locked",
         this));
    return false;
  }

  // If we're in handling an edit action, this method will be called later.
  if (mEditorBase && mEditorBase->IsInEditSubAction()) {
    MOZ_LOG(sIMECOLog, LogLevel::Debug,
            ("0x%p   IsSafeToNotifyIME(), it's not safe because of focused "
             "editor handling somethings",
             this));
    return false;
  }

  return true;
}

void IMEContentObserver::FlushMergeableNotifications() {
  if (!IsSafeToNotifyIME()) {
    // So, if this is already called, this should do nothing.
    MOZ_LOG(sIMECOLog, LogLevel::Warning,
            ("0x%p   FlushMergeableNotifications(), Warning, do nothing 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::Warning,
            ("0x%p   FlushMergeableNotifications(), Warning, do nothing due to "
             "already flushing pending notifications",
             this));
    return;
  }

  // If text change notification and/or position change notification becomes
  // unnecessary, let's cancel them.
  if (mNeedsToNotifyIMEOfTextChange && !NeedsTextChangeNotification()) {
    CancelNotifyingIMEOfTextChange();
  }
  if (mNeedsToNotifyIMEOfPositionChange && !NeedsPositionChangeNotification()) {
    CancelNotifyingIMEOfPositionChange();
  }

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

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

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

  // If contents in selection range is modified, the selection range still
  // has removed node from the tree.  In such case, ContentIterator won't
  // work well.  Therefore, we shouldn't use AddScriptRunner() 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);
  mQueuedSender->Dispatch(mDocShell);
  MOZ_LOG(sIMECOLog, LogLevel::Debug,
          ("0x%p   FlushMergeableNotifications(), finished", this));
}

void IMEContentObserver::TryToFlushPendingNotifications(bool aAllowAsync) {
  // If a sender instance is sending notifications, we shouldn't try to create
  // a new sender again because the sender will recreate by itself if there are
  // new pending notifications.
  if (mSendingNotification != NOTIFY_IME_OF_NOTHING) {
    return;
  }

  // When the caller allows to put off notifying IME, we can wait the next
  // call of this method or to run the queued sender.
  if (mQueuedSender && XRE_IsContentProcess() && aAllowAsync) {
    return;
  }

  if (!mQueuedSender) {
    // If it was not safe to dispatch notifications when the pending
    // notifications are posted, this may not have IMENotificationSender
    // instance because it couldn't dispatch it, e.g., when an edit sub-action
    // is being handled in the editor, we shouldn't do it even if it's safe to
    // run script.  Therefore, we need to create the sender instance here in the
    // case.
    if (!NeedsToNotifyIMEOfSomething()) {
      return;
    }
    mQueuedSender = new IMENotificationSender(this);
  }

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

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

bool IMEContentObserver::AChangeEvent::CanNotifyIME(
    ChangeEventType aChangeEventType) const {
  RefPtr<IMEContentObserver> observer = GetObserver();
  if (NS_WARN_IF(!observer)) {
    return false;
  }

  const LogLevel debugOrVerbose =
      aChangeEventType == ChangeEventType::eChangeEventType_Position
          ? LogLevel::Verbose
          : LogLevel::Debug;

  if (aChangeEventType == eChangeEventType_CompositionEventHandled) {
    if (observer->mWidget) {
      return true;
    }
    MOZ_LOG(sIMECOLog, debugOrVerbose,
            ("0x%p   AChangeEvent::CanNotifyIME(), Cannot notify IME of "
             "composition event handled because of no widget",
             this));
    return false;
  }
  State state = observer->GetState();
  // If it's not initialized, we should do nothing.
  if (state == eState_NotObserving) {
    MOZ_LOG(sIMECOLog, debugOrVerbose,
            ("0x%p   AChangeEvent::CanNotifyIME(), Cannot notify IME because "
             "of not observing",
             this));
    return false;
  }
  // If setting focus, just check the state.
  if (aChangeEventType == eChangeEventType_Focus) {
    if (!observer->mIMEHasFocus) {
      return true;
    }
    MOZ_LOG(sIMECOLog, debugOrVerbose,
            ("0x%p   AChangeEvent::CanNotifyIME(), Cannot notify IME of focus "
             "change because of already focused",
             this));
    NS_WARNING("IME already has focus");
    return false;
  }
  // If we've not notified IME of focus yet, we shouldn't notify anything.
  if (!observer->mIMEHasFocus) {
    MOZ_LOG(sIMECOLog, debugOrVerbose,
            ("0x%p   AChangeEvent::CanNotifyIME(), Cannot notify IME because "
             "of not focused",
             this));
    return false;
  }

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

  return true;
}

bool IMEContentObserver::AChangeEvent::IsSafeToNotifyIME(
    ChangeEventType aChangeEventType) const {
  const LogLevel warningOrVerbose =
      aChangeEventType == ChangeEventType::eChangeEventType_Position
          ? LogLevel::Verbose
          : LogLevel::Warning;

  if (NS_WARN_IF(!nsContentUtils::IsSafeToRunScript())) {
    MOZ_LOG(sIMECOLog, warningOrVerbose,
            ("0x%p   AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
             "IME because of not safe to run script",
             this));
    return false;
  }

  RefPtr<IMEContentObserver> observer = GetObserver();
  if (!observer) {
    MOZ_LOG(sIMECOLog, warningOrVerbose,
            ("0x%p   AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
             "IME because of no observer",
             this));
    return false;
  }

  // While we're sending a notification, we shouldn't send another notification
  // recursively.
  if (observer->mSendingNotification != NOTIFY_IME_OF_NOTHING) {
    MOZ_LOG(sIMECOLog, warningOrVerbose,
            ("0x%p   AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
             "IME because of the observer sending another notification",
             this));
    return false;
  }
  State state = observer->GetState();
  if (aChangeEventType == eChangeEventType_Focus) {
    if (NS_WARN_IF(state != eState_Initializing && state != eState_Observing)) {
      MOZ_LOG(sIMECOLog, warningOrVerbose,
              ("0x%p   AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot "
               "notify IME of focus because of not observing",
               this));
      return false;
    }
  } else if (aChangeEventType == eChangeEventType_CompositionEventHandled) {
    // It doesn't need to check the observing status.
  } else if (state != eState_Observing) {
    MOZ_LOG(sIMECOLog, warningOrVerbose,
            ("0x%p   AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
             "IME because of not observing",
             this));
    return false;
  }
  return observer->IsSafeToNotifyIME();
}

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

void IMEContentObserver::IMENotificationSender::Dispatch(
    nsIDocShell* aDocShell) {
  if (XRE_IsContentProcess() && aDocShell) {
    RefPtr<nsPresContext> presContext = aDocShell->GetPresContext();
    if (presContext) {
      nsRefreshDriver* refreshDriver = presContext->RefreshDriver();
      if (refreshDriver) {
        refreshDriver->AddEarlyRunner(this);
        return;
      }
    }
  }

  nsIScriptGlobalObject* globalObject =
      aDocShell ? aDocShell->GetScriptGlobalObject() : nullptr;
  if (globalObject) {
    RefPtr<IMENotificationSender> queuedSender = this;
    globalObject->Dispatch(TaskCategory::Other, queuedSender.forget());
  } else {
    NS_DispatchToCurrentThread(this);
  }
}

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

  RefPtr<IMEContentObserver> observer = GetObserver();
  if (!observer) {
    return NS_OK;
  }

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

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

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

  if (observer->mNeedsToNotifyIMEOfFocusSet) {
    observer->mNeedsToNotifyIMEOfFocusSet = false;
    SendFocusSet();
    observer->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 (observer->mNeedsToNotifyIMEOfFocusSet) {
      MOZ_ASSERT(!observer->mIMEHasFocus);
      MOZ_LOG(sIMECOLog, LogLevel::Debug,
              ("0x%p IMENotificationSender::Run(), posting "
               "IMENotificationSender to current thread",
               this));
      observer->mQueuedSender = new IMENotificationSender(observer);
      observer->mQueuedSender->Dispatch(observer->mDocShell);
      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.
    observer->ClearPendingNotifications();
    return NS_OK;
  }

  if (observer->mNeedsToNotifyIMEOfTextChange) {
    observer->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 (!observer->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 (observer->mNeedsToNotifyIMEOfSelectionChange) {
      observer->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 (!observer->mNeedsToNotifyIMEOfTextChange &&
      !observer->mNeedsToNotifyIMEOfSelectionChange) {
    if (observer->mNeedsToNotifyIMEOfPositionChange) {
      observer->mNeedsToNotifyIMEOfPositionChange = false;
      SendPositionChange();
    }
  }

  // Composition event handled notification should be sent after all the
  // other notifications because this notifies widget of finishing all pending
  // events are handled completely.
  if (!observer->mNeedsToNotifyIMEOfTextChange &&
      !observer->mNeedsToNotifyIMEOfSelectionChange &&
      !observer->mNeedsToNotifyIMEOfPositionChange) {
    if (observer->mNeedsToNotifyIMEOfCompositionEventHandled) {
      observer->mNeedsToNotifyIMEOfCompositionEventHandled = false;
      SendCompositionEventHandled();
    }
  }

  observer->mQueuedSender = nullptr;

  // If notifications caused some new change, we should notify them now.
  if (observer->NeedsToNotifyIMEOfSomething()) {
    if (observer->GetState() == eState_StoppedObserving) {
      MOZ_LOG(sIMECOLog, LogLevel::Debug,
              ("0x%p IMENotificationSender::Run(), waiting "
               "IMENotificationSender to be reinitialized",
               this));
    } else {
      MOZ_LOG(sIMECOLog, LogLevel::Debug,
              ("0x%p IMENotificationSender::Run(), posting "
               "IMENotificationSender to current thread",
               this));
      observer->mQueuedSender = new IMENotificationSender(observer);
      observer->mQueuedSender->Dispatch(observer->mDocShell);
    }
  }
  return NS_OK;
}

void IMEContentObserver::IMENotificationSender::SendFocusSet() {
  RefPtr<IMEContentObserver> observer = GetObserver();
  if (!observer) {
    return;
  }

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

  if (!IsSafeToNotifyIME(eChangeEventType_Focus)) {
    MOZ_LOG(
        sIMECOLog, LogLevel::Warning,
        ("0x%p   IMENotificationSender::SendFocusSet(), Warning, does not send "
         "notification due to unsafe, retrying to send NOTIFY_IME_OF_FOCUS...",
         this));
    observer->PostFocusSetNotification();
    return;
  }

  observer->mIMEHasFocus = true;
  // Initialize selection cache with the first selection data.
#ifdef XP_MACOSX
  // We need to flush layout only on macOS because character coordinates are
  // cached by cocoa with this call, but we don't have a way to update them
  // after that.  Therefore, we need the latest layout information right now.
  observer->UpdateSelectionCache(true);
#else
  // We avoid flushing for focus in the general case.
  observer->UpdateSelectionCache(false);
#endif  // #ifdef XP_MACOSX #else
  MOZ_LOG(sIMECOLog, LogLevel::Info,
          ("0x%p IMENotificationSender::SendFocusSet(), sending "
           "NOTIFY_IME_OF_FOCUS...",
           this));

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

  // IMENotificationRequests 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.
  observer->OnIMEReceivedFocus();

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

void IMEContentObserver::IMENotificationSender::SendSelectionChange() {
  RefPtr<IMEContentObserver> observer = GetObserver();
  if (!observer) {
    return;
  }

  if (!CanNotifyIME(eChangeEventType_Selection)) {
    MOZ_LOG(sIMECOLog, LogLevel::Warning,
            ("0x%p   IMENotificationSender::SendSelectionChange(), Warning, "
             "does not send notification due to impossible to notify IME of "
             "selection change",
             this));
    return;
  }

  if (!IsSafeToNotifyIME(eChangeEventType_Selection)) {
    MOZ_LOG(sIMECOLog, LogLevel::Warning,
            ("0x%p   IMENotificationSender::SendSelectionChange(), Warning, "
             "does not send notification due to unsafe, retrying to send "
             "NOTIFY_IME_OF_SELECTION_CHANGE...",
             this));
    observer->PostSelectionChangeNotification();
    return;
  }

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

  // The state may be changed since querying content causes flushing layout.
  if (!CanNotifyIME(eChangeEventType_Selection)) {
    MOZ_LOG(sIMECOLog, LogLevel::Error,
            ("0x%p   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.
  SelectionChangeData& newSelChangeData = observer->mSelectionData;
  if (lastSelChangeData.IsInitialized() &&
      lastSelChangeData.EqualsRangeAndDirectionAndWritingMode(
          newSelChangeData)) {
    MOZ_LOG(
        sIMECOLog, LogLevel::Debug,
        ("0x%p IMENotificationSender::SendSelectionChange(), not notifying IME "
         "of NOTIFY_IME_OF_SELECTION_CHANGE due to not changed actually",
         this));
    return;
  }

  MOZ_LOG(sIMECOLog, LogLevel::Info,
          ("0x%p IMENotificationSender::SendSelectionChange(), sending "
           "NOTIFY_IME_OF_SELECTION_CHANGE... newSelChangeData=%s",
           this, ToString(newSelChangeData).c_str()));

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

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

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

void IMEContentObserver::IMENotificationSender::SendTextChange() {
  RefPtr<IMEContentObserver> observer = GetObserver();
  if (!observer) {
    return;
  }

  if (!CanNotifyIME(eChangeEventType_Text)) {
    MOZ_LOG(
        sIMECOLog, LogLevel::Warning,
        ("0x%p   IMENotificationSender::SendTextChange(), Warning, does not "
         "send notification due to impossible to notify IME of text change",
         this));
    return;
  }

  if (!IsSafeToNotifyIME(eChangeEventType_Text)) {
    MOZ_LOG(sIMECOLog, LogLevel::Warning,
            ("0x%p   IMENotificationSender::SendTextChange(), Warning, does "
             "not send notification due to unsafe, retrying to send "
             "NOTIFY_IME_OF_TEXT_CHANGE...",
             this));
    observer->PostTextChangeNotification();
    return;
  }

  // If text change notification is unnecessary anymore, just cancel it.
  if (!observer->NeedsTextChangeNotification()) {
    MOZ_LOG(sIMECOLog, LogLevel::Warning,
            ("0x%p   IMENotificationSender::SendTextChange(), Warning, "
             "canceling sending NOTIFY_IME_OF_TEXT_CHANGE",
             this));
    observer->CancelNotifyingIMEOfTextChange();
    return;
  }

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

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

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

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

void IMEContentObserver::IMENotificationSender::SendPositionChange() {
  RefPtr<IMEContentObserver> observer = GetObserver();
  if (!observer) {
    return;
  }

  if (!CanNotifyIME(eChangeEventType_Position)) {
    MOZ_LOG(sIMECOLog, LogLevel::Verbose,
            ("0x%p   IMENotificationSender::SendPositionChange(), Warning, "
             "does not send notification due to impossible to notify IME of "
             "position change",
             this));
    return;
  }

  if (!IsSafeToNotifyIME(eChangeEventType_Position)) {
    MOZ_LOG(sIMECOLog, LogLevel::Verbose,
            ("0x%p   IMENotificationSender::SendPositionChange(), Warning, "
             "does not send notification due to unsafe, retrying to send "
             "NOTIFY_IME_OF_POSITION_CHANGE...",
             this));
    observer->PostPositionChangeNotification();
    return;
  }

  // If position change notification is unnecessary anymore, just cancel it.
  if (!observer->NeedsPositionChangeNotification()) {
    MOZ_LOG(sIMECOLog, LogLevel::Verbose,
            ("0x%p   IMENotificationSender::SendPositionChange(), Warning, "
             "canceling sending NOTIFY_IME_OF_POSITION_CHANGE",
             this));
    observer->CancelNotifyingIMEOfPositionChange();
    return;
  }

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

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

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

void IMEContentObserver::IMENotificationSender::SendCompositionEventHandled() {
  RefPtr<IMEContentObserver> observer = GetObserver();
  if (!observer) {
    return;
  }

  if (!CanNotifyIME(eChangeEventType_CompositionEventHandled)) {
    MOZ_LOG(sIMECOLog, LogLevel::Warning,
            ("0x%p   IMENotificationSender::SendCompositionEventHandled(), "
             "Warning, does not send notification due to impossible to notify "
             "IME of composition event handled",
             this));
    return;
  }

  if (!IsSafeToNotifyIME(eChangeEventType_CompositionEventHandled)) {
    MOZ_LOG(sIMECOLog, LogLevel::Warning,
            ("0x%p   IMENotificationSender::SendCompositionEventHandled(), "
             "Warning, does not send notification due to unsafe, retrying to "
             "send NOTIFY_IME_OF_POSITION_CHANGE...",
             this));
    observer->PostCompositionEventHandledNotification();
    return;
  }

  MOZ_LOG(sIMECOLog, LogLevel::Info,
          ("0x%p IMENotificationSender::SendCompositionEventHandled(), sending "
           "NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED...",
           this));

  MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING);
  observer->mSendingNotification = NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED;
  IMEStateManager::NotifyIME(
      IMENotification(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED),
      observer->mWidget);
  observer->mSendingNotification = NOTIFY_IME_OF_NOTHING;

  MOZ_LOG(sIMECOLog, LogLevel::Debug,
          ("0x%p   IMENotificationSender::SendCompositionEventHandled(), sent "
           "NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED",
           this));
}

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

NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver::DocumentObserver)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver::DocumentObserver)
  // StopObserving() releases mIMEContentObserver and mDocument.
  tmp->StopObserving();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver::DocumentObserver)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMEContentObserver)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver::DocumentObserver)
  NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
  NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver::DocumentObserver)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver::DocumentObserver)

void IMEContentObserver::DocumentObserver::Observe(Document* aDocument) {
  MOZ_ASSERT(aDocument);

  // Guarantee that aDocument won't be destroyed during a call of
  // StopObserving().
  RefPtr<Document> newDocument = aDocument;

  StopObserving();

  mDocument = std::move(newDocument);
  mDocument->AddObserver(this);
}

void IMEContentObserver::DocumentObserver::StopObserving() {
  if (!IsObserving()) {
    return;
  }

  // Grab IMEContentObserver which could be destroyed during method calls.
  RefPtr<IMEContentObserver> observer = std::move(mIMEContentObserver);

  // Stop observing the document first.
  RefPtr<Document> document = std::move(mDocument);
  document->RemoveObserver(this);

  // Notify IMEContentObserver of ending of document updates if this already
  // notified it of beginning of document updates.
  for (; IsUpdating(); --mDocumentUpdating) {
    // FYI: IsUpdating() returns true until mDocumentUpdating becomes 0.
    //      However, IsObserving() returns false now because mDocument was
    //      already cleared above.  Therefore, this method won't be called
    //      recursively.
    observer->EndDocumentUpdate();
  }
}

void IMEContentObserver::DocumentObserver::Destroy() {
  StopObserving();
  mIMEContentObserver = nullptr;
}

void IMEContentObserver::DocumentObserver::BeginUpdate(Document* aDocument) {
  if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving())) {
    return;
  }
  mDocumentUpdating++;
  mIMEContentObserver->BeginDocumentUpdate();
}

void IMEContentObserver::DocumentObserver::EndUpdate(Document* aDocument) {
  if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving()) ||
      NS_WARN_IF(!IsUpdating())) {
    return;
  }
  mDocumentUpdating--;
  mIMEContentObserver->EndDocumentUpdate();
}

}  // namespace mozilla
