Bug 705057 part.1 Ensure a set of composition events is fired on same content r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 26 Sep 2012 14:47:45 +0900
changeset 110733 cced04530e2f56dff6b4955756090340c9d5c335
parent 110732 35b7bc10cc4289773f3add34907863861c9ef9b5
child 110734 bbb31887247b728a892e5cdd7c0d8758d265fd29
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewerssmaug
bugs705057
milestone19.0a1
Bug 705057 part.1 Ensure a set of composition events is fired on same content r=smaug
content/events/src/Makefile.in
content/events/src/TextComposition.cpp
content/events/src/TextComposition.h
content/events/src/nsEventStateManager.cpp
content/events/src/nsIMEStateManager.cpp
content/events/src/nsIMEStateManager.h
dom/base/nsFocusManager.cpp
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/base/nsPresShell.cpp
layout/base/tests/bug746993-1.html
layout/build/nsLayoutStatics.cpp
testing/mochitest/tests/SimpleTest/EventUtils.js
testing/mochitest/tests/test_sanityEventUtils.html
--- a/content/events/src/Makefile.in
+++ b/content/events/src/Makefile.in
@@ -56,16 +56,17 @@ CPPSRCS		= \
 		nsDOMSimpleGestureEvent.cpp \
 		nsDOMEventTargetHelper.cpp \
 		nsDOMScrollAreaEvent.cpp \
 		nsDOMTransitionEvent.cpp \
 		nsDOMAnimationEvent.cpp \
 		nsDOMTouchEvent.cpp \
 		nsDOMCompositionEvent.cpp \
 		DOMWheelEvent.cpp \
+		TextComposition.cpp \
 		$(NULL)
 
 ifdef MOZ_B2G_RIL
 CPPSRCS += \
     nsDOMWifiEvent.cpp \
     $(NULL)
 endif
 
