fetch_tests.js (17932B)
1 var origin = "http://mochi.test:8888"; 2 3 function fetchXHRWithMethod(name, method, onload, onerror, headers) { 4 expectAsyncResult(); 5 6 onload = 7 onload || 8 function () { 9 my_ok(false, "XHR load should not complete successfully"); 10 finish(); 11 }; 12 onerror = 13 onerror || 14 function () { 15 my_ok( 16 false, 17 "XHR load for " + name + " should be intercepted successfully" 18 ); 19 finish(); 20 }; 21 22 var x = new XMLHttpRequest(); 23 x.open(method, name, true); 24 x.onload = function () { 25 onload(x); 26 }; 27 x.onerror = function () { 28 onerror(x); 29 }; 30 headers = headers || []; 31 headers.forEach(function (header) { 32 x.setRequestHeader(header[0], header[1]); 33 }); 34 x.send(); 35 } 36 37 var corsServerPath = 38 "/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs"; 39 var corsServerURL = "http://example.com" + corsServerPath; 40 41 function redirectURL(hops) { 42 return ( 43 hops[0].server + 44 corsServerPath + 45 "?hop=1&hops=" + 46 encodeURIComponent(JSON.stringify(hops)) 47 ); 48 } 49 50 function fetchXHR(name, onload, onerror, headers) { 51 return fetchXHRWithMethod(name, "GET", onload, onerror, headers); 52 } 53 54 fetchXHR("bare-synthesized.txt", function (xhr) { 55 my_ok(xhr.status == 200, "load should be successful"); 56 my_ok( 57 xhr.responseText == "synthesized response body", 58 "load should have synthesized response" 59 ); 60 finish(); 61 }); 62 63 fetchXHR("test-respondwith-response.txt", function (xhr) { 64 my_ok( 65 xhr.status == 200, 66 "test-respondwith-response load should be successful" 67 ); 68 my_ok( 69 xhr.responseText == "test-respondwith-response response body", 70 "load should have response" 71 ); 72 finish(); 73 }); 74 75 fetchXHR("synthesized-404.txt", function (xhr) { 76 my_ok(xhr.status == 404, "load should 404"); 77 my_ok( 78 xhr.responseText == "synthesized response body", 79 "404 load should have synthesized response" 80 ); 81 finish(); 82 }); 83 84 fetchXHR("synthesized-headers.txt", function (xhr) { 85 my_ok(xhr.status == 200, "load should be successful"); 86 my_ok( 87 xhr.getResponseHeader("X-Custom-Greeting") === "Hello", 88 "custom header should be set" 89 ); 90 my_ok( 91 xhr.responseText == "synthesized response body", 92 "custom header load should have synthesized response" 93 ); 94 finish(); 95 }); 96 97 fetchXHR("synthesized-redirect-real-file.txt", function (xhr) { 98 dump("Got status AARRGH " + xhr.status + " " + xhr.responseText + "\n"); 99 my_ok(xhr.status == 200, "load should be successful"); 100 my_ok( 101 xhr.responseText == "This is a real file.\n", 102 "Redirect to real file should complete." 103 ); 104 finish(); 105 }); 106 107 fetchXHR("synthesized-redirect-twice-real-file.txt", function (xhr) { 108 my_ok(xhr.status == 200, "load should be successful"); 109 my_ok( 110 xhr.responseText == "This is a real file.\n", 111 "Redirect to real file (twice) should complete." 112 ); 113 finish(); 114 }); 115 116 fetchXHR("synthesized-redirect-synthesized.txt", function (xhr) { 117 my_ok(xhr.status == 200, "synth+redirect+synth load should be successful"); 118 my_ok( 119 xhr.responseText == "synthesized response body", 120 "load should have redirected+synthesized response" 121 ); 122 finish(); 123 }); 124 125 fetchXHR("synthesized-redirect-twice-synthesized.txt", function (xhr) { 126 my_ok( 127 xhr.status == 200, 128 "synth+redirect+synth (twice) load should be successful" 129 ); 130 my_ok( 131 xhr.responseText == "synthesized response body", 132 "load should have redirected+synthesized (twice) response" 133 ); 134 finish(); 135 }); 136 137 fetchXHR("redirect.sjs", function (xhr) { 138 my_ok(xhr.status == 404, "redirected load should be uninterrupted"); 139 finish(); 140 }); 141 142 fetchXHR("ignored.txt", function (xhr) { 143 my_ok(xhr.status == 404, "load should be uninterrupted"); 144 finish(); 145 }); 146 147 fetchXHR("rejected.txt", null, function (xhr) { 148 my_ok(xhr.status == 0, "load should not complete"); 149 finish(); 150 }); 151 152 fetchXHR("nonresponse.txt", null, function (xhr) { 153 my_ok(xhr.status == 0, "load should not complete"); 154 finish(); 155 }); 156 157 fetchXHR("nonresponse2.txt", null, function (xhr) { 158 my_ok(xhr.status == 0, "load should not complete"); 159 finish(); 160 }); 161 162 fetchXHR("nonpromise.txt", null, function (xhr) { 163 my_ok(xhr.status == 0, "load should not complete"); 164 finish(); 165 }); 166 167 fetchXHR( 168 "headers.txt", 169 function (xhr) { 170 my_ok(xhr.status == 200, "load should be successful"); 171 my_ok(xhr.responseText == "1", "request header checks should have passed"); 172 finish(); 173 }, 174 null, 175 [ 176 ["X-Test1", "header1"], 177 ["X-Test2", "header2"], 178 ] 179 ); 180 181 fetchXHR("http://user:pass@mochi.test:8888/user-pass", function (xhr) { 182 my_ok(xhr.status == 200, "load should be successful"); 183 my_ok( 184 xhr.responseText == "http://user:pass@mochi.test:8888/user-pass", 185 "The username and password should be preserved" 186 ); 187 finish(); 188 }); 189 190 fetchXHR("readable-stream.txt", function (xhr) { 191 my_ok(xhr.status == 200, "loading completed"); 192 my_ok(xhr.responseText == "Hello!", "The message is correct!"); 193 finish(); 194 }); 195 196 fetchXHR( 197 "readable-stream-locked.txt", 198 function (xhr) { 199 my_ok(false, "This should not be called!"); 200 finish(); 201 }, 202 function () { 203 my_ok(true, "The exception has been correctly handled!"); 204 finish(); 205 } 206 ); 207 208 fetchXHR( 209 "readable-stream-with-exception.txt", 210 function (xhr) { 211 my_ok(false, "This should not be called!"); 212 finish(); 213 }, 214 function () { 215 my_ok(true, "The exception has been correctly handled!"); 216 finish(); 217 } 218 ); 219 220 fetchXHR( 221 "readable-stream-with-exception2.txt", 222 function (xhr) { 223 my_ok(false, "This should not be called!"); 224 finish(); 225 }, 226 function () { 227 my_ok(true, "The exception has been correctly handled!"); 228 finish(); 229 } 230 ); 231 232 fetchXHR( 233 "readable-stream-already-consumed.txt", 234 function (xhr) { 235 my_ok(false, "This should not be called!"); 236 finish(); 237 }, 238 function () { 239 my_ok(true, "The exception has been correctly handled!"); 240 finish(); 241 } 242 ); 243 244 var expectedUncompressedResponse = ""; 245 for (let i = 0; i < 10; ++i) { 246 expectedUncompressedResponse += "hello"; 247 } 248 expectedUncompressedResponse += "\n"; 249 250 // ServiceWorker does not intercept, at which point the network request should 251 // be correctly decoded. 252 fetchXHR("deliver-gzip.sjs", function (xhr) { 253 my_ok(xhr.status == 200, "network gzip load should be successful"); 254 my_ok( 255 xhr.responseText == expectedUncompressedResponse, 256 "network gzip load should have synthesized response." 257 ); 258 my_ok( 259 xhr.getResponseHeader("Content-Encoding") == "gzip", 260 "network Content-Encoding should be gzip." 261 ); 262 my_ok( 263 xhr.getResponseHeader("Content-Length") == "35", 264 "network Content-Length should be of original gzipped file." 265 ); 266 finish(); 267 }); 268 269 fetchXHR("hello.gz", function (xhr) { 270 my_ok(xhr.status == 200, "gzip load should be successful"); 271 my_ok( 272 xhr.responseText == expectedUncompressedResponse, 273 "gzip load should have synthesized response." 274 ); 275 my_ok( 276 xhr.getResponseHeader("Content-Encoding") == "gzip", 277 "Content-Encoding should be gzip." 278 ); 279 my_ok( 280 xhr.getResponseHeader("Content-Length") == "35", 281 "Content-Length should be of original gzipped file." 282 ); 283 finish(); 284 }); 285 286 fetchXHR("hello-after-extracting.gz", function (xhr) { 287 my_ok(xhr.status == 200, "gzip load after extracting should be successful"); 288 my_ok( 289 xhr.responseText == expectedUncompressedResponse, 290 "gzip load after extracting should have synthesized response." 291 ); 292 my_ok( 293 xhr.getResponseHeader("Content-Encoding") == "gzip", 294 "Content-Encoding after extracting should be gzip." 295 ); 296 my_ok( 297 xhr.getResponseHeader("Content-Length") == "35", 298 "Content-Length after extracting should be of original gzipped file." 299 ); 300 finish(); 301 }); 302 303 fetchXHR(corsServerURL + "?status=200&allowOrigin=*", function (xhr) { 304 my_ok( 305 xhr.status == 200, 306 "cross origin load with correct headers should be successful" 307 ); 308 my_ok( 309 xhr.getResponseHeader("access-control-allow-origin") == null, 310 "cors headers should be filtered out" 311 ); 312 finish(); 313 }); 314 315 // Verify origin header is sent properly even when we have a no-intercept SW. 316 var uriOrigin = encodeURIComponent(origin); 317 fetchXHR( 318 "http://example.org" + 319 corsServerPath + 320 "?ignore&status=200&origin=" + 321 uriOrigin + 322 "&allowOrigin=" + 323 uriOrigin, 324 function (xhr) { 325 my_ok( 326 xhr.status == 200, 327 "cross origin load with correct headers should be successful" 328 ); 329 my_ok( 330 xhr.getResponseHeader("access-control-allow-origin") == null, 331 "cors headers should be filtered out" 332 ); 333 finish(); 334 } 335 ); 336 337 // Verify that XHR is considered CORS tainted even when original URL is same-origin 338 // redirected to cross-origin. 339 fetchXHR( 340 redirectURL([ 341 { server: origin }, 342 { server: "http://example.org", allowOrigin: origin }, 343 ]), 344 function (xhr) { 345 my_ok( 346 xhr.status == 200, 347 "cross origin load with correct headers should be successful" 348 ); 349 my_ok( 350 xhr.getResponseHeader("access-control-allow-origin") == null, 351 "cors headers should be filtered out" 352 ); 353 finish(); 354 } 355 ); 356 357 // Test that CORS preflight requests cannot be intercepted. Performs a 358 // cross-origin XHR that the SW chooses not to intercept. This requires a 359 // preflight request, which the SW must not be allowed to intercept. 360 fetchXHR( 361 corsServerURL + "?status=200&allowOrigin=*", 362 null, 363 function (xhr) { 364 my_ok( 365 xhr.status == 0, 366 "cross origin load with incorrect headers should be a failure" 367 ); 368 finish(); 369 }, 370 [["X-Unsafe", "unsafe"]] 371 ); 372 373 // Test that CORS preflight requests cannot be intercepted. Performs a 374 // cross-origin XHR that the SW chooses to intercept and respond with a 375 // cross-origin fetch. This requires a preflight request, which the SW must not 376 // be allowed to intercept. 377 fetchXHR( 378 "http://example.org" + corsServerPath + "?status=200&allowOrigin=*", 379 null, 380 function (xhr) { 381 my_ok( 382 xhr.status == 0, 383 "cross origin load with incorrect headers should be a failure" 384 ); 385 finish(); 386 }, 387 [["X-Unsafe", "unsafe"]] 388 ); 389 390 // Test that when the page fetches a url the controlling SW forces a redirect to 391 // another location. This other location fetch should also be intercepted by 392 // the SW. 393 fetchXHR("something.txt", function (xhr) { 394 my_ok(xhr.status == 200, "load should be successful"); 395 my_ok( 396 xhr.responseText == "something else response body", 397 "load should have something else" 398 ); 399 finish(); 400 }); 401 402 // Test fetch will internally get it's SkipServiceWorker flag set. The request is 403 // made from the SW through fetch(). fetch() fetches a server-side JavaScript 404 // file that force a redirect. The redirect location fetch does not go through 405 // the SW. 406 fetchXHR("redirect_serviceworker.sjs", function (xhr) { 407 my_ok(xhr.status == 200, "load should be successful"); 408 my_ok( 409 xhr.responseText == "// empty worker, always succeed!\n", 410 "load should have redirection content" 411 ); 412 finish(); 413 }); 414 415 fetchXHR( 416 "empty-header", 417 function (xhr) { 418 my_ok(xhr.status == 200, "load should be successful"); 419 my_ok( 420 xhr.responseText == "emptyheader", 421 "load should have the expected content" 422 ); 423 finish(); 424 }, 425 null, 426 [["emptyheader", ""]] 427 ); 428 429 expectAsyncResult(); 430 fetch( 431 "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*" 432 ).then( 433 function (res) { 434 my_ok(res.ok, "Valid CORS request should receive valid response"); 435 my_ok(res.type == "cors", "Response type should be CORS"); 436 res.text().then(function (body) { 437 my_ok( 438 body === "<res>hello pass</res>\n", 439 "cors response body should match" 440 ); 441 finish(); 442 }); 443 }, 444 function (e) { 445 my_ok(false, "CORS Fetch failed"); 446 finish(); 447 } 448 ); 449 450 expectAsyncResult(); 451 fetch( 452 "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200", 453 { mode: "no-cors" } 454 ).then( 455 function (res) { 456 my_ok(res.type == "opaque", "Response type should be opaque"); 457 my_ok(res.status == 0, "Status should be 0"); 458 res.text().then(function (body) { 459 my_ok(body === "", "opaque response body should be empty"); 460 finish(); 461 }); 462 }, 463 function (e) { 464 my_ok(false, "no-cors Fetch failed"); 465 finish(); 466 } 467 ); 468 469 expectAsyncResult(); 470 fetch("opaque-on-same-origin").then( 471 function (res) { 472 my_ok( 473 false, 474 "intercepted opaque response for non no-cors request should fail." 475 ); 476 finish(); 477 }, 478 function (e) { 479 my_ok( 480 true, 481 "intercepted opaque response for non no-cors request should fail." 482 ); 483 finish(); 484 } 485 ); 486 487 expectAsyncResult(); 488 fetch("http://example.com/opaque-no-cors", { mode: "no-cors" }).then( 489 function (res) { 490 my_ok( 491 res.type == "opaque", 492 "intercepted opaque response for no-cors request should have type opaque." 493 ); 494 finish(); 495 }, 496 function (e) { 497 my_ok( 498 false, 499 "intercepted opaque response for no-cors request should pass." 500 ); 501 finish(); 502 } 503 ); 504 505 expectAsyncResult(); 506 fetch("http://example.com/cors-for-no-cors", { mode: "no-cors" }).then( 507 function (res) { 508 my_ok( 509 res.type == "cors", 510 "synthesize CORS response should result in outer CORS response" 511 ); 512 finish(); 513 }, 514 function (e) { 515 my_ok(false, "cors-for-no-cors request should not reject"); 516 finish(); 517 } 518 ); 519 520 function arrayBufferFromString(str) { 521 var arr = new Uint8Array(str.length); 522 for (let i = 0; i < str.length; ++i) { 523 arr[i] = str.charCodeAt(i); 524 } 525 return arr; 526 } 527 528 expectAsyncResult(); 529 fetch(new Request("body-simple", { method: "POST", body: "my body" })) 530 .then(function (res) { 531 return res.text(); 532 }) 533 .then(function (body) { 534 my_ok( 535 body == "my bodymy body", 536 "the body of the intercepted fetch should be visible in the SW" 537 ); 538 finish(); 539 }); 540 541 expectAsyncResult(); 542 fetch( 543 new Request("body-arraybufferview", { 544 method: "POST", 545 body: arrayBufferFromString("my body"), 546 }) 547 ) 548 .then(function (res) { 549 return res.text(); 550 }) 551 .then(function (body) { 552 my_ok( 553 body == "my bodymy body", 554 "the ArrayBufferView body of the intercepted fetch should be visible in the SW" 555 ); 556 finish(); 557 }); 558 559 expectAsyncResult(); 560 fetch( 561 new Request("body-arraybuffer", { 562 method: "POST", 563 body: arrayBufferFromString("my body").buffer, 564 }) 565 ) 566 .then(function (res) { 567 return res.text(); 568 }) 569 .then(function (body) { 570 my_ok( 571 body == "my bodymy body", 572 "the ArrayBuffer body of the intercepted fetch should be visible in the SW" 573 ); 574 finish(); 575 }); 576 577 expectAsyncResult(); 578 var usp = new URLSearchParams(); 579 usp.set("foo", "bar"); 580 usp.set("baz", "qux"); 581 fetch(new Request("body-urlsearchparams", { method: "POST", body: usp })) 582 .then(function (res) { 583 return res.text(); 584 }) 585 .then(function (body) { 586 my_ok( 587 body == "foo=bar&baz=quxfoo=bar&baz=qux", 588 "the URLSearchParams body of the intercepted fetch should be visible in the SW" 589 ); 590 finish(); 591 }); 592 593 expectAsyncResult(); 594 var fd = new FormData(); 595 fd.set("foo", "bar"); 596 fd.set("baz", "qux"); 597 fetch(new Request("body-formdata", { method: "POST", body: fd })) 598 .then(function (res) { 599 return res.text(); 600 }) 601 .then(function (body) { 602 my_ok( 603 body.indexOf('Content-Disposition: form-data; name="foo"\r\n\r\nbar') < 604 body.indexOf('Content-Disposition: form-data; name="baz"\r\n\r\nqux'), 605 "the FormData body of the intercepted fetch should be visible in the SW" 606 ); 607 finish(); 608 }); 609 610 expectAsyncResult(); 611 fetch( 612 new Request("body-blob", { 613 method: "POST", 614 body: new Blob(new String("my body")), 615 }) 616 ) 617 .then(function (res) { 618 return res.text(); 619 }) 620 .then(function (body) { 621 my_ok( 622 body == "my bodymy body", 623 "the Blob body of the intercepted fetch should be visible in the SW" 624 ); 625 finish(); 626 }); 627 628 expectAsyncResult(); 629 fetch("interrupt.sjs").then( 630 function (res) { 631 my_ok(true, "interrupted fetch succeeded"); 632 res.text().then( 633 function (body) { 634 my_ok(false, "interrupted fetch shouldn't have complete body"); 635 finish(); 636 }, 637 function () { 638 my_ok(true, "interrupted fetch shouldn't have complete body"); 639 finish(); 640 } 641 ); 642 }, 643 function (e) { 644 my_ok(false, "interrupted fetch failed"); 645 finish(); 646 } 647 ); 648 649 ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"].forEach(function (method) { 650 fetchXHRWithMethod("xhr-method-test.txt", method, function (xhr) { 651 my_ok(xhr.status == 200, method + " load should be successful"); 652 if (method === "HEAD") { 653 my_ok( 654 xhr.responseText == "", 655 method + "load should not have synthesized response" 656 ); 657 } else { 658 my_ok( 659 xhr.responseText == "intercepted " + method, 660 method + " load should have synthesized response" 661 ); 662 } 663 finish(); 664 }); 665 }); 666 667 expectAsyncResult(); 668 fetch(new Request("empty-header", { headers: { emptyheader: "" } })) 669 .then(function (res) { 670 return res.text(); 671 }) 672 .then( 673 function (body) { 674 my_ok( 675 body == "emptyheader", 676 "The empty header was observed in the fetch event" 677 ); 678 finish(); 679 }, 680 function (err) { 681 my_ok(false, "A promise was rejected with " + err); 682 finish(); 683 } 684 ); 685 686 expectAsyncResult(); 687 fetch("fetchevent-extendable") 688 .then(function (res) { 689 return res.text(); 690 }) 691 .then( 692 function (body) { 693 my_ok(body == "extendable", "FetchEvent inherits from ExtendableEvent"); 694 finish(); 695 }, 696 function (err) { 697 my_ok(false, "A promise was rejected with " + err); 698 finish(); 699 } 700 ); 701 702 expectAsyncResult(); 703 fetch("fetchevent-request") 704 .then(function (res) { 705 return res.text(); 706 }) 707 .then( 708 function (body) { 709 my_ok(body == "non-nullable", "FetchEvent.request must be non-nullable"); 710 finish(); 711 }, 712 function (err) { 713 my_ok(false, "A promise was rejected with " + err); 714 finish(); 715 } 716 );