Bug 816340 - Propagate events to chrome even if there is a disabled form control in the event target chain, r=jst,gavin
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Thu, 03 Jan 2013 17:17:36 +0200
changeset 117371 801ba75ac563
parent 117370 6955309291ee
child 117372 38407b98003b
push id24099
push useropettay@mozilla.com
push date2013-01-03 16:12 +0000
treeherdermozilla-central@801ba75ac563 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst, gavin
bugs816340
milestone20.0a1
first release with
nightly linux32
801ba75ac563 / 20.0a1 / 20130104030823 / files
nightly linux64
801ba75ac563 / 20.0a1 / 20130104030823 / files
nightly mac
801ba75ac563 / 20.0a1 / 20130104030823 / files
nightly win32
801ba75ac563 / 20.0a1 / 20130104030823 / files
nightly win64
801ba75ac563 / 20.0a1 / 20130104030823 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 816340 - Propagate events to chrome even if there is a disabled form control in the event target chain, r=jst,gavin
browser/base/content/nsContextMenu.js
content/base/src/contentAreaDropListener.js
content/base/test/chrome/Makefile.in
content/base/test/chrome/file_bug816340.xul
content/base/test/chrome/test_bug816340.xul
content/events/public/nsEventDispatcher.h
content/events/src/nsEventDispatcher.cpp
content/html/content/public/nsIFormControl.h
content/html/content/src/nsGenericHTMLElement.h
content/html/content/src/nsHTMLButtonElement.cpp
content/html/content/src/nsHTMLFieldSetElement.cpp
content/html/content/src/nsHTMLFieldSetElement.h
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
content/html/content/src/nsHTMLSelectElement.cpp
content/html/content/src/nsHTMLSelectElement.h
content/html/content/src/nsHTMLTextAreaElement.cpp
content/xul/content/src/nsXULElement.cpp
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
layout/base/nsPresShell.cpp
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -427,17 +427,18 @@ nsContextMenu.prototype = {
       inspector.selection.setNode(this.target, "browser-context-menu");
     }.bind(this));
   },
 
   // Set various context menu attributes based on the state of the world.
   setTarget: function (aNode, aRangeParent, aRangeOffset) {
     const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     if (aNode.namespaceURI == xulNS ||
-        aNode.nodeType == Node.DOCUMENT_NODE) {
+        aNode.nodeType == Node.DOCUMENT_NODE ||
+        this.isDisabledForEvents(aNode)) {
       this.shouldDisplay = false;
       return;
     }
 
     // Initialize contextual info.
     this.onImage           = false;
     this.onLoadedImage     = false;
     this.onCompletedImage  = false;
@@ -1285,16 +1286,26 @@ nsContextMenu.prototype = {
     return "contextMenu.target     = " + this.target + "\n" +
            "contextMenu.onImage    = " + this.onImage + "\n" +
            "contextMenu.onLink     = " + this.onLink + "\n" +
            "contextMenu.link       = " + this.link + "\n" +
            "contextMenu.inFrame    = " + this.inFrame + "\n" +
            "contextMenu.hasBGImage = " + this.hasBGImage + "\n";
   },
 
+  isDisabledForEvents: function(aNode) {
+    let ownerDoc = aNode.ownerDocument;
+    return
+      ownerDoc.defaultView &&
+      ownerDoc.defaultView
+              .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+              .getInterface(Components.interfaces.nsIDOMWindowUtils)
+              .isNodeDisabledForEvents(aNode);
+  },
+
   isTargetATextBox: function(node) {
     if (node instanceof HTMLInputElement)
       return node.mozIsTextField(false);
 
     return (node instanceof HTMLTextAreaElement);
   },
 
   isTargetAKeywordField: function(aNode) {
--- a/content/base/src/contentAreaDropListener.js
+++ b/content/base/src/contentAreaDropListener.js
@@ -90,16 +90,19 @@ ContentAreaDropListener.prototype =
 
     secMan.checkLoadURIStrWithPrincipal(principal, uriString, flags);
 
     return uriString;
   },
 
   canDropLink: function(aEvent, aAllowSameDocument)
   {
+    if (this._eventTargetIsDisabled(aEvent))
+      return false;
+
     let dataTransfer = aEvent.dataTransfer;
     let types = dataTransfer.types;
     if (!types.contains("application/x-moz-file") &&
         !types.contains("text/x-moz-url") &&
         !types.contains("text/uri-list") &&
         !types.contains("text/x-moz-text-internal") &&
         !types.contains("text/plain"))
       return false;
@@ -126,29 +129,43 @@ ContentAreaDropListener.prototype =
     }
 
     return true;
   },
 
   dropLink: function(aEvent, aName, aDisallowInherit)
   {
     aName.value = "";
+    if (this._eventTargetIsDisabled(aEvent))
+      return "";
 
     let dataTransfer = aEvent.dataTransfer;
     let [url, name] = this._getDropURL(dataTransfer);
 
     try {
       url = this._validateURI(dataTransfer, url, aDisallowInherit);
     } catch (ex) {
       aEvent.stopPropagation();
       aEvent.preventDefault();
       throw ex;
     }
 
     if (name)
       aName.value = name;
 
     return url;
+  },
+
+  _eventTargetIsDisabled: function(aEvent)
+  {
+    let ownerDoc = aEvent.originalTarget.ownerDocument;
+    if (!ownerDoc || !ownerDoc.defaultView)
+      return false;
+
+    return ownerDoc.defaultView
+                   .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                   .getInterface(Components.interfaces.nsIDOMWindowUtils)
+                   .isNodeDisabledForEvents(aEvent.originalTarget);
   }
 };
 
 var components = [ContentAreaDropListener];
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/content/base/test/chrome/Makefile.in
+++ b/content/base/test/chrome/Makefile.in
@@ -44,15 +44,17 @@ MOCHITEST_CHROME_FILES = \
     test_bug752226-3.xul \
     test_bug752226-4.xul \
     test_bug682305.html \
     test_bug780199.xul \
     test_bug780529.xul \
     test_csp_bug768029.html \
     test_bug800386.xul \
     test_csp_bug773891.html \
