Bug 566064 (2/2) - Implement formtarget for <input> and <button> which override <form> target attribute. r=sicking a2.0=blocking
authorMounir Lamouri <mounir.lamouri@gmail.com>
Fri, 20 Aug 2010 00:58:10 +0200
changeset 50991 73ad6efd753073dcfb72c998f8c685b1cf347b92
parent 50990 491b7284f637fab5222ec0c984fafdf53ff4f30d
child 50992 80b450a23c6beb4ec426e8fdc348c4db97466fd3
push idunknown
push userunknown
push dateunknown
reviewerssicking
bugs566064
milestone2.0b5pre
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 566064 (2/2) - Implement formtarget for <input> and <button> which override <form> target attribute. r=sicking a2.0=blocking
content/base/src/nsGkAtomList.h
content/html/content/src/nsHTMLButtonElement.cpp
content/html/content/src/nsHTMLFormElement.cpp
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/test/Makefile.in
content/html/content/test/test_bug566046.html
content/html/content/test/test_bug566064.html
dom/interfaces/html/nsIDOMHTMLButtonElement.idl
dom/interfaces/html/nsIDOMHTMLInputElement.idl
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -384,16 +384,17 @@ GK_ATOM(font, "font")
 GK_ATOM(fontWeight, "font-weight")
 GK_ATOM(fontpicker, "fontpicker")
 GK_ATOM(footer, "footer")
 GK_ATOM(_for, "for")
 GK_ATOM(forEach, "for-each")
 GK_ATOM(form, "form")
 GK_ATOM(format, "format")
 GK_ATOM(formatNumber, "format-number")
+GK_ATOM(formtarget, "formtarget")
 GK_ATOM(frame, "frame")
 GK_ATOM(frameborder, "frameborder")
 GK_ATOM(frameset, "frameset")
 GK_ATOM(from, "from")
 GK_ATOM(functionAvailable, "function-available")
 GK_ATOM(generateId, "generate-id")
 GK_ATOM(getter, "getter")
 GK_ATOM(grid, "grid")
--- a/content/html/content/src/nsHTMLButtonElement.cpp
+++ b/content/html/content/src/nsHTMLButtonElement.cpp
@@ -203,16 +203,17 @@ NS_IMETHODIMP
 nsHTMLButtonElement::GetForm(nsIDOMHTMLFormElement** aForm)
 {
   return nsGenericHTMLFormElement::GetForm(aForm);
 }
 
 NS_IMPL_STRING_ATTR(nsHTMLButtonElement, AccessKey, accesskey)
 NS_IMPL_BOOL_ATTR(nsHTMLButtonElement, Autofocus, autofocus)
 NS_IMPL_BOOL_ATTR(nsHTMLButtonElement, Disabled, disabled)
