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 119248 cced04530e2f56dff6b4955756090340c9d5c335
parent 119247 35b7bc10cc4289773f3add34907863861c9ef9b5
child 119249 bbb31887247b728a892e5cdd7c0d8758d265fd29
push id273
push userlsblakk@mozilla.com
push dateThu, 14 Feb 2013 23:19:38 +0000
treeherdermozilla-release@c5e807a3f8b8 [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.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");