+    test_bug816340.xul \
+    file_bug816340.xul \
     test_domparsing.xul \
     test_bug814638.xul \
     host_bug814638.xul \
     frame_bug814638.xul \
     $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/content/base/test/chrome/file_bug816340.xul
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=816340
+-->
+<window title="Mozilla Bug 816340"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  onload="start();">
+  <label value="Mozilla Bug 816340"/>
+  <!-- test code goes here -->
+  <script type="application/javascript"><![CDATA[
+
+    function ok(val, msg) {
+      opener.wrappedJSObject.ok(val, msg);
+    }
+
+    var elems = 
+      [
+        "input",
+        "textarea",
+        "select",
+        "fieldset",
+        "button",
+      ];
+
+    var chromeDidGetEvent = false;
+    function chromeListener() {
+      chromeDidGetEvent = true;
+    }
+
+    function testElement(el, disabled, contentShouldGetEvent) {
+      chromeDidGetEvent = false;
+      var b = document.getElementById("browser");
+      b.contentDocument.body.innerHTML = null;
+      var e = b.contentDocument.createElement(el);
+      if (disabled) {
+        e.setAttribute("disabled", "true");
+      }
+      b.contentDocument.body.appendChild(e);
+      var contentDidGetEvent = false;
+      b.contentDocument.body.addEventListener("foo",
+        function() { contentDidGetEvent = true }, true);
+
+      b.addEventListener("foo", chromeListener, true);
+      e.dispatchEvent(new Event("foo"));
+      b.removeEventListener("foo", chromeListener, true);
+      ok(contentDidGetEvent == contentShouldGetEvent, "content: " + el + (disabled ? " disabled" : ""));
+      ok(chromeDidGetEvent, "chrome: " + el + (disabled ? " disabled" : ""));
+    }
+      
+    function start() {
+      // Test common element.
+      testElement("div", false, true);
+      testElement("div", true, true);
+
+      for (var i = 0; i < elems.length; ++i) {
+        testElement(elems[i], false, true);
+        testElement(elems[i], true, false);
+      }
+      ok(true, "done");
+      opener.setTimeout("done()", 0);
+      window.close();
+    }
+
+  ]]></script>
+
+  <browser id="browser" type="content" src="about:blank"/>
+</window>
new file mode 100644
--- /dev/null
+++ b/content/base/test/chrome/test_bug816340.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=816340
+-->
+<window title="Mozilla Bug 816340"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=816340"
+     target="_blank">Mozilla Bug 816340</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript"><![CDATA[
+  SimpleTest.waitForExplicitFinish();
+
+  function done() {
+    SimpleTest.finish();
+  }
+
+  addLoadEvent(function() {
+    window.open("file_bug816340.xul", "", "chrome");
+  });
+  ]]></script>
+</window>
--- a/content/events/public/nsEventDispatcher.h
+++ b/content/events/public/nsEventDispatcher.h
@@ -95,41 +95,49 @@ public:
 class nsEventChainPreVisitor : public nsEventChainVisitor {
 public:
   nsEventChainPreVisitor(nsPresContext* aPresContext,
                          nsEvent* aEvent,
                          nsIDOMEvent* aDOMEvent,
                          nsEventStatus aEventStatus,
                          bool aIsInAnon)
   : nsEventChainVisitor(aPresContext, aEvent, aDOMEvent, aEventStatus),
-    mCanHandle(true), mForceContentDispatch(false),
-    mRelatedTargetIsInAnon(false), mOriginalTargetIsInAnon(aIsInAnon),
-    mWantsWillHandleEvent(false), mMayHaveListenerManager(true),
-    mParentTarget(nullptr), mEventTargetAtParent(nullptr) {}
+    mCanHandle(true), mAutomaticChromeDispatch(true),
+    mForceContentDispatch(false), mRelatedTargetIsInAnon(false),
+    mOriginalTargetIsInAnon(aIsInAnon), mWantsWillHandleEvent(false),
+    mMayHaveListenerManager(true), mParentTarget(nullptr),
+    mEventTargetAtParent(nullptr) {}
 
   void Reset() {
     mItemFlags = 0;
     mItemData = nullptr;
     mCanHandle = true;
+    mAutomaticChromeDispatch = true;
     mForceContentDispatch = false;
     mWantsWillHandleEvent = false;
     mMayHaveListenerManager = true;
     mParentTarget = nullptr;
     mEventTargetAtParent = nullptr;
   }
 
   /**
    * Member that must be set in PreHandleEvent by event targets. If set to false,
    * indicates that this event target will not be handling the event and
    * construction of the event target chain is complete. The target that sets
    * mCanHandle to false is NOT included in the event target chain.
    */
   bool                  mCanHandle;
 
   /**
+   * If mCanHandle is false and mAutomaticChromeDispatch is also false
+   * event will not be dispatched to the chrome event handler.
+   */
+  bool                  mAutomaticChromeDispatch;
+
+  /**
    * If mForceContentDispatch is set to true,
    * content dispatching is not disabled for this event target.
    * FIXME! This is here for backward compatibility. Bug 329119
    */
   bool                  mForceContentDispatch;
 
   /**
    * true if it is known that related target is or is a descendant of an
--- a/content/events/src/nsEventDispatcher.cpp
+++ b/content/events/src/nsEventDispatcher.cpp
@@ -192,17 +192,17 @@ public:
    */
   nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor,
                            nsCxPusher* aPusher);
 
   static uint32_t MaxEtciCount() { return sMaxEtciCount; }
 
   static void ResetMaxEtciCount()
   {
-    NS_ASSERTION(!sCurrentEtciCount, "Wrong time to call ResetMaxEtciCount()!");
+    MOZ_ASSERT(!sCurrentEtciCount, "Wrong time to call ResetMaxEtciCount()!");
     sMaxEtciCount = 0;
   }
 
   nsCOMPtr<nsIDOMEventTarget>       mTarget;
   nsEventTargetChainItem*           mChild;
   union {
     nsEventTargetChainItem*         mParent;
      // This is used only when caching ETCI objects.
@@ -433,16 +433,40 @@ public:
   static int32_t               sEtciPoolUsers;
 };
 
 nsFixedSizeAllocator* ChainItemPool::sEtciPool = nullptr;
 int32_t ChainItemPool::sEtciPoolUsers = 0;
 
 void NS_ShutdownChainItemPool() { ChainItemPool::Shutdown(); }
 