+NS_IMPL_STRING_ATTR(nsHTMLButtonElement, FormTarget, formtarget)
 NS_IMPL_STRING_ATTR(nsHTMLButtonElement, Name, name)
 NS_IMPL_INT_ATTR_DEFAULT_VALUE(nsHTMLButtonElement, TabIndex, tabindex, 0)
 NS_IMPL_STRING_ATTR(nsHTMLButtonElement, Value, value)
 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(nsHTMLButtonElement, Type, type,
                                 kButtonDefaultType->tag)
 
 NS_IMETHODIMP
 nsHTMLButtonElement::Blur()
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -764,16 +764,18 @@ nsHTMLFormElement::BuildSubmission(nsFor
 
   return NS_OK;
 }
 
 nsresult
 nsHTMLFormElement::SubmitSubmission(nsFormSubmission* aFormSubmission)
 {
   nsresult rv;
+  nsIContent* originatingElement = aFormSubmission->GetOriginatingElement();
+
   //
   // Get the action and target
   //
   nsCOMPtr<nsIURI> actionURI;
   rv = GetActionURL(getter_AddRefs(actionURI));
   NS_ENSURE_SUBMIT_SUCCESS(rv);
 
   if (!actionURI) {
@@ -802,18 +804,28 @@ nsHTMLFormElement::SubmitSubmission(nsFo
   // we're not submitting when submitting to a JS URL.  That's kinda bogus, but
   // there we are.
   PRBool schemeIsJavaScript = PR_FALSE;
   if (NS_SUCCEEDED(actionURI->SchemeIs("javascript", &schemeIsJavaScript)) &&
       schemeIsJavaScript) {
     mIsSubmitting = PR_FALSE;
   }
 
+  // The target is the originating element formtarget attribute if the element
+  // is a submit control and has such an attribute.
+  // Otherwise, the target is the form owner's target attribute,
+  // if it has such an attribute.
+  // Finally, if one of the child nodes of the head element is a base element
+  // with a target attribute, then the value of the target attribute of the
+  // first such base element; or, if there is no such element, the empty string.
   nsAutoString target;
-  if (!GetAttr(kNameSpaceID_None, nsGkAtoms::target, target)) {
+  if (!(originatingElement && originatingElement->GetAttr(kNameSpaceID_None,
+                                                          nsGkAtoms::formtarget,
+                                                          target)) &&
+      !GetAttr(kNameSpaceID_None, nsGkAtoms::target, target)) {
     GetBaseTarget(target);
   }
 
   //
   // Notify observers of submit
   //
   PRBool cancelSubmit = PR_FALSE;
   if (mNotifiedObservers) {
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -546,16 +546,17 @@ NS_IMPL_STRING_ATTR(nsHTMLInputElement, 
 NS_IMPL_BOOL_ATTR(nsHTMLInputElement, DefaultChecked, checked)
 NS_IMPL_STRING_ATTR(nsHTMLInputElement, Accept, accept)
 NS_IMPL_STRING_ATTR(nsHTMLInputElement, AccessKey, accesskey)
 NS_IMPL_STRING_ATTR(nsHTMLInputElement, Align, align)
 NS_IMPL_STRING_ATTR(nsHTMLInputElement, Alt, alt)
 NS_IMPL_BOOL_ATTR(nsHTMLInputElement, Autofocus, autofocus)
 //NS_IMPL_BOOL_ATTR(nsHTMLInputElement, Checked, checked)
 NS_IMPL_BOOL_ATTR(nsHTMLInputElement, Disabled, disabled)
+NS_IMPL_STRING_ATTR(nsHTMLInputElement, FormTarget, formtarget)
 NS_IMPL_BOOL_ATTR(nsHTMLInputElement, Multiple, multiple)
 NS_IMPL_NON_NEGATIVE_INT_ATTR(nsHTMLInputElement, MaxLength, maxlength)
 NS_IMPL_STRING_ATTR(nsHTMLInputElement, Name, name)
 NS_IMPL_BOOL_ATTR(nsHTMLInputElement, ReadOnly, readonly)
 NS_IMPL_BOOL_ATTR(nsHTMLInputElement, Required, required)
 NS_IMPL_URI_ATTR(nsHTMLInputElement, Src, src)
 NS_IMPL_INT_ATTR_DEFAULT_VALUE(nsHTMLInputElement, TabIndex, tabindex, 0)
 NS_IMPL_STRING_ATTR(nsHTMLInputElement, UseMap, usemap)
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -193,12 +193,13 @@ include $(topsrcdir)/config/rules.mk
 		test_bug585508.html \
 		test_bug345624-1.html \
 		test_bug345624-2.html \
 		test_bug561640.html \
 		test_bug345822.html \
 		test_bug555559.html \
 		test_bug344615.html \
 		test_bug345512.html \
+		test_bug566064.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
--- a/content/html/content/test/test_bug566046.html
+++ b/content/html/content/test/test_bug566046.html
@@ -2,72 +2,195 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=566046
 -->
 <head>
   <title>Test for Bug 566046</title>
   <script type="application/javascript" src="/MochiKit/packed.js"></script>
   <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"/>
+  <base>
   <base target='frame2'>
+  <base target=''>
 </head>
-<body onload="runTests();">
+<body onload="setTimeout(runTests);">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=566046">Mozilla Bug 566046</a>
 <p id="display"></p>
+<style>
+  iframe { width: 130px; height: 100px;}
+</style>
 <iframe name='frame1' id='frame1'></iframe>
 <iframe name='frame2' id='frame2'></iframe>
-<div id="content" style="display: none">
+<iframe name='frame3' id='frame3'></iframe>
+<iframe name='frame4' id='frame4'></iframe>
+<iframe name='frame5' id='frame5'></iframe>
+<iframe name='frame5bis' id='frame5bis'></iframe>
+<iframe name='frame6' id='frame6'></iframe>
+<iframe name='frame7' id='frame7'></iframe>
+<iframe name='frame8' id='frame8'></iframe>
+<iframe name='frame9' id='frame9'></iframe>
+<div id="content">
   <form target='frame1' action="data:text/html," method="GET">
     <input name='foo' value='foo'>
   </form>
   <form action="data:text/html," method="GET">
     <input name='bar' value='bar'>
   </form>
   <form target="">
   </form>
-  <form target="tulip">
+
+  <!-- submit controls with formtarget that are validated with a CLICK -->
+  <form target="tulip" action="data:text/html," method="GET">
+    <input name='tulip' value='tulip'>
+    <input type='submit' id='is' formtarget='frame3'>
+  </form>
+  <form action="data:text/html," method="GET">
+    <input name='foobar' value='foobar'>
+    <input type='image' id='ii' formtarget='frame4'>
+  </form>
+  <form action="data:text/html," method="GET">
+    <input name='tulip2' value='tulip2'>
+    <button type='submit' id='bs' formtarget='frame5'>submit</button>
+  </form>
+  <form action="data:text/html," method="GET">
+    <input name='tulip3' value='tulip3'>
+    <button type='submit' id='bsbis' formtarget='frame5bis'>submit</button>
+  </form>
+
+  <!-- submit controls with formtarget that are validated with ENTER -->
+  <form target="tulip" action="data:text/html," method="GET">
+    <input name='footulip' value='footulip'>
+    <input type='submit' id='is2' formtarget='frame6'>
+  </form>
+  <form action="data:text/html," method="GET">
+    <input name='tulipfoobar' value='tulipfoobar'>
+    <input type='image' id='ii2' formtarget='frame7'>
+  </form>
+  <form action="data:text/html," method="GET">
+    <input name='tulipbar' value='tulipbar'>
+    <button type='submit' id='bs2' formtarget='frame8'>submit</button>
+  </form>
+
+  <!-- check that a which is not a submit control do not use @formtarget -->
+  <form target='frame9' action="data:text/html," method="GET">
+    <input id='enter' name='input' value='enter' formtarget='frame6'>
   </form>
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 566046 **/
 
 SimpleTest.waitForExplicitFinish();
 
 var gTestResults = {
   frame1: "data:text/html,?foo=foo",
   frame2: "data:text/html,?bar=bar",
+  frame3: "data:text/html,?tulip=tulip",
+  frame4: "data:text/html,?foobar=foobar&x=0&y=0",
+  frame5: "data:text/html,?tulip2=tulip2",
+  frame5bis: "data:text/html,?tulip3=tulip3",
+  frame6: "data:text/html,?footulip=footulip",
+  frame7: "data:text/html,?tulipfoobar=tulipfoobar&x=0&y=0",
+  frame8: "data:text/html,?tulipbar=tulipbar",
+  frame9: "data:text/html,?input=enter",
 };
 
 var gPendingLoad = 0; // Has to be set after depending on the frames number.
 
 function runTests()
 {
   // Check the target IDL attribute.
   for (var i=0; i<document.forms.length; ++i) {
     var testValue = document.forms[i].getAttribute('target');
     is(document.forms[i].target, testValue ? testValue : "",
        "target IDL attribute should reflect the target content attribute");
   }
 
   // We add a load event for the frames which will be called when the forms
   // will be submitted.
   var frames = [ document.getElementById('frame1'),
-                 document.getElementById('frame2') ];
+                 document.getElementById('frame2'),
+                 document.getElementById('frame3'),
+                 document.getElementById('frame4'),
+                 document.getElementById('frame5'),
+                 document.getElementById('frame5bis'),
+                 document.getElementById('frame6'),
+                 document.getElementById('frame7'),
+                 document.getElementById('frame8'),
+                 document.getElementById('frame9'),
+               ];
   gPendingLoad = frames.length;
 
   for (var i=0; i<frames.length; i++) {
     frames[i].setAttribute('onload', "frameLoaded(this);");
   }
 
   // Submitting only the forms with a valid target.
   document.forms[0].submit();
   document.forms[1].submit();
+
+  /**
+   * We are going to focus each element before interacting with either for
+   * simulating the ENTER key (synthesizeKey) or a click (synthesizeMouse) or
+   * using .click(). This because it may be needed (ENTER) and because we want
+   * to have the element visible in the iframe.
+   *
+   * Focusing the first element (id='is') is launching the tests.
+   */
+  document.getElementById('is').addEventListener('focus', function(aEvent) {
+    aEvent.target.removeEventListener('focus', arguments.callee, false);
+    synthesizeMouse(document.getElementById('is'), 5, 5, {});
+    document.getElementById('ii').focus();
+  }, false);
+
+  document.getElementById('ii').addEventListener('focus', function(aEvent) {
+    aEvent.target.removeEventListener('focus', arguments.callee, false);
+    synthesizeMouse(document.getElementById('ii'), 5, 5, {});
+    document.getElementById('bs').focus();
+  }, false);
+
+  document.getElementById('bs').addEventListener('focus', function(aEvent) {
+    aEvent.target.removeEventListener('focus', arguments.callee, false);
+    synthesizeMouse(document.getElementById('bs'), 5, 5, {});
+    document.getElementById('bsbis').focus();
+  }, false);
+
+  document.getElementById('bsbis').addEventListener('focus', function(aEvent) {
+    aEvent.target.removeEventListener('focus', arguments.callee, false);
+    document.getElementById('bsbis').click();
+    document.getElementById('is2').focus();
+  }, false);
+
+  document.getElementById('is2').addEventListener('focus', function(aEvent) {
+    aEvent.target.removeEventListener('focus', arguments.callee, false);
+    synthesizeKey("VK_RETURN", {});
+    document.getElementById('ii2').focus();
+  }, false);
+
+  document.getElementById('ii2').addEventListener('focus', function(aEvent) {
+    aEvent.target.removeEventListener('focus', arguments.callee, false);
+    synthesizeKey("VK_RETURN", {});
+    document.getElementById('bs2').focus();
+  }, false);
+
+  document.getElementById('bs2').addEventListener('focus', function(aEvent) {
+    aEvent.target.removeEventListener('focus', arguments.callee, false);
+    synthesizeKey("VK_RETURN", {});
+    document.getElementById('enter').focus();
+  }, false);
+
+  document.getElementById('enter').addEventListener('focus', function(aEvent) {
+    aEvent.target.removeEventListener('focus', arguments.callee, false);
+    synthesizeKey("VK_RETURN", {});
+  }, false);
+
+  document.getElementById('is').focus();
 }
 
 function frameLoaded(aFrame) {
   // Check if when target is unspecified, it fallback correctly to the base
   // element target attribute.
   is(aFrame.contentWindow.location.href, gTestResults[aFrame.name],
      "the target attribute doesn't have the correct behavior");
 
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug566064.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=566064
+-->
+<head>
+  <title>Test for Bug 566064</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/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=566064">Mozilla Bug 566064</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 566064 **/
+
+// This test is only checking the IDL/content attribute of 'formtarget'.
+// The behavior is tested in test_bug566046.html.
+
+function isFormTargetEquals(aElement, aValue, aShouldBeNull)
+{
+  if (aShouldBeNull) {
+    contentAtttributeValue = null;
+  } else {
+    contentAtttributeValue = aValue;
+  }
+
+  is(aElement.formTarget, aValue,
+     "formTarget IDL attribute value should be " + aValue);
+  is(aElement.getAttribute('formtarget'), contentAtttributeValue,
+     "formTarget content attribute value should be " + contentAtttributeValue);
+}
+
+function checkFormTarget(aElement)
+{
+  isFormTargetEquals(aElement, "", true);
+
+  aElement.formTarget = "foo";
+  isFormTargetEquals(aElement, "foo");
+
+  aElement.setAttribute("formtarget", "bar");
+  isFormTargetEquals(aElement, "bar");
+
+  aElement.removeAttribute("formtarget");
+  isFormTargetEquals(aElement, "", true);
+}
+
+var input = document.createElement('input');
+var button = document.createElement('button');
+
+ok('formTarget' in input, "formTarget is a HTMLInputElement property");
+ok('formTarget' in button, "formTarget is a HTMLButtonElement property");
+
+checkFormTarget(input);
+checkFormTarget(button);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/interfaces/html/nsIDOMHTMLButtonElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLButtonElement.idl
@@ -44,22 +44,23 @@
  * button element.
  *
  * For more information on this interface please see
  * http://www.w3.org/TR/DOM-Level-2-HTML/
  */
 
 interface nsIDOMValidityState;
 
-[scriptable, uuid(5a1ef9b1-6782-4bee-a59e-7db1c352eb7d)]
+[scriptable, uuid(4a24ca8f-cc7b-43b2-aca1-4dae149c1ed3)]
 interface nsIDOMHTMLButtonElement : nsIDOMHTMLElement
 {
            attribute boolean               autofocus;
            attribute boolean               disabled;
   readonly attribute nsIDOMHTMLFormElement form;
+           attribute DOMString             formTarget;
 
            attribute DOMString             name;
            attribute DOMString             type;
            attribute DOMString             value;
 
            
            attribute DOMString             accessKey;
            attribute long                  tabIndex;
--- a/dom/interfaces/html/nsIDOMHTMLInputElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLInputElement.idl
@@ -46,27 +46,28 @@ interface nsIDOMValidityState;
  /**
   * The nsIDOMHTMLInputElement interface is the interface to a [X]HTML
   * input element.
   *
   * For more information on this interface please see
   * http://www.w3.org/TR/DOM-Level-2-HTML/
   */
  
-[scriptable, uuid(6f283298-168e-4787-bfe7-f207be2438df)]
+[scriptable, uuid(de8ae81f-85fd-4e6d-a864-00b38029c727)]
 interface nsIDOMHTMLInputElement : nsIDOMHTMLElement
 {
            attribute DOMString             accept;
            attribute DOMString             alt;
 
            attribute boolean               autofocus;
            attribute boolean               defaultChecked;
            attribute boolean               checked;
            attribute boolean               disabled;
   readonly attribute nsIDOMHTMLFormElement form;
+           attribute DOMString             formTarget;
 
   readonly attribute nsIDOMFileList        files;
 
            attribute boolean               indeterminate;
 
            attribute long                  maxLength;
 
            attribute boolean               multiple;