Bug 543789 part.4 Implement DOM3 composition event on Linux r=karlt+smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Thu, 22 Sep 2011 18:17:40 +0900
changeset 77313 71ffc9bbcb31e08cdbd72338251f8ead42655b37
parent 77312 61f11b38c268fe1c9e6337e7c699fe932185832c
child 77314 8d48a0f50b4c2c92de5320e97f48b69e7ebfba11
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewerskarlt
bugs543789
milestone9.0a1
Bug 543789 part.4 Implement DOM3 composition event on Linux r=karlt+smaug
widget/src/gtk2/nsGtkIMModule.cpp
widget/src/gtk2/nsGtkIMModule.h
widget/src/qt/nsWindow.cpp
--- a/widget/src/gtk2/nsGtkIMModule.cpp
+++ b/widget/src/gtk2/nsGtkIMModule.cpp
@@ -409,17 +409,17 @@ nsGtkIMModule::OnKeyEvent(nsWindow* aCal
     // it's probably part of a composition) or if the key event was
     // committed _and_ changed.  This way we still let key press
     // events go through as simple key press events instead of
     // composed characters.
     PRBool filterThisEvent = isFiltered && mFilterKeyEvent;
 
     if (mIsComposing && !isFiltered) {
         if (aEvent->type == GDK_KEY_PRESS) {
-            if (!mCompositionString.IsEmpty()) {
+            if (!mDispatchedCompositionString.IsEmpty()) {
                 // If there is composition string, we shouldn't dispatch
                 // any keydown events during composition.
                 filterThisEvent = PR_TRUE;
             } else {
                 // A Hangul input engine for SCIM doesn't emit preedit_end
                 // signal even when composition string becomes empty.  On the
                 // other hand, we should allow to make composition with empty
                 // string for other languages because there *might* be such
@@ -496,17 +496,17 @@ nsGtkIMModule::ResetInputState(nsWindow*
     }
 
     if (!mIsComposing) {
         return NS_OK;
     }
 
     // XXX We should commit composition ourselves temporary...
     ResetIME();
-    CommitCompositionBy(mCompositionString);
+    CommitCompositionBy(mDispatchedCompositionString);
 
     return NS_OK;
 }
 
 nsresult
 nsGtkIMModule::CancelIMEComposition(nsWindow* aCaller)
 {
     if (NS_UNLIKELY(IsDestroyed())) {
@@ -829,23 +829,18 @@ nsGtkIMModule::OnEndCompositionNative(Gt
     // Note that the native commit can be fired *after* ResetIME().
     mIgnoreNativeCompositionEvent = PR_FALSE;
 
     if (!mIsComposing || shouldIgnoreThisEvent) {
         // If we already handled the commit event, we should do nothing here.
         return;
     }
 
-#ifdef PR_LOGGING
-    if (!mCompositionString.IsEmpty()) {
-        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
-            ("    WARNING, the composition string is still there"));
-    }
-#endif
-    DispatchCompositionEnd(); // Be aware, widget can be gone
+    // Be aware, widget can be gone
+    DispatchCompositionEnd();
 }
 
 /* static */
 void
 nsGtkIMModule::OnChangeCompositionCallback(GtkIMContext *aContext,
                                            nsGtkIMModule* aModule)
 {
     aModule->OnChangeCompositionNative(aContext);
@@ -865,22 +860,25 @@ nsGtkIMModule::OnChangeCompositionNative
              GetContext()));
         return;
     }
 
     if (ShouldIgnoreNativeCompositionEvent()) {
         return;
     }
 
-    GetCompositionString(mCompositionString);
-    if (!mIsComposing && mCompositionString.IsEmpty()) {
+    nsAutoString compositionString;
+    GetCompositionString(compositionString);
+    if (!mIsComposing && compositionString.IsEmpty()) {
+        mDispatchedCompositionString.Truncate();
         return; // Don't start the composition with empty string.
     }
 
-    DispatchTextEvent(PR_TRUE); // Be aware, widget can be gone
+    // Be aware, widget can be gone
+    DispatchTextEvent(compositionString, PR_TRUE);
 }
 
 /* static */
 gboolean
 nsGtkIMModule::OnRetrieveSurroundingCallback(GtkIMContext  *aContext,
                                              nsGtkIMModule *aModule)
 {
     return aModule->OnRetrieveSurroundingNative(aContext);
@@ -1022,22 +1020,22 @@ nsGtkIMModule::OnCommitCompositionNative
     NS_ConvertUTF8toUTF16 str(commitString);
     CommitCompositionBy(str); // Be aware, widget can be gone
 }
 
 PRBool
 nsGtkIMModule::CommitCompositionBy(const nsAString& aString)
 {
     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
-        ("GtkIMModule(%p): CommitCompositionBy, aString=\"%s\", mCompositionString=\"%s\"",
+        ("GtkIMModule(%p): CommitCompositionBy, aString=\"%s\", "
+         "mDispatchedCompositionString=\"%s\"",
          this, NS_ConvertUTF16toUTF8(aString).get(),
-         NS_ConvertUTF16toUTF8(mCompositionString).get()));
+         NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get()));
 
-    mCompositionString = aString;
-    if (!DispatchTextEvent(PR_FALSE)) {
+    if (!DispatchTextEvent(aString, PR_FALSE)) {
         return PR_FALSE;
     }
     // We should dispatch the compositionend event here because some IMEs
     // might not fire "preedit_end" native event.
     return DispatchCompositionEnd(); // Be aware, widget can be gone
 }
 
 void
@@ -1088,16 +1086,17 @@ nsGtkIMModule::DispatchCompositionStart(
 
     if (!selection.mSucceeded || selection.mReply.mOffset == PR_UINT32_MAX) {
         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
             ("    FAILED, cannot query the selection offset"));
         return PR_FALSE;
     }
 
     mCompositionStart = selection.mReply.mOffset;
+    mDispatchedCompositionString.Truncate();
 
     if (mProcessingKeyEvent && !mKeyDownEventWasSent &&
         mProcessingKeyEvent->type == GDK_KEY_PRESS) {
         // If this composition is started by a native keydown event, we need to
         // dispatch our keydown event here (before composition start).
         nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
         PRBool isCancelled;
         mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent,
@@ -1135,51 +1134,56 @@ nsGtkIMModule::DispatchCompositionStart(
 
     return PR_TRUE;
 }
 
 PRBool
 nsGtkIMModule::DispatchCompositionEnd()
 {
     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
-        ("GtkIMModule(%p): DispatchCompositionEnd", this));
+        ("GtkIMModule(%p): DispatchCompositionEnd, "
+         "mDispatchedCompositionString=\"%s\"",
+         this, NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get()));
 
     if (!mIsComposing) {
         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
             ("    WARNING, we have alrady finished the composition"));
         return PR_FALSE;
     }
 
     if (!mLastFocusedWindow) {
+        mDispatchedCompositionString.Truncate();
         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
             ("    FAILED, there are no focused window in this module"));
         return PR_FALSE;
     }
 
     nsCompositionEvent compEvent(PR_TRUE, NS_COMPOSITION_END,
                                  mLastFocusedWindow);
     InitEvent(compEvent);
