Bug 1184607 P9 Add wpt tests to verify service worker redirect logic. r=nsm
authorBen Kelly <ben@wanderview.com>
Mon, 31 Aug 2015 14:26:30 -0700
changeset 260230 0f3779b36cb2799fba12a6e7a3d9e8747d67230f
parent 260229 e330761d6058b0ad78594fa0333a3c400e90d4f6
child 260231 96ee097b78c129c32677d6136b09d680f6e3b2d5
push id29304
push usercbook@mozilla.com
push dateTue, 01 Sep 2015 12:32:25 +0000
treeherdermozilla-central@dd509db16a13 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnsm
bugs1184607
milestone43.0a1
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 1184607 P9 Add wpt tests to verify service worker redirect logic. r=nsm
testing/web-platform/mozilla/meta/MANIFEST.json
testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event-redirect.https.html
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/get-host-info.sub.js
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/success.py
--- a/testing/web-platform/mozilla/meta/MANIFEST.json
+++ b/testing/web-platform/mozilla/meta/MANIFEST.json
@@ -137,16 +137,22 @@
           }
         ],
         "service-workers/service-worker/fetch-event-network-error.https.html": [
           {
             "path": "service-workers/service-worker/fetch-event-network-error.https.html",
             "url": "/_mozilla/service-workers/service-worker/fetch-event-network-error.https.html"
           }
         ],