new file mode 100644
--- /dev/null
+++ b/content/events/src/TextComposition.cpp
@@ -0,0 +1,136 @@
+/* -*- 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 "nsContentUtils.h"
+#include "nsEventDispatcher.h"
+#include "nsGUIEvent.h"
+#include "nsIPresShell.h"
+#include "nsIWidget.h"
+#include "nsPresContext.h"
+
+namespace mozilla {
+
+/******************************************************************************
+ * TextComposition
+ ******************************************************************************/
+
+TextComposition::TextComposition(nsPresContext* aPresContext,
+                                 nsINode* aNode,
+                                 nsGUIEvent* aEvent) :
+  mPresContext(aPresContext), mNode(aNode),
+  // temporarily, we should assume that one native IME context is per native
+  // widget.
+  mNativeContext(aEvent->widget)
+{
+}
+
+TextComposition::TextComposition(const TextComposition& aOther)
+{
+  mNativeContext = aOther.mNativeContext;
+  mPresContext = aOther.mPresContext;
+  mNode = aOther.mNode;
+}
+
+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);
+}
+
+bool
+TextComposition::MatchesEventTarget(nsPresContext* aPresContext,
+                                    nsINode* aNode) const
+{
+  return mPresContext == aPresContext && mNode == aNode;
+}
+
+void
+TextComposition::DispatchEvent(nsGUIEvent* aEvent,
+                               nsEventStatus* aStatus,
+                               nsDispatchingCallback* aCallBack)
+{
+  nsEventDispatcher::Dispatch(mNode, mPresContext,
+                              aEvent, nullptr, aStatus, aCallBack);
+}
+
+/******************************************************************************
+ * TextCompositionArray
+ ******************************************************************************/
+
+TextCompositionArray::index_type
+TextCompositionArray::IndexOf(nsIWidget* aWidget)
+{
+  for (index_type i = Length(); i > 0; --i) {
+    if (ElementAt(i - 1).MatchesNativeContext(aWidget)) {
+      return i - 1;
+    }
+  }
+  return NoIndex;
+}
+
+TextCompositionArray::index_type
+TextCompositionArray::IndexOf(nsPresContext* aPresContext)
+{
+  for (index_type i = Length(); i > 0; --i) {
+    if (ElementAt(i - 1).GetPresContext() == aPresContext) {
+      return i - 1;
+    }
+  }
+  return NoIndex;
+}
+
+TextCompositionArray::index_type
+TextCompositionArray::IndexOf(nsPresContext* aPresContext,
+                              nsINode* aNode)
+{
+  index_type index = IndexOf(aPresContext);
+  if (index == NoIndex) {
+    return NoIndex;
+  }
+  nsINode* node = ElementAt(index).GetEventTargetNode();
+  return node == aNode ? index : NoIndex;
+}
+
+TextComposition*
+TextCompositionArray::GetCompositionFor(nsIWidget* aWidget)
+{
+  index_type i = IndexOf(aWidget);
+  return i != NoIndex ? &ElementAt(i) : nullptr;
+}
+
+TextComposition*
+TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext)
+{
+  index_type i = IndexOf(aPresContext);
+  return i != NoIndex ? &ElementAt(i) : nullptr;
+}
+
+TextComposition*
+TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext,
+                                           nsINode* aNode)
+{
+  index_type i = IndexOf(aPresContext, aNode);
+  return i != NoIndex ? &ElementAt(i) : nullptr;
+}
+
+TextComposition*
+TextCompositionArray::GetCompositionInContent(nsPresContext* aPresContext,
+                                              nsIContent* aContent)
+{
+  // There should be only one composition per content object.
+  for (index_type i = Length(); i > 0; --i) {
+    nsINode* node = ElementAt(i - 1).GetEventTargetNode();
+    if (node && nsContentUtils::ContentIsDescendantOf(node, aContent)) {
+      return &ElementAt(i - 1);
+    }
+  }
+  return nullptr;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/events/src/TextComposition.h
@@ -0,0 +1,103 @@
+/* -*- 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/. */
+
+#ifndef mozilla_TextComposition_h
+#define mozilla_TextComposition_h
+
+#include "nsCOMPtr.h"
+#include "nsEvent.h"
+#include "nsINode.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+class nsCompositionEvent;
+class nsDispatchingCallback;
+class nsIMEStateManager;
+class nsIWidget;
+class nsPresContext;
+
+namespace mozilla {
+
+/**
+ * TextComposition represents a text composition.  This class stores the
+ * composition event target and its presContext.  At dispatching the event via
+ * this class, the instances use the stored event target.
+ */
+
+class TextComposition MOZ_FINAL
+{
+  friend class ::nsIMEStateManager;
+public:
+  TextComposition(nsPresContext* aPresContext,
+                  nsINode* aNode,
+                  nsGUIEvent* aEvent);
+
+  TextComposition(const TextComposition& aOther);
+
+  ~TextComposition()
+  {
+    // WARNING: mPresContext may be destroying, so, be careful if you touch it.
+  }
+
+  nsPresContext* GetPresContext() const { return mPresContext; }
+  nsINode* GetEventTargetNode() const { return mNode; }
+
+  bool MatchesNativeContext(nsIWidget* aWidget) const;
+  bool MatchesEventTarget(nsPresContext* aPresContext,
+                          nsINode* aNode) const;
+
+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;
+
+  // 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);
+};
+
+/**
+ * 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
+ * compositions in the process.  If the instance is it, each TextComposition
+ * in the array can be destroyed by calling some methods of itself.
+ */
+
+class TextCompositionArray MOZ_FINAL : public nsAutoTArray<TextComposition, 2>
+{
+public:
+  index_type IndexOf(nsIWidget* aWidget);
+  index_type IndexOf(nsPresContext* aPresContext);
+  index_type IndexOf(nsPresContext* aPresContext, nsINode* aNode);
+
+  TextComposition* GetCompositionFor(nsIWidget* aWidget);
+  TextComposition* GetCompositionFor(nsPresContext* aPresContext);
+  TextComposition* GetCompositionFor(nsPresContext* aPresContext,
+                                     nsINode* aNode);
+  TextComposition* GetCompositionInContent(nsPresContext* aPresContext,
+                                           nsIContent* aContent);
+};
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_TextComposition_h
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -4881,16 +4881,18 @@ nsEventStateManager::ContentRemoved(nsID
   if (aContent->IsHTML() &&
       (aContent->Tag() == nsGkAtoms::a || aContent->Tag() == nsGkAtoms::area) &&
       (aContent->AsElement()->State().HasAtLeastOneOfStates(NS_EVENT_STATE_FOCUS |
                                                             NS_EVENT_STATE_HOVER))) {
     nsGenericHTMLElement* element = static_cast<nsGenericHTMLElement*>(aContent);
     element->LeaveLink(element->GetPresContext());
   }
 
+  nsIMEStateManager::OnRemoveContent(mPresContext, aContent);
+
   // inform the focus manager that the content is being removed. If this
   // content is focused, the focus will be removed without firing events.
   nsFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm)
     fm->ContentRemoved(aDocument, aContent);
 
   if (mHoverContent &&
       nsContentUtils::ContentIsDescendantOf(mHoverContent, aContent)) {
--- a/content/events/src/nsIMEStateManager.cpp
+++ b/content/events/src/nsIMEStateManager.cpp
@@ -29,34 +29,59 @@
 #include "nsIMutationObserver.h"
 #include "nsContentEventHandler.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "nsIFormControl.h"
 #include "nsIForm.h"
 #include "nsHTMLFormElement.h"
 #include "mozilla/Attributes.h"
+#include "nsEventDispatcher.h"
+#include "TextComposition.h"
 
+using namespace mozilla;
 using namespace mozilla::widget;
 
 /******************************************************************/
 /* nsIMEStateManager                                              */
 /******************************************************************/
 
 nsIContent*    nsIMEStateManager::sContent      = nullptr;
 nsPresContext* nsIMEStateManager::sPresContext  = nullptr;
 bool           nsIMEStateManager::sInstalledMenuKeyboardListener = false;
 bool           nsIMEStateManager::sInSecureInputMode = false;
 
 nsTextStateManager* nsIMEStateManager::sTextStateObserver = nullptr;
+TextCompositionArray* nsIMEStateManager::sTextCompositions = nullptr;
+
+void
+nsIMEStateManager::Shutdown()
+{
+  MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length());
+  delete sTextCompositions;
+  sTextCompositions = nullptr;
+}
 
 nsresult
 nsIMEStateManager::OnDestroyPresContext(nsPresContext* aPresContext)
 {
   NS_ENSURE_ARG_POINTER(aPresContext);
+
+  // First, if there is a composition in the aPresContext, clean up it.
+  if (sTextCompositions) {
+    TextCompositionArray::index_type i =
+      sTextCompositions->IndexOf(aPresContext);
+    if (i != TextCompositionArray::NoIndex) {
+      // there should be only one composition per presContext object.
+      sTextCompositions->RemoveElementAt(i);
+      MOZ_ASSERT(sTextCompositions->IndexOf(aPresContext) ==
+                   TextCompositionArray::NoIndex);
+    }
+  }
+
   if (aPresContext != sPresContext)
     return NS_OK;
   nsCOMPtr<nsIWidget> widget = GetWidget(sPresContext);
   if (widget) {
     IMEState newState = GetNewIMEState(sPresContext, nullptr);
     InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
                               InputContextAction::LOST_FOCUS);
     SetIMEState(newState, nullptr, widget, action);
@@ -67,27 +92,45 @@ nsIMEStateManager::OnDestroyPresContext(
   return NS_OK;
 }
 
 nsresult
 nsIMEStateManager::OnRemoveContent(nsPresContext* aPresContext,
                                    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) {
+      // 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.
+      }
+    }
+  }
+
   if (!sPresContext || !sContent ||
-      aPresContext != sPresContext ||
-      aContent != sContent)
+      !nsContentUtils::ContentIsDescendantOf(sContent, aContent)) {
     return NS_OK;
+  }
 
   // Current IME transaction should commit
   nsCOMPtr<nsIWidget> widget = GetWidget(sPresContext);
   if (widget) {
-    nsresult rv = widget->CancelIMEComposition();
-    if (NS_FAILED(rv))
-      widget->ResetInputState();
     IMEState newState = GetNewIMEState(sPresContext, nullptr);
     InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
                               InputContextAction::LOST_FOCUS);
     SetIMEState(newState, nullptr, widget, action);
   }
 
   NS_IF_RELEASE(sContent);
   sPresContext = nullptr;
