Bug 1275914 part.2 Modify TextComposition::mCompositionStartOffset after every composition event dispatch r?smaug draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 10 Jun 2016 19:32:49 +0900
changeset 380215 127c900a3995eccf0a9fc0099d55a0c174f9aa55
parent 380214 18f6520f4621e140dfbca5a52ae19854db62be99
child 380216 41a5a069d53de2172e4060472b8d5f6749405319
push id21167
push usermasayuki@d-toybox.com
push dateTue, 21 Jun 2016 06:55:55 +0000
reviewerssmaug
bugs1275914
milestone50.0a1
Bug 1275914 part.2 Modify TextComposition::mCompositionStartOffset after every composition event dispatch r?smaug When composition string hasn't been non-empty, insertion point of the composition string can be changed by a DOM event handler. E.g., compositionstart, first compositionupdate and first text. Therefore, TextComposition should update the composition start offset cache after every event dispatch. MozReview-Commit-ID: FOPewPTRuCn
dom/events/TextComposition.cpp
dom/events/TextComposition.h
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -64,16 +64,17 @@ TextComposition::TextComposition(nsPresC
   , mIsEditorHandlingEvent(false)
   , mIsRequestingCommit(false)
   , mIsRequestingCancel(false)
   , mRequestedToCommitOrCancel(false)
   , mWasNativeCompositionEndEventDiscarded(false)
   , mAllowControlCharacters(
       Preferences::GetBool("dom.compositionevent.allow_control_characters",
                            false))
+  , mWasCompositionStringEmpty(true)
 {
   MOZ_ASSERT(aCompositionEvent->mNativeIMEContext.IsValid());
 }
 
 void
 TextComposition::Destroy()
 {
   mPresContext = nullptr;
@@ -147,16 +148,18 @@ TextComposition::DispatchEvent(WidgetCom
                                EventDispatchingCallback* aCallBack,
                                const WidgetCompositionEvent *aOriginalEvent)
 {
   nsPluginInstanceOwner::GeneratePluginEvent(aOriginalEvent,
                                              aDispatchEvent);
 
   EventDispatcher::Dispatch(mNode, mPresContext,
                             aDispatchEvent, nullptr, aStatus, aCallBack);
+
+  OnCompositionEventDispatched(aDispatchEvent);
 }
 
 void
 TextComposition::OnCompositionEventDiscarded(
                    WidgetCompositionEvent* aCompositionEvent)
 {
   // Note that this method is never called for synthesized events for emulating
   // commit or cancel composition.
@@ -236,16 +239,18 @@ RemoveControlCharactersFrom(nsAString& a
 
 void
 TextComposition::DispatchCompositionEvent(
                    WidgetCompositionEvent* aCompositionEvent,
                    nsEventStatus* aStatus,
                    EventDispatchingCallback* aCallBack,
                    bool aIsSynthesized)
 {
+  mWasCompositionStringEmpty = mString.IsEmpty();
+
   // If the content is a container of TabParent, composition should be in the
   // remote process.
   if (mTabParent) {
     Unused << mTabParent->SendCompositionEvent(*aCompositionEvent);
     aCompositionEvent->StopPropagation();
     if (aCompositionEvent->CausesDOMTextEvent()) {
       mLastData = aCompositionEvent->mData;
       mLastRanges = aCompositionEvent->mRanges;
@@ -397,17 +402,17 @@ TextComposition::DispatchCompositionEven
     // Dispatch a compositionend event if it's necessary.
     if (aCompositionEvent->mMessage != eCompositionEnd) {
       CloneAndDispatchAs(aCompositionEvent, eCompositionEnd);
     }
     MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?");
     MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?");
   }
 
-  OnCompositionEventHandled(aCompositionEvent);
+  MaybeNotifyIMEOfCompositionEventHandled(aCompositionEvent);
 }
 
 // static
 void
 TextComposition::HandleSelectionEvent(nsPresContext* aPresContext,
                                       TabParent* aTabParent,
                                       WidgetSelectionEvent* aSelectionEvent)
 {
@@ -425,42 +430,67 @@ TextComposition::HandleSelectionEvent(ns
   // XXX During setting selection, a selection listener may change selection
   //     again.  In such case, sHandlingSelectionEvent doesn't indicate if
   //     the selection change is caused by a selection event.  However, it
   //     must be non-realistic scenario.
   handler.OnSelectionEvent(aSelectionEvent);
 }
 
 void
-TextComposition::OnCompositionEventHandled(
+TextComposition::OnCompositionEventDispatched(
                    const WidgetCompositionEvent* aCompositionEvent)
 {
   MOZ_RELEASE_ASSERT(!mTabParent);
 
-  nsEventStatus status;
+  if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
+    return;
+  }
+
+  // Every composition event may cause changing composition start offset,
+  // especially when there is no composition string.  Therefore, we need to
+  // update mCompositionStartOffset with the latest offset.
 
-  // When compositon start, notify the rect of first offset character.
-  // When not compositon start, notify the rect of selected composition
-  // string if compositionchange event.
-  if (aCompositionEvent->mMessage == eCompositionStart) {
+  MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionStart ||
+               mWasCompositionStringEmpty,
+             "mWasCompositionStringEmpty should be true if the dispatched "
+             "event is eCompositionStart");
+
+  if (mWasCompositionStringEmpty &&
+      !aCompositionEvent->CausesDOMCompositionEndEvent()) {
+    // If there was no composition string, current selection start may be the
+    // offset for inserting composition string.
     nsCOMPtr<nsIWidget> widget = mPresContext->GetRootWidget();
-    // Update composition start offset
     WidgetQueryContentEvent selectedTextEvent(true, eQuerySelectedText, widget);
-    widget->DispatchEvent(&selectedTextEvent, status);
-    if (selectedTextEvent.mSucceeded) {
+    nsEventStatus status = nsEventStatus_eIgnore;
+    if (mString.IsEmpty()) {
+      widget->DispatchEvent(&selectedTextEvent, status);
+    } else {
+      MOZ_ASSERT(aCompositionEvent->mMessage == eCompositionChange);
+      // TODO: Update mCompositionStartOffset with first IME selection
+    }
+    if (NS_WARN_IF(!selectedTextEvent.mSucceeded)) {
+      // XXX Is this okay?
+      mCompositionStartOffset = 0;
+    } else {
       mCompositionStartOffset = selectedTextEvent.mReply.mOffset;
-    } else {
-      // Unknown offset
-      NS_WARNING("Cannot get start offset of IME composition");
-      mCompositionStartOffset = 0;
     }
     mTargetClauseOffsetInComposition = 0;
-  } else if (aCompositionEvent->CausesDOMTextEvent()) {
+  }
+
+  if (aCompositionEvent->CausesDOMTextEvent()) {
     mTargetClauseOffsetInComposition = aCompositionEvent->TargetClauseOffset();
-  } else {
+  }
+}
+
+void
+TextComposition::MaybeNotifyIMEOfCompositionEventHandled(
+                   const WidgetCompositionEvent* aCompositionEvent)
+{
+  if (aCompositionEvent->mMessage != eCompositionStart &&
+      !aCompositionEvent->CausesDOMTextEvent()) {
     return;
   }
 
   RefPtr<IMEContentObserver> contentObserver =
     IMEStateManager::GetActiveContentObserver();
   // When IMEContentObserver is managing the editor which has this composition,
   // composition event handled notification should be sent after the observer
   // notifies all pending notifications.  Therefore, we should use it.
--- a/dom/events/TextComposition.h
+++ b/dom/events/TextComposition.h
@@ -253,30 +253,35 @@ private:
 
   // Allow control characters appear in composition string.
   // When this is false, control characters except
   // CHARACTER TABULATION (horizontal tab) are removed from
   // both composition string and data attribute of compositionupdate
   // and compositionend events.
   bool mAllowControlCharacters;
 
+  // mWasCompositionStringEmpty is true if the composition string was empty
+  // when DispatchCompositionEvent() is called.
+  bool mWasCompositionStringEmpty;
+
   // Hide the default constructor and copy constructor.
   TextComposition()
     : mPresContext(nullptr)
     , mNativeContext(nullptr)
     , mCompositionStartOffset(0)
     , mTargetClauseOffsetInComposition(0)
     , mIsSynthesizedForTests(false)
     , mIsComposing(false)
     , mIsEditorHandlingEvent(false)
     , mIsRequestingCommit(false)
     , mIsRequestingCancel(false)
     , mRequestedToCommitOrCancel(false)
     , mWasNativeCompositionEndEventDiscarded(false)
     , mAllowControlCharacters(false)
+    , mWasCompositionStringEmpty(true)
   {}
   TextComposition(const TextComposition& aOther);
 
   /**
    * GetEditor() returns nsIEditor pointer of mEditorWeak.
    */
   already_AddRefed<nsIEditor> GetEditor() const;
 
@@ -370,19 +375,28 @@ private:
   /**
    * OnCompositionEventDiscarded() is called when PresShell discards
    * compositionupdate, compositionend or compositionchange event due to not
    * safe to dispatch event.
    */
   void OnCompositionEventDiscarded(WidgetCompositionEvent* aCompositionEvent);
 
   /**
-   * Calculate composition offset then notify composition update to widget
+   * OnCompositionEventDispatched() is called after a composition event is
+   * dispatched.
    */
-  void OnCompositionEventHandled(
+  void OnCompositionEventDispatched(
+         const WidgetCompositionEvent* aDispatchEvent);
+
+  /**
+   * MaybeNotifyIMEOfCompositionEventHandled() notifies IME of composition
+   * event handled.  This should be called after dispatching a composition
+   * event which came from widget.
+   */
+  void MaybeNotifyIMEOfCompositionEventHandled(
          const WidgetCompositionEvent* aCompositionEvent);
 
   /**
    * CompositionEventDispatcher dispatches the specified composition (or text)
    * event.
    */
   class CompositionEventDispatcher : public Runnable
   {