+        "service-workers/service-worker/fetch-event-redirect.https.html": [
+          {
+            "path": "service-workers/service-worker/fetch-event-redirect.https.html",
+            "url": "/_mozilla/service-workers/service-worker/fetch-event-redirect.https.html"
+          }
+        ],
         "service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html": [
           {
             "path": "service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html",
             "url": "/_mozilla/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html"
           }
         ],
         "service-workers/service-worker/fetch-event.https.html": [
           {
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event-redirect.https.html
@@ -0,0 +1,997 @@
+<!DOCTYPE html>
+<title>Service Worker: Fetch Event Redirect Handling</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+// ------------------------
+// Utilities for testing non-navigation requests that are intercepted with
+// a redirect.
+
+var host_info = get_host_info();
+var worker = 'resources/fetch-rewrite-worker.js';
+var frameURL = host_info['HTTPS_ORIGIN'] + base_path() +
+               'resources/fetch-event-redirect-iframe.html';
+var baseScope = 'resources/';
+var redirect = 'redirect.py';
+var success = base_path() + 'resources/success.py';
+
+function redirect_fetch_test(t, test) {
+  var scope = baseScope + test.name;
+  service_worker_unregister_and_register(t, worker, scope).then(function(reg) {
+    return wait_for_state(t, reg.installing, 'activated');
+  }).then(function() {
+    return with_iframe(scope + '?url=' + encodeURIComponent(frameURL));
+  }).then(function(frame) {
+    var hostKeySuffix = test['url_credentials'] ? '_WITH_CREDS' : '';
+
+    var acaorigin = '';
+    var host = host_info['HTTPS_ORIGIN' + hostKeySuffix];
+    if (test['redirect_dest'] === 'no-cors') {
+      host = host_info['HTTPS_REMOTE_ORIGIN' + hostKeySuffix]
+    } else if (test['redirect_dest'] === 'cors') {
+      acaorigin = '?ACAOrigin=' + encodeURIComponent(host_info['HTTPS_ORIGIN']);
+      host = host_info['HTTPS_REMOTE_ORIGIN' + hostKeySuffix]
+    }
+
+    var dest = '?Redirect=' + encodeURIComponent(host + success + acaorigin);
+
+    var expectedTypeParam = test['expected_type']
+                          ?  '&expected_type=' + test['expected_type']
+                          : '';
+
+    var url = scope +
+              '?url=' + encodeURIComponent(redirect + dest) +
+              expectedTypeParam
+
+    var p = new Promise(function(resolve, reject) {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = function(e) {
+        if (e.data.result === 'reject') {
+          frame.remove();
+          reject(e.data.detail);
+        } else if (e.data.result === 'success') {
+          frame.remove();
+          resolve(e.data.result);
+        } else {
+          frame.remove();
+          resolve(e.data.detail);
+        }
+      };
+      frame.contentWindow.postMessage({
+        url: url,
+        request_init: test.request_init
+      }, '*', [channel.port2]);
+    });
+
+    if (test.should_reject) {
+      return assert_promise_rejects(p);
+    }
+
+    return p.then(function(result) {
+      if (result !== 'success') {
+        throw(new Error(result));
+      }
+    });
+  }).then(function() {
+    return service_worker_unregister_and_done(t, scope);
+  }).catch(unreached_rejection(t));
+}
+
+// ------------------------
+// Test every combination of:
+//  - RequestMode (same-origin, cors, no-cors)
+//  - RequestRedirect (manual, follow, error)
+//  - redirect destination origin (same-origin, cors, no-cors)
+//  - redirect destination credentials (no user/pass, user/pass)
+//
+// TODO: add navigation requests
+// TODO: add redirects to data URI and verify same-origin data-URL flag behavior
+// TODO: add test where original redirect URI is cross-origin
+// TODO: verify final method is correct for 301, 302, and 303
+// TODO: verify CORS redirect results in all further redirects being
+//       considered cross origin
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-cors-redirects-to-sameorigin-nocreds',
+    redirect_dest: 'same-origin',
+    url_credentials: false,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, cors mode Request redirected to ' +
+   'same-origin without credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-cors-redirects-to-nocors-nocreds',
+    redirect_dest: 'no-cors',
+    url_credentials: false,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, cors mode Request redirected to ' +
+   'no-cors without credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-cors-redirects-to-cors-nocreds',
+    redirect_dest: 'cors',
+    url_credentials: false,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, cors mode Request redirected to ' +
+   'cors without credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-sameorigin-redirects-to-sameorigin-nocreds',
+    redirect_dest: 'same-origin',
+    url_credentials: false,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'same-origin'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' +
+   'same-origin without credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-sameorigin-redirects-to-nocors-nocreds',
+    redirect_dest: 'no-cors',
+    url_credentials: false,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'same-origin'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' +
+   'no-cors without credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-sameorigin-redirects-to-cors-nocreds',
+    redirect_dest: 'cors',
+    url_credentials: false,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'same-origin'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' +
+   'cors without credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-nocors-redirects-to-sameorigin-nocreds',
+    redirect_dest: 'same-origin',
+    url_credentials: false,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'no-cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
+   'same-origin without credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-nocors-redirects-to-nocors-nocreds',
+    redirect_dest: 'no-cors',
+    url_credentials: false,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'no-cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
+   'no-cors without credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-nocors-redirects-to-cors-nocreds',
+    redirect_dest: 'cors',
+    url_credentials: false,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'no-cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
+   'cors without credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-cors-redirects-to-sameorigin-creds',
+    redirect_dest: 'same-origin',
+    url_credentials: true,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, cors mode Request redirected to ' +
+   'same-origin with credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-cors-redirects-to-nocors-creds',
+    redirect_dest: 'no-cors',
+    url_credentials: true,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, cors mode Request redirected to ' +
+   'no-cors with credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-cors-redirects-to-cors-creds',
+    redirect_dest: 'cors',
+    url_credentials: true,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, cors mode Request redirected to ' +
+   'cors with credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-sameorigin-redirects-to-sameorigin-creds',
+    redirect_dest: 'same-origin',
+    url_credentials: true,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'same-origin'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' +
+   'same-origin with credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-sameorigin-redirects-to-nocors-creds',
+    redirect_dest: 'no-cors',
+    url_credentials: true,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'same-origin'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' +
+   'no-cors with credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-sameorigin-redirects-to-cors-creds',
+    redirect_dest: 'cors',
+    url_credentials: true,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'same-origin'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' +
+   'cors with credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-nocors-redirects-to-sameorigin-creds',
+    redirect_dest: 'same-origin',
+    url_credentials: true,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'no-cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
+   'same-origin with credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-nocors-redirects-to-nocors-creds',
+    redirect_dest: 'no-cors',
+    url_credentials: true,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'no-cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
+   'no-cors with credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-manual-nocors-redirects-to-cors-creds',
+    redirect_dest: 'cors',
+    url_credentials: true,
+    expected_type: 'opaqueredirect',
+    request_init: {
+      redirect: 'manual',
+      mode: 'no-cors'
+    },
+    // should reject because only navigations can be intercepted with
+    // opaqueredirect responses
+    should_reject: true
+  });
+}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
+   'cors with credentials should fail opaqueredirect interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-cors-redirects-to-sameorigin-nocreds',
+    redirect_dest: 'same-origin',
+    url_credentials: false,
+    expected_type: 'basic',
+    request_init: {
+      redirect: 'follow',
+      mode: 'cors'
+    },
+    should_reject: false
+  });
+}, 'Non-navigation, follow redirect, cors mode Request redirected to ' +
+   'same-origin without credentials should succeed interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-cors-redirects-to-nocors-nocreds',
+    redirect_dest: 'no-cors',
+    url_credentials: false,
+    expected_type: 'should-not-get-a-response',
+    request_init: {
+      redirect: 'follow',
+      mode: 'cors'
+    },
+    // should reject because CORS requests require CORS headers on cross-origin
+    // resources
+    should_reject: true
+  });
+}, 'Non-navigation, follow redirect, cors mode Request redirected to ' +
+   'no-cors without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-cors-redirects-to-cors-nocreds',
+    redirect_dest: 'cors',
+    url_credentials: false,
+    expected_type: 'cors',
+    request_init: {
+      redirect: 'follow',
+      mode: 'cors'
+    },
+    should_reject: false
+  });
+}, 'Non-navigation, follow redirect, cors mode Request redirected to ' +
+   'cors without credentials should succeed interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-sameorigin-redirects-to-sameorigin-nocreds',
+    redirect_dest: 'same-origin',
+    url_credentials: false,
+    expected_type: 'basic',
+    request_init: {
+      redirect: 'follow',
+      mode: 'same-origin'
+    },
+    should_reject: false
+  });
+}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' +
+   'same-origin without credentials should succeed interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-sameorigin-redirects-to-nocors-nocreds',
+    redirect_dest: 'no-cors',
+    url_credentials: false,
+    expected_type: 'should-not-get-a-response',
+    request_init: {
+      redirect: 'follow',
+      mode: 'same-origin'
+    },
+    // should reject because same-origin requests cannot load cross-origin
+    // resources
+    should_reject: true
+  });
+}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' +
+   'no-cors without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-sameorigin-redirects-to-cors-nocreds',
+    redirect_dest: 'cors',
+    url_credentials: false,
+    expected_type: 'should-not-get-a-response',
+    request_init: {
+      redirect: 'follow',
+      mode: 'same-origin'
+    },
+    // should reject because same-origin requests cannot load cross-origin
+    // resources
+    should_reject: true
+  });
+}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' +
+   'cors without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-nocors-redirects-to-sameorigin-nocreds',
+    redirect_dest: 'same-origin',
+    url_credentials: false,
+    expected_type: 'basic',
+    request_init: {
+      redirect: 'follow',
+      mode: 'no-cors'
+    },
+    should_reject: false
+  });
+}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' +
+   'same-origin without credentials should succeed interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-nocors-redirects-to-nocors-nocreds',
+    redirect_dest: 'no-cors',
+    url_credentials: false,
+    expected_type: 'opaque',
+    request_init: {
+      redirect: 'follow',
+      mode: 'no-cors'
+    },
+    should_reject: false
+  });
+}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' +
+   'no-cors without credentials should succeed interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-nocors-redirects-to-cors-nocreds',
+    redirect_dest: 'cors',
+    url_credentials: false,
+    expected_type: 'opaque',
+    request_init: {
+      redirect: 'follow',
+      mode: 'no-cors'
+    },
+    should_reject: false
+  });
+}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' +
+   'cors without credentials should succeed interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-cors-redirects-to-sameorigin-creds',
+    redirect_dest: 'same-origin',
+    url_credentials: true,
+    expected_type: 'basic',
+    request_init: {
+      redirect: 'follow',
+      mode: 'cors'
+    },
+    should_reject: false
+  });
+}, 'Non-navigation, follow redirect, cors mode Request redirected to ' +
+   'same-origin with credentials should succeed interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-cors-redirects-to-nocors-creds',
+    redirect_dest: 'no-cors',
+    url_credentials: true,
+    expected_type: 'should-not-get-a-response',
+    request_init: {
+      redirect: 'follow',
+      mode: 'cors'
+    },
+    // should reject because CORS requests require CORS headers on cross-origin
+    // resources
+    should_reject: true
+  });
+}, 'Non-navigation, follow redirect, cors mode Request redirected to ' +
+   'no-cors with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-cors-redirects-to-cors-creds',
+    redirect_dest: 'cors',
+    url_credentials: true,
+    expected_type: 'cors',
+    request_init: {
+      redirect: 'follow',
+      mode: 'cors'
+    },
+    // should reject because CORS requests do not allow user/pass entries in
+    // cross-origin URLs
+    // NOTE: https://github.com/whatwg/fetch/issues/112
+    should_reject: true
+  });
+}, 'Non-navigation, follow redirect, cors mode Request redirected to ' +
+   'cors with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-sameorigin-redirects-to-sameorigin-creds',
+    redirect_dest: 'same-origin',
+    url_credentials: true,
+    expected_type: 'basic',
+    request_init: {
+      redirect: 'follow',
+      mode: 'same-origin'
+    },
+    should_reject: false
+  });
+}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' +
+   'same-origin with credentials should succeed interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-sameorigin-redirects-to-nocors-creds',
+    redirect_dest: 'no-cors',
+    url_credentials: true,
+    expected_type: 'should-not-get-a-response',
+    request_init: {
+      redirect: 'follow',
+      mode: 'same-origin'
+    },
+    // should reject because same-origin requests cannot load cross-origin
+    // resources
+    should_reject: true
+  });
+}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' +
+   'no-cors with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-sameorigin-redirects-to-cors-creds',
+    redirect_dest: 'cors',
+    url_credentials: true,
+    expected_type: 'should-not-get-a-response',
+    request_init: {
+      redirect: 'follow',
+      mode: 'same-origin'
+    },
+    // should reject because same-origin requests cannot load cross-origin
+    // resources
+    should_reject: true
+  });
+}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' +
+   'cors with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-nocors-redirects-to-sameorigin-creds',
+    redirect_dest: 'same-origin',
+    url_credentials: true,
+    expected_type: 'basic',
+    request_init: {
+      redirect: 'follow',
+      mode: 'no-cors'
+    },
+    should_reject: false
+  });
+}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' +
+   'same-origin with credentials should succeed interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-nocors-redirects-to-nocors-creds',
+    redirect_dest: 'no-cors',
+    url_credentials: true,
+    expected_type: 'opaque',
+    request_init: {
+      redirect: 'follow',
+      mode: 'no-cors'
+    },
+    should_reject: false
+  });
+}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' +
+   'no-cors with credentials should succeed interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-follow-nocors-redirects-to-cors-creds',
+    redirect_dest: 'cors',
+    url_credentials: true,
+    expected_type: 'opaque',
+    request_init: {
+      redirect: 'follow',
+      mode: 'no-cors'
+    },
+    should_reject: false
+  });
+}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' +
+   'cors with credentials should succeed interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-cors-redirects-to-sameorigin-nocreds',
+    redirect_dest: 'same-origin',
+    url_credentials: false,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, cors mode Request redirected to ' +
+   'same-origin without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-cors-redirects-to-nocors-nocreds',
+    redirect_dest: 'no-cors',
+    url_credentials: false,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, cors mode Request redirected to ' +
+   'no-cors without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-cors-redirects-to-cors-nocreds',
+    redirect_dest: 'cors',
+    url_credentials: false,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, cors mode Request redirected to ' +
+   'cors without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-sameorigin-redirects-to-sameorigin-nocreds',
+    redirect_dest: 'same-origin',
+    url_credentials: false,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'same-origin'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' +
+   'same-origin without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-sameorigin-redirects-to-nocors-nocreds',
+    redirect_dest: 'no-cors',
+    url_credentials: false,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'same-origin'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' +
+   'no-cors without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-sameorigin-redirects-to-cors-nocreds',
+    redirect_dest: 'cors',
+    url_credentials: false,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'same-origin'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' +
+   'cors without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-nocors-redirects-to-sameorigin-nocreds',
+    redirect_dest: 'same-origin',
+    url_credentials: false,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'no-cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' +
+   'same-origin without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-nocors-redirects-to-nocors-nocreds',
+    redirect_dest: 'no-cors',
+    url_credentials: false,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'no-cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' +
+   'no-cors without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-nocors-redirects-to-cors-nocreds',
+    redirect_dest: 'cors',
+    url_credentials: false,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'no-cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' +
+   'cors without credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-cors-redirects-to-sameorigin-creds',
+    redirect_dest: 'same-origin',
+    url_credentials: true,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, cors mode Request redirected to ' +
+   'same-origin with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-cors-redirects-to-nocors-creds',
+    redirect_dest: 'no-cors',
+    url_credentials: true,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, cors mode Request redirected to ' +
+   'no-cors with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-cors-redirects-to-cors-creds',
+    redirect_dest: 'cors',
+    url_credentials: true,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, cors mode Request redirected to ' +
+   'cors with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-sameorigin-redirects-to-sameorigin-creds',
+    redirect_dest: 'same-origin',
+    url_credentials: true,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'same-origin'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' +
+   'same-origin with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-sameorigin-redirects-to-nocors-creds',
+    redirect_dest: 'no-cors',
+    url_credentials: true,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'same-origin'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' +
+   'no-cors with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-sameorigin-redirects-to-cors-creds',
+    redirect_dest: 'cors',
+    url_credentials: true,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'same-origin'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' +
+   'cors with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-nocors-redirects-to-sameorigin-creds',
+    redirect_dest: 'same-origin',
+    url_credentials: true,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'no-cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' +
+   'same-origin with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-nocors-redirects-to-nocors-creds',
+    redirect_dest: 'no-cors',
+    url_credentials: true,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'no-cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' +
+   'no-cors with credentials should fail interception');
+
+async_test(function(t) {
+  redirect_fetch_test(t, {
+    name: 'nonav-error-nocors-redirects-to-cors-creds',
+    redirect_dest: 'cors',
+    url_credentials: true,
+    expected_type: 'error',
+    request_init: {
+      redirect: 'error',
+      mode: 'no-cors'
+    },
+    // should reject because requests with 'error' RequestRedirect cannot be
+    // redirected.
+    should_reject: true
+  });
+}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' +
+   'cors with credentials should fail interception');
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html
@@ -0,0 +1,13 @@
+<script>
+window.addEventListener('message', function(evt) {
+  var port = evt.ports[0];
+  var data = evt.data;
+  fetch(new Request(data.url, data.request_init)).then(function(response) {
+    return response.json();
+  }).then(function(body) {
+    port.postMessage({result: body.result, detail: body.detail});
+  }).catch(function(e) {
+    port.postMessage({result: 'reject', detail: e.toString()});
+  });
+});
+</script>
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js
@@ -7,33 +7,28 @@ function get_query_params(url) {
   var params = search.substring(1).split('&');
   params.forEach(function(param) {
       var element = param.split('=');
       ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]);
     });
   return ret;
 }
 
