Bug 930374 part.1 Event.defaultPrevented should be false even if preventDefault() has been called by default action handler r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 09 Dec 2013 00:51:16 +0900
changeset 159755 c16b9aeb5de9fad305ae083b04f0bcb350a9ea5a
parent 159754 7b0b527637dd835d75612dbfecbd59067fe91cfe
child 159756 60c68bfe126e47b30993912eaaa6fc1b18794492
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewerssmaug
bugs930374
milestone28.0a1
Bug 930374 part.1 Event.defaultPrevented should be false even if preventDefault() has been called by default action handler r=smaug
content/events/src/moz.build
content/events/src/nsDOMEvent.cpp
content/events/src/nsDOMEvent.h
content/events/src/nsEventStateManager.cpp
content/events/src/nsEventStateManager.h
content/events/test/chrome.ini
content/events/test/mochitest.ini
content/events/test/test_bug930374-chrome.html
content/events/test/test_bug930374-content.html
dom/bindings/Bindings.conf
--- a/content/events/src/moz.build
+++ b/content/events/src/moz.build
@@ -81,16 +81,17 @@ FINAL_LIBRARY = 'gklayout'
 LOCAL_INCLUDES += [
     '/content/base/src',
     '/content/html/content/src',
     '/content/xml/content/src',
     '/content/xul/content/src',
     '/dom/base',
     '/dom/settings',
     '/dom/src/storage',
+    '/js/xpconnect/wrappers',
     '/layout/generic',
     '/layout/xul',
     '/layout/xul/tree/',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     LOCAL_INCLUDES += [
         '/dom/wifi',
--- a/content/events/src/nsDOMEvent.cpp
+++ b/content/events/src/nsDOMEvent.cpp
@@ -1,15 +1,16 @@
 /* -*- 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 "base/basictypes.h"
 
+#include "AccessCheck.h"
 #include "ipc/IPCMessageUtils.h"
 #include "nsCOMPtr.h"
 #include "nsError.h"
 #include "nsDOMEvent.h"
 #include "nsEventStateManager.h"
 #include "nsIFrame.h"
 #include "nsIContent.h"
 #include "nsIPresShell.h"
@@ -28,16 +29,24 @@
 #include "nsDOMEventTargetHelper.h"
 #include "nsPIWindowRoot.h"
 #include "nsGlobalWindow.h"
 #include "nsDeviceContext.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+namespace mozilla {
+namespace dom {
+namespace workers {
+extern bool IsCurrentThreadRunningChromeWorker();
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
 static char *sPopupAllowedEvents;
 
 
 nsDOMEvent::nsDOMEvent(mozilla::dom::EventTarget* aOwner,
                        nsPresContext* aPresContext, WidgetEvent* aEvent)
 {
   ConstructorInit(aOwner, aPresContext, aEvent);
 }
@@ -212,16 +221,24 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
     }
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresContext)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExplicitOriginalTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
+bool
+nsDOMEvent::IsChrome(JSContext* aCx) const
+{
+  return mIsMainThreadEvent ?
+    xpc::AccessCheck::isChrome(js::GetContextCompartment(aCx)) :
+    mozilla::dom::workers::IsCurrentThreadRunningChromeWorker();
+}
+
 // nsIDOMEventInterface
 NS_METHOD nsDOMEvent::GetType(nsAString& aType)
 {
   if (!mIsMainThreadEvent || !mEvent->typeString.IsEmpty()) {
     aType = mEvent->typeString;
     return NS_OK;
   }
   const char* name = GetEventName(mEvent->message);
@@ -431,35 +448,50 @@ nsDOMEvent::GetIsTrusted(bool *aIsTruste
 {
   *aIsTrusted = IsTrusted();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMEvent::PreventDefault()
 {
-  if (mEvent->mFlags.mCancelable) {
-    mEvent->mFlags.mDefaultPrevented = true;
+  // This method is called only from C++ code which must handle default action
+  // of this event.  So, pass true always.
+  PreventDefaultInternal(true);
+  return NS_OK;
+}
+
+void
+nsDOMEvent::PreventDefault(JSContext* aCx)
+{
+  MOZ_ASSERT(aCx, "JS context must be specified");
 
-    // Need to set an extra flag for drag events.
-    if (mEvent->eventStructType == NS_DRAG_EVENT && IsTrusted()) {
-      nsCOMPtr<nsINode> node = do_QueryInterface(mEvent->currentTarget);
-      if (!node) {
-        nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mEvent->currentTarget);
-        if (win) {
-          node = win->GetExtantDoc();
-        }
-      }
-      if (node && !nsContentUtils::IsChromeDoc(node->OwnerDoc())) {
-        mEvent->mFlags.mDefaultPreventedByContent = true;
-      }
-    }
+  // Note that at handling default action, another event may be dispatched.
+  // Then, JS in content mey be call preventDefault()
+  // even in the event is in system event group.  Therefore, don't refer
+  // mInSystemGroup here.
+  PreventDefaultInternal(IsChrome(aCx));
+}
+
+void
+nsDOMEvent::PreventDefaultInternal(bool aCalledByDefaultHandler)
+{
+  if (!mEvent->mFlags.mCancelable) {
+    return;
   }
 
-  return NS_OK;
+  mEvent->mFlags.mDefaultPrevented = true;
+
+  // Note that even if preventDefault() has already been called by chrome,
+  // a call of preventDefault() by content needs to overwrite
+  // mDefaultPreventedByContent to true because in such case, defaultPrevented
+  // must be true when web apps check it after they call preventDefault().
+  if (!aCalledByDefaultHandler) {
+    mEvent->mFlags.mDefaultPreventedByContent = true;
+  }
 }
 
 void
 nsDOMEvent::SetEventType(const nsAString& aEventTypeArg)
 {
   if (mIsMainThreadEvent) {
     mEvent->userType =
       nsContentUtils::GetEventIdAndAtom(aEventTypeArg, mEvent->eventStructType,
@@ -1139,38 +1171,62 @@ const char* nsDOMEvent::GetEventName(uin
   // create and that are not user defined events since this function and
   // SetEventType are incomplete.  (But fixing that requires fixing the
   // arrays in nsEventListenerManager too, since the events for which
   // this is a problem generally *are* created by nsDOMEvent.)
   return nullptr;
 }
 
 bool
+nsDOMEvent::DefaultPrevented(JSContext* aCx) const
+{
+  MOZ_ASSERT(aCx, "JS context must be specified");
+
+  NS_ENSURE_TRUE(mEvent, false);
+
+  // If preventDefault() has never been called, just return false.
+  if (!mEvent->mFlags.mDefaultPrevented) {
+    return false;
+  }
+
+  // If preventDefault() has been called by content, return true.  Otherwise,
+  // i.e., preventDefault() has been called by chrome, return true only when
+  // this is called by chrome.
+  return mEvent->mFlags.mDefaultPreventedByContent || IsChrome(aCx);
+}
+
+bool
 nsDOMEvent::GetPreventDefault() const
 {
   if (mOwner) {
     if (nsIDocument* doc = mOwner->GetExtantDoc()) {
       doc->WarnOnceAbout(nsIDocument::eGetPreventDefault);
     }
   }
+  // GetPreventDefault() is legacy and Gecko specific method.  Although,
+  // the result should be same as defaultPrevented, we don't need to break
+  // backward compatibility of legacy method.  Let's behave traditionally.
   return DefaultPrevented();
 }
 
 NS_IMETHODIMP
 nsDOMEvent::GetPreventDefault(bool* aReturn)
 {
   NS_ENSURE_ARG_POINTER(aReturn);
   *aReturn = GetPreventDefault();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMEvent::GetDefaultPrevented(bool* aReturn)
 {
   NS_ENSURE_ARG_POINTER(aReturn);
+  // This method must be called by only event handlers implemented by C++.
+  // Then, the handlers must handle default action.  So, this method don't need
+  // to check if preventDefault() has been called by content or chrome.
   *aReturn = DefaultPrevented();
   return NS_OK;
 }
 
 NS_IMETHODIMP_(void)
 nsDOMEvent::Serialize(IPC::Message* aMsg, bool aSerializeInterfaceType)
 {
   if (aSerializeInterfaceType) {
--- a/content/events/src/nsDOMEvent.h
+++ b/content/events/src/nsDOMEvent.h
@@ -148,19 +148,30 @@ public:
   bool Cancelable() const
   {
     return mEvent->mFlags.mCancelable;
   }
 
   // xpidl implementation
   // void PreventDefault();
 
+  // You MUST NOT call PreventDefaultJ(JSContext*) from C++ code.  A call of
+  // this method always sets Event.defaultPrevented true for web contents.
+  // If default action handler calls this, web applications meet wrong
+  // defaultPrevented value.
+  void PreventDefault(JSContext* aCx);
+
+  // You MUST NOT call DefaultPrevented(JSContext*) from C++ code.  This may
+  // return false even if PreventDefault() has been called.
+  // See comments in its implementation for the detail.
+  bool DefaultPrevented(JSContext* aCx) const;
+
   bool DefaultPrevented() const
   {
-    return mEvent && mEvent->mFlags.mDefaultPrevented;
+    return mEvent->mFlags.mDefaultPrevented;
   }
 
   bool MultipleActionsPrevented() const
   {
     return mEvent->mFlags.mMultipleActionsPrevented;
   }
 
   bool IsTrusted() const
@@ -185,16 +196,30 @@ public:
   bool GetPreventDefault() const;
 
 protected:
 
   // Internal helper functions
   void SetEventType(const nsAString& aEventTypeArg);
   already_AddRefed<nsIContent> GetTargetFromFrame();
 
+  /**
+   * IsChrome() returns true if aCx is chrome context or the event is created
+   * in chrome's thread.  Otherwise, false.
+   */
+  bool IsChrome(JSContext* aCx) const;
+
+  /**
+   * @param aCalledByDefaultHandler     Should be true when this is called by
+   *                                    C++ or Chrome.  Otherwise, e.g., called
+   *                                    by a call of Event.preventDefault() in
+   *                                    content script, false.
+   */
+  void PreventDefaultInternal(bool aCalledByDefaultHandler);
+
   mozilla::WidgetEvent*       mEvent;
   nsRefPtr<nsPresContext>     mPresContext;
   nsCOMPtr<mozilla::dom::EventTarget> mExplicitOriginalTarget;
   nsCOMPtr<nsPIDOMWindow>     mOwner; // nsPIDOMWindow for now.
   bool                        mEventIsInternal;
   bool                        mPrivateDataDuplicated;
   bool                        mIsMainThreadEvent;
 };
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -2605,134 +2605,140 @@ nsEventStateManager::DispatchLegacyMouse
   // Send the legacy events in following order:
   // 1. Vertical scroll
   // 2. Vertical pixel scroll (even if #1 isn't consumed)
   // 3. Horizontal scroll (even if #1 and/or #2 are consumed)
   // 4. Horizontal pixel scroll (even if #3 isn't consumed)
 
   nsWeakFrame targetFrame(aTargetFrame);
 
-  nsEventStatus statusX = *aStatus;
-  nsEventStatus statusY = *aStatus;
+  MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault &&
+             !aEvent->mFlags.mDefaultPrevented,
+             "If you make legacy events dispatched for default prevented wheel "
+             "event, you need to initialize stateX and stateY");
+  EventState stateX, stateY;
   if (scrollDeltaY) {
-    SendLineScrollEvent(aTargetFrame, aEvent, &statusY,
+    SendLineScrollEvent(aTargetFrame, aEvent, stateY,
                         scrollDeltaY, DELTA_DIRECTION_Y);
     if (!targetFrame.IsAlive()) {
       *aStatus = nsEventStatus_eConsumeNoDefault;
       return;
     }
   }
 
   if (pixelDeltaY) {
-    SendPixelScrollEvent(aTargetFrame, aEvent, &statusY,
+    SendPixelScrollEvent(aTargetFrame, aEvent, stateY,
                          pixelDeltaY, DELTA_DIRECTION_Y);
     if (!targetFrame.IsAlive()) {
       *aStatus = nsEventStatus_eConsumeNoDefault;
       return;
     }
   }
 
   if (scrollDeltaX) {
-    SendLineScrollEvent(aTargetFrame, aEvent, &statusX,
+    SendLineScrollEvent(aTargetFrame, aEvent, stateX,
                         scrollDeltaX, DELTA_DIRECTION_X);
     if (!targetFrame.IsAlive()) {
       *aStatus = nsEventStatus_eConsumeNoDefault;
       return;
     }
   }
 
   if (pixelDeltaX) {
-    SendPixelScrollEvent(aTargetFrame, aEvent, &statusX,
+    SendPixelScrollEvent(aTargetFrame, aEvent, stateX,
                          pixelDeltaX, DELTA_DIRECTION_X);
     if (!targetFrame.IsAlive()) {
       *aStatus = nsEventStatus_eConsumeNoDefault;
       return;
     }
   }
 
-  if (statusY == nsEventStatus_eConsumeNoDefault ||
-      statusX == nsEventStatus_eConsumeNoDefault) {
+  if (stateY.mDefaultPrevented || stateX.mDefaultPrevented) {
     *aStatus = nsEventStatus_eConsumeNoDefault;
-    return;
-  }
-  if (statusY == nsEventStatus_eConsumeDoDefault ||
-      statusX == nsEventStatus_eConsumeDoDefault) {
-    *aStatus = nsEventStatus_eConsumeDoDefault;
+    aEvent->mFlags.mDefaultPrevented = true;
+    aEvent->mFlags.mDefaultPreventedByContent |=
+      stateY.mDefaultPreventedByContent || stateX.mDefaultPreventedByContent;
   }
 }
 
 void
 nsEventStateManager::SendLineScrollEvent(nsIFrame* aTargetFrame,
                                          WidgetWheelEvent* aEvent,
-                                         nsEventStatus* aStatus,
+                                         EventState& aState,
                                          int32_t aDelta,
                                          DeltaDirection aDeltaDirection)
 {
   nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
   if (!targetContent)
     targetContent = GetFocusedContent();
   if (!targetContent)
     return;
 
   while (targetContent->IsNodeOfType(nsINode::eTEXT)) {
     targetContent = targetContent->GetParent();
   }
 
   WidgetMouseScrollEvent event(aEvent->mFlags.mIsTrusted, NS_MOUSE_SCROLL,
                                aEvent->widget);
-  if (*aStatus == nsEventStatus_eConsumeNoDefault) {
-    event.mFlags.mDefaultPrevented = true;
-  }
+  event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
+  event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
   event.refPoint = aEvent->refPoint;
   event.widget = aEvent->widget;
   event.time = aEvent->time;
   event.modifiers = aEvent->modifiers;
   event.buttons = aEvent->buttons;
   event.isHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
   event.delta = aDelta;
   event.inputSource = aEvent->inputSource;
 
+  nsEventStatus status = nsEventStatus_eIgnore;
   nsEventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(),
-                              &event, nullptr, aStatus);
+                              &event, nullptr, &status);
+  aState.mDefaultPrevented =
+    event.mFlags.mDefaultPrevented || status == nsEventStatus_eConsumeNoDefault;
+  aState.mDefaultPreventedByContent = event.mFlags.mDefaultPreventedByContent;
 }
 
 void
 nsEventStateManager::SendPixelScrollEvent(nsIFrame* aTargetFrame,
                                           WidgetWheelEvent* aEvent,
-                                          nsEventStatus* aStatus,
+                                          EventState& aState,
                                           int32_t aPixelDelta,
                                           DeltaDirection aDeltaDirection)
 {
   nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
   if (!targetContent) {
     targetContent = GetFocusedContent();
     if (!targetContent)
       return;
   }
 
   while (targetContent->IsNodeOfType(nsINode::eTEXT)) {
     targetContent = targetContent->GetParent();
   }
 
   WidgetMouseScrollEvent event(aEvent->mFlags.mIsTrusted, NS_MOUSE_PIXEL_SCROLL,
                                aEvent->widget);
-  if (*aStatus == nsEventStatus_eConsumeNoDefault) {
-    event.mFlags.mDefaultPrevented = true;
-  }
+  event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
+  event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
   event.refPoint = aEvent->refPoint;
   event.widget = aEvent->widget;
   event.time = aEvent->time;
   event.modifiers = aEvent->modifiers;
   event.buttons = aEvent->buttons;
   event.isHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
   event.delta = aPixelDelta;
   event.inputSource = aEvent->inputSource;
 
+  nsEventStatus status = nsEventStatus_eIgnore;
   nsEventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(),
-                              &event, nullptr, aStatus);
+                              &event, nullptr, &status);
+  aState.mDefaultPrevented =
+    event.mFlags.mDefaultPrevented || status == nsEventStatus_eConsumeNoDefault;
+  aState.mDefaultPreventedByContent = event.mFlags.mDefaultPreventedByContent;
 }
 
 nsIScrollableFrame*
 nsEventStateManager::ComputeScrollTarget(nsIFrame* aTargetFrame,
                                          WidgetWheelEvent* aEvent,
                                          ComputeScrollTargetOptions aOptions)
 {
   return ComputeScrollTarget(aTargetFrame, aEvent->deltaX, aEvent->deltaY,
--- a/content/events/src/nsEventStateManager.h
+++ b/content/events/src/nsEventStateManager.h
@@ -476,49 +476,62 @@ protected:
    * This is clearer than using bool.
    */
   enum DeltaDirection
   {
     DELTA_DIRECTION_X = 0,
     DELTA_DIRECTION_Y
   };
 
+  struct MOZ_STACK_CLASS EventState
+  {
+    bool mDefaultPrevented;
+    bool mDefaultPreventedByContent;
+
+    EventState() :
+      mDefaultPrevented(false), mDefaultPreventedByContent(false)
+    {
+    }
+  };
+
   /**
    * SendLineScrollEvent() dispatches a DOMMouseScroll event for the
    * WidgetWheelEvent.  This method shouldn't be called for non-trusted
    * wheel event because it's not necessary for compatiblity.
    *
    * @param aTargetFrame        The event target of wheel event.
    * @param aEvent              The original Wheel event.
-   * @param aStatus             The event status, must not be
-   *                            nsEventStatus_eConsumeNoDefault.
+   * @param aState              The event which should be set to the dispatching
+   *                            event.  This also returns the dispatched event
+   *                            state.
    * @param aDelta              The delta value of the event.
    * @param aDeltaDirection     The X/Y direction of dispatching event.
    */
   void SendLineScrollEvent(nsIFrame* aTargetFrame,
                            mozilla::WidgetWheelEvent* aEvent,
-                           nsEventStatus* aStatus,
+                           EventState& aState,
                            int32_t aDelta,
                            DeltaDirection aDeltaDirection);
 
   /**
    * SendPixelScrollEvent() dispatches a MozMousePixelScroll event for the
    * WidgetWheelEvent.  This method shouldn't be called for non-trusted
    * wheel event because it's not necessary for compatiblity.
    *
    * @param aTargetFrame        The event target of wheel event.
    * @param aEvent              The original Wheel event.
-   * @param aStatus             The event status, must not be
-   *                            nsEventStatus_eConsumeNoDefault.
+   * @param aState              The event which should be set to the dispatching
+   *                            event.  This also returns the dispatched event
+   *                            state.
    * @param aPixelDelta         The delta value of the event.
    * @param aDeltaDirection     The X/Y direction of dispatching event.
    */
   void SendPixelScrollEvent(nsIFrame* aTargetFrame,
                             mozilla::WidgetWheelEvent* aEvent,
-                            nsEventStatus* aStatus,
+                            EventState& aState,
                             int32_t aPixelDelta,
                             DeltaDirection aDeltaDirection);
 
   /**
    * ComputeScrollTarget() returns the scrollable frame which should be
    * scrolled.
    *
    * @param aTargetFrame        The event target of the wheel event.
--- a/content/events/test/chrome.ini
+++ b/content/events/test/chrome.ini
@@ -10,9 +10,10 @@ support-files =
 [test_bug336682.js]
 [test_bug336682_2.xul]
 [test_bug415498.xul]
 [test_bug586961.xul]
 [test_bug591249.xul]
 [test_bug602962.xul]
 [test_bug617528.xul]
 [test_bug679494.xul]
+[test_bug930374-chrome.html]
 [test_eventctors.xul]
--- a/content/events/test/mochitest.ini
+++ b/content/events/test/mochitest.ini
@@ -78,16 +78,17 @@ skip-if = true # Disabled due to timeout
 [test_bug689564.html]
 [test_bug698929.html]
 [test_bug741666.html]
 [test_bug742376.html]
 [test_bug812744.html]
 [test_bug847597.html]
 [test_bug855741.html]
 [test_bug864040.html]
+[test_bug930374-content.html]
 skip-if = toolkit == "gonk"
 [test_clickevent_on_input.html]
 [test_continuous_wheel_events.html]
 [test_dblclick_explicit_original_target.html]
 [test_dom_keyboard_event.html]
 [test_dom_mouse_event.html]
 [test_dom_wheel_event.html]
 [test_draggableprop.html]
new file mode 100644
--- /dev/null
+++ b/content/events/test/test_bug930374-chrome.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=930374
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 930374</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=930374">Mozilla Bug 930374</a>
+<div id="display">
+  <input id="input-text">
+</div>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+  <script type="application/javascript">
+    SimpleTest.waitForExplicitFinish();
+
+    var gKeyPress = null;
+    function onKeyPress(aEvent)
+    {
+      gKeyPress = aEvent;
+      is(aEvent.target, document.getElementById("input-text"), "input element should have focus");
+      ok(!aEvent.defaultPrevented, "keypress event should be consumed before keypress event handler");
+    }
+
+    function runTests()
+    {
+      document.addEventListener("keypress", onKeyPress, false);
+      var input = document.getElementById("input-text");
+      input.focus();
+
+      input.addEventListener("input", function (aEvent) {
+          input.removeEventListener("input", arguments.callee, false);
+          ok(gKeyPress,
+             "Test1: keypress event must be fired before an input event");
+          ok(gKeyPress.defaultPrevented,
+             "Test1: keypress event's defaultPrevented should be true in chrome even if it's consumed by default action handler of editor");
+          setTimeout(function () {
+            ok(gKeyPress.defaultPrevented,
+               "Test2: keypress event's defaultPrevented should be true after event dispatching finished");
+            SimpleTest.finish();
+          }, 0);
+        }, false);
+
+      sendChar("a");
+    }
+
+    SimpleTest.waitForFocus(runTests);
+  </script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/events/test/test_bug930374-content.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=930374
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 930374</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=930374">Mozilla Bug 930374</a>
+<div id="display">
+  <input id="input-text">
+</div>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+  <script type="application/javascript">
+    SimpleTest.waitForExplicitFinish();
+
+    var gKeyPress = null;
+    function onKeyPress(aEvent)
+    {
+      gKeyPress = aEvent;
+      is(aEvent.target, document.getElementById("input-text"), "input element should have focus");
+      ok(!aEvent.defaultPrevented, "keypress event should be consumed before keypress event handler");
+    }
+
+    function runTests()
+    {
+      document.addEventListener("keypress", onKeyPress, false);
+      var input = document.getElementById("input-text");
+      input.focus();
+
+      input.addEventListener("input", function (aEvent) {
+          input.removeEventListener("input", arguments.callee, false);
+          ok(gKeyPress,
+             "Test1: keypress event must be fired before an input event");
+          ok(!gKeyPress.defaultPrevented,
+             "Test1: keypress event's defaultPrevented should be false even though it's consumed by the default action handler of editor");
+          gKeyPress.preventDefault();
+          ok(gKeyPress.defaultPrevented,
+             "Test1: keypress event's defaultPrevented should become true because of a call of preventDefault()");
+        }, false);
+
+      sendChar("a");
+      gKeyPress = null;
+
+      input.addEventListener("input", function (aEvent) {
+          input.removeEventListener("input", arguments.callee, false);
+          ok(gKeyPress,
+             "Test2: keypress event must be fired before an input event");
+          ok(!gKeyPress.defaultPrevented,
+             "Test2: keypress event's defaultPrevented should be false even though it's consumed by the default action handler of editor");
+          setTimeout(function () {
+            ok(!gKeyPress.defaultPrevented,
+               "Test2: keypress event's defaultPrevented should not become true after event dispatching finished");
+            SimpleTest.finish();
+          }, 0);
+        }, false);
+
+      sendChar("b");
+    }
+
+    SimpleTest.waitForFocus(runTests);
+  </script>
+</pre>
+</body>
+</html>
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -380,16 +380,17 @@ DOMInterfaces = {
         'classList', 'attributes', 'children', 'firstElementChild',
         'lastElementChild', 'previousElementSibling', 'nextElementSibling',
         'getAttributeNode', 'getAttributeNodeNS', 'querySelector'
     ]
 },
 
 'Event': {
     'nativeType': 'nsDOMEvent',
+    'implicitJSContext': [ 'defaultPrevented', 'preventDefault' ],
 },
 
 'EventTarget': {
     'hasXPConnectImpls': True,
     'concrete': False,
     'jsImplParent': 'nsDOMEventTargetHelper'
 },