Bug 805162 - c. Implement new Java to Gecko IME events in widget; r=blassey
authorJim Chen <nchen@mozilla.com>
Wed, 31 Oct 2012 17:35:31 -0400
changeset 111955 973b0ed30b8b73fc00da5051f18030f943a2d532
parent 111954 39851bbcfaa18fa04a04d5d2de345c4219e6052f
child 111956 a01a327e8ec461cc63746fe1cf0256a412900cb6
push id17349
push usernchen@mozilla.com
push dateWed, 31 Oct 2012 21:36:42 +0000
treeherdermozilla-inbound@533faa3c50ed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey
bugs805162
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 805162 - c. Implement new Java to Gecko IME events in widget; r=blassey
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -156,17 +156,19 @@ nsWindow::DumpWindows(const nsTArray<nsW
         DumpWindows(w->mChildren, indent+1);
     }
 }
 
 nsWindow::nsWindow() :
     mIsVisible(false),
     mParent(nullptr),
     mFocus(nullptr),
-    mIMEComposing(false)
+    mIMEComposing(false),
+    mIMEMaskSelectionUpdate(false),
+    mIMEMaskTextUpdate(false)
 {
 }
 
 nsWindow::~nsWindow()
 {
     gTopLevelWindows.RemoveElement(this);
     nsWindow *top = FindTopLevel();
     if (top->mFocus == this)
@@ -634,22 +636,26 @@ nsWindow::DispatchEvent(nsGUIEvent *aEve
 nsEventStatus
 nsWindow::DispatchEvent(nsGUIEvent *aEvent)
 {
     if (mWidgetListener) {
         nsEventStatus status = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
 
         switch (aEvent->message) {
         case NS_COMPOSITION_START:
+            MOZ_ASSERT(!mIMEComposing);
             mIMEComposing = true;
             break;
         case NS_COMPOSITION_END:
+            MOZ_ASSERT(mIMEComposing);
             mIMEComposing = false;
+            mIMEComposingText.Truncate();
             break;
         case NS_TEXT_TEXT:
+            MOZ_ASSERT(mIMEComposing);
             mIMEComposingText = static_cast<nsTextEvent*>(aEvent)->theText;
             break;
         case NS_KEY_PRESS:
             // Sometimes the text changes after a key press do not generate notifications (see Bug 723810)
             // Call the corresponding methods explicitly to send those changes back to Java
             OnIMETextChange(0, 0, 0);
             OnIMESelectionChange();
             break;
@@ -1746,16 +1752,17 @@ nsWindow::HandleSpecialKey(AndroidGeckoE
         DispatchEvent(&event);
     }
 }
 
 void
 nsWindow::OnKeyEvent(AndroidGeckoEvent *ae)
 {
     nsRefPtr<nsWindow> kungFuDeathGrip(this);
+    RemoveIMEComposition();
     uint32_t msg;
     switch (ae->Action()) {
     case AndroidKeyEvent::ACTION_DOWN:
         msg = NS_KEY_DOWN;
         break;
     case AndroidKeyEvent::ACTION_UP:
         msg = NS_KEY_UP;
         break;
@@ -1811,192 +1818,246 @@ nsWindow::OnKeyEvent(AndroidGeckoEvent *
 }
 
 #ifdef DEBUG_ANDROID_IME
 #define ALOGIME(args...) ALOG(args)
 #else
 #define ALOGIME(args...)
 #endif
 
+static nscolor
+ConvertAndroidColor(uint32_t c)
+{
+    return NS_RGBA((c & 0x000000ff),
+                   (c & 0x0000ff00) >> 8,
+                   (c & 0x00ff0000) >> 16,
+                   (c & 0xff000000) >> 24);
+}
+
+/*
+    Remove the composition but leave the text content as-is
+*/
 void
-nsWindow::OnIMEAddRange(AndroidGeckoEvent *ae)
+nsWindow::RemoveIMEComposition()
 {
-    //ALOGIME("IME: IME_ADD_RANGE");
-    nsTextRange range;
-    range.mStartOffset = ae->Offset();
-    range.mEndOffset = range.mStartOffset + ae->Count();
-    range.mRangeType = ae->RangeType();
-    range.mRangeStyle.mDefinedStyles = ae->RangeStyles();
-    range.mRangeStyle.mLineStyle = nsTextRangeStyle::LINESTYLE_SOLID;
-    range.mRangeStyle.mForegroundColor = NS_RGBA(
-        ((ae->RangeForeColor() >> 16) & 0xff),
-        ((ae->RangeForeColor() >> 8) & 0xff),
-        (ae->RangeForeColor() & 0xff),
-        ((ae->RangeForeColor() >> 24) & 0xff));
-    range.mRangeStyle.mBackgroundColor = NS_RGBA(
-        ((ae->RangeBackColor() >> 16) & 0xff),
-        ((ae->RangeBackColor() >> 8) & 0xff),
-        (ae->RangeBackColor() & 0xff),
-        ((ae->RangeBackColor() >> 24) & 0xff));
-    mIMERanges.AppendElement(range);
-    return;
+    // Remove composition on Gecko side
+    if (!mIMEComposing)
+        return;
+
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
+    bool savedMaskSelection = mIMEMaskSelectionUpdate;
+    bool savedMaskText = mIMEMaskTextUpdate;
+    mIMEMaskSelectionUpdate = mIMEMaskTextUpdate = true;
+
+    nsTextEvent textEvent(true, NS_TEXT_TEXT, this);
+    InitEvent(textEvent, nullptr);
+    textEvent.theText = mIMEComposingText;
+    DispatchEvent(&textEvent);
+
+    nsCompositionEvent event(true, NS_COMPOSITION_END, this);
+    InitEvent(event, nullptr);
+    DispatchEvent(&event);
+
+    mIMEMaskSelectionUpdate = savedMaskSelection;
+    mIMEMaskTextUpdate = savedMaskText;
 }
 
 void
 nsWindow::OnIMEEvent(AndroidGeckoEvent *ae)
 {
+    MOZ_ASSERT(!mIMEMaskTextUpdate);
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+    /*
+        Rules for managing IME between Gecko and Java:
+
+        * Gecko controls the text content, and Java shadows the Gecko text
+           through text updates
+        * Java controls the selection, and Gecko shadows the Java selection
+           through set selection events
+        * Java controls the composition, and Gecko shadows the Java
+           composition through update composition events
+    */
     nsRefPtr<nsWindow> kungFuDeathGrip(this);
     switch (ae->Action()) {
-    case AndroidGeckoEvent::IME_COMPOSITION_END:
+    case AndroidGeckoEvent::IME_SYNCHRONIZE:
+        {
+            AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT, 0);
+        }
+        break;
+    case AndroidGeckoEvent::IME_REPLACE_TEXT:
         {
-            ALOGIME("IME: IME_COMPOSITION_END");
-            MOZ_ASSERT(mIMEComposing,
-                       "IME_COMPOSITION_END when we are not composing?!");
+            /*
+                Replace text in Gecko thread from ae->Start() to ae->End()
+                  with the string ae->Characters()
+
+                Selection updates are masked so the result of our temporary
+                  selection event is not passed on to Java
 
-            nsCompositionEvent event(true, NS_COMPOSITION_END, this);
-            InitEvent(event, nullptr);
-            event.data = mIMELastDispatchedComposingText;
-            mIMELastDispatchedComposingText.Truncate();
-            DispatchEvent(&event);
+                Text updates are passed on, so the Java text can shadow the
+                  Gecko text
+            */
+            mIMEMaskSelectionUpdate = true;
+            RemoveIMEComposition();
+            {
+                nsSelectionEvent event(true, NS_SELECTION_SET, this);
+                InitEvent(event, nullptr);
+                event.mOffset = uint32_t(ae->Start());
+                event.mLength = uint32_t(ae->End() - ae->Start());
+                event.mExpandToClusterBoundary = false;
+                DispatchEvent(&event);
+            }
+            {
+                nsCompositionEvent event(true, NS_COMPOSITION_START, this);
+                InitEvent(event, nullptr);
+                DispatchEvent(&event);
+            }
+            {
+                nsTextEvent event(true, NS_TEXT_TEXT, this);
+                InitEvent(event, nullptr);
+                event.theText = ae->Characters();
+                DispatchEvent(&event);
+            }
+            {
+                nsCompositionEvent event(true, NS_COMPOSITION_END, this);
+                InitEvent(event, nullptr);
+                event.data = ae->Characters();
+                DispatchEvent(&event);
+            }
+            AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT, 0);
+            mIMEMaskSelectionUpdate = false;
         }
-        return;
-    case AndroidGeckoEvent::IME_COMPOSITION_BEGIN:
+        break;
+    case AndroidGeckoEvent::IME_SET_SELECTION:
         {
-            ALOGIME("IME: IME_COMPOSITION_BEGIN");
-            MOZ_ASSERT(!mIMEComposing,
-                       "IME_COMPOSITION_BEGIN when we are already composing?!");
+            /*
+                Set Gecko selection to ae->Start() to ae->End()
 
-            mIMELastDispatchedComposingText.Truncate();
-            nsCompositionEvent event(true, NS_COMPOSITION_START, this);
-            InitEvent(event, nullptr);
-            DispatchEvent(&event);
+                Selection updates are masked to prevent Java from being
+                  notified of the new selection
+            */
+            MOZ_ASSERT(!mIMEMaskTextUpdate);
+            mIMEMaskSelectionUpdate = true;
+            nsSelectionEvent selEvent(true, NS_SELECTION_SET, this);
+            InitEvent(selEvent, nullptr);
+
+            int32_t start = ae->Start(), end = ae->End();
+
+            if (start < 0 || end < 0) {
+                nsQueryContentEvent event(true, NS_QUERY_SELECTED_TEXT, this);
+                InitEvent(event, nullptr);
+                DispatchEvent(&event);
+                MOZ_ASSERT(event.mSucceeded && !event.mWasAsync);
+
+                if (start < 0)
+                    start = int32_t(event.GetSelectionStart());
+                if (end < 0)
+                    end = int32_t(event.GetSelectionEnd());
+            }
+
+            selEvent.mOffset = std::min(start, end);
+            selEvent.mLength = std::max(start, end) - selEvent.mOffset;
+            selEvent.mReversed = start > end;
+            selEvent.mExpandToClusterBoundary = false;
+
+            DispatchEvent(&selEvent);
+            mIMEMaskSelectionUpdate = false;
         }
-        return;
-    case AndroidGeckoEvent::IME_ADD_RANGE:
+        break;
+    case AndroidGeckoEvent::IME_ADD_COMPOSITION_RANGE:
         {
-            NS_ASSERTION(mIMEComposing,
-                         "IME_ADD_RANGE when we are not composing?!");
-            OnIMEAddRange(ae);
+            nsTextRange range;
+            range.mStartOffset = ae->Start();
+            range.mEndOffset = ae->End();
+            range.mRangeType = ae->RangeType();
+            range.mRangeStyle.mDefinedStyles = ae->RangeStyles();
+            range.mRangeStyle.mLineStyle = nsTextRangeStyle::LINESTYLE_SOLID;
+            range.mRangeStyle.mForegroundColor =
+                    ConvertAndroidColor(uint32_t(ae->RangeForeColor()));
+            range.mRangeStyle.mBackgroundColor =
+                    ConvertAndroidColor(uint32_t(ae->RangeBackColor()));
+            mIMERanges.AppendElement(range);
         }
-        return;
-    case AndroidGeckoEvent::IME_SET_TEXT:
+        break;
+    case AndroidGeckoEvent::IME_UPDATE_COMPOSITION:
         {
-            NS_ASSERTION(mIMEComposing,
-                         "IME_SET_TEXT when we are not composing?!");
+            /*
+                Update the composition from ae->Start() to ae->End() using
+                  information from added ranges. This is only used for
+                  visual indication and does not affect the text content.
+                  Only the offsets are specified and not the text content
+                  to eliminate the possibility of this event altering the
+                  text content unintentionally.
 
-            OnIMEAddRange(ae);
+                Selection and text updates are masked so the result of
+                  temporary events are not passed on to Java
+            */
+            mIMEMaskSelectionUpdate = mIMEMaskTextUpdate = true;
+            RemoveIMEComposition();
 
             nsTextEvent event(true, NS_TEXT_TEXT, this);
             InitEvent(event, nullptr);
 
-            event.theText.Assign(ae->Characters());
             event.rangeArray = mIMERanges.Elements();
             event.rangeCount = mIMERanges.Length();
 
+            {
+                nsSelectionEvent event(true, NS_SELECTION_SET, this);
+                InitEvent(event, nullptr);
+                event.mOffset = uint32_t(ae->Start());
+                event.mLength = uint32_t(ae->End() - ae->Start());
+                event.mExpandToClusterBoundary = false;
+                DispatchEvent(&event);
+            }
+            {
+                nsQueryContentEvent queryEvent(true,
+                        NS_QUERY_SELECTED_TEXT, this);
+                InitEvent(queryEvent, nullptr);
+                DispatchEvent(&queryEvent);
+                MOZ_ASSERT(queryEvent.mSucceeded && !queryEvent.mWasAsync);
+                event.theText = queryEvent.mReply.mString;
+            }
+            {
+                nsCompositionEvent event(true, NS_COMPOSITION_START, this);
+                InitEvent(event, nullptr);
+                DispatchEvent(&event);
+            }
+
             if (mIMEComposing &&
-                event.theText != mIMELastDispatchedComposingText) {
+                event.theText != mIMEComposingText) {
                 nsCompositionEvent compositionUpdate(true,
                                                      NS_COMPOSITION_UPDATE,
                                                      this);
                 InitEvent(compositionUpdate, nullptr);
                 compositionUpdate.data = event.theText;
-                mIMELastDispatchedComposingText = event.theText;
                 DispatchEvent(&compositionUpdate);
                 if (Destroyed())
                     return;
             }
 
 #ifdef DEBUG_ANDROID_IME
             const NS_ConvertUTF16toUTF8 theText8(event.theText);
             const char* text = theText8.get();
             ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%u",
                     text, event.theText.Length(), mIMERanges.Length());
 #endif // DEBUG_ANDROID_IME
 
             DispatchEvent(&event);
             mIMERanges.Clear();
+            mIMEMaskSelectionUpdate = mIMEMaskTextUpdate = false;
         }
-        return;
-    case AndroidGeckoEvent::IME_GET_TEXT:
-        {
-            ALOGIME("IME: IME_GET_TEXT: o=%u, l=%u", ae->Offset(), ae->Count());
-
-            nsQueryContentEvent event(true, NS_QUERY_TEXT_CONTENT, this);
-            InitEvent(event, nullptr);
-
-            event.InitForQueryTextContent(ae->Offset(), ae->Count());
-            
-            DispatchEvent(&event);
-
-            if (!event.mSucceeded) {
-                ALOGIME("IME:     -> failed");
-                AndroidBridge::Bridge()->ReturnIMEQueryResult(
-                    nullptr, 0, 0, 0);
-                return;
-            } else if (!event.mWasAsync) {
-                AndroidBridge::Bridge()->ReturnIMEQueryResult(
-                    event.mReply.mString.get(), 
-                    event.mReply.mString.Length(), 0, 0);
-            }
-        }
-        return;
-    case AndroidGeckoEvent::IME_DELETE_TEXT:
-        {
-            ALOGIME("IME: IME_DELETE_TEXT");
-            NS_ASSERTION(mIMEComposing,
-                         "IME_DELETE_TEXT when we are not composing?!");
-
-            nsKeyEvent event(true, NS_KEY_PRESS, this);
-            ANPEvent pluginEvent;
-            InitKeyEvent(event, *ae, &pluginEvent);
-            event.keyCode = NS_VK_BACK;
-            DispatchEvent(&event);
-        }
-        return;
-    case AndroidGeckoEvent::IME_SET_SELECTION:
+        break;
+    case AndroidGeckoEvent::IME_REMOVE_COMPOSITION:
         {
-            ALOGIME("IME: IME_SET_SELECTION: o=%u, l=%d", ae->Offset(), ae->Count());
-
-            nsSelectionEvent selEvent(true, NS_SELECTION_SET, this);
-            InitEvent(selEvent, nullptr);
-
-            selEvent.mOffset = uint32_t(ae->Count() >= 0 ?
-                                        ae->Offset() :
-                                        ae->Offset() + ae->Count());
-            selEvent.mLength = uint32_t(NS_ABS(ae->Count()));
-            selEvent.mReversed = ae->Count() >= 0 ? false : true;
-            selEvent.mExpandToClusterBoundary = false;
-
-            DispatchEvent(&selEvent);
+            RemoveIMEComposition();
+            mIMERanges.Clear();
         }
-        return;
-    case AndroidGeckoEvent::IME_GET_SELECTION:
-        {
-            ALOGIME("IME: IME_GET_SELECTION");
-
-            nsQueryContentEvent event(true, NS_QUERY_SELECTED_TEXT, this);
-            InitEvent(event, nullptr);
-            DispatchEvent(&event);
-
-            if (!event.mSucceeded) {
-                ALOGIME("IME:     -> failed");
-                AndroidBridge::Bridge()->ReturnIMEQueryResult(
-                    nullptr, 0, 0, 0);
-                return;
-            } else if (!event.mWasAsync) {
-                AndroidBridge::Bridge()->ReturnIMEQueryResult(
-                    event.mReply.mString.get(),
-                    event.mReply.mString.Length(), 
-                    event.GetSelectionStart(),
-                    event.GetSelectionEnd() - event.GetSelectionStart());
-            }
-            //ALOGIME("IME:     -> o=%u, l=%u", event.mReply.mOffset, event.mReply.mString.Length());
-        }
-        return;
+        break;
     }
+    MOZ_ASSERT(!mIMEMaskTextUpdate);
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
 }
 
 nsWindow *
 nsWindow::FindWindowForPoint(const nsIntPoint& pt)
 {
     if (!mBounds.Contains(pt))
         return nullptr;
 
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -161,35 +161,35 @@ public:
 #endif
 
 protected:
     void BringToFront();
     nsWindow *FindTopLevel();
     bool DrawTo(gfxASurface *targetSurface);
     bool DrawTo(gfxASurface *targetSurface, const nsIntRect &aRect);
     bool IsTopLevel();
-    void OnIMEAddRange(mozilla::AndroidGeckoEvent *ae);
+    void RemoveIMEComposition();
 
     // Call this function when the users activity is the direct cause of an
     // event (like a keypress or mouse click).
     void UserActivity();
 
     bool mIsVisible;
     nsTArray<nsWindow*> mChildren;
     nsWindow* mParent;
     nsWindow* mFocus;
 
     double mStartDist;
     double mLastDist;
 
     nsCOMPtr<nsIIdleServiceInternal> mIdleService;
 
     bool mIMEComposing;
+    bool mIMEMaskSelectionUpdate, mIMEMaskTextUpdate;
     nsString mIMEComposingText;
-    nsString mIMELastDispatchedComposingText;
     nsAutoTArray<nsTextRange, 4> mIMERanges;
 
     InputContext mInputContext;
 
     static void DumpWindows();
     static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
     static void LogWindow(nsWindow *win, int index, int indent);