LCOV - code coverage report
Current view: top level - dom/events - TextComposition.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 361 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 37 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "ContentEventHandler.h"
       8             : #include "IMEContentObserver.h"
       9             : #include "IMEStateManager.h"
      10             : #include "nsContentUtils.h"
      11             : #include "nsIContent.h"
      12             : #include "nsIPresShell.h"
      13             : #include "nsPresContext.h"
      14             : #include "mozilla/AutoRestore.h"
      15             : #include "mozilla/EditorBase.h"
      16             : #include "mozilla/EventDispatcher.h"
      17             : #include "mozilla/IMEStateManager.h"
      18             : #include "mozilla/MiscEvents.h"
      19             : #include "mozilla/Preferences.h"
      20             : #include "mozilla/TextComposition.h"
      21             : #include "mozilla/TextEvents.h"
      22             : #include "mozilla/Unused.h"
      23             : #include "mozilla/dom/TabParent.h"
      24             : 
      25             : #ifdef XP_MACOSX
      26             : // Some defiens will be conflict with OSX SDK
      27             : #define TextRange _TextRange
      28             : #define TextRangeArray _TextRangeArray
      29             : #define Comment _Comment
      30             : #endif
      31             : 
      32             : #include "nsPluginInstanceOwner.h"
      33             : 
      34             : #ifdef XP_MACOSX
      35             : #undef TextRange
      36             : #undef TextRangeArray
      37             : #undef Comment
      38             : #endif
      39             : 
      40             : using namespace mozilla::widget;
      41             : 
      42             : namespace mozilla {
      43             : 
      44             : #define IDEOGRAPHIC_SPACE (NS_LITERAL_STRING(u"\x3000"))
      45             : 
      46             : /******************************************************************************
      47             :  * TextComposition
      48             :  ******************************************************************************/
      49             : 
      50             : bool TextComposition::sHandlingSelectionEvent = false;
      51             : 
      52           0 : TextComposition::TextComposition(nsPresContext* aPresContext,
      53             :                                  nsINode* aNode,
      54             :                                  TabParent* aTabParent,
      55           0 :                                  WidgetCompositionEvent* aCompositionEvent)
      56             :   : mPresContext(aPresContext)
      57             :   , mNode(aNode)
      58             :   , mTabParent(aTabParent)
      59             :   , mNativeContext(aCompositionEvent->mNativeIMEContext)
      60             :   , mCompositionStartOffset(0)
      61             :   , mTargetClauseOffsetInComposition(0)
      62           0 :   , mIsSynthesizedForTests(aCompositionEvent->mFlags.mIsSynthesizedForTests)
      63             :   , mIsComposing(false)
      64             :   , mIsEditorHandlingEvent(false)
      65             :   , mIsRequestingCommit(false)
      66             :   , mIsRequestingCancel(false)
      67             :   , mRequestedToCommitOrCancel(false)
      68             :   , mWasNativeCompositionEndEventDiscarded(false)
      69             :   , mAllowControlCharacters(
      70           0 :       Preferences::GetBool("dom.compositionevent.allow_control_characters",
      71             :                            false))
      72           0 :   , mWasCompositionStringEmpty(true)
      73             : {
      74           0 :   MOZ_ASSERT(aCompositionEvent->mNativeIMEContext.IsValid());
      75           0 : }
      76             : 
      77             : void
      78           0 : TextComposition::Destroy()
      79             : {
      80           0 :   mPresContext = nullptr;
      81           0 :   mNode = nullptr;
      82           0 :   mTabParent = nullptr;
      83             :   // TODO: If the editor is still alive and this is held by it, we should tell
      84             :   //       this being destroyed for cleaning up the stuff.
      85           0 : }
      86             : 
      87             : bool
      88           0 : TextComposition::IsValidStateForComposition(nsIWidget* aWidget) const
      89             : {
      90           0 :   return !Destroyed() && aWidget && !aWidget->Destroyed() &&
      91           0 :          mPresContext->GetPresShell() &&
      92           0 :          !mPresContext->GetPresShell()->IsDestroying();
      93             : }
      94             : 
      95             : bool
      96           0 : TextComposition::MaybeDispatchCompositionUpdate(
      97             :                    const WidgetCompositionEvent* aCompositionEvent)
      98             : {
      99           0 :   MOZ_RELEASE_ASSERT(!mTabParent);
     100             : 
     101           0 :   if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
     102           0 :     return false;
     103             :   }
     104             : 
     105           0 :   if (mLastData == aCompositionEvent->mData) {
     106           0 :     return true;
     107             :   }
     108           0 :   CloneAndDispatchAs(aCompositionEvent, eCompositionUpdate);
     109           0 :   return IsValidStateForComposition(aCompositionEvent->mWidget);
     110             : }
     111             : 
     112             : BaseEventFlags
     113           0 : TextComposition::CloneAndDispatchAs(
     114             :                    const WidgetCompositionEvent* aCompositionEvent,
     115             :                    EventMessage aMessage,
     116             :                    nsEventStatus* aStatus,
     117             :                    EventDispatchingCallback* aCallBack)
     118             : {
     119           0 :   MOZ_RELEASE_ASSERT(!mTabParent);
     120             : 
     121           0 :   MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent->mWidget),
     122             :              "Should be called only when it's safe to dispatch an event");
     123             : 
     124           0 :   WidgetCompositionEvent compositionEvent(aCompositionEvent->IsTrusted(),
     125           0 :                                           aMessage, aCompositionEvent->mWidget);
     126           0 :   compositionEvent.mTime = aCompositionEvent->mTime;
     127           0 :   compositionEvent.mTimeStamp = aCompositionEvent->mTimeStamp;
     128           0 :   compositionEvent.mData = aCompositionEvent->mData;
     129           0 :   compositionEvent.mNativeIMEContext = aCompositionEvent->mNativeIMEContext;
     130           0 :   compositionEvent.mOriginalMessage = aCompositionEvent->mMessage;
     131           0 :   compositionEvent.mFlags.mIsSynthesizedForTests =
     132           0 :     aCompositionEvent->mFlags.mIsSynthesizedForTests;
     133             : 
     134           0 :   nsEventStatus dummyStatus = nsEventStatus_eConsumeNoDefault;
     135           0 :   nsEventStatus* status = aStatus ? aStatus : &dummyStatus;
     136           0 :   if (aMessage == eCompositionUpdate) {
     137           0 :     mLastData = compositionEvent.mData;
     138           0 :     mLastRanges = aCompositionEvent->mRanges;
     139             :   }
     140             : 
     141           0 :   DispatchEvent(&compositionEvent, status, aCallBack, aCompositionEvent);
     142           0 :   return compositionEvent.mFlags;
     143             : }
     144             : 
     145             : void
     146           0 : TextComposition::DispatchEvent(WidgetCompositionEvent* aDispatchEvent,
     147             :                                nsEventStatus* aStatus,
     148             :                                EventDispatchingCallback* aCallBack,
     149             :                                const WidgetCompositionEvent *aOriginalEvent)
     150             : {
     151             :   nsPluginInstanceOwner::GeneratePluginEvent(aOriginalEvent,
     152           0 :                                              aDispatchEvent);
     153             : 
     154           0 :   EventDispatcher::Dispatch(mNode, mPresContext,
     155           0 :                             aDispatchEvent, nullptr, aStatus, aCallBack);
     156             : 
     157           0 :   OnCompositionEventDispatched(aDispatchEvent);
     158           0 : }
     159             : 
     160             : void
     161           0 : TextComposition::OnCompositionEventDiscarded(
     162             :                    WidgetCompositionEvent* aCompositionEvent)
     163             : {
     164             :   // Note that this method is never called for synthesized events for emulating
     165             :   // commit or cancel composition.
     166             : 
     167           0 :   MOZ_ASSERT(aCompositionEvent->IsTrusted(),
     168             :              "Shouldn't be called with untrusted event");
     169             : 
     170           0 :   if (mTabParent) {
     171             :     // The composition event should be discarded in the child process too.
     172           0 :     Unused << mTabParent->SendCompositionEvent(*aCompositionEvent);
     173             :   }
     174             : 
     175             :   // XXX If composition events are discarded, should we dispatch them with
     176             :   //     runnable event?  However, even if we do so, it might make native IME
     177             :   //     confused due to async modification.  Especially when native IME is
     178             :   //     TSF.
     179           0 :   if (!aCompositionEvent->CausesDOMCompositionEndEvent()) {
     180           0 :     return;
     181             :   }
     182             : 
     183           0 :   mWasNativeCompositionEndEventDiscarded = true;
     184             : }
     185             : 
     186             : static inline bool
     187           0 : IsControlChar(uint32_t aCharCode)
     188             : {
     189           0 :   return aCharCode < ' ' || aCharCode == 0x7F;
     190             : }
     191             : 
     192             : static size_t
     193           0 : FindFirstControlCharacter(const nsAString& aStr)
     194             : {
     195           0 :   const char16_t* sourceBegin = aStr.BeginReading();
     196           0 :   const char16_t* sourceEnd = aStr.EndReading();
     197             : 
     198           0 :   for (const char16_t* source = sourceBegin; source < sourceEnd; ++source) {
     199           0 :     if (*source != '\t' && IsControlChar(*source)) {
     200           0 :       return source - sourceBegin;
     201             :     }
     202             :   }
     203             : 
     204           0 :   return -1;
     205             : }
     206             : 
     207             : static void
     208           0 : RemoveControlCharactersFrom(nsAString& aStr, TextRangeArray* aRanges)
     209             : {
     210           0 :   size_t firstControlCharOffset = FindFirstControlCharacter(aStr);
     211           0 :   if (firstControlCharOffset == (size_t)-1) {
     212           0 :     return;
     213             :   }
     214             : 
     215           0 :   nsAutoString copy(aStr);
     216           0 :   const char16_t* sourceBegin = copy.BeginReading();
     217           0 :   const char16_t* sourceEnd = copy.EndReading();
     218             : 
     219           0 :   char16_t* dest = aStr.BeginWriting();
     220           0 :   if (NS_WARN_IF(!dest)) {
     221           0 :     return;
     222             :   }
     223             : 
     224           0 :   char16_t* curDest = dest + firstControlCharOffset;
     225           0 :   size_t i = firstControlCharOffset;
     226           0 :   for (const char16_t* source = sourceBegin + firstControlCharOffset;
     227           0 :        source < sourceEnd; ++source) {
     228           0 :     if (*source == '\t' || *source == '\n' || !IsControlChar(*source)) {
     229           0 :       *curDest = *source;
     230           0 :       ++curDest;
     231           0 :       ++i;
     232           0 :     } else if (aRanges) {
     233           0 :       aRanges->RemoveCharacter(i);
     234             :     }
     235             :   }
     236             : 
     237           0 :   aStr.SetLength(curDest - dest);
     238             : }
     239             : 
     240             : void
     241           0 : TextComposition::DispatchCompositionEvent(
     242             :                    WidgetCompositionEvent* aCompositionEvent,
     243             :                    nsEventStatus* aStatus,
     244             :                    EventDispatchingCallback* aCallBack,
     245             :                    bool aIsSynthesized)
     246             : {
     247           0 :   mWasCompositionStringEmpty = mString.IsEmpty();
     248             : 
     249             :   // If the content is a container of TabParent, composition should be in the
     250             :   // remote process.
     251           0 :   if (mTabParent) {
     252           0 :     Unused << mTabParent->SendCompositionEvent(*aCompositionEvent);
     253           0 :     aCompositionEvent->StopPropagation();
     254           0 :     if (aCompositionEvent->CausesDOMTextEvent()) {
     255           0 :       mLastData = aCompositionEvent->mData;
     256           0 :       mLastRanges = aCompositionEvent->mRanges;
     257             :       // Although, the composition event hasn't been actually handled yet,
     258             :       // emulate an editor to be handling the composition event.
     259           0 :       EditorWillHandleCompositionChangeEvent(aCompositionEvent);
     260           0 :       EditorDidHandleCompositionChangeEvent();
     261             :     }
     262           0 :     return;
     263             :   }
     264             : 
     265           0 :   if (!mAllowControlCharacters) {
     266           0 :     RemoveControlCharactersFrom(aCompositionEvent->mData,
     267           0 :                                 aCompositionEvent->mRanges);
     268             :   }
     269           0 :   if (aCompositionEvent->mMessage == eCompositionCommitAsIs) {
     270           0 :     NS_ASSERTION(!aCompositionEvent->mRanges,
     271             :                  "mRanges of eCompositionCommitAsIs should be null");
     272           0 :     aCompositionEvent->mRanges = nullptr;
     273           0 :     NS_ASSERTION(aCompositionEvent->mData.IsEmpty(),
     274             :                  "mData of eCompositionCommitAsIs should be empty string");
     275             :     bool removePlaceholderCharacter =
     276             :       Preferences::GetBool("intl.ime.remove_placeholder_character_at_commit",
     277           0 :                            false);
     278           0 :     if (removePlaceholderCharacter && mLastData == IDEOGRAPHIC_SPACE) {
     279             :       // If the last data is an ideographic space (FullWidth space), it might be
     280             :       // a placeholder character of some Chinese IME.  So, committing with
     281             :       // this data might not be expected by users.  Let's use empty string.
     282           0 :       aCompositionEvent->mData.Truncate();
     283             :     } else {
     284           0 :       aCompositionEvent->mData = mLastData;
     285             :     }
     286           0 :   } else if (aCompositionEvent->mMessage == eCompositionCommit) {
     287           0 :     NS_ASSERTION(!aCompositionEvent->mRanges,
     288             :                  "mRanges of eCompositionCommit should be null");
     289           0 :     aCompositionEvent->mRanges = nullptr;
     290             :   }
     291             : 
     292           0 :   if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
     293           0 :     *aStatus = nsEventStatus_eConsumeNoDefault;
     294           0 :     return;
     295             :   }
     296             : 
     297             :   // If this instance has requested to commit or cancel composition but
     298             :   // is not synthesizing commit event, that means that the IME commits or
     299             :   // cancels the composition asynchronously.  Typically, iBus behaves so.
     300             :   // Then, synthesized events which were dispatched immediately after
     301             :   // the request has already committed our editor's composition string and
     302             :   // told it to web apps.  Therefore, we should ignore the delayed events.
     303           0 :   if (mRequestedToCommitOrCancel && !aIsSynthesized) {
     304           0 :     *aStatus = nsEventStatus_eConsumeNoDefault;
     305           0 :     return;
     306             :   }
     307             : 
     308             :   // IME may commit composition with empty string for a commit request or
     309             :   // with non-empty string for a cancel request.  We should prevent such
     310             :   // unexpected result.  E.g., web apps may be confused if they implement
     311             :   // autocomplete which attempts to commit composition forcibly when the user
     312             :   // selects one of suggestions but composition string is cleared by IME.
     313             :   // Note that most Chinese IMEs don't expose actual composition string to us.
     314             :   // They typically tell us an IDEOGRAPHIC SPACE or empty string as composition
     315             :   // string.  Therefore, we should hack it only when:
     316             :   // 1. committing string is empty string at requesting commit but the last
     317             :   //    data isn't IDEOGRAPHIC SPACE.
     318             :   // 2. non-empty string is committed at requesting cancel.
     319           0 :   if (!aIsSynthesized && (mIsRequestingCommit || mIsRequestingCancel)) {
     320           0 :     nsString* committingData = nullptr;
     321           0 :     switch (aCompositionEvent->mMessage) {
     322             :       case eCompositionEnd:
     323             :       case eCompositionChange:
     324             :       case eCompositionCommitAsIs:
     325             :       case eCompositionCommit:
     326           0 :         committingData = &aCompositionEvent->mData;
     327           0 :         break;
     328             :       default:
     329             :         NS_WARNING("Unexpected event comes during committing or "
     330           0 :                    "canceling composition");
     331           0 :         break;
     332             :     }
     333           0 :     if (committingData) {
     334           0 :       if (mIsRequestingCommit && committingData->IsEmpty() &&
     335           0 :           mLastData != IDEOGRAPHIC_SPACE) {
     336           0 :         committingData->Assign(mLastData);
     337           0 :       } else if (mIsRequestingCancel && !committingData->IsEmpty()) {
     338           0 :         committingData->Truncate();
     339             :       }
     340             :     }
     341             :   }
     342             : 
     343           0 :   bool dispatchEvent = true;
     344           0 :   bool dispatchDOMTextEvent = aCompositionEvent->CausesDOMTextEvent();
     345             : 
     346             :   // When mIsComposing is false but the committing string is different from
     347             :   // the last data (E.g., previous eCompositionChange event made the
     348             :   // composition string empty or didn't have clause information), we don't
     349             :   // need to dispatch redundant DOM text event.
     350           0 :   if (dispatchDOMTextEvent &&
     351           0 :       aCompositionEvent->mMessage != eCompositionChange &&
     352           0 :       !mIsComposing && mLastData == aCompositionEvent->mData) {
     353           0 :     dispatchEvent = dispatchDOMTextEvent = false;
     354             :   }
     355             : 
     356             :   // widget may dispatch redundant eCompositionChange event
     357             :   // which modifies neither composition string, clauses nor caret
     358             :   // position.  In such case, we shouldn't dispatch DOM events.
     359           0 :   if (dispatchDOMTextEvent &&
     360           0 :       aCompositionEvent->mMessage == eCompositionChange &&
     361           0 :       mLastData == aCompositionEvent->mData &&
     362           0 :       mRanges && aCompositionEvent->mRanges &&
     363           0 :       mRanges->Equals(*aCompositionEvent->mRanges)) {
     364           0 :     dispatchEvent = dispatchDOMTextEvent = false;
     365             :   }
     366             : 
     367           0 :   if (dispatchDOMTextEvent) {
     368           0 :     if (!MaybeDispatchCompositionUpdate(aCompositionEvent)) {
     369           0 :       return;
     370             :     }
     371             :   }
     372             : 
     373           0 :   if (dispatchEvent) {
     374             :     // If the composition event should cause a DOM text event, we should
     375             :     // overwrite the event message as eCompositionChange because due to
     376             :     // the limitation of mapping between event messages and DOM event types,
     377             :     // we cannot map multiple event messages to a DOM event type.
     378           0 :     if (dispatchDOMTextEvent &&
     379           0 :         aCompositionEvent->mMessage != eCompositionChange) {
     380             :       aCompositionEvent->mFlags =
     381             :         CloneAndDispatchAs(aCompositionEvent, eCompositionChange,
     382           0 :                            aStatus, aCallBack);
     383             :     } else {
     384           0 :       DispatchEvent(aCompositionEvent, aStatus, aCallBack);
     385             :     }
     386             :   } else {
     387           0 :     *aStatus = nsEventStatus_eConsumeNoDefault;
     388             :   }
     389             : 
     390           0 :   if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
     391           0 :     return;
     392             :   }
     393             : 
     394             :   // Emulate editor behavior of compositionchange event (DOM text event) handler
     395             :   // if no editor handles composition events.
     396           0 :   if (dispatchDOMTextEvent && !HasEditor()) {
     397           0 :     EditorWillHandleCompositionChangeEvent(aCompositionEvent);
     398           0 :     EditorDidHandleCompositionChangeEvent();
     399             :   }
     400             : 
     401           0 :   if (aCompositionEvent->CausesDOMCompositionEndEvent()) {
     402             :     // Dispatch a compositionend event if it's necessary.
     403           0 :     if (aCompositionEvent->mMessage != eCompositionEnd) {
     404           0 :       CloneAndDispatchAs(aCompositionEvent, eCompositionEnd);
     405             :     }
     406           0 :     MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?");
     407           0 :     MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?");
     408             :   }
     409             : 
     410           0 :   MaybeNotifyIMEOfCompositionEventHandled(aCompositionEvent);
     411             : }
     412             : 
     413             : // static
     414             : void
     415           0 : TextComposition::HandleSelectionEvent(nsPresContext* aPresContext,
     416             :                                       TabParent* aTabParent,
     417             :                                       WidgetSelectionEvent* aSelectionEvent)
     418             : {
     419             :   // If the content is a container of TabParent, composition should be in the
     420             :   // remote process.
     421           0 :   if (aTabParent) {
     422           0 :     Unused << aTabParent->SendSelectionEvent(*aSelectionEvent);
     423           0 :     aSelectionEvent->StopPropagation();
     424           0 :     return;
     425             :   }
     426             : 
     427           0 :   ContentEventHandler handler(aPresContext);
     428           0 :   AutoRestore<bool> saveHandlingSelectionEvent(sHandlingSelectionEvent);
     429           0 :   sHandlingSelectionEvent = true;
     430             :   // XXX During setting selection, a selection listener may change selection
     431             :   //     again.  In such case, sHandlingSelectionEvent doesn't indicate if
     432             :   //     the selection change is caused by a selection event.  However, it
     433             :   //     must be non-realistic scenario.
     434           0 :   handler.OnSelectionEvent(aSelectionEvent);
     435             : }
     436             : 
     437             : uint32_t
     438           0 : TextComposition::GetSelectionStartOffset()
     439             : {
     440           0 :   nsCOMPtr<nsIWidget> widget = mPresContext->GetRootWidget();
     441           0 :   WidgetQueryContentEvent selectedTextEvent(true, eQuerySelectedText, widget);
     442             :   // Due to a bug of widget, mRanges may not be nullptr even though composition
     443             :   // string is empty.  So, we need to check it here for avoiding to return
     444             :   // odd start offset.
     445           0 :   if (!mLastData.IsEmpty() && mRanges && mRanges->HasClauses()) {
     446           0 :     selectedTextEvent.InitForQuerySelectedText(
     447           0 :                         ToSelectionType(mRanges->GetFirstClause()->mRangeType));
     448             :   } else {
     449           0 :     NS_WARNING_ASSERTION(
     450             :       !mLastData.IsEmpty() || !mRanges || !mRanges->HasClauses(),
     451             :       "Shouldn't have empty clause info when composition string is empty");
     452           0 :     selectedTextEvent.InitForQuerySelectedText(SelectionType::eNormal);
     453             :   }
     454             : 
     455             :   // The editor which has this composition is observed by active
     456             :   // IMEContentObserver, we can use the cache of it.
     457             :   RefPtr<IMEContentObserver> contentObserver =
     458           0 :     IMEStateManager::GetActiveContentObserver();
     459           0 :   bool doQuerySelection = true;
     460           0 :   if (contentObserver) {
     461           0 :     if (contentObserver->IsManaging(this)) {
     462           0 :       doQuerySelection = false;
     463           0 :       contentObserver->HandleQueryContentEvent(&selectedTextEvent);
     464             :     }
     465             :     // If another editor already has focus, we cannot retrieve selection
     466             :     // in the editor which has this composition...
     467           0 :     else if (NS_WARN_IF(contentObserver->GetPresContext() == mPresContext)) {
     468           0 :       return 0;  // XXX Is this okay?
     469             :     }
     470             :   }
     471             : 
     472             :   // Otherwise, using slow path (i.e., compute every time with
     473             :   // ContentEventHandler)
     474           0 :   if (doQuerySelection) {
     475           0 :     ContentEventHandler handler(mPresContext);
     476           0 :     handler.HandleQueryContentEvent(&selectedTextEvent);
     477             :   }
     478             : 
     479           0 :   if (NS_WARN_IF(!selectedTextEvent.mSucceeded)) {
     480           0 :     return 0; // XXX Is this okay?
     481             :   }
     482           0 :   return selectedTextEvent.mReply.mOffset;
     483             : }
     484             : 
     485             : void
     486           0 : TextComposition::OnCompositionEventDispatched(
     487             :                    const WidgetCompositionEvent* aCompositionEvent)
     488             : {
     489           0 :   MOZ_RELEASE_ASSERT(!mTabParent);
     490             : 
     491           0 :   if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
     492           0 :     return;
     493             :   }
     494             : 
     495             :   // Every composition event may cause changing composition start offset,
     496             :   // especially when there is no composition string.  Therefore, we need to
     497             :   // update mCompositionStartOffset with the latest offset.
     498             : 
     499           0 :   MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionStart ||
     500             :                mWasCompositionStringEmpty,
     501             :              "mWasCompositionStringEmpty should be true if the dispatched "
     502             :              "event is eCompositionStart");
     503             : 
     504           0 :   if (mWasCompositionStringEmpty &&
     505           0 :       !aCompositionEvent->CausesDOMCompositionEndEvent()) {
     506             :     // If there was no composition string, current selection start may be the
     507             :     // offset for inserting composition string.
     508             :     // Update composition start offset with current selection start.
     509           0 :     mCompositionStartOffset = GetSelectionStartOffset();
     510           0 :     mTargetClauseOffsetInComposition = 0;
     511             :   }
     512             : 
     513           0 :   if (aCompositionEvent->CausesDOMTextEvent()) {
     514           0 :     mTargetClauseOffsetInComposition = aCompositionEvent->TargetClauseOffset();
     515             :   }
     516             : }
     517             : 
     518             : void
     519           0 : TextComposition::OnStartOffsetUpdatedInChild(uint32_t aStartOffset)
     520             : {
     521           0 :   mCompositionStartOffset = aStartOffset;
     522           0 : }
     523             : 
     524             : void
     525           0 : TextComposition::MaybeNotifyIMEOfCompositionEventHandled(
     526             :                    const WidgetCompositionEvent* aCompositionEvent)
     527             : {
     528           0 :   if (aCompositionEvent->mMessage != eCompositionStart &&
     529           0 :       !aCompositionEvent->CausesDOMTextEvent()) {
     530           0 :     return;
     531             :   }
     532             : 
     533             :   RefPtr<IMEContentObserver> contentObserver =
     534           0 :     IMEStateManager::GetActiveContentObserver();
     535             :   // When IMEContentObserver is managing the editor which has this composition,
     536             :   // composition event handled notification should be sent after the observer
     537             :   // notifies all pending notifications.  Therefore, we should use it.
     538             :   // XXX If IMEContentObserver suddenly loses focus after here and notifying
     539             :   //     widget of pending notifications, we won't notify widget of composition
     540             :   //     event handled.  Although, this is a bug but it should be okay since
     541             :   //     destroying IMEContentObserver notifies IME of blur.  So, native IME
     542             :   //     handler can treat it as this notification too.
     543           0 :   if (contentObserver && contentObserver->IsManaging(this)) {
     544           0 :     contentObserver->MaybeNotifyCompositionEventHandled();
     545           0 :     return;
     546             :   }
     547             :   // Otherwise, e.g., this composition is in non-active window, we should
     548             :   // notify widget directly.
     549           0 :   NotifyIME(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED);
     550             : }
     551             : 
     552             : void
     553           0 : TextComposition::DispatchCompositionEventRunnable(EventMessage aEventMessage,
     554             :                                                   const nsAString& aData,
     555             :                                                   bool aIsSynthesizingCommit)
     556             : {
     557             :   nsContentUtils::AddScriptRunner(
     558             :     new CompositionEventDispatcher(this, mNode, aEventMessage, aData,
     559           0 :                                    aIsSynthesizingCommit));
     560           0 : }
     561             : 
     562             : nsresult
     563           0 : TextComposition::RequestToCommit(nsIWidget* aWidget, bool aDiscard)
     564             : {
     565             :   // If this composition is already requested to be committed or canceled,
     566             :   // we don't need to request it again because even if the first request
     567             :   // failed, new request won't success, probably.  And we shouldn't synthesize
     568             :   // events for committing or canceling composition twice or more times.
     569           0 :   if (mRequestedToCommitOrCancel) {
     570           0 :     return NS_OK;
     571             :   }
     572             : 
     573           0 :   RefPtr<TextComposition> kungFuDeathGrip(this);
     574           0 :   const nsAutoString lastData(mLastData);
     575             : 
     576             :   {
     577           0 :     AutoRestore<bool> saveRequestingCancel(mIsRequestingCancel);
     578           0 :     AutoRestore<bool> saveRequestingCommit(mIsRequestingCommit);
     579           0 :     if (aDiscard) {
     580           0 :       mIsRequestingCancel = true;
     581           0 :       mIsRequestingCommit = false;
     582             :     } else {
     583           0 :       mIsRequestingCancel = false;
     584           0 :       mIsRequestingCommit = true;
     585             :     }
     586             :     // FYI: CompositionEvents caused by a call of NotifyIME() may be
     587             :     //      discarded by PresShell if it's not safe to dispatch the event.
     588             :     nsresult rv =
     589           0 :       aWidget->NotifyIME(IMENotification(aDiscard ?
     590             :                                            REQUEST_TO_CANCEL_COMPOSITION :
     591           0 :                                            REQUEST_TO_COMMIT_COMPOSITION));
     592           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     593           0 :       return rv;
     594             :     }
     595             :   }
     596             : 
     597           0 :   mRequestedToCommitOrCancel = true;
     598             : 
     599             :   // If the request is performed synchronously, this must be already destroyed.
     600           0 :   if (Destroyed()) {
     601           0 :     return NS_OK;
     602             :   }
     603             : 
     604             :   // Otherwise, synthesize the commit in content.
     605           0 :   nsAutoString data(aDiscard ? EmptyString() : lastData);
     606           0 :   if (data == mLastData) {
     607           0 :     DispatchCompositionEventRunnable(eCompositionCommitAsIs, EmptyString(),
     608           0 :                                      true);
     609             :   } else {
     610           0 :     DispatchCompositionEventRunnable(eCompositionCommit, data, true);
     611             :   }
     612           0 :   return NS_OK;
     613             : }
     614             : 
     615             : nsresult
     616           0 : TextComposition::NotifyIME(IMEMessage aMessage)
     617             : {
     618           0 :   NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
     619           0 :   return IMEStateManager::NotifyIME(aMessage, mPresContext, mTabParent);
     620             : }
     621             : 
     622             : void
     623           0 : TextComposition::EditorWillHandleCompositionChangeEvent(
     624             :                    const WidgetCompositionEvent* aCompositionChangeEvent)
     625             : {
     626           0 :   mIsComposing = aCompositionChangeEvent->IsComposing();
     627           0 :   mRanges = aCompositionChangeEvent->mRanges;
     628           0 :   mIsEditorHandlingEvent = true;
     629             : 
     630           0 :   MOZ_ASSERT(mLastData == aCompositionChangeEvent->mData,
     631             :     "The text of a compositionchange event must be same as previous data "
     632             :     "attribute value of the latest compositionupdate event");
     633           0 : }
     634             : 
     635             : void
     636           0 : TextComposition::OnEditorDestroyed()
     637             : {
     638           0 :   MOZ_RELEASE_ASSERT(!mTabParent);
     639             : 
     640           0 :   MOZ_ASSERT(!mIsEditorHandlingEvent,
     641             :              "The editor should have stopped listening events");
     642           0 :   nsCOMPtr<nsIWidget> widget = GetWidget();
     643           0 :   if (NS_WARN_IF(!widget)) {
     644             :     // XXX If this could happen, how do we notify IME of destroying the editor?
     645           0 :     return;
     646             :   }
     647             : 
     648             :   // Try to cancel the composition.
     649           0 :   RequestToCommit(widget, true);
     650             : }
     651             : 
     652             : void
     653           0 : TextComposition::EditorDidHandleCompositionChangeEvent()
     654             : {
     655           0 :   mString = mLastData;
     656           0 :   mIsEditorHandlingEvent = false;
     657           0 : }
     658             : 
     659             : void
     660           0 : TextComposition::StartHandlingComposition(EditorBase* aEditorBase)
     661             : {
     662           0 :   MOZ_RELEASE_ASSERT(!mTabParent);
     663             : 
     664           0 :   MOZ_ASSERT(!HasEditor(), "There is a handling editor already");
     665           0 :   mEditorBaseWeak = do_GetWeakReference(static_cast<nsIEditor*>(aEditorBase));
     666           0 : }
     667             : 
     668             : void
     669           0 : TextComposition::EndHandlingComposition(EditorBase* aEditorBase)
     670             : {
     671           0 :   MOZ_RELEASE_ASSERT(!mTabParent);
     672             : 
     673             : #ifdef DEBUG
     674           0 :   RefPtr<EditorBase> editorBase = GetEditorBase();
     675           0 :   MOZ_ASSERT(editorBase == aEditorBase,
     676             :              "Another editor handled the composition?");
     677             : #endif // #ifdef DEBUG
     678           0 :   mEditorBaseWeak = nullptr;
     679           0 : }
     680             : 
     681             : already_AddRefed<EditorBase>
     682           0 : TextComposition::GetEditorBase() const
     683             : {
     684           0 :   nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditorBaseWeak);
     685           0 :   RefPtr<EditorBase> editorBase = static_cast<EditorBase*>(editor.get());
     686           0 :   return editorBase.forget();
     687             : }
     688             : 
     689             : bool
     690           0 : TextComposition::HasEditor() const
     691             : {
     692           0 :   return mEditorBaseWeak && mEditorBaseWeak->IsAlive();
     693             : }
     694             : 
     695             : /******************************************************************************
     696             :  * TextComposition::CompositionEventDispatcher
     697             :  ******************************************************************************/
     698             : 
     699           0 : TextComposition::CompositionEventDispatcher::CompositionEventDispatcher(
     700             :   TextComposition* aComposition,
     701             :   nsINode* aEventTarget,
     702             :   EventMessage aEventMessage,
     703             :   const nsAString& aData,
     704           0 :   bool aIsSynthesizedEvent)
     705             :   : Runnable("TextComposition::CompositionEventDispatcher")
     706             :   , mTextComposition(aComposition)
     707             :   , mEventTarget(aEventTarget)
     708             :   , mData(aData)
     709             :   , mEventMessage(aEventMessage)
     710           0 :   , mIsSynthesizedEvent(aIsSynthesizedEvent)
     711             : {
     712           0 : }
     713             : 
     714             : NS_IMETHODIMP
     715           0 : TextComposition::CompositionEventDispatcher::Run()
     716             : {
     717             :   // The widget can be different from the widget which has dispatched
     718             :   // composition events because GetWidget() returns a widget which is proper
     719             :   // for calling NotifyIME().  However, this must no be problem since both
     720             :   // widget should share native IME context.  Therefore, even if an event
     721             :   // handler uses the widget for requesting IME to commit or cancel, it works.
     722           0 :   nsCOMPtr<nsIWidget> widget(mTextComposition->GetWidget());
     723           0 :   if (!mTextComposition->IsValidStateForComposition(widget)) {
     724           0 :     return NS_OK; // cannot dispatch any events anymore
     725             :   }
     726             : 
     727           0 :   RefPtr<nsPresContext> presContext = mTextComposition->mPresContext;
     728           0 :   nsEventStatus status = nsEventStatus_eIgnore;
     729           0 :   switch (mEventMessage) {
     730             :     case eCompositionStart: {
     731           0 :       WidgetCompositionEvent compStart(true, eCompositionStart, widget);
     732           0 :       compStart.mNativeIMEContext = mTextComposition->mNativeContext;
     733           0 :       WidgetQueryContentEvent selectedText(true, eQuerySelectedText, widget);
     734           0 :       ContentEventHandler handler(presContext);
     735           0 :       handler.OnQuerySelectedText(&selectedText);
     736           0 :       NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
     737           0 :       compStart.mData = selectedText.mReply.mString;
     738           0 :       compStart.mFlags.mIsSynthesizedForTests =
     739           0 :         mTextComposition->IsSynthesizedForTests();
     740           0 :       IMEStateManager::DispatchCompositionEvent(mEventTarget, presContext,
     741             :                                                 &compStart, &status, nullptr,
     742           0 :                                                 mIsSynthesizedEvent);
     743           0 :       break;
     744             :     }
     745             :     case eCompositionChange:
     746             :     case eCompositionCommitAsIs:
     747             :     case eCompositionCommit: {
     748           0 :       WidgetCompositionEvent compEvent(true, mEventMessage, widget);
     749           0 :       compEvent.mNativeIMEContext = mTextComposition->mNativeContext;
     750           0 :       if (mEventMessage != eCompositionCommitAsIs) {
     751           0 :         compEvent.mData = mData;
     752             :       }
     753           0 :       compEvent.mFlags.mIsSynthesizedForTests =
     754           0 :         mTextComposition->IsSynthesizedForTests();
     755           0 :       IMEStateManager::DispatchCompositionEvent(mEventTarget, presContext,
     756             :                                                 &compEvent, &status, nullptr,
     757           0 :                                                 mIsSynthesizedEvent);
     758           0 :       break;
     759             :     }
     760             :     default:
     761           0 :       MOZ_CRASH("Unsupported event");
     762             :   }
     763           0 :   return NS_OK;
     764             : }
     765             : 
     766             : /******************************************************************************
     767             :  * TextCompositionArray
     768             :  ******************************************************************************/
     769             : 
     770             : TextCompositionArray::index_type
     771           0 : TextCompositionArray::IndexOf(const NativeIMEContext& aNativeIMEContext)
     772             : {
     773           0 :   if (!aNativeIMEContext.IsValid()) {
     774           0 :     return NoIndex;
     775             :   }
     776           0 :   for (index_type i = Length(); i > 0; --i) {
     777           0 :     if (ElementAt(i - 1)->GetNativeIMEContext() == aNativeIMEContext) {
     778           0 :       return i - 1;
     779             :     }
     780             :   }
     781           0 :   return NoIndex;
     782             : }
     783             : 
     784             : TextCompositionArray::index_type
     785           0 : TextCompositionArray::IndexOf(nsIWidget* aWidget)
     786             : {
     787           0 :   return IndexOf(aWidget->GetNativeIMEContext());
     788             : }
     789             : 
     790             : TextCompositionArray::index_type
     791           0 : TextCompositionArray::IndexOf(nsPresContext* aPresContext)
     792             : {
     793           0 :   for (index_type i = Length(); i > 0; --i) {
     794           0 :     if (ElementAt(i - 1)->GetPresContext() == aPresContext) {
     795           0 :       return i - 1;
     796             :     }
     797             :   }
     798           0 :   return NoIndex;
     799             : }
     800             : 
     801             : TextCompositionArray::index_type
     802           0 : TextCompositionArray::IndexOf(nsPresContext* aPresContext,
     803             :                               nsINode* aNode)
     804             : {
     805           0 :   index_type index = IndexOf(aPresContext);
     806           0 :   if (index == NoIndex) {
     807           0 :     return NoIndex;
     808             :   }
     809           0 :   nsINode* node = ElementAt(index)->GetEventTargetNode();
     810           0 :   return node == aNode ? index : NoIndex;
     811             : }
     812             : 
     813             : TextComposition*
     814           0 : TextCompositionArray::GetCompositionFor(nsIWidget* aWidget)
     815             : {
     816           0 :   index_type i = IndexOf(aWidget);
     817           0 :   if (i == NoIndex) {
     818           0 :     return nullptr;
     819             :   }
     820           0 :   return ElementAt(i);
     821             : }
     822             : 
     823             : TextComposition*
     824           0 : TextCompositionArray::GetCompositionFor(
     825             :                         const WidgetCompositionEvent* aCompositionEvent)
     826             : {
     827           0 :   index_type i = IndexOf(aCompositionEvent->mNativeIMEContext);
     828           0 :   if (i == NoIndex) {
     829           0 :     return nullptr;
     830             :   }
     831           0 :   return ElementAt(i);
     832             : }
     833             : 
     834             : TextComposition*
     835           0 : TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext)
     836             : {
     837           0 :   index_type i = IndexOf(aPresContext);
     838           0 :   if (i == NoIndex) {
     839           0 :     return nullptr;
     840             :   }
     841           0 :   return ElementAt(i);
     842             : }
     843             : 
     844             : TextComposition*
     845           0 : TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext,
     846             :                                            nsINode* aNode)
     847             : {
     848           0 :   index_type i = IndexOf(aPresContext, aNode);
     849           0 :   if (i == NoIndex) {
     850           0 :     return nullptr;
     851             :   }
     852           0 :   return ElementAt(i);
     853             : }
     854             : 
     855             : TextComposition*
     856           0 : TextCompositionArray::GetCompositionInContent(nsPresContext* aPresContext,
     857             :                                               nsIContent* aContent)
     858             : {
     859             :   // There should be only one composition per content object.
     860           0 :   for (index_type i = Length(); i > 0; --i) {
     861           0 :     nsINode* node = ElementAt(i - 1)->GetEventTargetNode();
     862           0 :     if (node && nsContentUtils::ContentIsDescendantOf(node, aContent)) {
     863           0 :       return ElementAt(i - 1);
     864             :     }
     865             :   }
     866           0 :   return nullptr;
     867             : }
     868             : 
     869             : } // namespace mozilla

Generated by: LCOV version 1.13