+nsEventTargetChainItem*
+EventTargetChainItemForChromeTarget(ChainItemPool& aPool,
+                                    nsINode* aNode,
+                                    nsEventTargetChainItem* aChild = nullptr)
+{
+  if (!aNode->IsInDoc()) {
+    return nullptr;
+  }
+  nsPIDOMWindow* win = aNode->OwnerDoc()->GetInnerWindow();
+  nsIDOMEventTarget* piTarget = win ? win->GetParentTarget() : nullptr;
+  NS_ENSURE_TRUE(piTarget, nullptr);
+
+  nsEventTargetChainItem* etci =
+    nsEventTargetChainItem::Create(aPool.GetPool(),
+                                   piTarget->GetTargetForEventTargetChain(),
+                                   aChild);
+  NS_ENSURE_TRUE(etci, nullptr);
+  if (!etci->IsValid()) {
+    nsEventTargetChainItem::Destroy(aPool.GetPool(), etci);
+    return nullptr;
+  }
+  return etci;
+}
+
 /* static */ nsresult
 nsEventDispatcher::Dispatch(nsISupports* aTarget,
                             nsPresContext* aPresContext,
                             nsEvent* aEvent,
                             nsIDOMEvent* aDOMEvent,
                             nsEventStatus* aEventStatus,
                             nsDispatchingCallback* aCallback,
                             nsCOMArray<nsIDOMEventTarget>* aTargets)
