Branch data Line data Source code
1 [ + ]: 2 : import assert from 'node:assert/strict';
2 : 2 : import { XMLHttpRequest } from '../../../lib/whatwg-xhr.js';
3 : 2 :
4 [ + ]: 2 : export default (activeURL) => {
5 : 1 :
6 : 1 : const stringBody = 'Test Message'.repeat(1000);
7 : 1 : const blobBody = new Blob(new Array(1000).fill('Test Message'));
8 : 1 :
9 : 1 : testRedirectPost( { name: '301', code: 301, expectResendPost: false, body: stringBody} );
10 : 1 : testRedirectPost( { name: '302', code: 302, expectResendPost: false, body: stringBody} );
11 : 1 : testRedirectPost( { name: '303', code: 303, expectResendPost: false, body: stringBody} );
12 : 1 : testRedirectPost( { name: '307 (string)', code: 307, expectResendPost: true, body: stringBody, expectedBody: stringBody } );
13 : 1 : testRedirectPost( { name: '307 (blob)', code: 307, expectResendPost: true, body: blobBody, expectedBody: stringBody, expectedContentType: 'NO' } );
14 : 1 :
15 [ + ]: 1 : function testRedirectPost(params){
16 : 5 : let actual = [];
17 : 5 : // We check upload.onprogress with a boolean because it *might* fire more than once
18 : 5 : let progressFiredReadyState1 = false;
19 : 5 :
20 : 5 : let expectedHeaders, expectedEvents;
21 : 5 :
22 : 5 : // 307 redirects should resend the POST data, and events and headers will be a little different..
23 [ + ]: 5 : if(params.expectResendPost){
24 : 2 : expectedHeaders = {
25 : 2 : 'X-Request-Content-Length': '12000',
26 : 2 : 'X-Request-Content-Type': 'text/plain;charset=UTF-8',
27 : 2 : 'X-Request-Method': 'POST',
28 : 2 : 'X-Request-Query': 'NO',
29 : 2 : 'Content-Length': '12000'
30 : 2 : };
31 : 2 :
32 : 2 : expectedEvents = [
33 : 2 : 'xhr onreadystatechange 1',
34 : 2 : 'xhr loadstart 1',
35 : 2 : 'upload loadstart 1',
36 : 2 : 'upload loadend 1',
37 : 2 : 'xhr onreadystatechange 2',
38 : 2 : 'xhr onreadystatechange 3',
39 : 2 : 'xhr onreadystatechange 4',
40 : 2 : 'xhr load 4',
41 : 2 : 'xhr loadend 4'
42 : 2 : ];
43 [ + ]: 2 : }
44 : 3 : else {
45 : 3 : // setting the right expectations for POST resent as GET without request body
46 : 3 : expectedHeaders = {
47 : 3 : 'X-Request-Content-Length': 'NO',
48 : 3 : 'X-Request-Content-Type': 'NO',
49 : 3 : 'X-Request-Method': 'GET',
50 : 3 : 'X-Request-Query': 'NO'
51 : 3 : };
52 : 3 :
53 : 3 : expectedEvents = [
54 : 3 : 'xhr onreadystatechange 1',
55 : 3 : 'xhr loadstart 1',
56 : 3 : 'upload loadstart 1',
57 : 3 : 'upload loadend 1',
58 : 3 : 'xhr onreadystatechange 2',
59 : 3 : // we expect no onreadystatechange readyState=3 event because there is no loading content
60 : 3 : 'xhr onreadystatechange 4',
61 : 3 : 'xhr load 4',
62 : 3 : 'xhr loadend 4'
63 : 3 : ];
64 : 3 : }
65 : 5 : // Override expectations if provided.
66 : 5 : if(params.expectedContentType)
67 [ + ]: 5 : expectedHeaders['X-Request-Content-Type'] = params.expectedContentType;
68 : 5 :
69 : 5 : let xhr = new XMLHttpRequest();
70 : 5 :
71 [ + ]: 5 : xhr.upload.onloadstart = (e) => {
72 : 5 : actual.push('upload loadstart ' + xhr.readyState);
73 : 5 : };
74 : 5 :
75 [ + ]: 5 : xhr.upload.onprogress = (e) => {
76 : 5 : // events every 50ms, one final when uploading is done
77 [ - ]: 5 : if(xhr.readyState >= xhr.HEADERS_RECEIVED) {
78 : 0 : assert.strictEqual(xhr.status, 200, 'JS never gets to see the 30x status code');
79 : 0 : }
80 : 5 : progressFiredReadyState1 = xhr.readyState === xhr.OPENED;
81 : 5 : };
82 : 5 :
83 [ + ]: 5 : xhr.upload.onloadend = () => {
84 : 5 : actual.push('upload loadend ' + xhr.readyState);
85 : 5 : };
86 : 5 :
87 [ + ]: 5 : xhr.onloadstart = () => {
88 : 5 : actual.push('xhr loadstart ' + xhr.readyState);
89 : 5 : };
90 : 5 :
91 [ + ]: 5 : xhr.onreadystatechange = () => {
92 [ + ]: 17 : if(xhr.readyState >= xhr.HEADERS_RECEIVED) {
93 : 12 : assert.strictEqual(xhr.status, 200, 'JS never gets to see the 30x status code');
94 : 12 : }
95 : 17 :
96 : 17 : // The UA may fire multiple 'readystatechange' events while in
97 : 17 : // the 'loading' state.
98 : 17 : // https://xhr.spec.whatwg.org/#the-send()-method
99 [ + ][ - ]: 17 : if(xhr.readyState === 3 && actual[actual.length - 1] === 'xhr onreadystatechange 3') {
100 : 0 : return;
101 : 0 : }
102 : 17 :
103 : 17 : actual.push('xhr onreadystatechange ' + xhr.readyState);
104 : 5 : };
105 : 5 :
106 [ + ]: 5 : xhr.onload = (e) => {
107 : 5 : actual.push('xhr load ' + xhr.readyState);
108 : 5 : };
109 : 5 :
110 [ + ]: 5 : xhr.onloadend = (e) => {
111 : 5 : actual.push('xhr loadend ' + xhr.readyState);
112 : 5 :
113 : 5 : assert(progressFiredReadyState1, 'One progress event should fire on xhr.upload when readyState is 1');
114 : 5 :
115 : 5 : // Headers will tell us if data was sent when expected
116 [ + ]: 5 : for(let header in expectedHeaders){
117 : 22 : assert.strictEqual(xhr.getResponseHeader(header), expectedHeaders[header], header);
118 : 22 : }
119 : 5 :
120 : 5 : assert.deepStrictEqual(actual, expectedEvents, 'events firing in expected order and states');
121 : 5 :
122 : 5 : if(params.expectedBody)
123 [ + ]: 5 : assert.strictEqual(xhr.response, params.expectedBody, 'request body was resent');
124 : 5 :
125 : 5 : xhr.upload.onloadstart = null;
126 : 5 : xhr.upload.onprogress = null;
127 : 5 : xhr.upload.onloadend = null;
128 : 5 :
129 : 5 : xhr.onloadstart = null;
130 : 5 : xhr.onprogress = null;
131 : 5 : xhr.onload = null;
132 : 5 : xhr.onloadend = null;
133 : 5 : };
134 : 5 :
135 : 5 : xhr.open('POST', `${activeURL}/redirect.py?location=/xhr/resources/content.py&code=${params.code}`);
136 : 5 : xhr.send(params.body);
137 : 5 : }
138 : 1 : }
139 : 2 :
140 : 2 : /*
141 : 2 : * send-redirect-post-upload.htm
142 : 2 : *
143 : 2 :
144 : 2 : <!DOCTYPE html>
145 : 2 : <html>
146 : 2 : <head>
147 : 2 : <link rel="help" href="https://xhr.spec.whatwg.org/#handler-xhr-onprogress" data-tested-assertations="../.." />
148 : 2 : <link rel="help" href="https://xhr.spec.whatwg.org/#event-xhr-progress" data-tested-assertations="../.." />
149 : 2 : <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following::dt[@id="dom-xmlhttprequest-send-bodyinit"]/following::dd[1]/p[2] following::ol[1]/li[9]//li[1] following::ol[1]/li[9]//li[2]" />
150 : 2 : <link rel="help" href="https://fetch.spec.whatwg.org/#http-fetch" data-tested-assertations="following::ol[1]/li[6]/dl/dd[1]//dd[3]" />
151 : 2 : <link rel="help" href="https://fetch.spec.whatwg.org/#concept-http-redirect-fetch" data-tested-assertations="following::li[16]" />
152 : 2 : <script src="/resources/testharness.js"></script>
153 : 2 : <script src="/resources/testharnessreport.js"></script>
154 : 2 : <title>XMLHttpRequest: The send() method: POSTing to URL that redirects</title>
155 : 2 : </head>
156 : 2 :
157 : 2 : <body>
158 : 2 : <div id="log"></div>
159 : 2 :
160 : 2 : <script type="text/javascript">
161 : 2 : function testRedirectPost(params) {
162 : 2 : var test = async_test(document.title + " (" + params.name + ")");
163 : 2 : var actual = [];
164 : 2 : // We check upload.onprogress with a boolean because it *might* fire more than once
165 : 2 : var progressFiredReadyState1 = false;
166 : 2 :
167 : 2 : var expectedHeaders, expectedEvents;
168 : 2 :
169 : 2 : // 307 redirects should resend the POST data, and events and headers will be a little different..
170 : 2 : if(params.expectResendPost) {
171 : 2 : expectedHeaders = {
172 : 2 : "X-Request-Content-Length": "12000",
173 : 2 : "X-Request-Content-Type": "text/plain;charset=UTF-8",
174 : 2 : "X-Request-Method": "POST",
175 : 2 : "X-Request-Query": "NO",
176 : 2 : "Content-Length": "12000"
177 : 2 : }
178 : 2 : expectedEvents = [
179 : 2 : "xhr onreadystatechange 1",
180 : 2 : "xhr loadstart 1",
181 : 2 : "upload loadstart 1",
182 : 2 : "upload loadend 1",
183 : 2 : "xhr onreadystatechange 2",
184 : 2 : "xhr onreadystatechange 3",
185 : 2 : "xhr onreadystatechange 4",
186 : 2 : "xhr load 4",
187 : 2 : "xhr loadend 4"
188 : 2 : ];
189 : 2 : } else {
190 : 2 : // setting the right expectations for POST resent as GET without request body
191 : 2 : expectedHeaders = {
192 : 2 : "X-Request-Content-Length": "NO",
193 : 2 : "X-Request-Content-Type": "NO",
194 : 2 : "X-Request-Method": "GET",
195 : 2 : "X-Request-Query": "NO"
196 : 2 : }
197 : 2 : expectedEvents = [
198 : 2 : "xhr onreadystatechange 1",
199 : 2 : "xhr loadstart 1",
200 : 2 : "upload loadstart 1",
201 : 2 : "upload loadend 1",
202 : 2 : "xhr onreadystatechange 2",
203 : 2 : // we expect no onreadystatechange readyState=3 event because there is no loading content
204 : 2 : "xhr onreadystatechange 4",
205 : 2 : "xhr load 4",
206 : 2 : "xhr loadend 4"
207 : 2 : ];
208 : 2 : }
209 : 2 : // Override expectations if provided.
210 : 2 : if(params.expectedContentType)
211 : 2 : expectedHeaders["X-Request-Content-Type"] = params.expectedContentType;
212 : 2 :
213 : 2 : test.step(function()
214 : 2 : {
215 : 2 : var xhr = new XMLHttpRequest();
216 : 2 :
217 : 2 : xhr.upload.onloadstart = test.step_func(function(e) {
218 : 2 : actual.push("upload loadstart " + xhr.readyState);
219 : 2 : });
220 : 2 : xhr.upload.onprogress = test.step_func(function(e) {
221 : 2 : // events every 50ms, one final when uploading is done
222 : 2 : if(xhr.readyState >= xhr.HEADERS_RECEIVED) {
223 : 2 : assert_equals(xhr.status, 200, "JS never gets to see the 30x status code");
224 : 2 : }
225 : 2 : progressFiredReadyState1 = xhr.readyState === xhr.OPENED;
226 : 2 : });
227 : 2 : xhr.upload.onloadend = test.step_func(function() {
228 : 2 : actual.push("upload loadend " + xhr.readyState);
229 : 2 : });
230 : 2 : xhr.onloadstart = test.step_func(function() {
231 : 2 : actual.push("xhr loadstart " + xhr.readyState);
232 : 2 : });
233 : 2 : xhr.onreadystatechange = test.step_func(function() {
234 : 2 : if(xhr.readyState >= xhr.HEADERS_RECEIVED) {
235 : 2 : assert_equals(xhr.status, 200, "JS never gets to see the 30x status code");
236 : 2 : }
237 : 2 :
238 : 2 : // The UA may fire multiple "readystatechange" events while in
239 : 2 : // the "loading" state.
240 : 2 : // https://xhr.spec.whatwg.org/#the-send()-method
241 : 2 : if (xhr.readyState === 3 && actual[actual.length - 1] === "xhr onreadystatechange 3") {
242 : 2 : return;
243 : 2 : }
244 : 2 :
245 : 2 : actual.push("xhr onreadystatechange " + xhr.readyState);
246 : 2 : });
247 : 2 : xhr.onload = test.step_func(function(e)
248 : 2 : {
249 : 2 : actual.push("xhr load " + xhr.readyState);
250 : 2 : });
251 : 2 : xhr.onloadend = test.step_func(function(e)
252 : 2 : {
253 : 2 : actual.push("xhr loadend " + xhr.readyState);
254 : 2 :
255 : 2 : assert_true(progressFiredReadyState1, "One progress event should fire on xhr.upload when readyState is 1");
256 : 2 :
257 : 2 : // Headers will tell us if data was sent when expected
258 : 2 : for(var header in expectedHeaders) {
259 : 2 : assert_equals(xhr.getResponseHeader(header), expectedHeaders[header], header);
260 : 2 : }
261 : 2 :
262 : 2 : assert_array_equals(actual, expectedEvents, "events firing in expected order and states");
263 : 2 : if (params.expectedBody)
264 : 2 : assert_equals(xhr.response, params.expectedBody, 'request body was resent');
265 : 2 : test.done();
266 : 2 : });
267 : 2 :
268 : 2 : xhr.open("POST", "./resources/redirect.py?location=content.py&code=" + params.code, true);
269 : 2 : xhr.send(params.body);
270 : 2 : });
271 : 2 : }
272 : 2 :
273 : 2 : const stringBody = "Test Message".repeat(1000);
274 : 2 : const blobBody = new Blob(new Array(1000).fill("Test Message"));
275 : 2 :
276 : 2 : testRedirectPost({name: "301", code: 301, expectResendPost: false, body: stringBody});
277 : 2 : testRedirectPost({name: "302", code: 302, expectResendPost: false, body: stringBody});
278 : 2 : testRedirectPost({name: "303", code: 303, expectResendPost: false, body: stringBody});
279 : 2 : testRedirectPost({name: "307 (string)", code: 307, expectResendPost: true, body: stringBody, expectedBody: stringBody });
280 : 2 : testRedirectPost({name: "307 (blob)", code: 307, expectResendPost: true, body: blobBody, expectedBody: stringBody, expectedContentType: "NO" });
281 : 2 : </script>
282 : 2 : </body>
283 : 2 : </html>
284 : 2 :
285 : 2 : *
286 : 2 : * send-redirect-post-upload.htm
287 : 2 : */
|