Bug 428135, form submission event shouldn't bubble to parent forms, r=jst,sr=sicking,a=beltzner
authorOlli.Pettay@helsinki.fi
Thu, 17 Apr 2008 15:15:07 -0700
changeset 14434 c5a7b03b7ed45865b96fc56fd98e95eac89c76f6
parent 14433 46705efbc0b4b19ff6b3f633aa61d3e9f64e6ded
child 14435 fa2f9d66bdc4379c7a5957c153702eec33975418
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst, sicking, beltzner
bugs428135
milestone1.9pre
Bug 428135, form submission event shouldn't bubble to parent forms, r=jst,sr=sicking,a=beltzner
content/events/public/nsPIDOMEventTarget.h
content/events/src/nsEventDispatcher.cpp
content/html/content/src/nsHTMLFormElement.cpp
content/html/content/test/Makefile.in
content/html/content/test/test_bug428135.xhtml
--- a/content/events/public/nsPIDOMEventTarget.h
+++ b/content/events/public/nsPIDOMEventTarget.h
@@ -44,20 +44,20 @@
 class nsIDOMEvent;
 class nsPresContext;
 class nsEventChainPreVisitor;
 class nsEventChainPostVisitor;
 class nsIEventListenerManager;
 class nsIDOMEventListener;
 class nsIDOMEventGroup;
 
-// 360fa72e-c709-42cc-9285-1f755ec90376
+// f35ffc3b-c8c0-43fd-b0b0-f339e95f574a
 #define NS_PIDOMEVENTTARGET_IID \
-  { 0x44a6597b, 0x9fc3, 0x4a8d, \
-    { 0xb7, 0xa4, 0xd9, 0x00, 0x9a, 0xbf, 0x9d, 0x15 } }
+  { 0xf35ffc3b, 0xc8c0, 0x43fd, \
+    { 0xb0, 0xb0, 0xf3, 0x39, 0xe9, 0x5f, 0x57, 0x4a } }
 
 class nsPIDOMEventTarget : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMEVENTTARGET_IID)
 
   /**
    * Returns the nsPIDOMEventTarget object which should be used as the target
@@ -88,16 +88,24 @@ public:
    * @param aVisitor the visitor object which is used to create the
    *                 event target chain for event dispatching.
    *
    * @note Only nsEventDispatcher should call this method.
    */
   virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor) = 0;
 
   /**
+   * Called just before possible event handlers on this object will be called.
+   */
+  virtual nsresult WillHandleEvent(nsEventChainPostVisitor& aVisitor)
+  {
+    return NS_OK;
+  }
+
+  /**
    * Called after the bubble phase of the system event group.
    * The default handling of the event should happen here.
    * @param aVisitor the visitor object which is used during post handling.
    *
    * @see nsEventDispatcher.h for documentation about aVisitor.
    * @note Only nsEventDispatcher should call this method.
    */
   virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor) = 0;
--- a/content/events/src/nsEventDispatcher.cpp
+++ b/content/events/src/nsEventDispatcher.cpp
@@ -189,16 +189,20 @@ nsEventTargetChainItem::PreHandleEvent(n
   mItemData = aVisitor.mItemData;
   return rv;
 }
 
 nsresult
 nsEventTargetChainItem::HandleEvent(nsEventChainPostVisitor& aVisitor,
                                     PRUint32 aFlags)
 {
+  mTarget->WillHandleEvent(aVisitor);
+  if (aVisitor.mEvent->flags & NS_EVENT_FLAG_STOP_DISPATCH) {
+    return NS_OK;
+  }
   if (!mManager) {
     mTarget->GetListenerManager(PR_FALSE, getter_AddRefs(mManager));
   }
   if (mManager) {
     aVisitor.mEvent->currentTarget = CurrentTarget()->GetTargetForDOMEvent();
     if (aVisitor.mEvent->currentTarget) {
       mManager->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent,
                             &aVisitor.mDOMEvent,
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -206,16 +206,17 @@ public:
                                   nsIFormControl* aRadio);
 
   // nsIContent
   virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult);
   virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor);