+    compEvent.data = mDispatchedCompositionString;
     nsEventStatus status;
     nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
     mLastFocusedWindow->DispatchEvent(&compEvent, status);
     mIsComposing = PR_FALSE;
     mCompositionStart = PR_UINT32_MAX;
-    mCompositionString.Truncate();
+    mDispatchedCompositionString.Truncate();
     if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
         kungFuDeathGrip != mLastFocusedWindow) {
         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
             ("    NOTE, the focused widget was destroyed/changed by compositionend event"));
         return PR_FALSE;
     }
 
     return PR_TRUE;
 }
 
 PRBool
-nsGtkIMModule::DispatchTextEvent(PRBool aCheckAttr)
+nsGtkIMModule::DispatchTextEvent(const nsAString &aCompositionString,
+                                 PRBool aCheckAttr)
 {
     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
         ("GtkIMModule(%p): DispatchTextEvent, aCheckAttr=%s",
          this, aCheckAttr ? "TRUE" : "FALSE"));
 
     if (!mLastFocusedWindow) {
         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
             ("    FAILED, there are no focused window in this module"));
@@ -1190,43 +1194,61 @@ nsGtkIMModule::DispatchTextEvent(PRBool 
         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
             ("    The composition wasn't started, force starting..."));
         nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
         if (!DispatchCompositionStart()) {
             return PR_FALSE;
         }
     }
 
+    nsEventStatus status;
+    nsRefPtr<nsWindow> lastFocusedWindow = mLastFocusedWindow;
+
+    if (aCompositionString != mDispatchedCompositionString) {
+      nsCompositionEvent compositionUpdate(PR_TRUE, NS_COMPOSITION_UPDATE,
+                                           mLastFocusedWindow);
+      InitEvent(compositionUpdate);
+      compositionUpdate.data = aCompositionString;
+      mDispatchedCompositionString = aCompositionString;
+      mLastFocusedWindow->DispatchEvent(&compositionUpdate, status);
+      if (lastFocusedWindow->IsDestroyed() ||
+          lastFocusedWindow != mLastFocusedWindow) {
+          PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
+              ("    NOTE, the focused widget was destroyed/changed by compositionupdate"));
+          return PR_FALSE;
+      }
+    }
+
     nsTextEvent textEvent(PR_TRUE, NS_TEXT_TEXT, mLastFocusedWindow);
     InitEvent(textEvent);
 
     PRUint32 targetOffset = mCompositionStart;
 
     nsAutoTArray<nsTextRange, 4> textRanges;
     if (aCheckAttr) {
+        // NOTE: SetTextRangeList() assumes that mDispatchedCompositionString
+        //       has been updated already.
         SetTextRangeList(textRanges);
         for (PRUint32 i = 0; i < textRanges.Length(); i++) {
             nsTextRange& range = textRanges[i];
             if (range.mRangeType == NS_TEXTRANGE_SELECTEDRAWTEXT ||
                 range.mRangeType == NS_TEXTRANGE_SELECTEDCONVERTEDTEXT) {
                 targetOffset += range.mStartOffset;
                 break;
             }
         }
     }
 
     textEvent.rangeCount = textRanges.Length();
     textEvent.rangeArray = textRanges.Elements();
