Bug 993253 Implement DOM InputEvent interface with isComposing attribute r=smaug+ehsan
authorMasayuki Nakano <masayuki@d-toybox.com>
Thu, 10 Apr 2014 16:11:37 +0900
changeset 177924 8a2a1efffcd579563b2e6914722f3299657432e5
parent 177923 d40a8916b7e70824d066488d309fed4662f3d8e1
child 177925 86cbe44bf63e331d341f4c13a88d5c89c6120bba
push id26569
push userryanvm@gmail.com
push dateFri, 11 Apr 2014 04:11:36 +0000
treeherdermozilla-central@783c5013dbec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs993253
milestone31.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 993253 Implement DOM InputEvent interface with isComposing attribute r=smaug+ehsan
content/html/content/public/nsIFormControl.h
content/html/content/src/HTMLInputElement.cpp
dom/events/Event.cpp
dom/events/EventDispatcher.cpp
dom/events/EventNameList.h
dom/events/InputEvent.cpp
dom/events/InputEvent.h
dom/events/moz.build
dom/events/test/test_all_synthetic_events.html
dom/events/test/test_eventctors.html
dom/interfaces/events/nsIDOMEvent.idl
dom/tests/mochitest/general/test_interfaces.html
dom/webidl/InputEvent.webidl
dom/webidl/moz.build
editor/libeditor/base/nsEditor.cpp
editor/libeditor/base/nsEditor.h
widget/BasicEvents.h
widget/EventClassList.h
widget/TextEvents.h
widget/tests/test_assign_event_data.html
widget/tests/window_composition_text_querycontent.xul
widget/xpwidgets/nsBaseWidget.cpp
--- a/content/html/content/public/nsIFormControl.h
+++ b/content/html/content/public/nsIFormControl.h
@@ -27,17 +27,16 @@ enum FormControlsTypes {
   NS_FORM_TEXTAREA,
   NS_FORM_OBJECT,
   eFormControlsWithoutSubTypesMax,
   // After this, all types will have sub-types which introduce new enum lists.
   // eFormControlsWithoutSubTypesMax let us know if the previous types values
   // are not overlapping with sub-types/masks.
 
   // Elements with different types, the value is used as a mask.
-  // Adding '_ELEMENT' because NS_FORM_INPUT is used for 'oninput' event.
   // When changing the order, adding or removing elements, be sure to update
   // the static_assert checks accordingly.
   NS_FORM_BUTTON_ELEMENT = 0x40, // 0b01000000
   NS_FORM_INPUT_ELEMENT  = 0x80  // 0b10000000
 };
 
 enum ButtonElementTypes {
   NS_FORM_BUTTON_BUTTON = NS_FORM_BUTTON_ELEMENT + 1,
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -3488,17 +3488,17 @@ HTMLInputElement::PreHandleEvent(EventCh
     // to do some special handling here.
     HTMLInputElement* textControl = nullptr;
     nsNumberControlFrame* numberControlFrame =
       do_QueryFrame(GetPrimaryFrame());
     if (numberControlFrame) {
       textControl = numberControlFrame->GetAnonTextControl();
     }
     if (textControl && aVisitor.mEvent->originalTarget == textControl) {
-      if (aVisitor.mEvent->message == NS_FORM_INPUT) {
+      if (aVisitor.mEvent->message == NS_EDITOR_INPUT) {
         // Propogate the anon text control's new value to our HTMLInputElement:
         nsAutoString value;
         numberControlFrame->GetValueOfAnonTextControl(value);
         numberControlFrame->HandlingInputEvent(true);
         nsWeakFrame weakNumberControlFrame(numberControlFrame);
         SetValueInternal(value, true, true);
         if (weakNumberControlFrame.IsAlive()) {
           numberControlFrame->HandlingInputEvent(false);
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -663,23 +663,23 @@ Event::GetEventPopupControlState(WidgetE
       case NS_FORM_CHANGE :
         if (PopupAllowedForEvent("change")) {
           abuse = openControlled;
         }
         break;
       }
     }
     break;
-  case NS_GUI_EVENT :
+  case NS_EDITOR_INPUT_EVENT :
     // For this following event only allow popups if it's triggered
     // while handling user input. See
     // nsPresShell::HandleEventInternal() for details.
     if (EventStateManager::IsHandlingUserInput()) {
       switch(aEvent->message) {
-      case NS_FORM_INPUT :
+      case NS_EDITOR_INPUT:
         if (PopupAllowedForEvent("input")) {
           abuse = openControlled;
         }
         break;
       }
     }
     break;
   case NS_INPUT_EVENT :
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -702,16 +702,19 @@ EventDispatcher::CreateEvent(EventTarget
       return NS_NewDOMFocusEvent(aDOMEvent, aOwner, aPresContext,
                                  aEvent->AsFocusEvent());
     case NS_MOUSE_SCROLL_EVENT:
       return NS_NewDOMMouseScrollEvent(aDOMEvent, aOwner, aPresContext,
                                        aEvent->AsMouseScrollEvent());
     case NS_WHEEL_EVENT:
       return NS_NewDOMWheelEvent(aDOMEvent, aOwner, aPresContext,
                                  aEvent->AsWheelEvent());
+    case NS_EDITOR_INPUT_EVENT:
+      return NS_NewDOMInputEvent(aDOMEvent, aOwner, aPresContext,
+                                 aEvent->AsEditorInputEvent());
     case NS_DRAG_EVENT:
       return NS_NewDOMDragEvent(aDOMEvent, aOwner, aPresContext,
                                 aEvent->AsDragEvent());
     case NS_TEXT_EVENT:
       return NS_NewDOMUIEvent(aDOMEvent, aOwner, aPresContext,
                               aEvent->AsTextEvent());
     case NS_CLIPBOARD_EVENT:
       return NS_NewDOMClipboardEvent(aDOMEvent, aOwner, aPresContext,
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -213,19 +213,19 @@ EVENT(emptied,
       NS_EMPTIED,
       EventNameType_HTML,
       NS_EVENT)
 EVENT(ended,
       NS_ENDED,
       EventNameType_HTML,
       NS_EVENT)
 EVENT(input,
-      NS_FORM_INPUT,
+      NS_EDITOR_INPUT,
       EventNameType_HTMLXUL,
-      NS_UI_EVENT)
+      NS_EDITOR_INPUT_EVENT)
 EVENT(invalid,
       NS_FORM_INVALID,
       EventNameType_HTMLXUL,
       NS_EVENT)
 EVENT(keydown,
       NS_KEY_DOWN,
       EventNameType_HTMLXUL,
       NS_KEY_EVENT)
new file mode 100644
--- /dev/null
+++ b/dom/events/InputEvent.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/dom/InputEvent.h"
+#include "mozilla/TextEvents.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace dom {
+
+InputEvent::InputEvent(EventTarget* aOwner,
+                       nsPresContext* aPresContext,
+                       InternalEditorInputEvent* aEvent)
+  : UIEvent(aOwner, aPresContext,
+            aEvent ? aEvent : new InternalEditorInputEvent(false, 0, nullptr))
+{
+  NS_ASSERTION(mEvent->eventStructType == NS_EDITOR_INPUT_EVENT,
+               "event type mismatch");
+
+  if (aEvent) {
+    mEventIsInternal = false;
+  } else {
+    mEventIsInternal = true;
+    mEvent->time = PR_Now();
+  }
+}
+
+NS_IMPL_ADDREF_INHERITED(InputEvent, UIEvent)
+NS_IMPL_RELEASE_INHERITED(InputEvent, UIEvent)
+
+NS_INTERFACE_MAP_BEGIN(InputEvent)
+NS_INTERFACE_MAP_END_INHERITING(UIEvent)
+
+bool
+InputEvent::IsComposing()
+{
+  return mEvent->AsEditorInputEvent()->mIsComposing;
+}
+
+already_AddRefed<InputEvent>
+InputEvent::Constructor(const GlobalObject& aGlobal,
+                        const nsAString& aType,
+                        const InputEventInit& aParam,
+                        ErrorResult& aRv)
+{
+  nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
+  nsRefPtr<InputEvent> e = new InputEvent(t, nullptr, nullptr);
+  bool trusted = e->Init(t);
+  aRv = e->InitUIEvent(aType, aParam.mBubbles, aParam.mCancelable,
+                       aParam.mView, aParam.mDetail);
+  InternalEditorInputEvent* internalEvent = e->mEvent->AsEditorInputEvent();
+  internalEvent->mIsComposing = aParam.mIsComposing;
+  e->SetTrusted(trusted);
+  return e.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsresult
+NS_NewDOMInputEvent(nsIDOMEvent** aInstancePtrResult,
+                    EventTarget* aOwner,
+                    nsPresContext* aPresContext,
+                    InternalEditorInputEvent* aEvent)
+{
+  InputEvent* it = new InputEvent(aOwner, aPresContext, aEvent);
+  NS_ADDREF(it);
+  *aInstancePtrResult = static_cast<Event*>(it);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/events/InputEvent.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_dom_InputEvent_h_
+#define mozilla_dom_InputEvent_h_
+
+#include "mozilla/dom/UIEvent.h"
+#include "mozilla/dom/InputEventBinding.h"
+#include "mozilla/EventForwards.h"
+
+namespace mozilla {
+namespace dom {
+
+class InputEvent : public UIEvent
+{
+public:
+  InputEvent(EventTarget* aOwner,
+             nsPresContext* aPresContext,
+             InternalEditorInputEvent* aEvent);
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+  // Forward to base class
+  NS_FORWARD_TO_UIEVENT
+
+
+  static already_AddRefed<InputEvent> Constructor(const GlobalObject& aGlobal,
+                                                  const nsAString& aType,
+                                                  const InputEventInit& aParam,
+                                                  ErrorResult& aRv);
+
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE
+  {
+    return InputEventBinding::Wrap(aCx, this);
+  }
+
+  bool IsComposing();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_InputEvent_h_
--- a/dom/events/moz.build
+++ b/dom/events/moz.build
@@ -37,16 +37,17 @@ EXPORTS.mozilla.dom += [
     'CompositionEvent.h',
     'DataContainerEvent.h',
     'DataTransfer.h',
     'DeviceMotionEvent.h',
     'DragEvent.h',
     'Event.h',
     'EventTarget.h',
     'FocusEvent.h',
+    'InputEvent.h',
     'KeyboardEvent.h',
     'MessageEvent.h',
     'MouseEvent.h',
     'MouseScrollEvent.h',
     'MutationEvent.h',
     'NotifyPaintEvent.h',
     'PaintRequest.h',
     'PointerEvent.h',
@@ -79,16 +80,17 @@ UNIFIED_SOURCES += [
     'Event.cpp',
     'EventDispatcher.cpp',
     'EventListenerManager.cpp',
     'EventListenerService.cpp',
     'EventTarget.cpp',
     'FocusEvent.cpp',
     'IMEContentObserver.cpp',
     'IMEStateManager.cpp',
+    'InputEvent.cpp',
     'JSEventHandler.cpp',
     'KeyboardEvent.cpp',
     'MessageEvent.cpp',
     'MouseEvent.cpp',
     'MouseScrollEvent.cpp',
     'MutationEvent.cpp',
     'NotifyPaintEvent.cpp',
     'PaintRequest.cpp',
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -178,16 +178,20 @@ const kEventConstructors = {
   IccChangeEvent:                            { create: function (aName, aProps) {
                                                          return new IccChangeEvent(aName, aProps);
                                                        },
                                              },
   IDBVersionChangeEvent:                     { create: function (aName, aProps) {
                                                          return new IDBVersionChangeEvent(aName, aProps);
                                                        },
                                              },
+  InputEvent:                                { create: function (aName, aProps) {
+                                                         return new InputEvent(aName, aProps);
+                                                       },
+                                             },
   KeyEvent:                                  { create: function (aName, aProps) {
                                                          var e = document.createEvent("keyboardevent");
                                                          e.initKeyEvent(aName, aProps.bubbles, aProps.cancelable,
                                                                         aProps.view,
                                                                         aProps.ctrlKey, aProps.altKey, aProps.shiftKey, aProps.metaKey,
                                                                         aProps.keyCode, aProps.charCode);
                                                          return e;
                                                        },
--- a/dom/events/test/test_eventctors.html
+++ b/dom/events/test/test_eventctors.html
@@ -309,16 +309,42 @@ is(e.type, "hello", "Wrong event type!")
 ok(!e.isTrusted, "Event shouldn't be trusted!");
 ok(e.bubbles, "Event should bubble!");
 ok(e.cancelable, "Event should be cancelable!");
 is(e.oldURL, "", "oldURL should be ''");
 is(e.newURL, "new", "newURL should be 'new'");
 document.dispatchEvent(e);
 is(receivedEvent, e, "Wrong event!");
 
+// InputEvent
+
+e = new InputEvent("hello");
+is(e.type, "hello", "Wrong event type!");
+ok(!e.isTrusted, "Event shouldn't be trusted!");
+ok(!e.bubbles, "Event shouldn't bubble!");
+ok(!e.cancelable, "Event shouldn't be cancelable!");
+is(e.detail, 0, "detail should be 0");
+ok(!e.isComposing, "isComposing should be false");
+
+e = new InputEvent("hi!", { bubbles: true, detail: 5, isComposing: false });
+is(e.type, "hi!", "Wrong event type!");
+ok(!e.isTrusted, "Event shouldn't be trusted!");
+ok(e.bubbles, "Event should bubble!");
+ok(!e.cancelable, "Event shouldn't be cancelable!");
+is(e.detail, 5, "detail should be 5");
+ok(!e.isComposing, "isComposing should be false");
+
+e = new InputEvent("hi!", { cancelable: true, detail: 0, isComposing: true });
+is(e.type, "hi!", "Wrong event type!");
+ok(!e.isTrusted, "Event shouldn't be trusted!");
+ok(!e.bubbles, "Event shouldn't bubble!");
+ok(e.cancelable, "Event should be cancelable!");
+is(e.detail, 0, "detail should be 0");
+ok(e.isComposing, "isComposing should be true");
+
 // PageTransitionEvent
 
 try {
   e = new PageTransitionEvent();
 } catch(exp) {
   ex = true;
 }
 ok(ex, "First parameter is required!");
--- a/dom/interfaces/events/nsIDOMEvent.idl
+++ b/dom/interfaces/events/nsIDOMEvent.idl
@@ -257,17 +257,21 @@ NS_NewDOMDragEvent(nsIDOMEvent** aInstan
                    mozilla::dom::EventTarget* aOwner,
                    nsPresContext* aPresContext,
                    mozilla::WidgetDragEvent* aEvent);
 nsresult
 NS_NewDOMClipboardEvent(nsIDOMEvent** aInstancePtrResult,
                         mozilla::dom::EventTarget* aOwner,
                         nsPresContext* aPresContext,
                         mozilla::InternalClipboardEvent* aEvent);
-
+nsresult
+NS_NewDOMInputEvent(nsIDOMEvent** aInstancePtrResult,
+                    mozilla::dom::EventTarget* aOwner,
+                    nsPresContext* aPresContext,
+                    mozilla::InternalEditorInputEvent* aEvent);
 nsresult
 NS_NewDOMKeyboardEvent(nsIDOMEvent** aInstancePtrResult,
                        mozilla::dom::EventTarget* aOwner,
                        nsPresContext* aPresContext,
                        mozilla::WidgetKeyboardEvent* aEvent);
 nsresult
 NS_NewDOMCompositionEvent(nsIDOMEvent** aInstancePtrResult,
                           mozilla::dom::EventTarget* aOwner,
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -555,16 +555,18 @@ var interfaceNamesInGlobalScope =
     "IDBTransaction",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBVersionChangeEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Image",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ImageData",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "InputEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "InstallTrigger", b2g: false, xbl: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "KeyEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "KeyboardEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "LocalMediaStream",
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/InputEvent.webidl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+[Constructor(DOMString type, optional InputEventInit eventInitDict)]
+interface InputEvent : UIEvent
+{
+  readonly attribute boolean       isComposing;
+};
+
+dictionary InputEventInit : UIEventInit
+{
+  boolean isComposing = false;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -202,16 +202,17 @@ WEBIDL_FILES = [
     'IDBKeyRange.webidl',
     'IDBObjectStore.webidl',
     'IDBOpenDBRequest.webidl',
     'IDBRequest.webidl',
     'IDBTransaction.webidl',
     'IDBVersionChangeEvent.webidl',
     'ImageData.webidl',
     'ImageDocument.webidl',
+    'InputEvent.webidl',
     'InputMethod.webidl',
     'InspectorUtils.webidl',
     'InterAppConnection.webidl',
     'InterAppConnectionRequest.webidl',
     'InterAppMessagePort.webidl',
     'KeyboardEvent.webidl',
     'KeyEvent.webidl',
     'LegacyQueryInterface.webidl',
--- a/editor/libeditor/base/nsEditor.cpp
+++ b/editor/libeditor/base/nsEditor.cpp
@@ -562,16 +562,27 @@ nsEditor::GetPresShell()
 {
   NS_PRECONDITION(mDocWeak, "bad state, null mDocWeak");
   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
   NS_ENSURE_TRUE(doc, nullptr);
   nsCOMPtr<nsIPresShell> ps = doc->GetShell();
   return ps.forget();
 }
 
+already_AddRefed<nsIWidget>
+nsEditor::GetWidget()
+{
+  nsCOMPtr<nsIPresShell> ps = GetPresShell();
+  NS_ENSURE_TRUE(ps, nullptr);
+  nsPresContext* pc = ps->GetPresContext();
+  NS_ENSURE_TRUE(pc, nullptr);
+  nsCOMPtr<nsIWidget> widget = pc->GetRootWidget();
+  NS_ENSURE_TRUE(widget.get(), nullptr);
+  return widget.forget();
+}
 
 /* attribute string contentsMIMEType; */
 NS_IMETHODIMP
 nsEditor::GetContentsMIMEType(char * *aContentsMIMEType)
 {
   NS_ENSURE_ARG_POINTER(aContentsMIMEType);
   *aContentsMIMEType = ToNewCString(mContentMIMEType);
   return NS_OK;
@@ -1799,18 +1810,21 @@ nsEditor::RemoveEditorObserver(nsIEditor
 
   return NS_OK;
 }
 
 class EditorInputEventDispatcher : public nsRunnable
 {
 public:
   EditorInputEventDispatcher(nsEditor* aEditor,
-                             nsIContent* aTarget) :
-    mEditor(aEditor), mTarget(aTarget)
+                             nsIContent* aTarget,
+                             bool aIsComposing)
+    : mEditor(aEditor)
+    , mTarget(aTarget)
+    , mIsComposing(aIsComposing)
   {
   }
 
   NS_IMETHOD Run()
   {
     // Note that we don't need to check mDispatchInputEvent here.  We need
     // to check it only when the editor requests to dispatch the input event.
 
@@ -1818,31 +1832,37 @@ public:
       return NS_OK;
     }
 
     nsCOMPtr<nsIPresShell> ps = mEditor->GetPresShell();
     if (!ps) {
       return NS_OK;
     }
 
+    nsCOMPtr<nsIWidget> widget = mEditor->GetWidget();
+    if (!widget) {
+      return NS_OK;
+    }
+
     // Even if the change is caused by untrusted event, we need to dispatch
     // trusted input event since it's a fact.
-    WidgetEvent inputEvent(true, NS_FORM_INPUT);
-    inputEvent.mFlags.mCancelable = false;
+    InternalEditorInputEvent inputEvent(true, NS_EDITOR_INPUT, widget);
     inputEvent.time = static_cast<uint64_t>(PR_Now() / 1000);
+    inputEvent.mIsComposing = mIsComposing;
     nsEventStatus status = nsEventStatus_eIgnore;
     nsresult rv =
       ps->HandleEventWithTarget(&inputEvent, nullptr, mTarget, &status);
     NS_ENSURE_SUCCESS(rv, NS_OK); // print the warning if error
     return NS_OK;
   }
 
 private:
   nsRefPtr<nsEditor> mEditor;
   nsCOMPtr<nsIContent> mTarget;
+  bool mIsComposing;
 };
 
 void nsEditor::NotifyEditorObservers(void)
 {
   for (int32_t i = 0; i < mEditorObservers.Count(); i++) {
     mEditorObservers[i]->EditAction();
   }
 
@@ -1859,18 +1879,21 @@ nsEditor::FireInputEvent()
   // We don't need to dispatch multiple input events if there is a pending
   // input event.  However, it may have different event target.  If we resolved
   // this issue, we need to manage the pending events in an array.  But it's
   // overwork.  We don't need to do it for the very rare case.
 
   nsCOMPtr<nsIContent> target = GetInputEventTargetContent();
   NS_ENSURE_TRUE_VOID(target);
 
+  // NOTE: Don't refer IsIMEComposing() because it returns false even before
+  //       compositionend.  However, DOM Level 3 Events defines it should be
+  //       true after compositionstart and before compositionend.
   nsContentUtils::AddScriptRunner(
-    new EditorInputEventDispatcher(this, target));
+    new EditorInputEventDispatcher(this, target, !!GetComposition()));
 }
 
 NS_IMETHODIMP
 nsEditor::AddEditActionListener(nsIEditActionListener *aListener)
 {
   NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
 
   // Make sure the listener isn't already on the list
--- a/editor/libeditor/base/nsEditor.h
+++ b/editor/libeditor/base/nsEditor.h
@@ -166,16 +166,17 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsEditor,
                                            nsIEditor)
 
   /* ------------ utility methods   -------------- */
   already_AddRefed<nsIDOMDocument> GetDOMDocument();
   already_AddRefed<nsIDocument> GetDocument();
   already_AddRefed<nsIPresShell> GetPresShell();
+  already_AddRefed<nsIWidget> GetWidget();
   void NotifyEditorObservers();
 
   /* ------------ nsIEditor methods -------------- */
   NS_DECL_NSIEDITOR
 
   /* ------------ nsIEditorIMESupport methods -------------- */
   NS_DECL_NSIEDITORIMESUPPORT
 
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -30,16 +30,17 @@ enum nsEventStructType
   NS_UI_EVENT,                       // InternalUIEvent
 
   // TextEvents.h
   NS_KEY_EVENT,                      // WidgetKeyboardEvent
   NS_COMPOSITION_EVENT,              // WidgetCompositionEvent
   NS_TEXT_EVENT,                     // WidgetTextEvent
   NS_QUERY_CONTENT_EVENT,            // WidgetQueryContentEvent
   NS_SELECTION_EVENT,                // WidgetSelectionEvent
+  NS_EDITOR_INPUT_EVENT,             // InternalEditorInputEvent
 
   // MouseEvents.h
   NS_MOUSE_EVENT,                    // WidgetMouseEvent
   NS_DRAG_EVENT,                     // WidgetDragEvent
   NS_MOUSE_SCROLL_EVENT,             // WidgetMouseScrollEvent
   NS_WHEEL_EVENT,                    // WidgetWheelEvent
   NS_POINTER_EVENT,                  // PointerEvent
 
@@ -161,18 +162,17 @@ enum nsEventStructType
 #define NS_PAGE_RESTORE                 (NS_STREAM_EVENT_START + 7)
 #define NS_READYSTATECHANGE             (NS_STREAM_EVENT_START + 8)
  
 #define NS_FORM_EVENT_START             1200
 #define NS_FORM_SUBMIT                  (NS_FORM_EVENT_START)
 #define NS_FORM_RESET                   (NS_FORM_EVENT_START + 1)
 #define NS_FORM_CHANGE                  (NS_FORM_EVENT_START + 2)
 #define NS_FORM_SELECTED                (NS_FORM_EVENT_START + 3)
-#define NS_FORM_INPUT                   (NS_FORM_EVENT_START + 4)
-#define NS_FORM_INVALID                 (NS_FORM_EVENT_START + 5)
+#define NS_FORM_INVALID                 (NS_FORM_EVENT_START + 4)
 
 //Need separate focus/blur notifications for non-native widgets
 #define NS_FOCUS_EVENT_START            1300
 #define NS_FOCUS_CONTENT                (NS_FOCUS_EVENT_START)
 #define NS_BLUR_CONTENT                 (NS_FOCUS_EVENT_START + 1)
 
 #define NS_DRAGDROP_EVENT_START         1400
 #define NS_DRAGDROP_ENTER               (NS_DRAGDROP_EVENT_START)
@@ -468,16 +468,20 @@ enum nsEventStructType
 #define NS_GAMEPAD_BUTTONUP      (NS_GAMEPAD_START+1)
 #define NS_GAMEPAD_AXISMOVE      (NS_GAMEPAD_START+2)
 #define NS_GAMEPAD_CONNECTED     (NS_GAMEPAD_START+3)
 #define NS_GAMEPAD_DISCONNECTED  (NS_GAMEPAD_START+4)
 // Keep this defined to the same value as the event above
 #define NS_GAMEPAD_END           (NS_GAMEPAD_START+4)
 #endif
 
+// input and beforeinput events.
+#define NS_EDITOR_EVENT_START    6100
+#define NS_EDITOR_INPUT          (NS_EDITOR_EVENT_START)
+
 namespace mozilla {
 
 /******************************************************************************
  * mozilla::BaseEventFlags
  *
  * BaseEventFlags must be a POD struct for safe to use memcpy (including
  * in ParamTraits<BaseEventFlags>).  So don't make virtual methods, constructor,
  * destructor and operators.
@@ -1052,16 +1056,28 @@ public:
  * mozilla::InternalUIEvent
  *
  * XXX Why this inherits WidgetGUIEvent rather than WidgetEvent?
  ******************************************************************************/
 
 class InternalUIEvent : public WidgetGUIEvent
 {
 protected:
+  InternalUIEvent()
+    : detail(0)
+  {
+  }
+
+  InternalUIEvent(bool aIsTrusted, uint32_t aMessage, nsIWidget* aWidget,
+                  nsEventStructType aStructType)
+    : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, aStructType)
+    , detail(0)
+  {
+  }
+
   InternalUIEvent(bool aIsTrusted, uint32_t aMessage,
                   nsEventStructType aStructType)
     : WidgetGUIEvent(aIsTrusted, aMessage, nullptr, aStructType)
     , detail(0)
   {
   }
 
 public:
--- a/widget/EventClassList.h
+++ b/widget/EventClassList.h
@@ -21,16 +21,17 @@ NS_EVENT_CLASS(Widget, InputEvent)
 NS_EVENT_CLASS(Internal, UIEvent)
 
 // TextEvents.h
 NS_EVENT_CLASS(Widget, KeyboardEvent)
 NS_EVENT_CLASS(Widget, TextEvent)
 NS_EVENT_CLASS(Widget, CompositionEvent)
 NS_EVENT_CLASS(Widget, QueryContentEvent)
 NS_EVENT_CLASS(Widget, SelectionEvent)
+NS_EVENT_CLASS(Internal, EditorInputEvent)
 
 // MouseEvents.h
 NS_EVENT_CLASS(Widget, MouseEventBase)
 NS_EVENT_CLASS(Widget, MouseEvent)
 NS_EVENT_CLASS(Widget, DragEvent)
 NS_EVENT_CLASS(Widget, MouseScrollEvent)
 NS_EVENT_CLASS(Widget, WheelEvent)
 NS_EVENT_CLASS(Widget, PointerEvent)
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -492,11 +492,67 @@ public:
   // Selection "anchor" should be in front
   bool mReversed;
   // Cluster-based or character-based
   bool mExpandToClusterBoundary;
   // true if setting selection succeeded.
   bool mSucceeded;
 };
 
+/******************************************************************************
+ * mozilla::InternalEditorInputEvent
+ ******************************************************************************/
+
+class InternalEditorInputEvent : public InternalUIEvent
+{
+private:
+  InternalEditorInputEvent()
+    : mIsComposing(false)
+  {
+  }
+
+public:
+  virtual InternalEditorInputEvent* AsEditorInputEvent() MOZ_OVERRIDE
+  {
+    return this;
+  }
+
+  InternalEditorInputEvent(bool aIsTrusted, uint32_t aMessage,
+                           nsIWidget* aWidget)
+    : InternalUIEvent(aIsTrusted, aMessage, aWidget, NS_EDITOR_INPUT_EVENT)
+    , mIsComposing(false)
+  {
+    if (!aIsTrusted) {
+      mFlags.mBubbles = false;
+      mFlags.mCancelable = false;
+      return;
+    }
+
+    mFlags.mBubbles = true;
+    mFlags.mCancelable = false;
+  }
+
+  virtual WidgetEvent* Duplicate() const MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(eventStructType == NS_EDITOR_INPUT_EVENT,
+               "Duplicate() must be overridden by sub class");
+    // Not copying widget, it is a weak reference.
+    InternalEditorInputEvent* result =
+      new InternalEditorInputEvent(false, message, nullptr);
+    result->AssignEditorInputEventData(*this, true);
+    result->mFlags = mFlags;
+    return result;
+  }
+
+  bool mIsComposing;
+
+  void AssignEditorInputEventData(const InternalEditorInputEvent& aEvent,
+                                  bool aCopyTargets)
+  {
+    AssignUIEventData(aEvent, aCopyTargets);
+
+    mIsComposing = aEvent.mIsComposing;
+  }
+};
+
 } // namespace mozilla
 
 #endif // mozilla_TextEvents_h__
--- a/widget/tests/test_assign_event_data.html
+++ b/widget/tests/test_assign_event_data.html
@@ -43,17 +43,17 @@
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 </pre>
 
 <script class="testbody" type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
-SimpleTest.expectAssertions(0, 16);
+SimpleTest.expectAssertions(0, 32);
 
 const kIsMac = (navigator.platform.indexOf("Mac") == 0);
 const kIsWin = (navigator.platform.indexOf("Win") == 0);
 
 var gEvent = null;
 var gCopiedEvent = [];
 var gCallback = null;
 var gCallPreventDefault = false;
@@ -327,16 +327,70 @@ const kTests = [
       });
       synthesizeComposition({ type: "compositionend", data: "\u30E9\u30FC\u30E1\u30F3" });
     },
     canRun: function () {
       return true;
     },
     todoMismatch: [ ],
   },
+  { description: "InternalEditorInputEvent (input at key input)",
+    targetID: "input-text", eventType: "input",
+    dispatchEvent: function () {
+      document.getElementById(this.targetID).value = "";
+      document.getElementById(this.targetID).focus();
+      synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_B : MAC_VK_ANSI_B,
+                          { shiftKey: true }, "B", "B");
+    },
+    canRun: function () {
+      return (kIsMac || kIsWin);
+    },
+    todoMismatch: [],
+  },
+  { description: "InternalEditorInputEvent (input at composing)",
+    targetID: "input-text", eventType: "input",
+    dispatchEvent: function () {
+      document.getElementById(this.targetID).value = "";
+      document.getElementById(this.targetID).focus();
+      synthesizeComposition({ type: "compositionstart" });
+      synthesizeComposition({ type: "compositionupdate", data: "\u30E9\u30FC\u30E1\u30F3" });
+      synthesizeText({ "composition":
+        { "string": "\u30E9\u30FC\u30E1\u30F3",
+          "clauses":
+          [
+            { "length": 4, "attr": COMPOSITION_ATTR_RAWINPUT }
+          ]
+        },
+        "caret": { "start": 4, "length": 0 }
+      });
+    },
+    canRun: function () {
+      return true;
+    },
+    todoMismatch: [ ],
+  },
+  { description: "InternalEditorInputEvent (input at committing)",
+    targetID: "input-text", eventType: "input",
+    dispatchEvent: function () {
+      synthesizeText({ "composition":
+        { "string": "\u30E9\u30FC\u30E1\u30F3",
+          "clauses":
+          [
+            { "length": 0, "attr": 0 }
+          ]
+        },
+        "caret": { "start": 4, "length": 0 }
+      });
+      synthesizeComposition({ type: "compositionend", data: "\u30E9\u30FC\u30E1\u30F3" });
+    },
+    canRun: function () {
+      return true;
+    },
+    todoMismatch: [ ],
+  },
   { description: "WidgetMouseScrollEvent (DOMMouseScroll, vertical)",
     targetID: "input-text", eventType: "DOMMouseScroll",
     dispatchEvent: function () {
       document.getElementById(this.targetID).value = "";
       synthesizeWheel(document.getElementById(this.targetID), 3, 4,
                       { deltaY: 30, lineOrPageDeltaY: 2 });
     },
     canRun: function () {
--- a/widget/tests/window_composition_text_querycontent.xul
+++ b/widget/tests/window_composition_text_querycontent.xul
@@ -2794,23 +2794,29 @@ function runBug811755Test()
 function runIsComposingTest()
 {
   var expectedIsComposing = false;
   var descriptionBase = "runIsComposingTest: ";
   var description = "";
 
   function eventHandler(aEvent)
   {
-    is(aEvent.isComposing, expectedIsComposing,
-       "runIsComposingTest: " + description + " (type=" + aEvent.type + ", key=" + aEvent.key + ")");
+    if (aEvent.type == "keydown" || aEvent.type == "keyup") {
+      is(aEvent.isComposing, expectedIsComposing,
+         "runIsComposingTest: " + description + " (type=" + aEvent.type + ", key=" + aEvent.key + ")");
+    } else {
+      is(aEvent.isComposing, expectedIsComposing,
+         "runIsComposingTest: " + description + " (type=" + aEvent.type + ")");
+    }
   }
 
   textarea.addEventListener("keydown", eventHandler, true);
   textarea.addEventListener("keypress", eventHandler, true);
   textarea.addEventListener("keyup", eventHandler, true);
+  textarea.addEventListener("input", eventHandler, true);
 
   textarea.focus();
   textarea.value = "";
 
   // XXX These cases shouldn't occur in actual native key events because we
   //     don't dispatch key events while composition (bug 354358).
   expectedIsComposing = false;
   description = "events before dispatching compositionstart";
@@ -2843,25 +2849,28 @@ function runIsComposingTest()
       { "string": "\u3042",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
       "caret": { "start": 1, "length": 0 }
     });
-  synthesizeComposition({ type: "compositionend" });
-
+
+  // input event will be fired by synthesizing compositionend event.
+  // Then, its isComposing should be false.
   expectedIsComposing = false;
   description = "events after dispatching compositionend";
+  synthesizeComposition({ type: "compositionend" });
   synthesizeKey("VK_RETURN", { type: "keyup" });
 
   textarea.removeEventListener("keydown", eventHandler, true);
   textarea.removeEventListener("keypress", eventHandler, true);
   textarea.removeEventListener("keyup", eventHandler, true);
+  textarea.removeEventListener("input", eventHandler, true);
 
   textarea.value = "";
 }
 
 function runRemoveContentTest(aCallback)
 {
   var events = [];
   function eventHandler(aEvent)
--- a/widget/xpwidgets/nsBaseWidget.cpp
+++ b/widget/xpwidgets/nsBaseWidget.cpp
@@ -1608,20 +1608,20 @@ case _value: eventName.AssignLiteral(_na
   switch(aGuiEvent->message)
   {
     _ASSIGN_eventName(NS_BLUR_CONTENT,"NS_BLUR_CONTENT");
     _ASSIGN_eventName(NS_DRAGDROP_GESTURE,"NS_DND_GESTURE");
     _ASSIGN_eventName(NS_DRAGDROP_DROP,"NS_DND_DROP");
     _ASSIGN_eventName(NS_DRAGDROP_ENTER,"NS_DND_ENTER");
     _ASSIGN_eventName(NS_DRAGDROP_EXIT,"NS_DND_EXIT");
     _ASSIGN_eventName(NS_DRAGDROP_OVER,"NS_DND_OVER");
+    _ASSIGN_eventName(NS_EDITOR_INPUT,"NS_EDITOR_INPUT");
     _ASSIGN_eventName(NS_FOCUS_CONTENT,"NS_FOCUS_CONTENT");
     _ASSIGN_eventName(NS_FORM_SELECTED,"NS_FORM_SELECTED");
     _ASSIGN_eventName(NS_FORM_CHANGE,"NS_FORM_CHANGE");
-    _ASSIGN_eventName(NS_FORM_INPUT,"NS_FORM_INPUT");
     _ASSIGN_eventName(NS_FORM_RESET,"NS_FORM_RESET");
     _ASSIGN_eventName(NS_FORM_SUBMIT,"NS_FORM_SUBMIT");
     _ASSIGN_eventName(NS_IMAGE_ABORT,"NS_IMAGE_ABORT");
     _ASSIGN_eventName(NS_LOAD_ERROR,"NS_LOAD_ERROR");
     _ASSIGN_eventName(NS_KEY_DOWN,"NS_KEY_DOWN");
     _ASSIGN_eventName(NS_KEY_PRESS,"NS_KEY_PRESS");
     _ASSIGN_eventName(NS_KEY_UP,"NS_KEY_UP");
     _ASSIGN_eventName(NS_MOUSE_ENTER,"NS_MOUSE_ENTER");