@@ -379,16 +422,71 @@ nsIMEStateManager::GetWidget(nsPresConte
   if (!vm)
     return nullptr;
   nsCOMPtr<nsIWidget> widget = nullptr;
   nsresult rv = vm->GetRootWidget(getter_AddRefs(widget));
   NS_ENSURE_SUCCESS(rv, nullptr);
   return widget;
 }
 
+void
+nsIMEStateManager::EnsureTextCompositionArray()
+{
+  if (sTextCompositions) {
+    return;
+  }
+  sTextCompositions = new TextCompositionArray();
+}
+
+void
+nsIMEStateManager::DispatchCompositionEvent(nsINode* aEventTargetNode,
+                                            nsPresContext* aPresContext,
+                                            nsEvent* aEvent,
+                                            nsEventStatus* aStatus,
+                                            nsDispatchingCallback* aCallBack)
+{
+  MOZ_ASSERT(aEvent->eventStructType == NS_COMPOSITION_EVENT ||
+             aEvent->eventStructType == NS_TEXT_EVENT);
+  if (!NS_IS_TRUSTED_EVENT(aEvent) ||
+      (aEvent->flags & NS_EVENT_FLAG_STOP_DISPATCH) != 0) {
+    return;
+  }
+
+  EnsureTextCompositionArray();
+
+  nsGUIEvent* GUIEvent = static_cast<nsGUIEvent*>(aEvent);
+
+  TextComposition* composition =
+    sTextCompositions->GetCompositionFor(GUIEvent->widget);
+  if (!composition) {
+    MOZ_ASSERT(GUIEvent->message == NS_COMPOSITION_START);
+    TextComposition newComposition(aPresContext, aEventTargetNode, GUIEvent);
+    composition = sTextCompositions->AppendElement(newComposition);
+  }
+#ifdef DEBUG
+  else {
+    MOZ_ASSERT(GUIEvent->message != NS_COMPOSITION_START);
+  }
+#endif // #ifdef DEBUG
+
+  // Dispatch the event on composing target.
+  composition->DispatchEvent(GUIEvent, aStatus, aCallBack);
+
+  // WARNING: the |composition| might have been destroyed already.
+
+  // Remove the ended composition from the array.
+  if (aEvent->message == NS_COMPOSITION_END) {
+    TextCompositionArray::index_type i =
+      sTextCompositions->IndexOf(GUIEvent->widget);
+    if (i != TextCompositionArray::NoIndex) {
+      sTextCompositions->RemoveElementAt(i);
+    }
+  }
+}
+
 
 // nsTextStateManager notifies widget of any text and selection changes
 //  in the currently focused editor
 // sTextStateObserver points to the currently active nsTextStateManager
 // sTextStateObserver is null if there is no focused editor
 
 class nsTextStateManager MOZ_FINAL : public nsISelectionListener,
                                      public nsStubMutationObserver