-    textEvent.theText = mCompositionString.get();
+    textEvent.theText = mDispatchedCompositionString.get();
 
-    nsEventStatus status;
-    nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
     mLastFocusedWindow->DispatchEvent(&textEvent, status);
-    if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
-        kungFuDeathGrip != mLastFocusedWindow) {
+    if (lastFocusedWindow->IsDestroyed() ||
+        lastFocusedWindow != mLastFocusedWindow) {
         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
             ("    NOTE, the focused widget was destroyed/changed by text event"));
         return PR_FALSE;
     }
 
     SetCursorPosition(targetOffset);
 
     return PR_TRUE;
@@ -1332,18 +1354,18 @@ nsGtkIMModule::SetTextRangeList(nsTArray
             ("    mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
              range.mStartOffset, range.mEndOffset,
              GetRangeTypeName(range.mRangeType)));
     } while (pango_attr_iterator_next(iter));
 
     nsTextRange range;
     if (cursor_pos < 0) {
         range.mStartOffset = 0;
-    } else if (PRUint32(cursor_pos) > mCompositionString.Length()) {
-        range.mStartOffset = mCompositionString.Length();
+    } else if (PRUint32(cursor_pos) > mDispatchedCompositionString.Length()) {
+        range.mStartOffset = mDispatchedCompositionString.Length();
     } else {
         range.mStartOffset = PRUint32(cursor_pos);
     }
     range.mEndOffset = range.mStartOffset;
     range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
     aTextRangeList.AppendElement(range);
 
     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
--- a/widget/src/gtk2/nsGtkIMModule.h
+++ b/widget/src/gtk2/nsGtkIMModule.h
@@ -153,19 +153,19 @@ protected:
     IMEContext mIMEContext;
 
     // mCompositionStart is the start offset of the composition string in the
     // current content.  When <textarea> or <input> have focus, it means offset
     // from the first character of them.  When a HTML editor has focus, it
     // means offset from the first character of the root element of the editor.
     PRUint32 mCompositionStart;
 
-    // mCompositionString is the current composing string. Even if this is
-    // empty, we can be composing.  See mIsComposing.
-    nsString mCompositionString;
+    // mDispatchedCompositionString is the latest composition string which
+    // was dispatched by compositionupdate event.
+    nsString mDispatchedCompositionString;
 
     // OnKeyEvent() temporarily sets mProcessingKeyEvent to the given native
     // event.
     GdkEventKey* mProcessingKeyEvent;
 
 
     // mIsComposing is set to TRUE when we dispatch the composition start
     // event.  And it's set to FALSE when we dispatches the composition end
@@ -293,13 +293,14 @@ protected:
     PRBool CommitCompositionBy(const nsAString& aString);
 
     // Dispatches a composition start event or a composition end event.
     PRBool DispatchCompositionStart();
     PRBool DispatchCompositionEnd();
 
     // Dispatches a text event.  If aCheckAttr is TRUE, dispatches a committed
     // text event.  Otherwise, dispatches a composing text event.
-    PRBool DispatchTextEvent(PRBool aCheckAttr);
+    PRBool DispatchTextEvent(const nsAString& aCompositionString,
+                             PRBool aCheckAttr);
 
 };
 
 #endif // __nsGtkIMModule_h__
--- a/widget/src/qt/nsWindow.cpp
+++ b/widget/src/qt/nsWindow.cpp
@@ -2810,24 +2810,36 @@ nsEventStatus
 nsWindow::contextMenuEvent(QGraphicsSceneContextMenuEvent *)
 {
     return nsEventStatus_eIgnore;
 }
 
 nsEventStatus
 nsWindow::imComposeEvent(QInputMethodEvent *event, PRBool &handled)
 {
+    // XXX Needs to check whether this widget has been destroyed or not after
+    //     each DispatchEvent().
+
     nsCompositionEvent start(PR_TRUE, NS_COMPOSITION_START, this);
     DispatchEvent(&start);
 
+    nsAutoString compositionStr(event->commitString().utf16());
+
+    if (!compositionStr.IsEmpty()) {
+      nsCompositionEvent update(PR_TRUE, NS_COMPOSITION_UPDATE, this);
+      update.data = compositionStr;
+      DispatchEvent(&update);
+    }
+
     nsTextEvent text(PR_TRUE, NS_TEXT_TEXT, this);
-    text.theText.Assign(event->commitString().utf16());
+    text.theText = compositionStr;
     DispatchEvent(&text);
 
     nsCompositionEvent end(PR_TRUE, NS_COMPOSITION_END, this);
+    end.data = compositionStr;
     DispatchEvent(&end);
 
     return nsEventStatus_eIgnore;
 }
 
 nsIWidget *
 nsWindow::GetParent(void)
 {