Bug 1267733 P4 Add a wpt test that verifies a service worker update can recover from a broken navigation interception. r=jdm
authorBen Kelly <ben@wanderview.com>
Wed, 27 Apr 2016 02:24:04 -0700
changeset 334161 b396a6c173814ac0ecce81cfc6b8f69ae56b9b15
parent 334160 23da91f0f80eec72940f3572e3c26def3ad872fe
child 334162 69f8521535b0b41fb37325b6ac7a5a2159805b5f
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
bugs1267733
milestone49.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 1267733 P4 Add a wpt test that verifies a service worker update can recover from a broken navigation interception. r=jdm
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/service-workers/service-worker/resources/update-recovery-worker.py
testing/web-platform/tests/service-workers/service-worker/update-recovery.https.html
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -35373,16 +35373,22 @@
           }
         ],
         "service-workers/service-worker/navigate-window.https.html": [
           {
             "path": "service-workers/service-worker/navigate-window.https.html",
             "url": "/service-workers/service-worker/navigate-window.https.html"
           }
         ],
+        "service-workers/service-worker/update-recovery.https.html": [
+          {
+            "path": "service-workers/service-worker/update-recovery.https.html",
+            "url": "/service-workers/service-worker/update-recovery.https.html"
+          }
+        ],
         "web-animations/animation/startTime.html": [
           {
             "path": "web-animations/animation/startTime.html",
             "url": "/web-animations/animation/startTime.html"
           }
         ],
         "web-animations/timing-model/animation-effects/active-time.html": [
           {
copy from testing/web-platform/tests/service-workers/service-worker/resources/update-worker.py
copy to testing/web-platform/tests/service-workers/service-worker/resources/update-recovery-worker.py
--- a/testing/web-platform/tests/service-workers/service-worker/resources/update-worker.py
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/update-recovery-worker.py
@@ -1,46 +1,25 @@
-import time
-
 def main(request, response):
     # Set mode to 'init' for initial fetch.
     mode = 'init'
-    if 'mode' in request.cookies:
-        mode = request.cookies['mode'].value
+    if 'update-recovery-mode' in request.cookies:
+        mode = request.cookies['update-recovery-mode'].value
 
     # no-cache itself to ensure the user agent finds a new version for each update.
     headers = [('Cache-Control', 'no-cache, must-revalidate'),
                ('Pragma', 'no-cache')]
 
-    content_type = ''
     extra_body = ''
 
     if mode == 'init':
-        # Set a normal mimetype.
-        # Set cookie value to 'normal' so the next fetch will work in 'normal' mode.
-        content_type = 'application/javascript'
-        response.set_cookie('mode', 'normal')
-    elif mode == 'normal':
-        # Set a normal mimetype.
-        # Set cookie value to 'error' so the next fetch will work in 'error' mode.
-        content_type = 'application/javascript'
-        response.set_cookie('mode', 'error');
-    elif mode == 'error':
-        # Set a disallowed mimetype.
-        # Set cookie value to 'syntax-error' so the next fetch will work in 'syntax-error' mode.
-        content_type = 'text/html'
-        response.set_cookie('mode', 'syntax-error');
-    elif mode == 'syntax-error':
-        # Set cookie value to 'throw-install' so the next fetch will work in 'throw-install' mode.
-        content_type = 'application/javascript'
-        response.set_cookie('mode', 'throw-install');
-        extra_body = 'badsyntax(isbad;'
-    elif mode == 'throw-install':
-        # Unset and delete cookie to clean up the test setting.
-        content_type = 'application/javascript'
-        response.delete_cookie('mode')
-        extra_body = "addEventListener('install', function(e) { throw new Error('boom'); });"
+        # Install a bad service worker that will break the controlled
+        # document navigation.
+        response.set_cookie('update-recovery-mode', 'bad')
+        extra_body = "addEventListener('fetch', function(e) { e.respondWith(Promise.reject()); });"
+    elif mode == 'bad':
+        # When the update tries to pull the script again, update to
+        # a worker service worker that does not break document
+        # navigation.  Serve the same script from then on.
+        response.delete_cookie('update-recovery-mode')
 
-    headers.append(('Content-Type', content_type))
-    # Return a different script for each access.  Use .time() and .clock() for
-    # best time resolution across different platforms.
-    return headers, '/* %s %s */ %s' % (time.time(), time.clock(), extra_body)
-
+    headers.append(('Content-Type', 'application/javascript'))
+    return headers, '%s' % (extra_body)
copy from testing/web-platform/tests/service-workers/service-worker/update.https.html
copy to testing/web-platform/tests/service-workers/service-worker/update-recovery.https.html
--- a/testing/web-platform/tests/service-workers/service-worker/update.https.html
+++ b/testing/web-platform/tests/service-workers/service-worker/update-recovery.https.html
@@ -1,124 +1,71 @@
 <!DOCTYPE html>
-<title>Service Worker: Registration update()</title>
+<title>Service Worker: recovery by navigation update</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/testharness-helpers.js"></script>
 <script src="resources/test-helpers.sub.js"></script>
 <script>
-promise_test(function(t) {
+async_test(function(t) {
     var scope = 'resources/simple.txt';
-    var worker_url = 'resources/update-worker.py';
+    var worker_url = 'resources/update-recovery-worker.py';
     var expected_url = normalizeURL(worker_url);
     var registration;
-    var iframe;
-    return service_worker_unregister_and_register(t, worker_url, scope)
+
+    function with_bad_iframe(url) {
+      return new Promise(function(resolve, reject) {
+        var frame = document.createElement('iframe');
+
+        // There is no cross-browser event to listen for to detect an
+        // iframe that fails to load due to a bad interception.  Unfortunately
+        // we have to use a timeout.
+        var timeout = setTimeout(function() {
+          frame.remove();
+          resolve();
+        }, 5000);
+
+        // If we do get a load event, though, we know something went wrong.
+        frame.addEventListener('load', function() {
+          clearTimeout(timeout);
+          frame.remove();
+          reject('expected bad iframe should not fire a load event!');
+        });
+
+        frame.src = url;
+        document.body.appendChild(frame);
+      });
+    }
+
+    function with_update(t) {
+      return new Promise(function(resolve, reject) {
+        registration.addEventListener('updatefound', function onUpdate() {
+          registration.removeEventListener('updatefound', onUpdate);
+          wait_for_state(t, registration.installing, 'activated').then(function() {
+            resolve();
+          });
+        });
+      });
+    }
+
+    service_worker_unregister_and_register(t, worker_url, scope)
       .then(function(r) {
           registration = r;
           return wait_for_state(t, registration.installing, 'activated');
         })
       .then(function() {
-          assert_equals(registration.installing, null,
-                        'installing should be null in the initial state.');
-          assert_equals(registration.waiting, null,
-                        'waiting should be null in the initial state.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should exist in the initial state.');
-          // A new worker (generated by update-worker.py) should be found.
-          // The returned promise should resolve when a new worker script is
-          // fetched and starts installing.
-          return Promise.all([registration.update(),
-                              wait_for_update(t, registration)]);
-        })
-      .then(function() {
-          assert_equals(registration.installing.scriptURL, expected_url,
-                        'new installing should be set after update resolves.');
-          assert_equals(registration.waiting, null,
-                        'waiting should still be null after update resolves.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update found.');
-          return wait_for_state(t, registration.installing, 'installed');
-        })
-      .then(function() {
-          assert_equals(registration.installing, null,
-                        'installing should be null after installing.');
-          if (registration.waiting) {
-            assert_equals(registration.waiting.scriptURL, expected_url,
-                          'waiting should be set after installing.');
-            assert_equals(registration.active.scriptURL, expected_url,
-                          'active should still exist after installing.');
-            return wait_for_state(t, registration.waiting, 'activated');
-          }
-        })
-      .then(function() {
-          assert_equals(registration.installing, null,
-                        'installing should be null after activated.');
-          assert_equals(registration.waiting, null,
-                        'waiting should be null after activated.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'new worker should be promoted to active.');
+          return Promise.all([
+            with_update(t),
+            with_bad_iframe(scope)
+          ]);
         })
       .then(function() {
-          // A new worker(generated by update-worker.py) should be found.
-          // The returned promise should reject as update-worker.py sets the
-          // mimetype to a disallowed value for this attempt.
-          return registration.update();
-        })
-      .then(
-        function() { assert_unreached("update() should reject."); },
-        function(e) {
-          assert_throws('SecurityError', function() { throw e; },
-                        'Using a disallowed mimetype should make update() ' +
-                        'promise reject with a SecurityError.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update failure.');
-
-          // A new worker(generated by update-worker.py) should be found.
-          // The returned promise should reject as update-worker.py returns
-          // a worker script with a syntax error.
-          return registration.update();
-        })
-      .then(
-        function() { assert_unreached("update() should reject."); },
-        function(e) {
-          assert_throws({name: 'TypeError'}, function () { throw e; },
-                        'A script syntax error should make update() ' +
-                        'promise reject with a TypeError.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update failure.');
-
-          // A new worker(generated by update-worker.py) should be found.
-          // The returned promise should not reject, even though
-          // update-worker.py returns a worker script that throws in the
-          // install event handler.
-          return Promise.all([registration.update(),
-                              wait_for_update(t, registration)]);
-        })
-      .then(function() {
-          assert_equals(registration.installing.scriptURL, expected_url,
-                        'new installing should be set after update resolves.');
-          assert_equals(registration.waiting, null,
-                        'waiting should be null after activated.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update found.');
-
-          // We need to hold a client alive so that unregister() below doesn't
-          // remove the registration before update() has had a chance to look
-          // at the pending uninstall flag.
           return with_iframe(scope);
         })
       .then(function(frame) {
-          iframe = frame;
-
-          return assert_promise_rejects(
-              Promise.all([registration.unregister(), registration.update()]),
-              new TypeError(),
-              'Calling update() while the uninstalling flag is set' +
-              'should return a promise that rejects with an ' +
-              'InvalidStateError.');
+          assert_equals(frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
+                        expected_url);
+          frame.remove();
+          return service_worker_unregister_and_done(t, scope);
         })
-      .then(function() {
-        iframe.remove();
-        return t.done();
-        });
-  }, 'Update a registration.');
+      .catch(unreached_rejection(t));
+  }, 'Recover from a bad service worker by updating after a failed navigation.');
 </script>