--- a/content/events/src/nsIMEStateManager.h
+++ b/content/events/src/nsIMEStateManager.h
@@ -4,35 +4,43 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsIMEStateManager_h__
 #define nsIMEStateManager_h__
 
 #include "nscore.h"
 #include "nsIWidget.h"
 
+class nsDispatchingCallback;
 class nsIContent;
 class nsIDOMMouseEvent;
+class nsINode;
 class nsPIDOMWindow;
 class nsPresContext;
 class nsTextStateManager;
 class nsISelection;
 
+namespace mozilla {
+class TextCompositionArray;
+} // namespace mozilla
+
 /*
  * IME state manager
  */
 
 class nsIMEStateManager
 {
 protected:
   typedef mozilla::widget::IMEState IMEState;
   typedef mozilla::widget::InputContext InputContext;
   typedef mozilla::widget::InputContextAction InputContextAction;
 
 public:
+  static void Shutdown();
+
   static nsresult OnDestroyPresContext(nsPresContext* aPresContext);
   static nsresult OnRemoveContent(nsPresContext* aPresContext,
                                   nsIContent* aContent);
   /**
    * OnChangeFocus() should be called when focused content is changed or
    * IME enabled state is changed.  If focus isn't actually changed and IME
    * enabled state isn't changed, this will do nothing.
    */
@@ -70,30 +78,50 @@ public:
   // aContent must be:
   //   If the editor is for <input> or <textarea>, the element.
   //   If the editor is for contenteditable, the active editinghost.
   //   If the editor is for designMode, NULL.
   static void OnClickInEditor(nsPresContext* aPresContext,
                               nsIContent* aContent,
                               nsIDOMMouseEvent* aMouseEvent);
 
+  /**
+   * All DOM composition events and DOM text events must be dispatched via
+   * DispatchCompositionEvent() for storing the composition target
+   * and ensuring a set of composition events must be fired the stored target.
+   * If the stored composition event target is destroying, this removes the
+   * stored composition automatically.
+   */
+  static void DispatchCompositionEvent(nsINode* aEventTargetNode,
+                                       nsPresContext* aPresContext,
+                                       nsEvent* aEvent,
+                                       nsEventStatus* aStatus,
+                                       nsDispatchingCallback* aCallBack);
+
 protected:
   static nsresult OnChangeFocusInternal(nsPresContext* aPresContext,
                                         nsIContent* aContent,
                                         InputContextAction aAction);
   static void SetIMEState(const IMEState &aState,
                           nsIContent* aContent,
                           nsIWidget* aWidget,
                           InputContextAction aAction);
   static IMEState GetNewIMEState(nsPresContext* aPresContext,
                                  nsIContent* aContent);
 
   static nsIWidget* GetWidget(nsPresContext* aPresContext);
+  static void EnsureTextCompositionArray();
 
   static nsIContent*    sContent;
   static nsPresContext* sPresContext;
   static bool           sInstalledMenuKeyboardListener;
   static bool           sInSecureInputMode;
 
   static nsTextStateManager* sTextStateObserver;
+
+  // All active compositions in the process are stored by this array.
+  // When you get an item of this array and use it, please be careful.
+  // The instances in this array can be destroyed automatically if you do
+  // something to cause committing or canceling the composition.
+  static mozilla::TextCompositionArray* sTextCompositions;
 };
 
 #endif // nsIMEStateManager_h__
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -807,23 +807,16 @@ nsFocusManager::ContentRemoved(nsIDocume
 
   // if the content is currently focused in the window, or is an ancestor
   // of the currently focused element, reset the focus within that window.
   nsIContent* content = window->GetFocusedNode();
   if (content && nsContentUtils::ContentIsDescendantOf(content, aContent)) {
     bool shouldShowFocusRing = window->ShouldShowFocusRing();
     window->SetFocusedNode(nullptr);
 
-    nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
-    if (docShell) {
-      nsCOMPtr<nsIPresShell> presShell;
-      docShell->GetPresShell(getter_AddRefs(presShell));
-      nsIMEStateManager::OnRemoveContent(presShell->GetPresContext(), content);
-    }
-
     // if this window is currently focused, clear the global focused
     // element as well, but don't fire any events.
     if (window == mFocusedWindow) {
       mFocusedContent = nullptr;
     }
     else {
       // Check if the node that was focused is an iframe or similar by looking
       // if it has a subdocument. This would indicate that this focused iframe
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -1165,16 +1165,25 @@ nsPresContext::GetToplevelContentDocumen
   for (;;) {
     nsPresContext* parent = pc->GetParentPresContext();
     if (!parent || parent->IsChrome())
       return pc;
     pc = parent;
   }
 }
 
+nsIWidget*
+nsPresContext::GetNearestWidget(nsPoint* aOffset)
+{
+  NS_ENSURE_TRUE(mShell, nullptr);
+  nsIFrame* frame = mShell->GetRootFrame();
+  NS_ENSURE_TRUE(frame, nullptr);
+  return frame->GetView()->GetNearestWidget(aOffset);
+}
+
 // We may want to replace this with something faster, maybe caching the root prescontext
 nsRootPresContext*
 nsPresContext::GetRootPresContext()
 {
   nsPresContext* pc = this;
   for (;;) {
     nsPresContext* parent = pc->GetParentPresContext();
     if (!parent)
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -186,16 +186,26 @@ public:
 
   /**
    * Returns the prescontext of the toplevel content document that contains
    * this presentation, or null if there isn't one.
    */
   nsPresContext* GetToplevelContentDocumentPresContext();
 
   /**
+   * Returns the nearest widget for the root frame of this.
+   *
+   * @param aOffset     If non-null the offset from the origin of the root
+   *                    frame's view to the widget's origin (usually positive)
+   *                    expressed in appunits of this will be returned in
+   *                    aOffset.
+   */
+  nsIWidget* GetNearestWidget(nsPoint* aOffset = nullptr);
+
+  /**
    * Return the presentation context for the root of the view manager
    * hierarchy that contains this presentation context, or nullptr if it can't
    * be found (e.g. it's detached).
    */
   nsRootPresContext* GetRootPresContext();
   nsRootPresContext* GetDisplayRootPresContext();
   virtual bool IsRoot() { return false; }
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -68,16 +68,17 @@
 #include "nsUnicharUtils.h"
 #include "nsIPageSequenceFrame.h"
 #include "nsCaret.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMXMLDocument.h"
 #include "nsViewsCID.h"
 #include "nsFrameManager.h"
 #include "nsEventStateManager.h"
+#include "nsIMEStateManager.h"
 #include "nsXPCOM.h"
 #include "nsISupportsPrimitives.h"
 #include "nsILayoutHistoryState.h"
 #include "nsILineIterator.h" // for ScrollContentIntoView
 #include "nsWeakPtr.h"
 #include "pldhash.h"
 #include "nsDOMTouchEvent.h"
 #include "nsIObserverService.h"
@@ -6430,33 +6431,43 @@ PresShell::HandleEventInternal(nsEvent* 
         nsContentUtils::IsHandlingKeyBoardEvent();
       if (aEvent->eventStructType == NS_KEY_EVENT) {
         nsContentUtils::SetIsHandlingKeyBoardEvent(true);
       }
       if (NS_IsAllowedToDispatchDOMEvent(aEvent)) {
         nsPresShellEventCB eventCB(this);
         if (aEvent->eventStructType == NS_TOUCH_EVENT) {
           DispatchTouchEvent(aEvent, aStatus, &eventCB, touchIsNew);
-        }
-        else if (mCurrentEventContent) {
-          nsEventDispatcher::Dispatch(mCurrentEventContent, mPresContext,
-                                      aEvent, nullptr, aStatus, &eventCB);
-        }
-        else {
-          nsCOMPtr<nsIContent> targetContent;
-          if (mCurrentEventFrame) {
-            rv = mCurrentEventFrame->GetContentForEvent(aEvent,
-                                                        getter_AddRefs(targetContent));
+        } else {
+          nsCOMPtr<nsINode> eventTarget = mCurrentEventContent.get();
+          nsPresShellEventCB* eventCBPtr = &eventCB;
+          if (!eventTarget) {
+            nsCOMPtr<nsIContent> targetContent;
+            if (mCurrentEventFrame) {
+              rv = mCurrentEventFrame->
+                     GetContentForEvent(aEvent, getter_AddRefs(targetContent));
+            }
+            if (NS_SUCCEEDED(rv) && targetContent) {
+              eventTarget = do_QueryInterface(targetContent);
+            } else if (mDocument) {
+              eventTarget = do_QueryInterface(mDocument);
+              // If we don't have any content, the callback wouldn't probably
+              // do nothing.
+              eventCBPtr = nullptr;
+            }
           }
-          if (NS_SUCCEEDED(rv) && targetContent) {
-            nsEventDispatcher::Dispatch(targetContent, mPresContext, aEvent,
-                                        nullptr, aStatus, &eventCB);
-          } else if (mDocument) {
-            nsEventDispatcher::Dispatch(mDocument, mPresContext, aEvent,
-                                        nullptr, aStatus, nullptr);
+          if (eventTarget) {
+            if (aEvent->eventStructType == NS_COMPOSITION_EVENT ||
+                aEvent->eventStructType == NS_TEXT_EVENT) {
+              nsIMEStateManager::DispatchCompositionEvent(eventTarget,
+                mPresContext, aEvent, aStatus, eventCBPtr);
+            } else {
+              nsEventDispatcher::Dispatch(eventTarget, mPresContext,
+                                          aEvent, nullptr, aStatus, eventCBPtr);
+            }
           }
         }
       }
 
       nsContentUtils::SetIsHandlingKeyBoardEvent(wasHandlingKeyBoardEvent);
 
       // 3. Give event to event manager for post event state changes and
       //    generation of synthetic events.
--- a/layout/base/tests/bug746993-1.html
+++ b/layout/base/tests/bug746993-1.html
@@ -8,26 +8,15 @@
     <script>
       function start() {
         var iframe = document.querySelector("iframe");
         var win = iframe.contentWindow;
         var doc = iframe.contentDocument;
         iframe.focus();
         doc.body.focus();
         win.getSelection().collapse(doc.body, 0);
-        synthesizeText(
-          { "composition":
-            {
-              "string": "Here's some text.",
-              "clauses":
-              [
-                { "length": 17, "attr": COMPOSITION_ATTR_RAWINPUT }
-              ]
-            },
-            "caret": { "start": 2, "length": 0 }
-          }
-         )
+        sendString("Here's some text.");
         synthesizeKey("VK_RETURN", {});
         document.documentElement.removeAttribute("class");
       }
     </script>
   </body>
 </html>
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -98,16 +98,17 @@
 #include "nsEditorSpellCheck.h"
 #include "nsWindowMemoryReporter.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ipc/ProcessPriorityManager.h"
 #include "nsPermissionManager.h"
 #include "nsCookieService.h"
 #include "nsApplicationCacheService.h"
 #include "mozilla/dom/time/DateCacheCleaner.h"
+#include "nsIMEStateManager.h"
 
 extern void NS_ShutdownChainItemPool();
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::ipc;
 using namespace mozilla::dom::time;
 
@@ -279,16 +280,17 @@ nsLayoutStatics::Shutdown()
   nsFocusManager::Shutdown();
 #ifdef MOZ_XUL
   nsXULPopupManager::Shutdown();
 #endif
   nsDOMStorageManager::Shutdown();
   txMozillaXSLTProcessor::Shutdown();
   nsDOMAttribute::Shutdown();
   nsEventListenerManager::Shutdown();
+  nsIMEStateManager::Shutdown();
   nsComputedDOMStyle::Shutdown();
   nsCSSParser::Shutdown();
   nsCSSRuleProcessor::Shutdown();
   nsTextFrameTextRunCache::Shutdown();
   nsHTMLDNSPrefetch::Shutdown();
   nsCSSRendering::Shutdown();
 #ifdef DEBUG
   nsFrame::DisplayReflowShutdown();
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -81,30 +81,57 @@ function sendMouseEvent(aEvent, aTarget,
   SpecialPowers.dispatchEvent(aWindow, aTarget, event);
 }
 
 /**
  * Send the char aChar to the focused element.  This method handles casing of
  * chars (sends the right charcode, and sends a shift key for uppercase chars).
  * No other modifiers are handled at this point.
  *
- * For now this method only works for English letters (lower and upper case)
- * and the digits 0-9.
+ * For now this method only works for ASCII characters and emulates the shift
+ * key state on US keyboard layout.
  */
 function sendChar(aChar, aWindow) {
-  // DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9.
-  var hasShift = (aChar == aChar.toUpperCase());
+  var hasShift;
+  // Emulate US keyboard layout for the shiftKey state.
+  switch (aChar) {
+    case "!":
+    case "@":
+    case "#":
+    case "$":
+    case "%":
+    case "^":
+    case "&":
+    case "*":
+    case "(":
+    case ")":
+    case "_":
+    case "+":
+    case "{":
+    case "}":
+    case ":":
+    case "\"":
+    case "|":
+    case "<":
+    case ">":
+    case "?":
+      hasShift = true;
+      break;
+    default:
+      hasShift = (aChar == aChar.toUpperCase());
+      break;
+  }
   synthesizeKey(aChar, { shiftKey: hasShift }, aWindow);
 }
 
 /**
  * Send the string aStr to the focused element.
  *
- * For now this method only works for English letters (lower and upper case)
- * and the digits 0-9.
+ * For now this method only works for ASCII characters and emulates the shift
+ * key state on US keyboard layout.
  */
 function sendString(aStr, aWindow) {
   for (var i = 0; i < aStr.length; ++i) {
     sendChar(aStr.charAt(i), aWindow);
   }
 }
 
 /**
--- a/testing/mochitest/tests/test_sanityEventUtils.html
+++ b/testing/mochitest/tests/test_sanityEventUtils.html
@@ -110,47 +110,59 @@ function starttest() {
     
       check = false;
       synthesizeKeyExpectEvent("a", {}, $("testKeyEvent"), "keypress");
       is($("testKeyEvent").value, "a", "synthesizeKey should work");
       is(check, true, "synthesizeKey should dispatch keyPress");
       $("testKeyEvent").value = "";
     
       /* test synthesizeComposition */
+      $("textBoxB").focus();
       check = false;
       window.addEventListener("compositionstart", function() { check = true; }, false);
       synthesizeComposition({ type: "compositionstart" });
       is(check, true, 'synthesizeComposition() should dispatch compositionstart');
     
       check = false;
       window.addEventListener("compositionupdate", function() { check = true; }, false);
-      synthesizeComposition({ type: "compositionupdate" });
+      synthesizeComposition({ type: "compositionupdate", data: "a" });
       is(check, true, 'synthesizeComposition() should dispatch compositionupdate');
-    
+
       check = false;
-      window.addEventListener("compositionend", function() { check = true; }, false);
-      synthesizeComposition({ type: "compositionend" });
-      is(check, true, 'synthesizeComposition() should dispatch compositionend');
-      check = false;
-    
-      $("textBoxB").focus();
-      const nsIDOMWindowUtils = SpecialPowers.Ci.nsIDOMWindowUtils;
+      window.addEventListener("text", function() { check = true; }, false);
       synthesizeText(
         { "composition":
           { "string": "a",
             "clauses":
             [
-              { "length": 1, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
+              { "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
             ]
           },
           "caret": { "start": 1, "length": 0 }
         }
       );
-      is(check, false, "synthesizeText shouldn't start or end composition");
-      is($("textBoxB").value, "a", "synthesizeText should send text");
+      is(check, true, "synthesizeText should dispatch text event");
+
+      synthesizeText(
+        { "composition":
+          { "string": "a",
+            "clauses":
+            [
+              { "length": 0, "attr": 0 }
+            ]
+          },
+          "caret": { "start": 1, "length": 0 }
+        }
+      );
+
+      check = false;
+      window.addEventListener("compositionend", function() { check = true; }, false);
+      synthesizeComposition({ type: "compositionend", data: "a" });
+      is(check, true, 'synthesizeComposition() should dispatch compositionend');
+
       var querySelectedText = synthesizeQuerySelectedText();
       ok(querySelectedText, "query selected text event result is null");
       netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); //get permission to check members of querySelectedText
       ok(querySelectedText.succeeded, "query selected text event failed");
       is(querySelectedText.offset, 1,
          "query selected text event returns wrong offset");
       is(querySelectedText.text, "",
          "query selected text event returns wrong selected text");