@@ -572,23 +596,31 @@ nsEventDispatcher::Dispatch(nsISupports*
 
   // Create visitor object and start event dispatching.
   // PreHandleEvent for the original target.
   nsEventStatus status = aEventStatus ? *aEventStatus : nsEventStatus_eIgnore;
   nsEventChainPreVisitor preVisitor(aPresContext, aEvent, aDOMEvent, status,
                                     isInAnon);
   targetEtci->PreHandleEvent(preVisitor);
 
+  if (!preVisitor.mCanHandle && preVisitor.mAutomaticChromeDispatch && content) {
+    // Event target couldn't handle the event. Try to propagate to chrome.
+    nsEventTargetChainItem::Destroy(pool.GetPool(), targetEtci);
+    targetEtci = EventTargetChainItemForChromeTarget(pool, content);
+    NS_ENSURE_STATE(targetEtci);
+    targetEtci->PreHandleEvent(preVisitor);
+  }
   if (preVisitor.mCanHandle) {
     // At least the original target can handle the event.
     // Setting the retarget to the |target| simplifies retargeting code.
     nsCOMPtr<nsIDOMEventTarget> t = aEvent->target;
     targetEtci->SetNewTarget(t);
     nsEventTargetChainItem* topEtci = targetEtci;
     while (preVisitor.mParentTarget) {
+      nsIDOMEventTarget* parentTarget = preVisitor.mParentTarget;
       nsEventTargetChainItem* parentEtci =
         nsEventTargetChainItem::Create(pool.GetPool(), preVisitor.mParentTarget,
                                        topEtci);
       if (!parentEtci) {
         rv = NS_ERROR_OUT_OF_MEMORY;
         break;
       }
       if (!parentEtci->IsValid()) {
@@ -605,16 +637,34 @@ nsEventDispatcher::Dispatch(nsISupports*
       }
 
       parentEtci->PreHandleEvent(preVisitor);
       if (preVisitor.mCanHandle) {
         topEtci = parentEtci;
       } else {
         nsEventTargetChainItem::Destroy(pool.GetPool(), parentEtci);
         parentEtci = nullptr;
+        if (preVisitor.mAutomaticChromeDispatch && content) {
+          // Even if the current target can't handle the event, try to
+          // propagate to chrome.
+          nsCOMPtr<nsINode> disabledTarget = do_QueryInterface(parentTarget);
+          if (disabledTarget) {
+            parentEtci = EventTargetChainItemForChromeTarget(pool,
+                                                             disabledTarget,
+                                                             topEtci);
+            if (parentEtci) {
+              parentEtci->PreHandleEvent(preVisitor);
+              if (preVisitor.mCanHandle) {
+                targetEtci->SetNewTarget(parentTarget);
+                topEtci = parentEtci;
+                continue;
+              }
+            }
+          }
+        }
         break;
       }
     }
     if (NS_SUCCEEDED(rv)) {
       if (aTargets) {
         aTargets->Clear();
         nsEventTargetChainItem* item = targetEtci;
         while(item) {
--- a/content/html/content/public/nsIFormControl.h
+++ b/content/html/content/public/nsIFormControl.h
@@ -65,18 +65,18 @@ enum InputElementTypes {
   eInputElementTypesMax
 };
 
 PR_STATIC_ASSERT((uint32_t)eFormControlsWithoutSubTypesMax < (uint32_t)NS_FORM_BUTTON_ELEMENT);
 PR_STATIC_ASSERT((uint32_t)eButtonElementTypesMax < (uint32_t)NS_FORM_INPUT_ELEMENT);
 PR_STATIC_ASSERT((uint32_t)eInputElementTypesMax  < 1<<8);
 
 #define NS_IFORMCONTROL_IID   \
-{ 0xbc53dcf5, 0xbd4f, 0x4991, \
- { 0xa1, 0x87, 0xc4, 0x57, 0x98, 0x54, 0xda, 0x6e } }
+{ 0x4b89980c, 0x4dcd, 0x428f, \
+  { 0xb7, 0xad, 0x43, 0x5b, 0x93, 0x29, 0x79, 0xec } }
 
 /**
  * Interface which all form controls (e.g. buttons, checkboxes, text,
  * radio buttons, select, etc) implement in addition to their dom specific
  * interface.
  */
 class nsIFormControl : public nsISupports
 {
@@ -177,16 +177,20 @@ public:
   inline bool IsSubmittableControl() const;
 
   /**
    * Returns whether this form control can have draggable children.
    * @return whether this form control can have draggable children.
    */
   inline bool AllowDraggableChildren() const;
 
+  virtual bool IsDisabledForEvents(uint32_t aMessage)
+  {
+    return false;
+  }
 protected:
 
   /**
    * Returns whether mType corresponds to a single line text control type.
    * @param aExcludePassword to have NS_FORM_INPUT_PASSWORD ignored.
    * @param aType the type to be tested.
    * @return whether mType corresponds to a single line text control type.
    */
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -1173,17 +1173,17 @@ protected:
    * This method is a a callback for IDTargetObserver (from nsIDocument).
    * It will be called each time the element associated with the id in @form
    * changes.
    */
   static bool FormIdUpdated(Element* aOldElement, Element* aNewElement,
                               void* aData);
 
   // Returns true if the event should not be handled from PreHandleEvent
-  virtual bool IsElementDisabledForEvents(uint32_t aMessage, nsIFrame* aFrame);
+  bool IsElementDisabledForEvents(uint32_t aMessage, nsIFrame* aFrame);
 
   // The focusability state of this form control.  eUnfocusable means that it
   // shouldn't be focused at all, eInactiveWindow means it's in an inactive
   // window, eActiveWindow means it's in an active window.
   enum FocusTristate {
     eUnfocusable,
     eInactiveWindow,
     eActiveWindow
--- a/content/html/content/src/nsHTMLButtonElement.cpp
+++ b/content/html/content/src/nsHTMLButtonElement.cpp
@@ -77,16 +77,17 @@ public:
   NS_DECL_NSIDOMHTMLBUTTONELEMENT
 
   // overriden nsIFormControl methods
   NS_IMETHOD_(uint32_t) GetType() const { return mType; }
   NS_IMETHOD Reset();
   NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
   NS_IMETHOD SaveState();
   bool RestoreState(nsPresState* aState);
+  virtual bool IsDisabledForEvents(uint32_t aMessage);
 
   nsEventStates IntrinsicState() const;
 
   /**
    * Called when an attribute is about to be changed
    */
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                  const nsAttrValueOrString* aValue,
@@ -246,27 +247,32 @@ nsHTMLButtonElement::ParseAttribute(int3
       return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
     }
   }
 
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
-nsresult
-nsHTMLButtonElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
+bool
+nsHTMLButtonElement::IsDisabledForEvents(uint32_t aMessage)
 {
   nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
   nsIFrame* formFrame = NULL;
   if (formControlFrame) {
     formFrame = do_QueryFrame(formControlFrame);
   }
+  return IsElementDisabledForEvents(aMessage, formFrame);
+}
 
+nsresult
+nsHTMLButtonElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
+{
   aVisitor.mCanHandle = false;
-  if (IsElementDisabledForEvents(aVisitor.mEvent->message, formFrame)) {
+  if (IsDisabledForEvents(aVisitor.mEvent->message)) {
     return NS_OK;
   }
 
   // Track whether we're in the outermost Dispatch invocation that will
   // cause activation of the input.  That is, if we're a click event, or a
   // DOMActivate that was dispatched directly, this will be set, but if we're
   // a DOMActivate dispatched from click handling, it will not be set.
   bool outerActivateEvent =
--- a/content/html/content/src/nsHTMLFieldSetElement.cpp
+++ b/content/html/content/src/nsHTMLFieldSetElement.cpp
@@ -69,23 +69,29 @@ NS_IMPL_ELEMENT_CLONE(nsHTMLFieldSetElem
 
 
 NS_IMPL_BOOL_ATTR(nsHTMLFieldSetElement, Disabled, disabled)
 NS_IMPL_STRING_ATTR(nsHTMLFieldSetElement, Name, name)
 
 // nsIConstraintValidation
 NS_IMPL_NSICONSTRAINTVALIDATION(nsHTMLFieldSetElement)
 
+bool
+nsHTMLFieldSetElement::IsDisabledForEvents(uint32_t aMessage)
+{
+  return IsElementDisabledForEvents(aMessage, nullptr);
+}
+
 // nsIContent
 nsresult
 nsHTMLFieldSetElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
 {
   // Do not process any DOM events if the element is disabled.
   aVisitor.mCanHandle = false;
-  if (IsElementDisabledForEvents(aVisitor.mEvent->message, NULL)) {
+  if (IsDisabledForEvents(aVisitor.mEvent->message)) {
     return NS_OK;
   }
 
   return nsGenericHTMLFormElement::PreHandleEvent(aVisitor);
 }
 
 nsresult
 nsHTMLFieldSetElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
--- a/content/html/content/src/nsHTMLFieldSetElement.h
+++ b/content/html/content/src/nsHTMLFieldSetElement.h
@@ -46,16 +46,17 @@ public:
   virtual nsresult InsertChildAt(nsIContent* aChild, uint32_t aIndex,
                                      bool aNotify);
   virtual void RemoveChildAt(uint32_t aIndex, bool aNotify);
 
   // nsIFormControl
   NS_IMETHOD_(uint32_t) GetType() const { return NS_FORM_FIELDSET; }
   NS_IMETHOD Reset();
   NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
+  virtual bool IsDisabledForEvents(uint32_t aMessage);
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
   virtual nsXPCClassInfo* GetClassInfo();
   virtual nsIDOMNode* AsDOMNode() { return this; }
 
   const nsIContent* GetFirstLegend() const { return mFirstLegend; }
 
   void AddElement(nsGenericHTMLFormElement* aElement) {
     mDependentElements.AppendElement(aElement);
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -2238,22 +2238,28 @@ nsHTMLInputElement::NeedToInitializeEdit
   case NS_MOUSE_ENTER_SYNTH:
   case NS_MOUSE_EXIT_SYNTH:
     return false;
   default:
     return true;
   }
 }
 
+bool
+nsHTMLInputElement::IsDisabledForEvents(uint32_t aMessage)
+{
+  return IsElementDisabledForEvents(aMessage, GetPrimaryFrame());
+}
+
 nsresult
 nsHTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
 {
   // Do not process any DOM events if the element is disabled
   aVisitor.mCanHandle = false;
-  if (IsElementDisabledForEvents(aVisitor.mEvent->message, GetPrimaryFrame())) {
+  if (IsDisabledForEvents(aVisitor.mEvent->message)) {
     return NS_OK;
   }
 
   // Initialize the editor if needed.
   if (NeedToInitializeEditorForEvent(aVisitor)) {
     nsITextControlFrame* textControlFrame = do_QueryFrame(GetPrimaryFrame());
     if (textControlFrame)
       textControlFrame->EnsureEditorInitialized();
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -99,16 +99,17 @@ public:
 
   // Overriden nsIFormControl methods
   NS_IMETHOD_(uint32_t) GetType() const { return mType; }
   NS_IMETHOD Reset();
   NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
   NS_IMETHOD SaveState();
   virtual bool RestoreState(nsPresState* aState);
   virtual bool AllowDrop();
+  virtual bool IsDisabledForEvents(uint32_t aMessage);
 
   virtual void FieldSetDisabledChanged(bool aNotify);
 
   // nsIContent
   virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex);
 
   virtual bool ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
--- a/content/html/content/src/nsHTMLSelectElement.cpp
+++ b/content/html/content/src/nsHTMLSelectElement.cpp
@@ -1478,28 +1478,32 @@ nsHTMLSelectElement::IsAttributeMapped(c
 }
 
 nsMapRuleToAttributesFunc
 nsHTMLSelectElement::GetAttributeMappingFunction() const
 {
   return &MapAttributesIntoRule;
 }
 
-
-nsresult
-nsHTMLSelectElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
+bool
+nsHTMLSelectElement::IsDisabledForEvents(uint32_t aMessage)
 {
   nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
   nsIFrame* formFrame = nullptr;
   if (formControlFrame) {
     formFrame = do_QueryFrame(formControlFrame);
   }
+  return IsElementDisabledForEvents(aMessage, formFrame);
+}
 
+nsresult
+nsHTMLSelectElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
+{
   aVisitor.mCanHandle = false;
-  if (IsElementDisabledForEvents(aVisitor.mEvent->message, formFrame)) {
+  if (IsDisabledForEvents(aVisitor.mEvent->message)) {
     return NS_OK;
   }
 
   return nsGenericHTMLFormElement::PreHandleEvent(aVisitor);
 }
 
 nsresult
 nsHTMLSelectElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
--- a/content/html/content/src/nsHTMLSelectElement.h
+++ b/content/html/content/src/nsHTMLSelectElement.h
@@ -266,16 +266,17 @@ public:
   virtual void RemoveChildAt(uint32_t aIndex, bool aNotify);
 
   // Overriden nsIFormControl methods
   NS_IMETHOD_(uint32_t) GetType() const { return NS_FORM_SELECT; }
   NS_IMETHOD Reset();
   NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
   NS_IMETHOD SaveState();
   virtual bool RestoreState(nsPresState* aState);
+  virtual bool IsDisabledForEvents(uint32_t aMessage);
 
   virtual void FieldSetDisabledChanged(bool aNotify);
 
   nsEventStates IntrinsicState() const;
 
   /**
    * To be called when stuff is added under a child of the select--but *before*
    * they are actually added.
--- a/content/html/content/src/nsHTMLTextAreaElement.cpp
+++ b/content/html/content/src/nsHTMLTextAreaElement.cpp
@@ -92,16 +92,17 @@ public:
   NS_IMETHOD SetUserInput(const nsAString& aInput);
 
   // nsIFormControl
   NS_IMETHOD_(uint32_t) GetType() const { return NS_FORM_TEXTAREA; }
   NS_IMETHOD Reset();
   NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
   NS_IMETHOD SaveState();
   virtual bool RestoreState(nsPresState* aState);
+  virtual bool IsDisabledForEvents(uint32_t aMessage);
 
   virtual void FieldSetDisabledChanged(bool aNotify);
 
   virtual nsEventStates IntrinsicState() const;
 
   // nsITextControlElemet
   NS_IMETHOD SetValueChanged(bool aValueChanged);
   NS_IMETHOD_(bool) IsSingleLineTextControl() const;
@@ -660,27 +661,32 @@ nsHTMLTextAreaElement::IsAttributeMapped
 }
 
 nsMapRuleToAttributesFunc
 nsHTMLTextAreaElement::GetAttributeMappingFunction() const
 {
   return &MapAttributesIntoRule;
 }
 
-nsresult
-nsHTMLTextAreaElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
+bool
+nsHTMLTextAreaElement::IsDisabledForEvents(uint32_t aMessage)
 {
   nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
   nsIFrame* formFrame = NULL;
   if (formControlFrame) {
     formFrame = do_QueryFrame(formControlFrame);
   }
+  return IsElementDisabledForEvents(aMessage, formFrame);
+}
 
+nsresult
+nsHTMLTextAreaElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
+{
   aVisitor.mCanHandle = false;
-  if (IsElementDisabledForEvents(aVisitor.mEvent->message, formFrame)) {
+  if (IsDisabledForEvents(aVisitor.mEvent->message)) {
     return NS_OK;
   }
 
   // Don't dispatch a second select event if we are already handling
   // one.
   if (aVisitor.mEvent->message == NS_FORM_SELECTED) {
     if (mHandlingSelect) {
       return NS_OK;
--- a/content/xul/content/src/nsXULElement.cpp
+++ b/content/xul/content/src/nsXULElement.cpp
@@ -1121,16 +1121,17 @@ nsXULElement::PreHandleEvent(nsEventChai
         // See if we have a command elt.  If so, we execute on the command
         // instead of on our content element.
         nsAutoString command;
         if (xulEvent && GetAttr(kNameSpaceID_None, nsGkAtoms::command, command) &&
             !command.IsEmpty()) {
             // Stop building the event target chain for the original event.
             // We don't want it to propagate to any DOM nodes.
             aVisitor.mCanHandle = false;
+            aVisitor.mAutomaticChromeDispatch = false;
 
             // XXX sXBL/XBL2 issue! Owner or current document?
             nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(GetCurrentDoc()));
             NS_ENSURE_STATE(domDoc);
             nsCOMPtr<nsIDOMElement> commandElt;
             domDoc->GetElementById(command, getter_AddRefs(commandElt));
             nsCOMPtr<nsIContent> commandContent(do_QueryInterface(commandElt));
             if (commandContent) {
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -62,16 +62,17 @@
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/indexedDB/FileInfo.h"
 #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
 #include "sampler.h"
 #include "nsDOMBlobBuilder.h"
 #include "nsIDOMFileHandle.h"
 #include "nsPrintfCString.h"
 #include "nsViewportInfo.h"
+#include "nsIFormControl.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 using namespace mozilla::widget;
 
 DOMCI_DATA(WindowUtils, nsDOMWindowUtils)
 
@@ -3085,8 +3086,31 @@ nsDOMWindowUtils::AllowScriptsToClose()
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
   nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
   NS_ENSURE_STATE(window);
   static_cast<nsGlobalWindow*>(window.get())->AllowScriptsToClose();
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsDOMWindowUtils::IsNodeDisabledForEvents(nsIDOMNode* aNode, bool* aRetVal)
+{
+  *aRetVal = false;
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+  nsCOMPtr<nsINode> n = do_QueryInterface(aNode);
+  nsINode* node = n;
+  while (node) {
+    if (node->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
+      nsCOMPtr<nsIFormControl> fc = do_QueryInterface(node);
+      if (fc && fc->IsDisabledForEvents(NS_EVENT_NULL)) {
+        *aRetVal = true;
+        break;
+      }
+    }
+    node = node->GetParentNode();
+  }
+
+  return NS_OK;
+}
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -35,17 +35,17 @@ interface nsIQueryContentEventResult;
 interface nsIDOMWindow;
 interface nsIDOMBlob;
 interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMTouch;
 interface nsIDOMClientRect;
 interface nsIURI;
 
-[scriptable, uuid(C98B7275-93C4-4EAD-B7CF-573D872C1071)]
+[scriptable, uuid(2196a216-ed3c-46dd-aa24-9f5b3ac17539)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -1280,9 +1280,16 @@ interface nsIDOMWindowUtils : nsISupport
    */
   readonly attribute boolean isHandlingUserInput;
 
   /**
    * After calling the method, the window for which this DOMWindowUtils
    * was created can be closed using scripts.
    */
    void allowScriptsToClose();
+
+   /**
+    * In certain cases the event handling of nodes, form controls in practice,
+    * may be disabled. Such cases are for example the existence of disabled
+    * attribute or -moz-user-input: none/disabled.
+    */
+   boolean isNodeDisabledForEvents(in nsIDOMNode aNode);
 };
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -6558,44 +6558,16 @@ PresShell::HandleEventInternal(nsEvent* 
             } else if (mDocument) {
               eventTarget = do_QueryInterface(mDocument);
               // If we don't have any content, the callback wouldn't probably
               // do nothing.
               eventCBPtr = nullptr;
             }
           }
           if (eventTarget) {
-#ifdef MOZ_B2G
-            // Horrible hack for B2G to propagate events even from
-            // disabled form elements to chrome. See bug 804811.
-            // See also nsGenericHTMLFormElement::IsElementDisabledForEvents.
-            if (aEvent->message != NS_MOUSE_MOVE) {
-              nsINode* possibleFormElement = eventTarget->ChromeOnlyAccess() ?
-                static_cast<nsIContent*>(eventTarget.get())->
-                  FindFirstNonChromeOnlyAccessContent() :
-                eventTarget;
-              if (possibleFormElement &&
-                  possibleFormElement->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
-                nsEvent event(true, NS_EVENT_TYPE_NULL);
-                nsCOMArray<nsIDOMEventTarget> targets;
-                nsEventDispatcher::Dispatch(eventTarget, nullptr, &event, nullptr,
-                                            nullptr, nullptr, &targets);
-                nsCOMPtr<nsIContent> last;
-                if (targets.Count()) {
-                  last = do_QueryInterface(targets[targets.Count() - 1]);
-                }
-                if (!targets.Count() ||
-                    (last &&
-                     nsContentUtils::ContentIsDescendantOf(last,
-                                                           possibleFormElement))) {
-                  aEvent->mFlags.mOnlyChromeDispatch = true;
-                }
-              }
-            }
-#endif
             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);
             }