Bug 705057 part.2 Dispatch composition events on removed content for canceling the composition if widget's IME APIs don't work r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 26 Sep 2012 14:47:51 +0900
changeset 110602 bbb31887247b728a892e5cdd7c0d8758d265fd29
parent 110601 cced04530e2f56dff6b4955756090340c9d5c335
child 110603 bc1f210a91f6b8f5975d8e9bf9ebc541d536e3a5
push id23700
push userryanvm@gmail.com
push dateThu, 18 Oct 2012 02:10:26 +0000
treeherdermozilla-central@5142bbd4da12 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs705057
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 705057 part.2 Dispatch composition events on removed content for canceling the composition if widget's IME APIs don't work r=smaug
content/events/src/TextComposition.cpp
content/events/src/TextComposition.h
content/events/src/nsIMEStateManager.cpp
--- a/content/events/src/TextComposition.cpp
+++ b/content/events/src/TextComposition.cpp
@@ -1,18 +1,20 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "TextComposition.h"
+#include "nsContentEventHandler.h"
 #include "nsContentUtils.h"
 #include "nsEventDispatcher.h"
 #include "nsGUIEvent.h"
+#include "nsIMEStateManager.h"
 #include "nsIPresShell.h"
 #include "nsIWidget.h"
 #include "nsPresContext.h"
 
 namespace mozilla {
 
 /******************************************************************************
  * TextComposition
@@ -28,16 +30,17 @@ TextComposition::TextComposition(nsPresC
 {
 }
 
 TextComposition::TextComposition(const TextComposition& aOther)
 {
   mNativeContext = aOther.mNativeContext;
   mPresContext = aOther.mPresContext;
   mNode = aOther.mNode;
+  mLastData = aOther.mLastData;
 }
 
 bool
 TextComposition::MatchesNativeContext(nsIWidget* aWidget) const
 {
   // temporarily, we should assume that one native IME context is per one
   // native widget.
   return mNativeContext == static_cast<void*>(aWidget);
@@ -50,20 +53,105 @@ TextComposition::MatchesEventTarget(nsPr
   return mPresContext == aPresContext && mNode == aNode;
 }
 
 void
 TextComposition::DispatchEvent(nsGUIEvent* aEvent,
                                nsEventStatus* aStatus,
                                nsDispatchingCallback* aCallBack)
 {
+  if (aEvent->message == NS_COMPOSITION_UPDATE) {
+    mLastData = static_cast<nsCompositionEvent*>(aEvent)->data;
+  }
+
   nsEventDispatcher::Dispatch(mNode, mPresContext,
                               aEvent, nullptr, aStatus, aCallBack);
 }
 
+void
+TextComposition::DispatchCompsotionEventRunnable(uint32_t aEventMessage,
+                                                 const nsAString& aData)
+{
+  nsContentUtils::AddScriptRunner(
+    new CompositionEventDispatcher(mPresContext, mNode,
+                                   aEventMessage, aData));
+}
+
+void
+TextComposition::SynthesizeCommit(bool aDiscard)
+{
+  // backup this instance and use it since this instance might be destroyed
+  // by nsIMEStateManager if this is managed by it.
+  TextComposition composition = *this;
+  nsAutoString data(aDiscard ? EmptyString() : composition.mLastData);
+  if (composition.mLastData != data) {
+    composition.DispatchCompsotionEventRunnable(NS_COMPOSITION_UPDATE, data);
+    composition.DispatchCompsotionEventRunnable(NS_TEXT_TEXT, data);
+  }
+  composition.DispatchCompsotionEventRunnable(NS_COMPOSITION_END, data);
+}
+
+/******************************************************************************
+ * TextComposition::CompositionEventDispatcher
+ ******************************************************************************/
+
+TextComposition::CompositionEventDispatcher::CompositionEventDispatcher(
+                                               nsPresContext* aPresContext,
+                                               nsINode* aEventTarget,
+                                               uint32_t aEventMessage,
+                                               const nsAString& aData) :
+  mPresContext(aPresContext), mEventTarget(aEventTarget),
+  mEventMessage(aEventMessage), mData(aData)
+{
+  mWidget = mPresContext->GetNearestWidget();
+}
+
+NS_IMETHODIMP
+TextComposition::CompositionEventDispatcher::Run()
+{
+  if (!mPresContext->GetPresShell() ||
+      mPresContext->GetPresShell()->IsDestroying()) {
+    return NS_OK; // cannot dispatch any events anymore
+  }
+
+  nsEventStatus status = nsEventStatus_eIgnore;
+  switch (mEventMessage) {
+    case NS_COMPOSITION_START: {
+      nsCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
+      nsQueryContentEvent selectedText(true, NS_QUERY_SELECTED_TEXT, mWidget);
+      nsContentEventHandler handler(mPresContext);
+      handler.OnQuerySelectedText(&selectedText);
+      NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
+      compStart.data = selectedText.mReply.mString;
+      nsIMEStateManager::DispatchCompositionEvent(mEventTarget, mPresContext,
+                                                  &compStart, &status, nullptr);
+      break;
+    }
+    case NS_COMPOSITION_UPDATE:
+    case NS_COMPOSITION_END: {
+      nsCompositionEvent compEvent(true, mEventMessage, mWidget);
+      compEvent.data = mData;
+      nsIMEStateManager::DispatchCompositionEvent(mEventTarget, mPresContext,
+                                                  &compEvent, &status, nullptr);
+      break;
+    }
+    case NS_TEXT_TEXT: {
+      nsTextEvent textEvent(true, NS_TEXT_TEXT, mWidget);
+      textEvent.theText = mData;
+      nsIMEStateManager::DispatchCompositionEvent(mEventTarget, mPresContext,
+                                                  &textEvent, &status, nullptr);
+      break;
+    }
+    default:
+      MOZ_NOT_REACHED("Unsupported event");
+      break;
+  }
+  return NS_OK;
+}
+
 /******************************************************************************
  * TextCompositionArray
  ******************************************************************************/
 
 TextCompositionArray::index_type
 TextCompositionArray::IndexOf(nsIWidget* aWidget)
 {
   for (index_type i = Length(); i > 0; --i) {
--- a/content/events/src/TextComposition.h
+++ b/content/events/src/TextComposition.h
@@ -5,17 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_TextComposition_h
 #define mozilla_TextComposition_h
 
 #include "nsCOMPtr.h"
 #include "nsEvent.h"
 #include "nsINode.h"
+#include "nsString.h"
 #include "nsTArray.h"
+#include "nsThreadUtils.h"
 #include "mozilla/Attributes.h"
 
 class nsCompositionEvent;
 class nsDispatchingCallback;
 class nsIMEStateManager;
 class nsIWidget;
 class nsPresContext;
 
@@ -39,43 +41,96 @@ public:
 
   ~TextComposition()
   {
     // WARNING: mPresContext may be destroying, so, be careful if you touch it.
   }
 
   nsPresContext* GetPresContext() const { return mPresContext; }
   nsINode* GetEventTargetNode() const { return mNode; }
+  // The latest CompositionEvent.data value except compositionstart event.
+  const nsString& GetLastData() const { return mLastData; }
 
   bool MatchesNativeContext(nsIWidget* aWidget) const;
   bool MatchesEventTarget(nsPresContext* aPresContext,
                           nsINode* aNode) const;
 
+  /**
+   * SynthesizeCommit() dispatches compositionupdate, text and compositionend
+   * events for emulating commit on the content.
+   *
+   * @param aDiscard true when committing with empty string.  Otherwise, false.
+   */
+  void SynthesizeCommit(bool aDiscard);
+
 private:
   // This class holds nsPresContext weak.  This instance shouldn't block
   // destroying it.  When the presContext is being destroyed, it's notified to
   // nsIMEStateManager::OnDestroyPresContext(), and then, it destroy
   // this instance.
   nsPresContext* mPresContext;
   nsCOMPtr<nsINode> mNode;
 
   // mNativeContext stores a opaque pointer.  This works as the "ID" for this
   // composition.  Don't access the instance, it may not be available.
   void* mNativeContext;
 
+  // mLastData stores the data attribute of the latest composition event (except
+  // the compositionstart event).
+  nsString mLastData;
+
   // Hide the default constructor
   TextComposition() {}
 
   /**
    * DispatchEvent() dispatches the aEvent to the mContent synchronously.
    * The caller must ensure that it's safe to dispatch the event.
    */
   void DispatchEvent(nsGUIEvent* aEvent,
                      nsEventStatus* aStatus,
                      nsDispatchingCallback* aCallBack);
+
+  /**
+   * CompositionEventDispatcher dispatches the specified composition (or text)
+   * event.
+   */
+  class CompositionEventDispatcher : public nsRunnable
+  {
+  public:
+    CompositionEventDispatcher(nsPresContext* aPresContext,
+                               nsINode* aEventTarget,
+                               uint32_t aEventMessage,
+                               const nsAString& aData);
+    NS_IMETHOD Run();
+
+  private:
+    nsRefPtr<nsPresContext> mPresContext;
+    nsCOMPtr<nsINode> mEventTarget;
+    nsCOMPtr<nsIWidget> mWidget;
+    uint32_t mEventMessage;
+    nsString mData;
+
+    CompositionEventDispatcher() {};
+  };
+
+  /**
+   * DispatchCompsotionEventRunnable() dispatches a composition or text event
+   * to the content.  Be aware, if you use this method, nsPresShellEventCB
+   * isn't used.  That means that nsIFrame::HandleEvent() is never called.
+   * WARNING: The instance which is managed by nsIMEStateManager may be
+   *          destroyed by this method call.
+   *
+   * @param aEventMessage       Must be one of composition event or text event.
+   * @param aData               Used for data value if aEventMessage is
+   *                            NS_COMPOSITION_UPDATE or NS_COMPOSITION_END.
+   *                            Used for theText value if aEventMessage is
+   *                            NS_TEXT_TEXT.
+   */
+  void DispatchCompsotionEventRunnable(uint32_t aEventMessage,
+                                       const nsAString& aData);
 };
 
 /**
  * TextCompositionArray manages the instances of TextComposition class.
  * Managing with array is enough because only one composition is typically
  * there.  Even if user switches native IME context, it's very rare that
  * second or more composition is started.
  * It's assumed that this is used by nsIMEStateManager for storing all active
--- a/content/events/src/nsIMEStateManager.cpp
+++ b/content/events/src/nsIMEStateManager.cpp
@@ -97,30 +97,43 @@ nsIMEStateManager::OnRemoveContent(nsPre
                                    nsIContent* aContent)
 {
   NS_ENSURE_ARG_POINTER(aPresContext);
 
   // First, if there is a composition in the aContent, clean up it.
   if (sTextCompositions) {
     TextComposition* compositionInContent =
       sTextCompositions->GetCompositionInContent(aPresContext, aContent);
+
     if (compositionInContent) {
+      // Store the composition before accessing the native IME.
+      TextComposition storedComposition = *compositionInContent;
       // Try resetting the native IME state.  Be aware, typically, this method
       // is called during the content being removed.  Then, the native
       // composition events which are caused by following APIs are ignored due
       // to unsafe to run script (in PresShell::HandleEvent()).
       nsCOMPtr<nsIWidget> widget = aPresContext->GetNearestWidget();
       if (widget) {
         nsresult rv = widget->CancelIMEComposition();
         if (NS_FAILED(rv)) {
           widget->ResetInputState();
         }
-        // WARNING: the |compositionInContent| may have been destroyed.
+        // By calling the APIs, the composition may have been finished normally.
+        compositionInContent =
+          sTextCompositions->GetCompositionFor(
+                               storedComposition.GetPresContext(),
+                               storedComposition.GetEventTargetNode());
       }
     }
+
+    // If the compositionInContent is still available, we should finish the
+    // composition just on the content forcibly.
+    if (compositionInContent) {
+      compositionInContent->SynthesizeCommit(true);
+    }
   }
 
   if (!sPresContext || !sContent ||
       !nsContentUtils::ContentIsDescendantOf(sContent, aContent)) {
     return NS_OK;
   }
 
   // Current IME transaction should commit