tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TextComposition.cpp (41262B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "TextComposition.h"
      8 
      9 #include "ContentEventHandler.h"
     10 #include "IMEContentObserver.h"
     11 #include "IMEStateManager.h"
     12 #include "mozilla/AutoRestore.h"
     13 #include "mozilla/EditorBase.h"
     14 #include "mozilla/EventDispatcher.h"
     15 #include "mozilla/IMEStateManager.h"
     16 #include "mozilla/IntegerRange.h"
     17 #include "mozilla/MiscEvents.h"
     18 #include "mozilla/PresShell.h"
     19 #include "mozilla/RangeBoundary.h"
     20 #include "mozilla/StaticPrefs_dom.h"
     21 #include "mozilla/StaticPrefs_intl.h"
     22 #include "mozilla/TextEvents.h"
     23 #include "mozilla/dom/BrowserParent.h"
     24 #include "nsContentUtils.h"
     25 #include "nsIContent.h"
     26 #include "nsIMutationObserver.h"
     27 #include "nsPresContext.h"
     28 
     29 #ifdef XP_MACOSX
     30 // Some defiens will be conflict with OSX SDK
     31 #  define TextRange _TextRange
     32 #  define TextRangeArray _TextRangeArray
     33 #  define Comment _Comment
     34 #endif
     35 
     36 #ifdef XP_MACOSX
     37 #  undef TextRange
     38 #  undef TextRangeArray
     39 #  undef Comment
     40 #endif
     41 
     42 using namespace mozilla::widget;
     43 
     44 namespace mozilla {
     45 
     46 #define IDEOGRAPHIC_SPACE (u"\x3000"_ns)
     47 
     48 static uint32_t GetOrCreateCompositionId(WidgetCompositionEvent* aEvent) {
     49  // If we're in the parent process, return new composition ID.
     50  if (XRE_IsParentProcess()) {
     51    static uint32_t sNextCompositionId = 1u;
     52    if (MOZ_UNLIKELY(sNextCompositionId == UINT32_MAX)) {
     53      sNextCompositionId = 1u;
     54    }
     55    // FYI: When we send the event to a remote process, TextComposition will
     56    // set aEvent->mCompositionId to this value.  Therefore, we don't need to
     57    // set it here.
     58    return sNextCompositionId++;
     59  }
     60  // If aEvent comes from the parent process, the event has composition ID
     61  // considered by the parent process.  Then, we should use it.
     62  // Otherwise, aEvent is synthesized in this process, it won't cross the
     63  // process boundary between this process and the parent process.  Therefore,
     64  // we don't need to set meaningful composition ID for the text composition.
     65  return aEvent->mCompositionId;
     66 }
     67 
     68 /******************************************************************************
     69 * TextComposition
     70 ******************************************************************************/
     71 
     72 bool TextComposition::sHandlingSelectionEvent = false;
     73 
     74 TextComposition::TextComposition(nsPresContext* aPresContext, nsINode* aNode,
     75                                 BrowserParent* aBrowserParent,
     76                                 WidgetCompositionEvent* aCompositionEvent)
     77    : mPresContext(aPresContext),
     78      mNode(aNode),
     79      mBrowserParent(aBrowserParent),
     80      mNativeContext(aCompositionEvent->mNativeIMEContext),
     81      mCompositionId(GetOrCreateCompositionId(aCompositionEvent)),
     82      mCompositionStartOffset(0),
     83      mTargetClauseOffsetInComposition(0),
     84      mCompositionStartOffsetInTextNode(UINT32_MAX),
     85      mCompositionLengthInTextNode(UINT32_MAX),
     86      mIsSynthesizedForTests(aCompositionEvent->mFlags.mIsSynthesizedForTests),
     87      mIsComposing(false),
     88      mIsRequestingCommit(false),
     89      mIsRequestingCancel(false),
     90      mRequestedToCommitOrCancel(false),
     91      mHasDispatchedDOMTextEvent(false),
     92      mHasReceivedCommitEvent(false),
     93      mWasNativeCompositionEndEventDiscarded(false),
     94      mAllowControlCharacters(
     95          StaticPrefs::dom_compositionevent_allow_control_characters()),
     96      mWasCompositionStringEmpty(true) {
     97  MOZ_ASSERT(aCompositionEvent->mNativeIMEContext.IsValid());
     98 }
     99 
    100 void TextComposition::Destroy() {
    101  mPresContext = nullptr;
    102  mNode = nullptr;
    103  mBrowserParent = nullptr;
    104  mContainerTextNode = nullptr;
    105  mCompositionStartOffsetInTextNode = UINT32_MAX;
    106  mCompositionLengthInTextNode = UINT32_MAX;
    107  // TODO: If the editor is still alive and this is held by it, we should tell
    108  //       this being destroyed for cleaning up the stuff.
    109 }
    110 
    111 void TextComposition::OnCharacterDataChanged(
    112    Text& aText, const CharacterDataChangeInfo& aInfo) {
    113  if (mContainerTextNode != &aText ||
    114      mCompositionStartOffsetInTextNode == UINT32_MAX ||
    115      mCompositionLengthInTextNode == UINT32_MAX) {
    116    return;
    117  }
    118 
    119  // Ignore changes after composition string.
    120  if (aInfo.mChangeStart >=
    121      mCompositionStartOffsetInTextNode + mCompositionLengthInTextNode) {
    122    return;
    123  }
    124 
    125  // If the change ends before the composition string, we need only to adjust
    126  // the start offset.
    127  if (aInfo.mChangeEnd <= mCompositionStartOffsetInTextNode) {
    128    MOZ_ASSERT(aInfo.LengthOfRemovedText() <=
    129               mCompositionStartOffsetInTextNode);
    130    mCompositionStartOffsetInTextNode -= aInfo.LengthOfRemovedText();
    131    mCompositionStartOffsetInTextNode += aInfo.mReplaceLength;
    132    return;
    133  }
    134 
    135  // If this is caused by a splitting text node, the composition string
    136  // may be split out to the new right node.  In the case,
    137  // CompositionTransaction::DoTransaction handles it with walking the
    138  // following text nodes.  Therefore, we should NOT shrink the composing
    139  // range for avoind breaking the fix of bug 1310912.  Although the handling
    140  // looks buggy so that we need to move the handling into here later.
    141  if (aInfo.mDetails &&
    142      aInfo.mDetails->mType == CharacterDataChangeInfo::Details::eSplit) {
    143    return;
    144  }
    145 
    146  // If the change removes/replaces the last character of the composition
    147  // string, we should shrink the composition range before the change start.
    148  // Then, the replace string will be never updated by coming composition
    149  // updates.
    150  if (aInfo.mChangeEnd >=
    151      mCompositionStartOffsetInTextNode + mCompositionLengthInTextNode) {
    152    // If deleting the first character of the composition string, collapse IME
    153    // selection temporarily.  Updating composition string will insert new
    154    // composition string there.
    155    if (aInfo.mChangeStart <= mCompositionStartOffsetInTextNode) {
    156      mCompositionStartOffsetInTextNode = aInfo.mChangeStart;
    157      mCompositionLengthInTextNode = 0u;
    158      return;
    159    }
    160    // If some characters in the composition still stay, composition range
    161    // should be shrunken.
    162    MOZ_ASSERT(aInfo.mChangeStart > mCompositionStartOffsetInTextNode);
    163    mCompositionLengthInTextNode =
    164        aInfo.mChangeStart - mCompositionStartOffsetInTextNode;
    165    return;
    166  }
    167 
    168  // If removed range starts in the composition string, we need only adjust
    169  // the length to make composition range contain the replace string.
    170  if (aInfo.mChangeStart >= mCompositionStartOffsetInTextNode) {
    171    if (!mCompositionLengthInTextNode) {
    172      // However, don't extend composition range if there is no composition
    173      // string.
    174      return;
    175    }
    176    MOZ_ASSERT(aInfo.LengthOfRemovedText() <= mCompositionLengthInTextNode);
    177    mCompositionLengthInTextNode -= aInfo.LengthOfRemovedText();
    178    mCompositionLengthInTextNode += aInfo.mReplaceLength;
    179    return;
    180  }
    181 
    182  // If preceding characters of the composition string is also removed, new
    183  // composition start will be there and new composition ends at current
    184  // position.
    185  const uint32_t removedLengthInCompositionString =
    186      aInfo.mChangeEnd - mCompositionStartOffsetInTextNode;
    187  mCompositionStartOffsetInTextNode = aInfo.mChangeStart;
    188  if (!mCompositionLengthInTextNode) {
    189    // However, don't extend composition range if there is no composition
    190    // string.
    191    return;
    192  }
    193  mCompositionLengthInTextNode -= removedLengthInCompositionString;
    194  mCompositionLengthInTextNode += aInfo.mReplaceLength;
    195 }
    196 
    197 bool TextComposition::IsValidStateForComposition(nsIWidget* aWidget) const {
    198  return !Destroyed() && aWidget && !aWidget->Destroyed() &&
    199         mPresContext->GetPresShell() &&
    200         !mPresContext->PresShell()->IsDestroying();
    201 }
    202 
    203 bool TextComposition::MaybeDispatchCompositionUpdate(
    204    const WidgetCompositionEvent* aCompositionEvent) {
    205  MOZ_RELEASE_ASSERT(!mBrowserParent);
    206 
    207  if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
    208    return false;
    209  }
    210 
    211  // Note that we don't need to dispatch eCompositionUpdate event even if
    212  // mHasDispatchedDOMTextEvent is false and eCompositionCommit event is
    213  // dispatched with empty string immediately after eCompositionStart
    214  // because composition string has never been changed from empty string to
    215  // non-empty string in such composition even if selected string was not
    216  // empty string (mLastData isn't set to selected text when this receives
    217  // eCompositionStart).
    218  if (mLastData == aCompositionEvent->mData) {
    219    // Even if the new composition event does not update the composition string,
    220    // it may change IME selection.
    221    mLastRanges = aCompositionEvent->mRanges;
    222    return true;
    223  }
    224  CloneAndDispatchAs(aCompositionEvent, eCompositionUpdate);
    225  return IsValidStateForComposition(aCompositionEvent->mWidget);
    226 }
    227 
    228 BaseEventFlags TextComposition::CloneAndDispatchAs(
    229    const WidgetCompositionEvent* aCompositionEvent, EventMessage aMessage,
    230    nsEventStatus* aStatus, EventDispatchingCallback* aCallBack) {
    231  MOZ_RELEASE_ASSERT(!mBrowserParent);
    232 
    233  MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent->mWidget),
    234             "Should be called only when it's safe to dispatch an event");
    235 
    236  WidgetCompositionEvent compositionEvent(aCompositionEvent->IsTrusted(),
    237                                          aMessage, aCompositionEvent->mWidget);
    238  compositionEvent.mTimeStamp = aCompositionEvent->mTimeStamp;
    239  compositionEvent.mData = aCompositionEvent->mData;
    240  compositionEvent.mNativeIMEContext = aCompositionEvent->mNativeIMEContext;
    241  compositionEvent.mOriginalMessage = aCompositionEvent->mMessage;
    242  compositionEvent.mFlags.mIsSynthesizedForTests =
    243      aCompositionEvent->mFlags.mIsSynthesizedForTests;
    244 
    245  nsEventStatus dummyStatus = nsEventStatus_eConsumeNoDefault;
    246  nsEventStatus* status = aStatus ? aStatus : &dummyStatus;
    247  if (aMessage == eCompositionUpdate) {
    248    mLastData = compositionEvent.mData;
    249    mLastRanges = aCompositionEvent->mRanges;
    250  }
    251 
    252  DispatchEvent(&compositionEvent, status, aCallBack, aCompositionEvent);
    253  return compositionEvent.mFlags;
    254 }
    255 
    256 void TextComposition::DispatchEvent(
    257    WidgetCompositionEvent* aDispatchEvent, nsEventStatus* aStatus,
    258    EventDispatchingCallback* aCallBack,
    259    const WidgetCompositionEvent* aOriginalEvent) {
    260  if (aDispatchEvent->mMessage == eCompositionChange) {
    261    aDispatchEvent->mFlags.mOnlySystemGroupDispatchInContent = true;
    262  }
    263  RefPtr<nsINode> node = mNode;
    264  RefPtr<nsPresContext> presContext = mPresContext;
    265  EventDispatcher::Dispatch(node, presContext, aDispatchEvent, nullptr, aStatus,
    266                            aCallBack);
    267 
    268  OnCompositionEventDispatched(aDispatchEvent);
    269 }
    270 
    271 void TextComposition::OnCompositionEventDiscarded(
    272    WidgetCompositionEvent* aCompositionEvent) {
    273  // Note that this method is never called for synthesized events for emulating
    274  // commit or cancel composition.
    275 
    276  MOZ_ASSERT(aCompositionEvent->IsTrusted(),
    277             "Shouldn't be called with untrusted event");
    278 
    279  if (mBrowserParent) {
    280    (void)mBrowserParent->SendCompositionEvent(*aCompositionEvent,
    281                                               mCompositionId);
    282  }
    283 
    284  // XXX If composition events are discarded, should we dispatch them with
    285  //     runnable event?  However, even if we do so, it might make native IME
    286  //     confused due to async modification.  Especially when native IME is
    287  //     TSF.
    288  if (!aCompositionEvent->CausesDOMCompositionEndEvent()) {
    289    return;
    290  }
    291 
    292  mWasNativeCompositionEndEventDiscarded = true;
    293 }
    294 
    295 static inline bool IsControlChar(uint32_t aCharCode) {
    296  return aCharCode < ' ' || aCharCode == 0x7F;
    297 }
    298 
    299 static size_t FindFirstControlCharacter(const nsAString& aStr) {
    300  const char16_t* sourceBegin = aStr.BeginReading();
    301  const char16_t* sourceEnd = aStr.EndReading();
    302 
    303  for (const char16_t* source = sourceBegin; source < sourceEnd; ++source) {
    304    if (*source != '\t' && IsControlChar(*source)) {
    305      return source - sourceBegin;
    306    }
    307  }
    308 
    309  return -1;
    310 }
    311 
    312 static void RemoveControlCharactersFrom(nsAString& aStr,
    313                                        TextRangeArray* aRanges) {
    314  size_t firstControlCharOffset = FindFirstControlCharacter(aStr);
    315  if (firstControlCharOffset == (size_t)-1) {
    316    return;
    317  }
    318 
    319  nsAutoString copy(aStr);
    320  const char16_t* sourceBegin = copy.BeginReading();
    321  const char16_t* sourceEnd = copy.EndReading();
    322 
    323  char16_t* dest = aStr.BeginWriting();
    324  if (NS_WARN_IF(!dest)) {
    325    return;
    326  }
    327 
    328  char16_t* curDest = dest + firstControlCharOffset;
    329  size_t i = firstControlCharOffset;
    330  for (const char16_t* source = sourceBegin + firstControlCharOffset;
    331       source < sourceEnd; ++source) {
    332    if (*source == '\t' || *source == '\n' || !IsControlChar(*source)) {
    333      *curDest = *source;
    334      ++curDest;
    335      ++i;
    336    } else if (aRanges) {
    337      aRanges->RemoveCharacter(i);
    338    }
    339  }
    340 
    341  aStr.SetLength(curDest - dest);
    342 }
    343 
    344 nsString TextComposition::CommitStringIfCommittedAsIs() const {
    345  nsString result(mLastData);
    346  if (!mAllowControlCharacters) {
    347    RemoveControlCharactersFrom(result, nullptr);
    348  }
    349  if (StaticPrefs::intl_ime_remove_placeholder_character_at_commit() &&
    350      mLastData == IDEOGRAPHIC_SPACE) {
    351    return EmptyString();
    352  }
    353  return result;
    354 }
    355 
    356 void TextComposition::DispatchCompositionEvent(
    357    WidgetCompositionEvent* aCompositionEvent, nsEventStatus* aStatus,
    358    EventDispatchingCallback* aCallBack, bool aIsSynthesized) {
    359  mWasCompositionStringEmpty = mString.IsEmpty();
    360 
    361  if (aCompositionEvent->IsFollowedByCompositionEnd()) {
    362    mHasReceivedCommitEvent = true;
    363  }
    364 
    365  // If this instance has requested to commit or cancel composition but
    366  // is not synthesizing commit event, that means that the IME commits or
    367  // cancels the composition asynchronously.  Typically, iBus behaves so.
    368  // Then, synthesized events which were dispatched immediately after
    369  // the request has already committed our editor's composition string and
    370  // told it to web apps.  Therefore, we should ignore the delayed events.
    371  if (mRequestedToCommitOrCancel && !aIsSynthesized) {
    372    *aStatus = nsEventStatus_eConsumeNoDefault;
    373    return;
    374  }
    375 
    376  // If the content is a container of BrowserParent, composition should be in
    377  // the remote process.
    378  if (mBrowserParent) {
    379    (void)mBrowserParent->SendCompositionEvent(*aCompositionEvent,
    380                                               mCompositionId);
    381    aCompositionEvent->StopPropagation();
    382    if (aCompositionEvent->CausesDOMTextEvent()) {
    383      mLastData = aCompositionEvent->mData;
    384      mLastRanges = aCompositionEvent->mRanges;
    385      // Although, the composition event hasn't been actually handled yet,
    386      // emulate an editor to be handling the composition event.
    387      EditorWillHandleCompositionChangeEvent(aCompositionEvent);
    388      EditorDidHandleCompositionChangeEvent();
    389    }
    390    return;
    391  }
    392 
    393  if (!mAllowControlCharacters) {
    394    RemoveControlCharactersFrom(aCompositionEvent->mData,
    395                                aCompositionEvent->mRanges);
    396  }
    397  if (aCompositionEvent->mMessage == eCompositionCommitAsIs) {
    398    NS_ASSERTION(!aCompositionEvent->mRanges,
    399                 "mRanges of eCompositionCommitAsIs should be null");
    400    aCompositionEvent->mRanges = nullptr;
    401    NS_ASSERTION(aCompositionEvent->mData.IsEmpty(),
    402                 "mData of eCompositionCommitAsIs should be empty string");
    403    if (StaticPrefs::intl_ime_remove_placeholder_character_at_commit() &&
    404        mLastData == IDEOGRAPHIC_SPACE) {
    405      // If the last data is an ideographic space (FullWidth space), it might be
    406      // a placeholder character of some Chinese IME.  So, committing with
    407      // this data might not be expected by users.  Let's use empty string.
    408      aCompositionEvent->mData.Truncate();
    409    } else {
    410      aCompositionEvent->mData = mLastData;
    411    }
    412  } else if (aCompositionEvent->mMessage == eCompositionCommit) {
    413    NS_ASSERTION(!aCompositionEvent->mRanges,
    414                 "mRanges of eCompositionCommit should be null");
    415    aCompositionEvent->mRanges = nullptr;
    416  }
    417 
    418  if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
    419    *aStatus = nsEventStatus_eConsumeNoDefault;
    420    return;
    421  }
    422 
    423  // IME may commit composition with empty string for a commit request or
    424  // with non-empty string for a cancel request.  We should prevent such
    425  // unexpected result.  E.g., web apps may be confused if they implement
    426  // autocomplete which attempts to commit composition forcibly when the user
    427  // selects one of suggestions but composition string is cleared by IME.
    428  // Note that most Chinese IMEs don't expose actual composition string to us.
    429  // They typically tell us an IDEOGRAPHIC SPACE or empty string as composition
    430  // string.  Therefore, we should hack it only when:
    431  // 1. committing string is empty string at requesting commit but the last
    432  //    data isn't IDEOGRAPHIC SPACE.
    433  // 2. non-empty string is committed at requesting cancel.
    434  if (!aIsSynthesized && (mIsRequestingCommit || mIsRequestingCancel)) {
    435    nsString* committingData = nullptr;
    436    switch (aCompositionEvent->mMessage) {
    437      case eCompositionEnd:
    438      case eCompositionChange:
    439      case eCompositionCommitAsIs:
    440      case eCompositionCommit:
    441        committingData = &aCompositionEvent->mData;
    442        break;
    443      default:
    444        NS_WARNING(
    445            "Unexpected event comes during committing or "
    446            "canceling composition");
    447        break;
    448    }
    449    if (committingData) {
    450      if (mIsRequestingCommit && committingData->IsEmpty() &&
    451          mLastData != IDEOGRAPHIC_SPACE) {
    452        committingData->Assign(mLastData);
    453      } else if (mIsRequestingCancel && !committingData->IsEmpty()) {
    454        committingData->Truncate();
    455      }
    456    }
    457  }
    458 
    459  bool dispatchEvent = true;
    460  bool dispatchDOMTextEvent = aCompositionEvent->CausesDOMTextEvent();
    461 
    462  // When mIsComposing is false but the committing string is different from
    463  // the last data (E.g., previous eCompositionChange event made the
    464  // composition string empty or didn't have clause information), we don't
    465  // need to dispatch redundant DOM text event.  (But note that we need to
    466  // dispatch eCompositionChange event if we have not dispatched
    467  // eCompositionChange event yet and commit string replaces selected string
    468  // with empty string since selected string hasn't been replaced with empty
    469  // string yet.)
    470  if (dispatchDOMTextEvent &&
    471      aCompositionEvent->mMessage != eCompositionChange && !mIsComposing &&
    472      mHasDispatchedDOMTextEvent && mLastData == aCompositionEvent->mData) {
    473    dispatchEvent = dispatchDOMTextEvent = false;
    474  }
    475 
    476  // widget may dispatch redundant eCompositionChange event
    477  // which modifies neither composition string, clauses nor caret
    478  // position.  In such case, we shouldn't dispatch DOM events.
    479  if (dispatchDOMTextEvent &&
    480      aCompositionEvent->mMessage == eCompositionChange &&
    481      mLastData == aCompositionEvent->mData && mRanges &&
    482      aCompositionEvent->mRanges &&
    483      mRanges->Equals(*aCompositionEvent->mRanges)) {
    484    dispatchEvent = dispatchDOMTextEvent = false;
    485  }
    486 
    487  if (dispatchDOMTextEvent) {
    488    if (!MaybeDispatchCompositionUpdate(aCompositionEvent)) {
    489      return;
    490    }
    491  }
    492 
    493  if (dispatchEvent) {
    494    // If the composition event should cause a DOM text event, we should
    495    // overwrite the event message as eCompositionChange because due to
    496    // the limitation of mapping between event messages and DOM event types,
    497    // we cannot map multiple event messages to a DOM event type.
    498    if (dispatchDOMTextEvent &&
    499        aCompositionEvent->mMessage != eCompositionChange) {
    500      mHasDispatchedDOMTextEvent = true;
    501      aCompositionEvent->mFlags = CloneAndDispatchAs(
    502          aCompositionEvent, eCompositionChange, aStatus, aCallBack);
    503    } else {
    504      if (aCompositionEvent->mMessage == eCompositionChange) {
    505        mHasDispatchedDOMTextEvent = true;
    506      }
    507      DispatchEvent(aCompositionEvent, aStatus, aCallBack);
    508    }
    509  } else {
    510    *aStatus = nsEventStatus_eConsumeNoDefault;
    511  }
    512 
    513  if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
    514    return;
    515  }
    516 
    517  // Emulate editor behavior of compositionchange event (DOM text event) handler
    518  // if no editor handles composition events.
    519  if (dispatchDOMTextEvent && !HasEditor()) {
    520    EditorWillHandleCompositionChangeEvent(aCompositionEvent);
    521    EditorDidHandleCompositionChangeEvent();
    522  }
    523 
    524  if (aCompositionEvent->CausesDOMCompositionEndEvent()) {
    525    // Dispatch a compositionend event if it's necessary.
    526    if (aCompositionEvent->mMessage != eCompositionEnd) {
    527      CloneAndDispatchAs(aCompositionEvent, eCompositionEnd);
    528    }
    529    MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?");
    530    MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?");
    531  }
    532 
    533  MaybeNotifyIMEOfCompositionEventHandled(aCompositionEvent);
    534 }
    535 
    536 // static
    537 void TextComposition::HandleSelectionEvent(
    538    nsPresContext* aPresContext, BrowserParent* aBrowserParent,
    539    WidgetSelectionEvent* aSelectionEvent) {
    540  // If the content is a container of BrowserParent, composition should be in
    541  // the remote process.
    542  if (aBrowserParent) {
    543    (void)aBrowserParent->SendSelectionEvent(*aSelectionEvent);
    544    aSelectionEvent->StopPropagation();
    545    return;
    546  }
    547 
    548  AutoRestore<bool> saveHandlingSelectionEvent(sHandlingSelectionEvent);
    549  sHandlingSelectionEvent = true;
    550 
    551  if (RefPtr<IMEContentObserver> contentObserver =
    552          IMEStateManager::GetActiveContentObserver()) {
    553    contentObserver->MaybeHandleSelectionEvent(aPresContext, aSelectionEvent);
    554    return;
    555  }
    556 
    557  ContentEventHandler handler(aPresContext);
    558  // XXX During setting selection, a selection listener may change selection
    559  //     again.  In such case, sHandlingSelectionEvent doesn't indicate if
    560  //     the selection change is caused by a selection event.  However, it
    561  //     must be non-realistic scenario.
    562  handler.OnSelectionEvent(aSelectionEvent);
    563 }
    564 
    565 uint32_t TextComposition::GetSelectionStartOffset() {
    566  nsCOMPtr<nsIWidget> widget = mPresContext->GetRootWidget();
    567  WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
    568                                                 widget);
    569  // Due to a bug of widget, mRanges may not be nullptr even though composition
    570  // string is empty.  So, we need to check it here for avoiding to return
    571  // odd start offset.
    572  if (!mLastData.IsEmpty() && mRanges && mRanges->HasClauses()) {
    573    querySelectedTextEvent.InitForQuerySelectedText(
    574        ToSelectionType(mRanges->GetFirstClause()->mRangeType));
    575  } else {
    576    NS_WARNING_ASSERTION(
    577        !mLastData.IsEmpty() || !mRanges || !mRanges->HasClauses(),
    578        "Shouldn't have empty clause info when composition string is empty");
    579    querySelectedTextEvent.InitForQuerySelectedText(SelectionType::eNormal);
    580  }
    581 
    582  // The editor which has this composition is observed by active
    583  // IMEContentObserver, we can use the cache of it.
    584  RefPtr<IMEContentObserver> contentObserver =
    585      IMEStateManager::GetActiveContentObserver();
    586  bool doQuerySelection = true;
    587  if (contentObserver) {
    588    if (contentObserver->IsObserving(*this)) {
    589      doQuerySelection = false;
    590      contentObserver->HandleQueryContentEvent(&querySelectedTextEvent);
    591    }
    592    // If another editor already has focus, we cannot retrieve selection
    593    // in the editor which has this composition...
    594    else if (NS_WARN_IF(contentObserver->GetPresContext() == mPresContext)) {
    595      return 0;  // XXX Is this okay?
    596    }
    597  }
    598 
    599  // Otherwise, using slow path (i.e., compute every time with
    600  // ContentEventHandler)
    601  if (doQuerySelection) {
    602    ContentEventHandler handler(mPresContext);
    603    handler.HandleQueryContentEvent(&querySelectedTextEvent);
    604  }
    605 
    606  if (NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection())) {
    607    return 0;  // XXX Is this okay?
    608  }
    609  return querySelectedTextEvent.mReply->AnchorOffset();
    610 }
    611 
    612 void TextComposition::OnCompositionEventDispatched(
    613    const WidgetCompositionEvent* aCompositionEvent) {
    614  MOZ_RELEASE_ASSERT(!mBrowserParent);
    615 
    616  if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
    617    return;
    618  }
    619 
    620  // Every composition event may cause changing composition start offset,
    621  // especially when there is no composition string.  Therefore, we need to
    622  // update mCompositionStartOffset with the latest offset.
    623 
    624  MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionStart ||
    625                 mWasCompositionStringEmpty,
    626             "mWasCompositionStringEmpty should be true if the dispatched "
    627             "event is eCompositionStart");
    628 
    629  if (mWasCompositionStringEmpty &&
    630      !aCompositionEvent->CausesDOMCompositionEndEvent()) {
    631    // If there was no composition string, current selection start may be the
    632    // offset for inserting composition string.
    633    // Update composition start offset with current selection start.
    634    mCompositionStartOffset = GetSelectionStartOffset();
    635    mTargetClauseOffsetInComposition = 0;
    636  }
    637 
    638  if (aCompositionEvent->CausesDOMTextEvent()) {
    639    mTargetClauseOffsetInComposition = aCompositionEvent->TargetClauseOffset();
    640  }
    641 }
    642 
    643 void TextComposition::OnStartOffsetUpdatedInChild(uint32_t aStartOffset) {
    644  mCompositionStartOffset = aStartOffset;
    645 }
    646 
    647 void TextComposition::MaybeNotifyIMEOfCompositionEventHandled(
    648    const WidgetCompositionEvent* aCompositionEvent) {
    649  if (aCompositionEvent->mMessage != eCompositionStart &&
    650      !aCompositionEvent->CausesDOMTextEvent()) {
    651    return;
    652  }
    653 
    654  RefPtr<IMEContentObserver> contentObserver =
    655      IMEStateManager::GetActiveContentObserver();
    656  // When IMEContentObserver is managing the editor which has this composition,
    657  // composition event handled notification should be sent after the observer
    658  // notifies all pending notifications.  Therefore, we should use it.
    659  // XXX If IMEContentObserver suddenly loses focus after here and notifying
    660  //     widget of pending notifications, we won't notify widget of composition
    661  //     event handled.  Although, this is a bug but it should be okay since
    662  //     destroying IMEContentObserver notifies IME of blur.  So, native IME
    663  //     handler can treat it as this notification too.
    664  if (contentObserver && contentObserver->IsObserving(*this)) {
    665    contentObserver->MaybeNotifyCompositionEventHandled();
    666    return;
    667  }
    668  // Otherwise, e.g., this composition is in non-active window, we should
    669  // notify widget directly.
    670  NotifyIME(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED);
    671 }
    672 
    673 void TextComposition::DispatchCompositionEventRunnable(
    674    EventMessage aEventMessage, const nsAString& aData,
    675    bool aIsSynthesizingCommit) {
    676  nsContentUtils::AddScriptRunner(new CompositionEventDispatcher(
    677      this, mNode, aEventMessage, aData, aIsSynthesizingCommit));
    678 }
    679 
    680 nsresult TextComposition::RequestToCommit(nsIWidget* aWidget, bool aDiscard) {
    681  MOZ_ASSERT(this == IMEStateManager::GetTextCompositionFor(aWidget));
    682  // If this composition is already requested to be committed or canceled,
    683  // or has already finished in IME, we don't need to request it again because
    684  // request from this instance shouldn't cause committing nor canceling current
    685  // composition in IME, and even if the first request failed, new request
    686  // won't success, probably.  And we shouldn't synthesize events for
    687  // committing or canceling composition twice or more times.
    688  if (!CanRequsetIMEToCommitOrCancelComposition()) {
    689    return NS_OK;
    690  }
    691 
    692  RefPtr<TextComposition> kungFuDeathGrip(this);
    693  const nsAutoString lastData(mLastData);
    694 
    695  if (IMEStateManager::CanSendNotificationToWidget()) {
    696    AutoRestore<bool> saveRequestingCancel(mIsRequestingCancel);
    697    AutoRestore<bool> saveRequestingCommit(mIsRequestingCommit);
    698    if (aDiscard) {
    699      mIsRequestingCancel = true;
    700      mIsRequestingCommit = false;
    701    } else {
    702      mIsRequestingCancel = false;
    703      mIsRequestingCommit = true;
    704    }
    705    // FYI: CompositionEvents caused by a call of NotifyIME() may be
    706    //      discarded by PresShell if it's not safe to dispatch the event.
    707    nsresult rv = aWidget->NotifyIME(
    708        IMENotification(aDiscard ? REQUEST_TO_CANCEL_COMPOSITION
    709                                 : REQUEST_TO_COMMIT_COMPOSITION));
    710    if (NS_WARN_IF(NS_FAILED(rv))) {
    711      return rv;
    712    }
    713  }
    714 
    715  mRequestedToCommitOrCancel = true;
    716 
    717  // If the request is performed synchronously, this must be already destroyed.
    718  if (Destroyed()) {
    719    return NS_OK;
    720  }
    721 
    722  // Otherwise, synthesize the commit in content.
    723  nsAutoString data(aDiscard ? EmptyString() : lastData);
    724  if (data == mLastData) {
    725    DispatchCompositionEventRunnable(eCompositionCommitAsIs, u""_ns, true);
    726  } else {
    727    DispatchCompositionEventRunnable(eCompositionCommit, data, true);
    728  }
    729  return NS_OK;
    730 }
    731 
    732 nsresult TextComposition::NotifyIME(IMEMessage aMessage) {
    733  NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
    734  return IMEStateManager::NotifyIME(aMessage, mPresContext, mBrowserParent);
    735 }
    736 
    737 void TextComposition::EditorWillHandleCompositionChangeEvent(
    738    const WidgetCompositionEvent* aCompositionChangeEvent) {
    739  mIsComposing = aCompositionChangeEvent->IsComposing();
    740  mRanges = aCompositionChangeEvent->mRanges;
    741  mEditorIsHandlingEvent = true;
    742 
    743  MOZ_ASSERT(
    744      mLastData == aCompositionChangeEvent->mData,
    745      "The text of a compositionchange event must be same as previous data "
    746      "attribute value of the latest compositionupdate event");
    747 }
    748 
    749 void TextComposition::OnEditorDestroyed() {
    750  MOZ_RELEASE_ASSERT(!mBrowserParent);
    751 
    752  MOZ_ASSERT(!mEditorIsHandlingEvent,
    753             "The editor should have stopped listening events");
    754  nsCOMPtr<nsIWidget> widget = GetWidget();
    755  if (NS_WARN_IF(!widget)) {
    756    // XXX If this could happen, how do we notify IME of destroying the editor?
    757    return;
    758  }
    759 
    760  // Try to cancel the composition.
    761  RequestToCommit(widget, true);
    762 }
    763 
    764 void TextComposition::EditorDidHandleCompositionChangeEvent() {
    765  mString = mLastData;
    766  mEditorIsHandlingEvent = false;
    767 }
    768 
    769 void TextComposition::StartHandlingComposition(EditorBase* aEditorBase) {
    770  MOZ_RELEASE_ASSERT(!mBrowserParent);
    771 
    772  MOZ_ASSERT(!HasEditor(), "There is a handling editor already");
    773  mEditorBaseWeak = do_GetWeakReference(static_cast<nsIEditor*>(aEditorBase));
    774 }
    775 
    776 void TextComposition::EndHandlingComposition(EditorBase* aEditorBase) {
    777  MOZ_RELEASE_ASSERT(!mBrowserParent);
    778 
    779 #ifdef DEBUG
    780  RefPtr<EditorBase> editorBase = GetEditorBase();
    781  MOZ_ASSERT(!editorBase || editorBase == aEditorBase,
    782             "Another editor handled the composition?");
    783 #endif  // #ifdef DEBUG
    784  mEditorBaseWeak = nullptr;
    785 }
    786 
    787 already_AddRefed<EditorBase> TextComposition::GetEditorBase() const {
    788  nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditorBaseWeak);
    789  RefPtr<EditorBase> editorBase = static_cast<EditorBase*>(editor.get());
    790  return editorBase.forget();
    791 }
    792 
    793 bool TextComposition::HasEditor() const {
    794  return mEditorBaseWeak && mEditorBaseWeak->IsAlive();
    795 }
    796 
    797 RawRangeBoundary TextComposition::FirstIMESelectionStartRef() const {
    798  RefPtr<EditorBase> editorBase = GetEditorBase();
    799  if (!editorBase) {
    800    return RawRangeBoundary();
    801  }
    802 
    803  nsISelectionController* selectionController =
    804      editorBase->GetSelectionController();
    805  if (NS_WARN_IF(!selectionController)) {
    806    return RawRangeBoundary();
    807  }
    808 
    809  const nsRange* firstRange = nullptr;
    810  static const SelectionType kIMESelectionTypes[] = {
    811      SelectionType::eIMERawClause, SelectionType::eIMESelectedRawClause,
    812      SelectionType::eIMEConvertedClause, SelectionType::eIMESelectedClause};
    813  for (auto selectionType : kIMESelectionTypes) {
    814    dom::Selection* selection =
    815        selectionController->GetSelection(ToRawSelectionType(selectionType));
    816    if (!selection) {
    817      continue;
    818    }
    819    const uint32_t rangeCount = selection->RangeCount();
    820    for (const uint32_t i : IntegerRange(rangeCount)) {
    821      MOZ_ASSERT(selection->RangeCount() == rangeCount);
    822      const nsRange* range = selection->GetRangeAt(i);
    823      MOZ_ASSERT(range);
    824      if (MOZ_UNLIKELY(NS_WARN_IF(!range)) ||
    825          MOZ_UNLIKELY(NS_WARN_IF(!range->GetStartContainer()))) {
    826        continue;
    827      }
    828      if (!firstRange) {
    829        firstRange = range;
    830        continue;
    831      }
    832      // In most cases, all composition string should be in same text node.
    833      if (firstRange->GetStartContainer() == range->GetStartContainer()) {
    834        if (firstRange->StartOffset() > range->StartOffset()) {
    835          firstRange = range;
    836        }
    837        continue;
    838      }
    839      // However, if web apps have inserted different nodes in composition
    840      // string, composition string may span 2 or more nodes.
    841      if (firstRange->GetStartContainer()->GetNextSibling() ==
    842          range->GetStartContainer()) {
    843        // Fast path for some known applications like Google Keep.
    844        firstRange = range;
    845        continue;
    846      }
    847      // Unfortunately, really slow path.
    848      // The ranges should always have a common ancestor, hence, be comparable.
    849      if (*nsContentUtils::ComparePoints(range->StartRef(),
    850                                         firstRange->StartRef()) == -1) {
    851        firstRange = range;
    852      }
    853    }
    854  }
    855  return firstRange ? firstRange->StartRef().AsRaw() : RawRangeBoundary();
    856 }
    857 
    858 RawRangeBoundary TextComposition::LastIMESelectionEndRef() const {
    859  RefPtr<EditorBase> editorBase = GetEditorBase();
    860  if (!editorBase) {
    861    return RawRangeBoundary();
    862  }
    863 
    864  nsISelectionController* selectionController =
    865      editorBase->GetSelectionController();
    866  if (NS_WARN_IF(!selectionController)) {
    867    return RawRangeBoundary();
    868  }
    869 
    870  const nsRange* lastRange = nullptr;
    871  static const SelectionType kIMESelectionTypes[] = {
    872      SelectionType::eIMERawClause, SelectionType::eIMESelectedRawClause,
    873      SelectionType::eIMEConvertedClause, SelectionType::eIMESelectedClause};
    874  for (auto selectionType : kIMESelectionTypes) {
    875    dom::Selection* selection =
    876        selectionController->GetSelection(ToRawSelectionType(selectionType));
    877    if (!selection) {
    878      continue;
    879    }
    880    const uint32_t rangeCount = selection->RangeCount();
    881    for (const uint32_t i : IntegerRange(rangeCount)) {
    882      MOZ_ASSERT(selection->RangeCount() == rangeCount);
    883      const nsRange* range = selection->GetRangeAt(i);
    884      MOZ_ASSERT(range);
    885      if (MOZ_UNLIKELY(NS_WARN_IF(!range)) ||
    886          MOZ_UNLIKELY(NS_WARN_IF(!range->GetEndContainer()))) {
    887        continue;
    888      }
    889      if (!lastRange) {
    890        lastRange = range;
    891        continue;
    892      }
    893      // In most cases, all composition string should be in same text node.
    894      if (lastRange->GetEndContainer() == range->GetEndContainer()) {
    895        if (lastRange->EndOffset() < range->EndOffset()) {
    896          lastRange = range;
    897        }
    898        continue;
    899      }
    900      // However, if web apps have inserted different nodes in composition
    901      // string, composition string may span 2 or more nodes.
    902      if (lastRange->GetEndContainer() ==
    903          range->GetEndContainer()->GetNextSibling()) {
    904        // Fast path for some known applications like Google Keep.
    905        lastRange = range;
    906        continue;
    907      }
    908      // Unfortunately, really slow path.
    909      // The ranges should always have a common ancestor, hence, be comparable.
    910      if (*nsContentUtils::ComparePoints(lastRange->EndRef(),
    911                                         range->EndRef()) == -1) {
    912        lastRange = range;
    913      }
    914    }
    915  }
    916  return lastRange ? lastRange->EndRef().AsRaw() : RawRangeBoundary();
    917 }
    918 
    919 /******************************************************************************
    920 * TextComposition::CompositionEventDispatcher
    921 ******************************************************************************/
    922 
    923 TextComposition::CompositionEventDispatcher::CompositionEventDispatcher(
    924    TextComposition* aTextComposition, nsINode* aEventTarget,
    925    EventMessage aEventMessage, const nsAString& aData,
    926    bool aIsSynthesizedEvent)
    927    : Runnable("TextComposition::CompositionEventDispatcher"),
    928      mTextComposition(aTextComposition),
    929      mEventTarget(aEventTarget),
    930      mData(aData),
    931      mEventMessage(aEventMessage),
    932      mIsSynthesizedEvent(aIsSynthesizedEvent) {}
    933 
    934 NS_IMETHODIMP
    935 TextComposition::CompositionEventDispatcher::Run() {
    936  // The widget can be different from the widget which has dispatched
    937  // composition events because GetWidget() returns a widget which is proper
    938  // for calling NotifyIME().  However, this must no be problem since both
    939  // widget should share native IME context.  Therefore, even if an event
    940  // handler uses the widget for requesting IME to commit or cancel, it works.
    941  nsCOMPtr<nsIWidget> widget(mTextComposition->GetWidget());
    942  if (!mTextComposition->IsValidStateForComposition(widget)) {
    943    return NS_OK;  // cannot dispatch any events anymore
    944  }
    945 
    946  RefPtr<nsPresContext> presContext = mTextComposition->mPresContext;
    947  nsCOMPtr<nsINode> eventTarget = mEventTarget;
    948  RefPtr<BrowserParent> browserParent = mTextComposition->mBrowserParent;
    949  nsEventStatus status = nsEventStatus_eIgnore;
    950  switch (mEventMessage) {
    951    case eCompositionStart: {
    952      WidgetCompositionEvent compStart(true, eCompositionStart, widget);
    953      compStart.mNativeIMEContext = mTextComposition->mNativeContext;
    954      WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
    955                                                     widget);
    956      ContentEventHandler handler(presContext);
    957      handler.OnQuerySelectedText(&querySelectedTextEvent);
    958      NS_ASSERTION(querySelectedTextEvent.Succeeded(),
    959                   "Failed to get selected text");
    960      if (querySelectedTextEvent.FoundSelection()) {
    961        compStart.mData = querySelectedTextEvent.mReply->DataRef();
    962      }
    963      compStart.mFlags.mIsSynthesizedForTests =
    964          mTextComposition->IsSynthesizedForTests();
    965      IMEStateManager::DispatchCompositionEvent(
    966          eventTarget, presContext, browserParent, &compStart, &status, nullptr,
    967          mIsSynthesizedEvent);
    968      break;
    969    }
    970    case eCompositionChange:
    971    case eCompositionCommitAsIs:
    972    case eCompositionCommit: {
    973      WidgetCompositionEvent compEvent(true, mEventMessage, widget);
    974      compEvent.mNativeIMEContext = mTextComposition->mNativeContext;
    975      if (mEventMessage != eCompositionCommitAsIs) {
    976        compEvent.mData = mData;
    977      }
    978      compEvent.mFlags.mIsSynthesizedForTests =
    979          mTextComposition->IsSynthesizedForTests();
    980      IMEStateManager::DispatchCompositionEvent(
    981          eventTarget, presContext, browserParent, &compEvent, &status, nullptr,
    982          mIsSynthesizedEvent);
    983      break;
    984    }
    985    default:
    986      MOZ_CRASH("Unsupported event");
    987  }
    988  return NS_OK;
    989 }
    990 
    991 /******************************************************************************
    992 * TextCompositionArray
    993 ******************************************************************************/
    994 
    995 TextCompositionArray::index_type TextCompositionArray::IndexOf(
    996    const NativeIMEContext& aNativeIMEContext) {
    997  if (!aNativeIMEContext.IsValid()) {
    998    return NoIndex;
    999  }
   1000  for (index_type i = Length(); i > 0; --i) {
   1001    if (ElementAt(i - 1)->GetNativeIMEContext() == aNativeIMEContext) {
   1002      return i - 1;
   1003    }
   1004  }
   1005  return NoIndex;
   1006 }
   1007 
   1008 TextCompositionArray::index_type TextCompositionArray::IndexOf(
   1009    nsIWidget* aWidget) {
   1010  return IndexOf(aWidget->GetNativeIMEContext());
   1011 }
   1012 
   1013 TextCompositionArray::index_type TextCompositionArray::IndexOf(
   1014    nsPresContext* aPresContext) {
   1015  for (index_type i = Length(); i > 0; --i) {
   1016    if (ElementAt(i - 1)->GetPresContext() == aPresContext) {
   1017      return i - 1;
   1018    }
   1019  }
   1020  return NoIndex;
   1021 }
   1022 
   1023 TextCompositionArray::index_type TextCompositionArray::IndexOf(
   1024    nsPresContext* aPresContext, nsINode* aNode) {
   1025  index_type index = IndexOf(aPresContext);
   1026  if (index == NoIndex) {
   1027    return NoIndex;
   1028  }
   1029  nsINode* node = ElementAt(index)->GetEventTargetNode();
   1030  return node == aNode ? index : NoIndex;
   1031 }
   1032 
   1033 TextComposition* TextCompositionArray::GetCompositionFor(nsIWidget* aWidget) {
   1034  index_type i = IndexOf(aWidget);
   1035  if (i == NoIndex) {
   1036    return nullptr;
   1037  }
   1038  return ElementAt(i);
   1039 }
   1040 
   1041 TextComposition* TextCompositionArray::GetCompositionFor(
   1042    const WidgetCompositionEvent* aCompositionEvent) {
   1043  index_type i = IndexOf(aCompositionEvent->mNativeIMEContext);
   1044  if (i == NoIndex) {
   1045    return nullptr;
   1046  }
   1047  return ElementAt(i);
   1048 }
   1049 
   1050 TextComposition* TextCompositionArray::GetCompositionFor(
   1051    nsPresContext* aPresContext) {
   1052  index_type i = IndexOf(aPresContext);
   1053  if (i == NoIndex) {
   1054    return nullptr;
   1055  }
   1056  return ElementAt(i);
   1057 }
   1058 
   1059 TextComposition* TextCompositionArray::GetCompositionFor(
   1060    nsPresContext* aPresContext, nsINode* aNode) {
   1061  index_type i = IndexOf(aPresContext, aNode);
   1062  if (i == NoIndex) {
   1063    return nullptr;
   1064  }
   1065  return ElementAt(i);
   1066 }
   1067 
   1068 TextComposition* TextCompositionArray::GetCompositionInContent(
   1069    nsPresContext* aPresContext, nsIContent* aContent) {
   1070  // There should be only one composition per content object.
   1071  for (TextComposition* const composition : Reversed(*this)) {
   1072    nsINode* node = composition->GetEventTargetNode();
   1073    if (node && node->IsInclusiveFlatTreeDescendantOf(aContent)) {
   1074      return composition;
   1075    }
   1076  }
   1077  return nullptr;
   1078 }
   1079 
   1080 }  // namespace mozilla