+  virtual nsresult WillHandleEvent(nsEventChainPostVisitor& aVisitor);
   virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor);
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               PRBool aCompileEventHandlers);
   virtual void UnbindFromTree(PRBool aDeep = PR_TRUE,
                               PRBool aNullParent = PR_TRUE);
   nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
@@ -828,16 +829,31 @@ nsHTMLFormElement::PreHandleEvent(nsEven
       }
       mGeneratingReset = PR_TRUE;
     }
   }
   return nsGenericHTMLElement::PreHandleEvent(aVisitor);
 }
 
 nsresult
+nsHTMLFormElement::WillHandleEvent(nsEventChainPostVisitor& aVisitor)
+{
+  // If this is the bubble stage and there is a nested form below us which
+  // received a submit event we do *not* want to handle the submit event
+  // for this form too.
+  if ((aVisitor.mEvent->message == NS_FORM_SUBMIT ||
+       aVisitor.mEvent->message == NS_FORM_RESET) &&
+      aVisitor.mEvent->flags & NS_EVENT_FLAG_BUBBLE &&
+      aVisitor.mEvent->originalTarget != static_cast<nsIContent*>(this)) {
+    aVisitor.mEvent->flags |= NS_EVENT_FLAG_STOP_DISPATCH;
+  }
+  return NS_OK;
+}
+
+nsresult
 nsHTMLFormElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
 {
   if (aVisitor.mEvent->originalTarget == static_cast<nsIContent*>(this)) {
     PRUint32 msg = aVisitor.mEvent->message;
     if (msg == NS_FORM_SUBMIT) {
       // let the form know not to defer subsequent submissions
       mDeferSubmission = PR_FALSE;
     }
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -104,14 +104,15 @@ include $(topsrcdir)/config/rules.mk
 		bug392567.jar       \
 		bug392567.jar^headers^ \
 		test_bug394700.html \
 		test_bug395107.html \
 		test_bug401160.xhtml \
 		test_bug408231.html \
 		test_bug417760.html \
 		file_bug417760.png \
+		test_bug428135.xhtml \
 		test_bug406596.html \
 		test_bug421640.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug428135.xhtml
@@ -0,0 +1,157 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=428135
+-->
+<head>
+  <title>Test for Bug 428135</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=428135">Mozilla Bug 428135</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 428135 **/
+
+var expectedCurrentTargets = new Array();
+
+function d(el, ename) {
+  var e = document.createEvent("Events");
+  e.initEvent(ename, true, true);
+  el.dispatchEvent(e);
+}
+
+function testListener(e) {
+  e.preventDefault();
+  var expected = expectedCurrentTargets.shift();
+  ok(expected == e.currentTarget,
+     "Unexpected current target [" + e.currentTarget + "], event=" + e.type +
+     ", phase=" + e.eventPhase + ", target should have been " + expected);
+}
+
+function getAndAddListeners(elname) {
+  var el = document;
+  if (elname) {
+    el = document.getElementById(elname);
+  }
+  el.addEventListener("submit", testListener, true);
+  el.addEventListener("submit", testListener, false);
+  el.addEventListener("reset", testListener, true);
+  el.addEventListener("reset", testListener, false);
+  el.addEventListener("fooEvent", testListener, true);
+  el.addEventListener("fooEvent", testListener, false);
+  return el;
+}
+
+function testSubmitResetEvents() {
+  getAndAddListeners(null);
+  var outerForm = getAndAddListeners("outerForm");
+  var outerSubmit = getAndAddListeners("outerSubmit");
+  var outerReset = getAndAddListeners("outerReset");
+  var outerSubmitDispatcher = getAndAddListeners("outerSubmitDispatcher");
+  var outerResetDispatcher = getAndAddListeners("outerResetDispatcher");
+  var outerChild = getAndAddListeners("outerChild");
+  var innerForm = getAndAddListeners("innerForm");
+  var innerSubmit = getAndAddListeners("innerSubmit");
+  var innerReset = getAndAddListeners("innerReset");
+  var innerSubmitDispatcher = getAndAddListeners("innerSubmitDispatcher");
+  var innerResetDispatcher = getAndAddListeners("innerResetDispatcher");
+
+  expectedCurrentTargets = new Array(document, outerForm, outerForm, document);
+  outerSubmit.click();
+  ok(expectedCurrentTargets.length == 0,
+     "(1) expectedCurrentTargets isn't empty!");
+
+  expectedCurrentTargets = new Array(document, outerForm, outerForm, document);
+  outerReset.click();
+  ok(expectedCurrentTargets.length == 0,
+     "(2) expectedCurrentTargets isn't empty!");
+
+  // Because of bug 428135, submit shouldn't propagate
+  // back to outerForm and document!
+  expectedCurrentTargets = 
+    new Array(document, outerForm, outerSubmitDispatcher, outerSubmitDispatcher);
+  outerSubmitDispatcher.click();
+  ok(expectedCurrentTargets.length == 0,
+     "(3) expectedCurrentTargets isn't empty!");
+
+  // Because of bug 428135, reset shouldn't propagate
+  // back to outerForm and document!
+  expectedCurrentTargets =
+    new Array(document, outerForm, outerResetDispatcher, outerResetDispatcher);
+  outerResetDispatcher.click();
+  ok(expectedCurrentTargets.length == 0,
+     "(4) expectedCurrentTargets isn't empty!");
+
+  // Because of bug 428135, submit shouldn't propagate
+  // back to outerForm and document!
+  expectedCurrentTargets = 
+    new Array(document, outerForm, outerChild, innerForm, innerForm, outerChild);
+  innerSubmit.click();
+  ok(expectedCurrentTargets.length == 0,
+     "(5) expectedCurrentTargets isn't empty!");
+
+  // Because of bug 428135, reset shouldn't propagate
+  // back to outerForm and document!
+  expectedCurrentTargets = 
+    new Array(document, outerForm, outerChild, innerForm, innerForm, outerChild);
+  innerReset.click();
+  ok(expectedCurrentTargets.length == 0,
+     "(6) expectedCurrentTargets isn't empty!");
+
+  // Because of bug 428135, submit shouldn't propagate
+  // back to inner/outerForm or document!
+  expectedCurrentTargets = 
+    new Array(document, outerForm, outerChild, innerForm, innerSubmitDispatcher,
+              innerSubmitDispatcher);
+  innerSubmitDispatcher.click();
+  ok(expectedCurrentTargets.length == 0,
+     "(7) expectedCurrentTargets isn't empty!");
+
+  // Because of bug 428135, reset shouldn't propagate
+  // back to inner/outerForm or document!
+  expectedCurrentTargets =
+    new Array(document, outerForm, outerChild, innerForm, innerResetDispatcher,
+              innerResetDispatcher);
+  innerResetDispatcher.click();
+  ok(expectedCurrentTargets.length == 0,
+     "(8) expectedCurrentTargets isn't empty!");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(testSubmitResetEvents);
+addLoadEvent(SimpleTest.finish);
+
+
+]]>
+</script>
+</pre>
+<form id="outerForm">
+  <input type="submit" value="outer" id="outerSubmit"/>
+  <input type="reset" value="reset outer" id="outerReset"/>
+  <input type="button" value="dispatch submit" onclick="d(this, 'submit')"
+         id="outerSubmitDispatcher"/>
+  <input type="button" value="dispatch reset" onclick="d(this, 'reset')"
+         id="outerResetDispatcher"/>
+  <div id="outerChild">
+    <form id="innerForm">
+      <input type="submit" value="inner" id="innerSubmit"/>
+      <input type="reset" value="reset inner" id="innerReset"/>
+      <input type="button" value="dispatch submit" onclick="d(this, 'submit')"
+             id="innerSubmitDispatcher"/>
+      <input type="button" value="dispatch reset" onclick="d(this, 'reset')"
+             id="innerResetDispatcher"/>
+    </form>
+  </div>
+</form>
+</body>
+</html>
+