summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/forms/form-submission-0/enctypes-helper.js
blob: 0f0d68163da4f32393445439a91f6f0fc5680f3a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// This file exposes the `formSubmissionTemplate` function, which can be used
// to create tests for the form submission encoding of various enctypes:
//
//   const urlencodedTest = formSubmissionTemplate(
//     "application/x-www-form-urlencoded",
//     (expected, _actualFormBody) => expected
//   );
//
//   urlencodedTest({
//     name: "a",
//     value: "b",
//     expected: "a=b",
//     formEncoding: "UTF-8", // optional
//     description: "Simple urlencoded test"
//   });
//
// The above call to `urlencodedTest` tests the urlencoded form submission for a
// form whose entry list contains a single entry with name "a" and value "b",
// and it checks that the form payload matches the `expected` property after
// isomorphic-encoding.
//
// Since per the spec no normalization of the form entries should happen before
// the actual form encoding, each call to `urlencodedTest` will in fact add two
// tests: one submitting the entry as a form control (marked "normal form"), and
// one adding the entry through the `formdata` event (marked "formdata event").
// Both cases are compared against the same expected value.
//
// Since multipart/form-data boundary strings can't be predicted ahead of time,
// the second parameter of `formSubmissionTemplate` allows transforming the
// expected value passed to each test. The second argument of that callback
// is the actual form body (isomorphic-decoded). When this callback is used, the
// `expected` property doesn't need to be a string.

(() => {
  // Using echo-content-escaped.py rather than
  // /fetch/api/resources/echo-content.py to work around WebKit not
  // percent-encoding \x00, which causes the response to be detected as
  // a binary file and served as a download.
  const ACTION_URL = "/FileAPI/file/resources/echo-content-escaped.py";

  const IFRAME_NAME = "formtargetframe";

  // Undoes the escapes from echo-content-escaped.py
  function unescape(str) {
    return str
      .replace(/\r\n?|\n/g, "\r\n")
      .replace(
        /\\x[0-9A-Fa-f]{2}/g,
        (escape) => String.fromCodePoint(parseInt(escape.substring(2), 16)),
      )
      .replace(/\\\\/g, "\\");
  }

  // Tests the form submission of an entry list containing a single entry.
  //
  // `expectedBuilder` is a function that takes in the actual form body
  // (necessary to get the multipart/form-data payload) and returns the form
  // body that should be expected.
  //
  // If `testFormData` is false, the form entry will be submitted in for
  // controls. If it is true, it will submitted by modifying the entry list
  // during the `formdata` event.
  async function formSubmissionTest({
    name,
    value,
    expectedBuilder,
    enctype,
    formEncoding,
    testFormData = false,
    testCase,
  }) {
    if (document.readyState !== "complete") {
      await new Promise((resolve) => addEventListener("load", resolve));
    }

    const formTargetFrame = Object.assign(document.createElement("iframe"), {
      name: IFRAME_NAME,
    });
    document.body.append(formTargetFrame);
    testCase.add_cleanup(() => {
      document.body.removeChild(formTargetFrame);
    });

    const form = Object.assign(document.createElement("form"), {
      acceptCharset: formEncoding,
      action: ACTION_URL,
      method: "POST",
      enctype,
      target: IFRAME_NAME,
    });
    document.body.append(form);
    testCase.add_cleanup(() => {
      document.body.removeChild(form);
    });

    if (!testFormData) {
      const input = document.createElement("input");
      input.name = name;
      if (value instanceof File) {
        input.type = "file";
        const dataTransfer = new DataTransfer();
        dataTransfer.items.add(value);
        input.files = dataTransfer.files;
      } else {
        input.type = "hidden";
        input.value = value;
      }
      form.append(input);
    } else {
      form.addEventListener("formdata", (evt) => {
        evt.formData.append(name, value);
      });
    }

    await new Promise((resolve) => {
      form.submit();
      formTargetFrame.onload = resolve;
    });

    const serialized = unescape(
      formTargetFrame.contentDocument.body.textContent,
    );
    const expected = expectedBuilder(serialized);
    assert_equals(serialized, expected);
  }

  // This function returns a function to add individual form tests corresponding
  // to some enctype.
  // `expectedBuilder` is an optional callback that takes two parameters:
  // `expected` (the `expected` property passed to a test) and `actualFormBody`
  // (the actual form body submitted by the browser, isomorphic-decoded). It
  // must return the correct form body that should have been submitted,
  // isomorphic-encoded. This is necessary in order to account for the
  // multipart/form-data boundary.
  //
  // The returned function takes an object with the following properties:
  // - `name`, the form entry's name. Must be a string.
  // - `value`, the form entry's value, either a string or a `File` object.
  // - `expected`, the expected form body. Usually a string, but it can be
  //   anything depending on `expectedBuilder`.
  // - `formEncoding` (optional), the character encoding used for submitting the
  //   form.
  // - `description`, used as part of the testharness test's description.
  window.formSubmissionTemplate = (
    enctype,
    expectedBuilder = (expected) => expected
  ) => {
    function form({
      name,
      value,
      expected,
      formEncoding = "utf-8",
      description,
    }) {
      const commonParams = {
        name,
        value,
        expectedBuilder: expectedBuilder.bind(null, expected),
        enctype,
        formEncoding,
      };

      // Normal form
      promise_test(
        (testCase) =>
          formSubmissionTest({
            ...commonParams,
            testCase,
          }),
        `${enctype}: ${description} (normal form)`,
      );

      // formdata event
      promise_test(
        (testCase) =>
          formSubmissionTest({
            ...commonParams,
            testFormData: true,
            testCase,
          }),
        `${enctype}: ${description} (formdata event)`,
      );
    }

    return form;
  };
})();