-function get_request_init(params) {
+function get_request_init(base, params) {
   var init = {};
-  if (params['method']) {
-    init['method'] = params['method'];
-  }
-  if (params['mode']) {
-    init['mode'] = params['mode'];
-  }
-  if (params['credentials']) {
-    init['credentials'] = params['credentials'];
-  }
+  init['method'] = params['method'] || base['method'];
+  init['mode'] = params['mode'] || base['mode'];
+  init['credentials'] = params['credentials'] || base['credentials'];
+  init['redirect'] = params['redirect'] || base['redirect'];
   return init;
 }
 
 self.addEventListener('fetch', function(event) {
     var params = get_query_params(event.request.url);
-    var init = get_request_init(params);
+    var init = get_request_init(event.request, params);
     var url = params['url'];
     if (params['ignore']) {
       return;
     }
     if (params['reject']) {
       event.respondWith(new Promise(function(resolve, reject) {
           reject();
         }));
@@ -57,11 +52,24 @@ self.addEventListener('fetch', function(
       event.respondWith(new Response(new Blob([array], {type: 'image/png'})));
       return;
     }
     event.respondWith(new Promise(function(resolve, reject) {
         var request = event.request;
         if (url) {
           request = new Request(url, init);
         }
-        fetch(request).then(resolve, reject);
+        fetch(request).then(function(response) {
+          var expectedType = params['expected_type'];
+          if (expectedType && response.type !== expectedType) {
+            // Resolve a JSON object with a failure instead of rejecting
+            // in order to distinguish this from a NetworkError, which
+            // may be expected even if the type is correct.
+            resolve(new Response(JSON.stringify({
+              result: 'failure',
+              detail: 'got ' + response.type + ' Response.type instead of ' +
+                      expectedType
+            })));
+          }
+          resolve(response);
+        }, reject)
       }));
   });
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/get-host-info.sub.js
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/get-host-info.sub.js
@@ -12,13 +12,15 @@ function get_host_info() {
     HTTPS_PORT = eval('{{ports[https][0]}}');
     ORIGINAL_HOST = eval('\'{{host}}\'');
     REMOTE_HOST = 'www1.' + ORIGINAL_HOST;
   } catch (e) {
   }
   return {
     HTTP_ORIGIN: 'http://' + ORIGINAL_HOST + ':' + HTTP_PORT,
     HTTPS_ORIGIN: 'https://' + ORIGINAL_HOST + ':' + HTTPS_PORT,
+    HTTPS_ORIGIN_WITH_CREDS: 'https://foo:bar@' + ORIGINAL_HOST + ':' + HTTPS_PORT,
     HTTP_REMOTE_ORIGIN: 'http://' + REMOTE_HOST + ':' + HTTP_PORT,
     HTTPS_REMOTE_ORIGIN: 'https://' + REMOTE_HOST + ':' + HTTPS_PORT,
+    HTTPS_REMOTE_ORIGIN_WITH_CREDS: 'https://foo:bar@' + REMOTE_HOST + ':' + HTTPS_PORT,
     UNAUTHENTICATED_ORIGIN: 'http://' + UNAUTHENTICATED_HOST + ':' + HTTP_PORT
   };
 }
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/success.py
@@ -0,0 +1,8 @@
+def main(request, response):
+    headers = []
+
+    if "ACAOrigin" in request.GET:
+        for item in request.GET["ACAOrigin"].split(","):
+            headers.append(("Access-Control-Allow-Origin", item))
+
+    return headers, "{ \"result\": \"success\" }"