Skip to content

Commit

Permalink
Implement FormData constructor's submitter argument
Browse files Browse the repository at this point in the history
This was recently added to the standard in whatwg/xhr#366.
  • Loading branch information
jenseng committed May 27, 2023
1 parent c9d6b72 commit d2103e4
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 9 deletions.
12 changes: 12 additions & 0 deletions lib/jsdom/living/nodes/HTMLFormElement-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ class HTMLFormElementImpl extends HTMLElementImpl {
super._descendantRemoved(parent, child);
}

_getSubmittableElementNodes() {
return domSymbolTree.treeToArray(this.getRootNode({}), {
filter: node => {
if (!isSubmittable(node)) {
return false;
}

return formOwner(node) === this;
}
});
}

_getElementNodes() {
return domSymbolTree.treeToArray(this.getRootNode({}), {
filter: node => {
Expand Down
11 changes: 9 additions & 2 deletions lib/jsdom/living/nodes/HTMLInputElement-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
}
}

_activationBehavior() {
_activationBehavior(event) {
if (!this._mutable && this.type !== "checkbox" && this.type !== "radio") {
return;
}
Expand All @@ -200,7 +200,14 @@ class HTMLInputElementImpl extends HTMLElementImpl {
fireAnEvent("input", this, undefined, { bubbles: true });
fireAnEvent("change", this, undefined, { bubbles: true });
}
} else if (form && (this.type === "submit" || this.type === "image")) {
} else if (form && this.type === "image") {
// https://html.spec.whatwg.org/multipage/input.html#image-button-state-(type=image):input-activation-behavior

// TODO: if/when layout is implemented, record the selected coordinate at the start of dispatch and use it here,
// rather than relying on these getters that just mirror pageX/Y outside of dispatch
this._selectedCoordinate = { x: event.offsetX, y: event.offsetY };
form._doRequestSubmit(this);
} else if (form && this.type === "submit") {
form._doRequestSubmit(this);
} else if (form && this.type === "reset") {
form._doReset();
Expand Down
30 changes: 25 additions & 5 deletions lib/jsdom/living/xhr/FormData-impl.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"use strict";
const DOMException = require("domexception/webidl2js-wrapper");
const idlUtils = require("../generated/utils");
const { closest } = require("../helpers/traversal");
const { isDisabled, isSubmittable, isButton } = require("../helpers/form-controls");
const { isDisabled, isButton, isSubmitButton } = require("../helpers/form-controls");
const Blob = require("../generated/Blob.js");
const File = require("../generated/File.js");
const conversions = require("webidl-conversions");
Expand All @@ -12,7 +13,19 @@ exports.implementation = class FormDataImpl {
this._entries = [];

if (args[0] !== undefined) {
this._entries = constructTheEntryList(args[0]);
const [form, submitter = null] = args;
if (submitter !== null) {
if (!isSubmitButton(submitter)) {
throw new TypeError("The specified element is not a submit button");
}
if (submitter.form !== form) {
throw DOMException.create(this._globalObject, [
"The specified element is not owned by this form element",
"NotFoundError"
]);
}
}
this._entries = constructTheEntryList(form, submitter);
}
}

Expand Down Expand Up @@ -96,7 +109,7 @@ function constructTheEntryList(form, submitter) {
// TODO: handle encoding
// TODO: handling "constructing entry list"

const controls = form.elements.filter(isSubmittable); // submittable is a subset of listed
const controls = form._getSubmittableElementNodes();
const entryList = [];

for (const field of controls) {
Expand All @@ -119,10 +132,17 @@ function constructTheEntryList(form, submitter) {
continue;
}

// TODO: Handle <input type="image">
const name = field.getAttributeNS(null, "name");
if (field.localName === "input" && field.type === "image") {
const prefix = name ? `${name}.` : "";
const coordinate = field._selectedCoordinate ?? { x: 0, y: 0 };
appendAnEntry(entryList, `${prefix}x`, coordinate.x);
appendAnEntry(entryList, `${prefix}y`, coordinate.y);
continue;
}

// TODO: handle form-associated custom elements.

const name = field.getAttributeNS(null, "name");
if (name === null || name === "") {
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/jsdom/living/xhr/FormData.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ typedef (File or USVString) FormDataEntryValue;

[Exposed=(Window,Worker)]
interface FormData {
constructor(optional HTMLFormElement form);
constructor(optional HTMLFormElement form, optional HTMLElement? submitter = null);

undefined append(USVString name, USVString value);
undefined append(USVString name, Blob value, optional USVString filename);
Expand Down
1 change: 0 additions & 1 deletion test/web-platform-tests/to-run.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1479,7 +1479,6 @@ event-upload-progress.any.html: [fail, Unknown]
formdata.html:
"Newly created FormData contains entries added to \"formData\" IDL attribute of FormDataEvent.": [fail, FormDataEvent not implemented]
"|new FormData()| in formdata event handler should throw": [fail, FormDataEvent not implemented]
formdata/constructor-submitter.html: [fail, Not implemented]
getallresponseheaders.htm: [fail, Unknown]
getresponseheader.any.html: [fail, Unknown]
headers-normalize-response.htm: [timeout, Unknown]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<meta charset='utf-8'>
<link rel='help' href='https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set'>
<link ref='help' href='https://xhr.spec.whatwg.org/#dom-formdata'>
<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>

<form>
<input type=image src='/media/1x1-green.png'></button>
</form>

<script>
"use strict";
test(() => {
const form = document.querySelector("form");
const submitter = form.querySelector("input[type=image]");

let formData;
form.addEventListener("submit", e => {
e.preventDefault();
formData = new FormData(form, submitter);
});

// use the DOMRect to construct a coordinate relative to the button, that way we get predictable values regardless of
// layout support
const domRect = submitter.getBoundingClientRect();
submitter.dispatchEvent(
new MouseEvent("click", {
clientX: Math.round(domRect.x) + 1,
clientY: Math.round(domRect.y) + 2
})
);

assert_equals(formData?.get("x"), "1");
assert_equals(formData?.get("y"), "2");

}, "The constructed FormData object should contain correct entries for Image Button submitter's dispatched coordinate");
</script>

0 comments on commit d2103e4

Please sign in to comment.