summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/service-workers
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/service-workers')
-rw-r--r--testing/web-platform/tests/service-workers/OWNERS1
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/OWNERS2
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/common.https.html52
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/resources/blank.html2
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/resources/common-worker.js15
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/resources/credentials-iframe.html38
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/resources/credentials-worker.js59
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/resources/fetch-status.py2
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/resources/iframe.html18
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/resources/simple.txt1
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/resources/test-helpers.js237
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/resources/testharness-helpers.js33
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-add.js238
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-delete.js96
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-match.js193
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-matchAll.js154
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-put.js305
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-keys.js36
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-match.js124
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage.js197
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-add.https.html10
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-delete.https.html10
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-match.https.html10
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-matchAll.https.html10
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-put.https.html10
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-keys.https.html10
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-match.https.html10
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage.https.html10
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/serviceworker/credentials.html46
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/window/cache-add.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/window/cache-delete.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/window/cache-match.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/window/cache-matchAll.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/window/cache-put.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-keys.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-match.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/window/cache-storage.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/window/sandboxed-iframes.https.html67
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/worker/cache-add.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/worker/cache-delete.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/worker/cache-match.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/worker/cache-matchAll.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/worker/cache-put.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-keys.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-match.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage.https.html9
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html37
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-worker.js132
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-controlling-worker.html0
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-worker.js23
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js25
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py14
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/unregister.https.html127
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html45
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html32
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/activation-after-registration.https.html29
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/activation.https.html179
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/active.https.html55
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/appcache-ordering-main.https.html91
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/claim-not-using-registration.https.html123
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/claim-using-registration.https.html100
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/client-navigate.https.html117
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/clients-get-cross-origin.https.html42
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/clients-get.https.html70
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/clients-matchall-client-types.https.html78
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html93
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/clients-matchall.https.html45
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/controller-on-disconnect.https.html40
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/controller-on-load.https.html45
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/controller-on-reload.https.html54
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/extendable-event-async-waituntil.https.html30
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/extendable-event-waituntil.https.html125
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting-cache.https.html38
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting.https.html38
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-cors-xhr.https.html38
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-csp.https.html32
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-event-after-navigation-within-page.https.html65
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-event-async-respond-with.https.html34
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-event-network-error.https.html42
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-event-redirect.https.html1102
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html35
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-event.https.html575
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-frame-resource.https.html221
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-header-visibility.https.html52
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html27
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html27
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-request-css-base-url.https.html56
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-request-css-images.https.html176
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-request-fallback.https.html113
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-request-no-freshness-headers.https.html53
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-request-redirect.https.html372
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-request-resources.https.html189
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-request-xhr.https.html33
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-response-xhr.https.html37
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/fetch-waits-for-activate.https.html63
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/getregistration.https.html87
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/getregistrations.https.html191
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/indexeddb.https.html35
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/install-event-type.https.html30
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/installing.https.html37
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/interfaces.https.html56
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/invalid-blobtype.https.html30
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/invalid-header.https.html30
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/multiple-register.https.html116
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/multiple-update.https.html92
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/navigate-window.https.html141
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html449
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/onactivate-script-error.https.html75
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/oninstall-script-error.https.html67
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/performance-timeline.https.html11
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/postmessage-msgport-to-client.https.html51
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/postmessage-to-client.https.html49
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/postmessage.https.html60
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/ready.https.html172
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/referer.https.html30
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/register-closed-window.https.html35
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/register-default-scope.https.html66
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/register-same-scope-different-script-url.https.html240
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/register-wait-forever-in-install-worker.https.html56
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/registration-end-to-end.https.html96
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/registration-events.https.html37
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/registration-iframe.https.html108
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/registration-service-worker-attributes.https.html70
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/registration.https.html368
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/rejections.https.html21
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/request-end-to-end.https.html61
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html58
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/404.py5
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.install.html26
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.html13
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.js1
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.manifest7
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/blank.html2
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/claim-worker.js14
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-frame.html12
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-worker.js71
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/client-navigated-frame.html3
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/clients-get-frame.html12
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/clients-get-other-origin.html64
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/clients-get-worker.js53
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html8
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-shared-worker.js4
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-worker.js24
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js7
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-interceptor.js21
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-script.py9
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/dummy.html2
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/dummy.txt1
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/empty-worker.js1
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/empty.js0
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/end-to-end-worker.js7
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/events-worker.js12
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-async-waituntil.js20
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-waituntil.js75
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fail-on-fetch-worker.js5
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control-login.html16
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control.py69
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html294
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html210
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html72
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html.sub.headers1
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-after-navigation-within-page-iframe.html22
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js19
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html59
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-worker.js46
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html25
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-respond-with-stops-propagation-worker.js15
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-test-worker.js151
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-header-visibility-iframe.html66
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-inscope.html71
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-outscope.html80
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe.html71
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-iframe.html1
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-style.css1
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-worker.js27
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-iframe.html15
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js13
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html1
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-worker.js18
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html35
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html67
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-worker.js24
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html179
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-worker.js22
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-iframe.https.html35
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-worker.js12
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js149
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js17
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/frame-for-getregistrations.html19
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/get-host-info.sub.js26
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/indexeddb-worker.js26
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/install-event-type-worker.js8
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/interfaces-worker.sub.js107
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/interfaces.js15
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-iframe.https.html29
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js10
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding-with-flush.py11
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding.py2
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-iframe.https.html26
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-worker.js12
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/load_worker.js29
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/loaded.html9
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/malformed-worker.py10
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/mime-type-worker.py4
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/mint-new-worker.py25
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/navigate-window-worker.js21
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html66
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py15
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py15
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py15
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-worker.js75
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-from-nested-event-worker.js13
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-cancel-worker.js3
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-prevent-default-worker.js7
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-with-empty-onerror-worker.js2
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-worker.js7
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-from-nested-event-worker.js12
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-cancel-worker.js3
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-prevent-default-worker.js7
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-with-empty-onerror-worker.js2
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-worker.js7
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/other.html3
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/override_assert_object_equals.js58
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/performance-timeline-worker.js58
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/postmessage-msgport-to-client-worker.js20
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/postmessage-to-client-worker.js10
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/postmessage-worker.js19
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/redirect.py25
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/referer-iframe.html39
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/register-closed-window-iframe.html16
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/registration-worker.js1
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/reject-install-worker.js3
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/request-end-to-end-worker.js32
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/request-headers.py6
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-iframe.html4
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js5
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/service-worker-csp-worker.py153
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-controlled.js8
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-import.js1
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/silence.ogabin0 -> 12983 bytes
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/simple-intercept-worker.js5
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/simple.html3
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/simple.txt1
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js24
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-worker.js21
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/square.pngbin0 -> 18299 bytes
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/success.py8
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/test-helpers.sub.js227
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/testharness-helpers.js163
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/unregister-controller-page.html16
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/update-nocookie-worker.py15
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/update-recovery-worker.py25
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/update-worker.py46
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/update/update-after-oneday.https.html8
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/wait-forever-in-install-worker.js12
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/websocket.js7
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/worker-interception-iframe.https.html39
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/worker-load-interceptor.js13
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/worker-testharness.js49
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/resources/xhr.js6
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/service-worker-csp-connect.https.html10
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/service-worker-csp-default.https.html10
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/service-worker-csp-script.https.html10
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/serviceworkerobject-scripturl.https.html26
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/shared-worker-controlled.https.html77
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/skip-waiting-installed.https.html67
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/skip-waiting-using-registration.https.html60
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-client.https.html13
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-using-registration.https.html42
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/skip-waiting.https.html54
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/state.https.html69
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/synced-state.https.html64
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/uncontrolled-page.https.html39
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/unregister-controller.https.html108
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/unregister-then-register-new-script.https.html159
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/unregister-then-register.https.html129
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/unregister.https.html40
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/update-after-navigation-fetch-event.https.html34
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/update-after-oneday.https.html50
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/update-recovery.https.html71
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/update.https.html123
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/waiting.https.html44
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/websocket.https.html46
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/worker-interception.https.html155
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/xhr.https.html35
-rw-r--r--testing/web-platform/tests/service-workers/specgen.json658
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.1-service-worker-obj.html63
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.1.1-service-worker-scope.html46
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.1.2-service-worker-url.html43
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.1.3-service-worker-state.html76
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.1.4-service-worker-on-state-change.html35
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2-navigator-service-worker.html84
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.1-navigator-service-worker-installing.html43
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.10-navigator-service-worker-oncontrollerchange.html45
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.11-navigator-service-worker-onreloadpage.html41
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.12-navigator-service-worker-onerror.html37
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.2-navigator-service-worker-waiting.html34
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.3-navigator-service-worker-active.html40
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.4-navigator-service-worker-controller.html37
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.5-navigator-service-worker-ready.html67
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.6-navigator-service-worker-getAll.html30
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.7-navigator-service-worker-register.html32
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.8-navigator-service-worker-unregister.html31
-rw-r--r--testing/web-platform/tests/service-workers/stub-3.2.9-navigator-service-worker-onupdatefound.html42
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.1-service-worker-global-scope.html75
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.1.1-service-worker-global-scope-caches.html36
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.1.2-service-worker-global-scope-clients.html33
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.1.3-service-worker-global-scope-scope.html36
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.1.4-service-worker-global-scope-fetch.html55
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.1.5-service-worker-global-scope-update.html36
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.1.6-service-worker-global-scope-unregister.html31
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.1.7-service-worker-global-scope-onmessage.html45
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.2-client.html61
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.3-service-worker-clients.html48
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.3.1-get-serviced-method.html34
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.3.2-reloadall-method.html37
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.4-request-objects.html72
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.5-response-objects.html75
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.5.2-response.html36
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.5.4-opaque-response.html36
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.6-cache-objects.html37
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.6.1-cache-lifetimes.html38
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.6.2-cache.html64
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.6.3-cache-storage.html62
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.7.1-install-phase-event.html51
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.7.1.1-wait-until-method.html39
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.7.2-install-event.html35
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.7.2.1-install-event-section.html47
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.7.2.2-replace-method.html38
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.7.3-activate-event.html41
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.7.4.1-fetch-event-section.html71
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.7.4.2-respond-with-method.html46
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.7.4.3-default-method.html52
-rw-r--r--testing/web-platform/tests/service-workers/stub-4.7.4.4-is-reload-attribute.html32
-rw-r--r--testing/web-platform/tests/service-workers/stub-5.1-origin-relativity.html35
-rw-r--r--testing/web-platform/tests/service-workers/stub-5.2-cross-origin-resources.html48
-rw-r--r--testing/web-platform/tests/service-workers/tools/blink-import.py204
337 files changed, 19208 insertions, 0 deletions
diff --git a/testing/web-platform/tests/service-workers/OWNERS b/testing/web-platform/tests/service-workers/OWNERS
new file mode 100644
index 000000000..e210b8f2d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/OWNERS
@@ -0,0 +1 @@
+@ehsan
diff --git a/testing/web-platform/tests/service-workers/cache-storage/OWNERS b/testing/web-platform/tests/service-workers/cache-storage/OWNERS
new file mode 100644
index 000000000..2e63dd220
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/OWNERS
@@ -0,0 +1,2 @@
+@inexorabletash
+@wanderview
diff --git a/testing/web-platform/tests/service-workers/cache-storage/common.https.html b/testing/web-platform/tests/service-workers/cache-storage/common.https.html
new file mode 100644
index 000000000..b00199844
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/common.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Cache Storage: Verify that Window and Workers see same storage</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helpers.js"></script>
+<script>
+
+function wait_for_message(worker) {
+ return new Promise(function(resolve) {
+ worker.addEventListener('message', function listener(e) {
+ resolve(e.data);
+ worker.removeEventListener('message', listener);
+ });
+ });
+}
+
+promise_test(function(t) {
+ var cache_name = 'common-test';
+ return self.caches.delete(cache_name)
+ .then(function() {
+ var worker = new Worker('resources/common-worker.js');
+ worker.postMessage({name: cache_name});
+ return wait_for_message(worker);
+ })
+ .then(function(message) {
+ return self.caches.open(cache_name);
+ })
+ .then(function(cache) {
+ return Promise.all([
+ cache.match('https://example.com/a'),
+ cache.match('https://example.com/b'),
+ cache.match('https://example.com/c')
+ ]);
+ })
+ .then(function(responses) {
+ return Promise.all(responses.map(
+ function(response) { return response.text(); }
+ ));
+ })
+ .then(function(bodies) {
+ assert_equals(bodies[0], 'a',
+ 'Body should match response put by worker');
+ assert_equals(bodies[1], 'b',
+ 'Body should match response put by worker');
+ assert_equals(bodies[2], 'c',
+ 'Body should match response put by worker');
+ });
+}, 'Window sees cache puts by Worker');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/blank.html b/testing/web-platform/tests/service-workers/cache-storage/resources/blank.html
new file mode 100644
index 000000000..a3c3a4689
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/resources/blank.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<title>Empty doc</title>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/common-worker.js b/testing/web-platform/tests/service-workers/cache-storage/resources/common-worker.js
new file mode 100644
index 000000000..d0e8544b5
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/resources/common-worker.js
@@ -0,0 +1,15 @@
+self.onmessage = function(e) {
+ var cache_name = e.data.name;
+
+ self.caches.open(cache_name)
+ .then(function(cache) {
+ return Promise.all([
+ cache.put('https://example.com/a', new Response('a')),
+ cache.put('https://example.com/b', new Response('b')),
+ cache.put('https://example.com/c', new Response('c'))
+ ]);
+ })
+ .then(function() {
+ self.postMessage('ok');
+ });
+};
diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-iframe.html b/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-iframe.html
new file mode 100644
index 000000000..00702df9e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-iframe.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Controlled frame for Cache API test with credentials</title>
+<script>
+
+function xhr(url, username, password) {
+ return new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest(), async = true;
+ xhr.open('GET', url, async, username, password);
+ xhr.send();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState !== XMLHttpRequest.DONE)
+ return;
+ if (xhr.status === 200) {
+ resolve(xhr.responseText);
+ } else {
+ reject(new Error(xhr.statusText));
+ }
+ };
+ });
+}
+
+window.onmessage = function(e) {
+ Promise.all(e.data.map(function(item) {
+ return xhr(item.name, item.username, item.password);
+ }))
+ .then(function() {
+ navigator.serviceWorker.controller.postMessage('keys');
+ navigator.serviceWorker.onmessage = function(e) {
+ window.parent.postMessage(e.data, '*');
+ };
+ });
+};
+
+</script>
+<body>
+Hello? Yes, this is iframe.
+</body>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-worker.js b/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-worker.js
new file mode 100644
index 000000000..43965b5fe
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-worker.js
@@ -0,0 +1,59 @@
+var cache_name = 'credentials';
+
+function assert_equals(actual, expected, message) {
+ if (!Object.is(actual, expected))
+ throw Error(message + ': expected: ' + expected + ', actual: ' + actual);
+}
+
+self.onfetch = function(e) {
+ if (!/\.txt$/.test(e.request.url)) return;
+ var content = e.request.url;
+ var cache;
+ e.respondWith(
+ self.caches.open(cache_name)
+ .then(function(result) {
+ cache = result;
+ return cache.put(e.request, new Response(content));
+ })
+
+ .then(function() { return cache.match(e.request); })
+ .then(function(result) { return result.text(); })
+ .then(function(text) {
+ assert_equals(text, content, 'Cache.match() body should match');
+ })
+
+ .then(function() { return cache.matchAll(e.request); })
+ .then(function(results) {
+ assert_equals(results.length, 1, 'Should have one response');
+ return results[0].text();
+ })
+ .then(function(text) {
+ assert_equals(text, content, 'Cache.matchAll() body should match');
+ })
+
+ .then(function() { return self.caches.match(e.request); })
+ .then(function(result) { return result.text(); })
+ .then(function(text) {
+ assert_equals(text, content, 'CacheStorage.match() body should match');
+ })
+
+ .then(function() {
+ return new Response('dummy');
+ })
+ );
+};
+
+self.onmessage = function(e) {
+ if (e.data === 'keys') {
+ self.caches.open(cache_name)
+ .then(function(cache) { return cache.keys(); })
+ .then(function(requests) {
+ var urls = requests.map(function(request) { return request.url; });
+ self.clients.matchAll().then(function(clients) {
+ clients.forEach(function(client) {
+ client.postMessage(urls);
+ });
+ });
+ });
+ }
+};
diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/fetch-status.py b/testing/web-platform/tests/service-workers/cache-storage/resources/fetch-status.py
new file mode 100644
index 000000000..71f13ebc3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/resources/fetch-status.py
@@ -0,0 +1,2 @@
+def main(request, response):
+ return int(request.GET["status"]), [], ""
diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/iframe.html b/testing/web-platform/tests/service-workers/cache-storage/resources/iframe.html
new file mode 100644
index 000000000..a2f1e502b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/resources/iframe.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>ok</title>
+<script>
+window.onmessage = function(e) {
+ var id = e.data.id;
+ try {
+ var name = 'checkallowed';
+ self.caches.open(name).then(function (cache) {
+ self.caches.delete(name);
+ window.parent.postMessage({id: id, result: 'allowed'}, '*');
+ }).catch(function(e) {
+ window.parent.postMessage({id: id, result: 'denied', name: e.name, message: e.message}, '*');
+ });
+ } catch (e) {
+ window.parent.postMessage({id: id, result: 'unexpecteddenied', name: e.name, message: e.message}, '*');
+ }
+};
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/simple.txt b/testing/web-platform/tests/service-workers/cache-storage/resources/simple.txt
new file mode 100644
index 000000000..9e3cb91fb
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/resources/simple.txt
@@ -0,0 +1 @@
+a simple text file
diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/test-helpers.js b/testing/web-platform/tests/service-workers/cache-storage/resources/test-helpers.js
new file mode 100644
index 000000000..f4145e621
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/resources/test-helpers.js
@@ -0,0 +1,237 @@
+(function() {
+ var next_cache_index = 1;
+
+ // Returns a promise that resolves to a newly created Cache object. The
+ // returned Cache will be destroyed when |test| completes.
+ function create_temporary_cache(test) {
+ var uniquifier = String(++next_cache_index);
+ var cache_name = self.location.pathname + '/' + uniquifier;
+
+ test.add_cleanup(function() {
+ self.caches.delete(cache_name);
+ });
+
+ return self.caches.delete(cache_name)
+ .then(function() {
+ return self.caches.open(cache_name);
+ });
+ }
+
+ self.create_temporary_cache = create_temporary_cache;
+})();
+
+// Runs |test_function| with a temporary unique Cache passed in as the only
+// argument. The function is run as a part of Promise chain owned by
+// promise_test(). As such, it is expected to behave in a manner identical (with
+// the exception of the argument) to a function passed into promise_test().
+//
+// E.g.:
+// cache_test(function(cache) {
+// // Do something with |cache|, which is a Cache object.
+// }, "Some Cache test");
+function cache_test(test_function, description) {
+ promise_test(function(test) {
+ return create_temporary_cache(test)
+ .then(test_function);
+ }, description);
+}
+
+// A set of Request/Response pairs to be used with prepopulated_cache_test().
+var simple_entries = [
+ {
+ name: 'a',
+ request: new Request('http://example.com/a'),
+ response: new Response('')
+ },
+
+ {
+ name: 'b',
+ request: new Request('http://example.com/b'),
+ response: new Response('')
+ },
+
+ {
+ name: 'a_with_query',
+ request: new Request('http://example.com/a?q=r'),
+ response: new Response('')
+ },
+
+ {
+ name: 'A',
+ request: new Request('http://example.com/A'),
+ response: new Response('')
+ },
+
+ {
+ name: 'a_https',
+ request: new Request('https://example.com/a'),
+ response: new Response('')
+ },
+
+ {
+ name: 'a_org',
+ request: new Request('http://example.org/a'),
+ response: new Response('')
+ },
+
+ {
+ name: 'cat',
+ request: new Request('http://example.com/cat'),
+ response: new Response('')
+ },
+
+ {
+ name: 'catmandu',
+ request: new Request('http://example.com/catmandu'),
+ response: new Response('')
+ },
+
+ {
+ name: 'cat_num_lives',
+ request: new Request('http://example.com/cat?lives=9'),
+ response: new Response('')
+ },
+
+ {
+ name: 'cat_in_the_hat',
+ request: new Request('http://example.com/cat/in/the/hat'),
+ response: new Response('')
+ },
+
+ {
+ name: 'non_2xx_response',
+ request: new Request('http://example.com/non2xx'),
+ response: new Response('', {status: 404, statusText: 'nope'})
+ },
+
+ {
+ name: 'error_response',
+ request: new Request('http://example.com/error'),
+ response: Response.error()
+ },
+];
+
+// A set of Request/Response pairs to be used with prepopulated_cache_test().
+// These contain a mix of test cases that use Vary headers.
+var vary_entries = [
+ {
+ name: 'vary_cookie_is_cookie',
+ request: new Request('http://example.com/c',
+ {headers: {'Cookies': 'is-for-cookie'}}),
+ response: new Response('',
+ {headers: {'Vary': 'Cookies'}})
+ },
+
+ {
+ name: 'vary_cookie_is_good',
+ request: new Request('http://example.com/c',
+ {headers: {'Cookies': 'is-good-enough-for-me'}}),
+ response: new Response('',
+ {headers: {'Vary': 'Cookies'}})
+ },
+
+ {
+ name: 'vary_cookie_absent',
+ request: new Request('http://example.com/c'),
+ response: new Response('',
+ {headers: {'Vary': 'Cookies'}})
+ }
+];
+
+// Run |test_function| with a Cache object and a map of entries. Prior to the
+// call, the Cache is populated by cache entries from |entries|. The latter is
+// expected to be an Object mapping arbitrary keys to objects of the form
+// {request: <Request object>, response: <Response object>}. There's no
+// guarantee on the order in which entries will be added to the cache.
+//
+// |test_function| should return a Promise that can be used with promise_test.
+function prepopulated_cache_test(entries, test_function, description) {
+ cache_test(function(cache) {
+ var p = Promise.resolve();
+ var hash = {};
+ return Promise.all(entries.map(function(entry) {
+ hash[entry.name] = entry;
+ return cache.put(entry.request.clone(),
+ entry.response.clone())
+ .catch(function(e) {
+ assert_unreached(
+ 'Test setup failed for entry ' + entry.name + ': ' + e);
+ });
+ }))
+ .then(function() {
+ assert_equals(Object.keys(hash).length, entries.length);
+ })
+ .then(function() {
+ return test_function(cache, hash);
+ });
+ }, description);
+}
+
+// Helper for testing with Headers objects. Compares Headers instances
+// by serializing |expected| and |actual| to arrays and comparing.
+function assert_header_equals(actual, expected, description) {
+ assert_class_string(actual, "Headers", description);
+ var header;
+ var actual_headers = [];
+ var expected_headers = [];
+ for (header of actual)
+ actual_headers.push(header[0] + ": " + header[1]);
+ for (header of expected)
+ expected_headers.push(header[0] + ": " + header[1]);
+ assert_array_equals(actual_headers, expected_headers,
+ description + " Headers differ.");
+}
+
+// Helper for testing with Response objects. Compares simple
+// attributes defined on the interfaces, as well as the headers. It
+// does not compare the response bodies.
+function assert_response_equals(actual, expected, description) {
+ assert_class_string(actual, "Response", description);
+ ["type", "url", "status", "ok", "statusText"].forEach(function(attribute) {
+ assert_equals(actual[attribute], expected[attribute],
+ description + " Attributes differ: " + attribute + ".");
+ });
+ assert_header_equals(actual.headers, expected.headers, description);
+}
+
+// Assert that the two arrays |actual| and |expected| contain the same
+// set of Responses as determined by assert_response_equals. The order
+// is not significant.
+//
+// |expected| is assumed to not contain any duplicates.
+function assert_response_array_equivalent(actual, expected, description) {
+ assert_true(Array.isArray(actual), description);
+ assert_equals(actual.length, expected.length, description);
+ expected.forEach(function(expected_element) {
+ // assert_response_in_array treats the first argument as being
+ // 'actual', and the second as being 'expected array'. We are
+ // switching them around because we want to be resilient
+ // against the |actual| array containing duplicates.
+ assert_response_in_array(expected_element, actual, description);
+ });
+}
+
+// Asserts that two arrays |actual| and |expected| contain the same
+// set of Responses as determined by assert_response_equals(). The
+// corresponding elements must occupy corresponding indices in their
+// respective arrays.
+function assert_response_array_equals(actual, expected, description) {
+ assert_true(Array.isArray(actual), description);
+ assert_equals(actual.length, expected.length, description);
+ actual.forEach(function(value, index) {
+ assert_response_equals(value, expected[index],
+ description + " : object[" + index + "]");
+ });
+}
+
+// Equivalent to assert_in_array, but uses assert_response_equals.
+function assert_response_in_array(actual, expected_array, description) {
+ assert_true(expected_array.some(function(element) {
+ try {
+ assert_response_equals(actual, element);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }), description);
+}
diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/testharness-helpers.js b/testing/web-platform/tests/service-workers/cache-storage/resources/testharness-helpers.js
new file mode 100644
index 000000000..e4885727b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/resources/testharness-helpers.js
@@ -0,0 +1,33 @@
+/*
+ * testharness-helpers contains various useful extensions to testharness.js to
+ * allow them to be used across multiple tests before they have been
+ * upstreamed. This file is intended to be usable from both document and worker
+ * environments, so code should for example not rely on the DOM.
+ */
+
+// Returns a promise that fulfills after the provided |promise| is fulfilled.
+// The |test| succeeds only if |promise| rejects with an exception matching
+// |code|. Accepted values for |code| follow those accepted for assert_throws().
+// The optional |description| describes the test being performed.
+//
+// E.g.:
+// assert_promise_rejects(
+// new Promise(...), // something that should throw an exception.
+// 'NotFoundError',
+// 'Should throw NotFoundError.');
+//
+// assert_promise_rejects(
+// new Promise(...),
+// new TypeError(),
+// 'Should throw TypeError');
+function assert_promise_rejects(promise, code, description) {
+ return promise.then(
+ function() {
+ throw 'assert_promise_rejects: ' + description + ' Promise did not reject.';
+ },
+ function(e) {
+ if (code !== undefined) {
+ assert_throws(code, function() { throw e; }, description);
+ }
+ });
+}
diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-add.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-add.js
new file mode 100644
index 000000000..01fd605ad
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-add.js
@@ -0,0 +1,238 @@
+if (self.importScripts) {
+ importScripts('/resources/testharness.js');
+ importScripts('../resources/testharness-helpers.js');
+ importScripts('../resources/test-helpers.js');
+}
+
+cache_test(function(cache) {
+ return assert_promise_rejects(
+ cache.add(),
+ new TypeError(),
+ 'Cache.add should throw a TypeError when no arguments are given.');
+ }, 'Cache.add called with no arguments');
+
+cache_test(function(cache) {
+ return cache.add('../resources/simple.txt')
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.add should resolve with undefined on success.');
+ return cache.match('../resources/simple.txt');
+ })
+ .then(function(response) {
+ assert_class_string(response, 'Response',
+ 'Cache.add should put a resource in the cache.');
+ return response.text();
+ })
+ .then(function(body) {
+ assert_equals(body, 'a simple text file\n',
+ 'Cache.add should retrieve the correct body.');
+ });
+ }, 'Cache.add called with relative URL specified as a string');
+
+cache_test(function(cache) {
+ return assert_promise_rejects(
+ cache.add('javascript://this-is-not-http-mmkay'),
+ new TypeError(),
+ 'Cache.add should throw a TypeError for non-HTTP/HTTPS URLs.');
+ }, 'Cache.add called with non-HTTP/HTTPS URL');
+
+cache_test(function(cache) {
+ var request = new Request('../resources/simple.txt');
+ return cache.add(request)
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.add should resolve with undefined on success.');
+ });
+ }, 'Cache.add called with Request object');
+
+cache_test(function(cache) {
+ var request = new Request('../resources/simple.txt',
+ {method: 'POST', body: 'This is a body.'});
+ return assert_promise_rejects(
+ cache.add(request),
+ new TypeError(),
+ 'Cache.add should throw a TypeError for non-GET requests.');
+ }, 'Cache.add called with POST request');
+
+cache_test(function(cache) {
+ var request = new Request('../resources/simple.txt');
+ return cache.add(request)
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.add should resolve with undefined on success.');
+ })
+ .then(function() {
+ return cache.add(request);
+ })
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.add should resolve with undefined on success.');
+ });
+ }, 'Cache.add called twice with the same Request object');
+
+cache_test(function(cache) {
+ var request = new Request('../resources/simple.txt');
+ return request.text()
+ .then(function() {
+ assert_false(request.bodyUsed);
+ })
+ .then(function() {
+ return cache.add(request);
+ });
+ }, 'Cache.add with request with null body (not consumed)');
+
+cache_test(function(cache) {
+ return assert_promise_rejects(
+ cache.add('this-does-not-exist-please-dont-create-it'),
+ new TypeError(),
+ 'Cache.add should reject if response is !ok');
+ }, 'Cache.add with request that results in a status of 404');
+
+cache_test(function(cache) {
+ return assert_promise_rejects(
+ cache.add('../resources/fetch-status.php?status=500'),
+ new TypeError(),
+ 'Cache.add should reject if response is !ok');
+ }, 'Cache.add with request that results in a status of 500');
+
+cache_test(function(cache) {
+ return assert_promise_rejects(
+ cache.addAll(),
+ new TypeError(),
+ 'Cache.addAll with no arguments should throw TypeError.');
+ }, 'Cache.addAll with no arguments');
+
+cache_test(function(cache) {
+ // Assumes the existence of ../resources/simple.txt and ../resources/blank.html
+ var urls = ['../resources/simple.txt', undefined, '../resources/blank.html'];
+ return assert_promise_rejects(
+ cache.addAll(),
+ new TypeError(),
+ 'Cache.addAll should throw TypeError for an undefined argument.');
+ }, 'Cache.addAll with a mix of valid and undefined arguments');
+
+cache_test(function(cache) {
+ return cache.addAll([])
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.addAll should resolve with undefined on ' +
+ 'success.');
+ return cache.keys();
+ })
+ .then(function(result) {
+ assert_equals(result.length, 0,
+ 'There should be no entry in the cache.');
+ });
+ }, 'Cache.addAll with an empty array');
+
+cache_test(function(cache) {
+ // Assumes the existence of ../resources/simple.txt and
+ // ../resources/blank.html
+ var urls = ['../resources/simple.txt',
+ self.location.href,
+ '../resources/blank.html'];
+ return cache.addAll(urls)
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.addAll should resolve with undefined on ' +
+ 'success.');
+ return Promise.all(
+ urls.map(function(url) { return cache.match(url); }));
+ })
+ .then(function(responses) {
+ assert_class_string(
+ responses[0], 'Response',
+ 'Cache.addAll should put a resource in the cache.');
+ assert_class_string(
+ responses[1], 'Response',
+ 'Cache.addAll should put a resource in the cache.');
+ assert_class_string(
+ responses[2], 'Response',
+ 'Cache.addAll should put a resource in the cache.');
+ return Promise.all(
+ responses.map(function(response) { return response.text(); }));
+ })
+ .then(function(bodies) {
+ assert_equals(
+ bodies[0], 'a simple text file\n',
+ 'Cache.add should retrieve the correct body.');
+ assert_equals(
+ bodies[2], '<!DOCTYPE html>\n<title>Empty doc</title>\n',
+ 'Cache.add should retrieve the correct body.');
+ });
+ }, 'Cache.addAll with string URL arguments');
+
+cache_test(function(cache) {
+ // Assumes the existence of ../resources/simple.txt and
+ // ../resources/blank.html
+ var urls = ['../resources/simple.txt',
+ self.location.href,
+ '../resources/blank.html'];
+ var requests = urls.map(function(url) {
+ return new Request(url);
+ });
+ return cache.addAll(requests)
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.addAll should resolve with undefined on ' +
+ 'success.');
+ return Promise.all(
+ urls.map(function(url) { return cache.match(url); }));
+ })
+ .then(function(responses) {
+ assert_class_string(
+ responses[0], 'Response',
+ 'Cache.addAll should put a resource in the cache.');
+ assert_class_string(
+ responses[1], 'Response',
+ 'Cache.addAll should put a resource in the cache.');
+ assert_class_string(
+ responses[2], 'Response',
+ 'Cache.addAll should put a resource in the cache.');
+ return Promise.all(
+ responses.map(function(response) { return response.text(); }));
+ })
+ .then(function(bodies) {
+ assert_equals(
+ bodies[0], 'a simple text file\n',
+ 'Cache.add should retrieve the correct body.');
+ assert_equals(
+ bodies[2], '<!DOCTYPE html>\n<title>Empty doc</title>\n',
+ 'Cache.add should retrieve the correct body.');
+ });
+ }, 'Cache.addAll with Request arguments');
+
+cache_test(function(cache) {
+ // Assumes that ../resources/simple.txt and ../resources/blank.html exist.
+ // The second resource does not.
+ var urls = ['../resources/simple.txt',
+ 'this-resource-should-not-exist',
+ '../resources/blank.html'];
+ var requests = urls.map(function(url) {
+ return new Request(url);
+ });
+ return assert_promise_rejects(
+ cache.addAll(requests),
+ new TypeError(),
+ 'Cache.addAll should reject with TypeError if any request fails')
+ .then(function() {
+ return Promise.all(urls.map(function(url) { return cache.match(url); }));
+ })
+ .then(function(matches) {
+ assert_array_equals(
+ matches,
+ [undefined, undefined, undefined],
+ 'If any response fails, no response should be added to cache');
+ });
+ }, 'Cache.addAll with a mix of succeeding and failing requests');
+
+cache_test(function(cache) {
+ var request = new Request('../resources/simple.txt');
+ return assert_promise_rejects(
+ cache.addAll([request, request]),
+ 'InvalidStateError',
+ 'Cache.addAll should throw InvalidStateError if the same request is added ' +
+ 'twice.');
+ }, 'Cache.addAll called with the same Request object specified twice');
+
+done();
diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-delete.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-delete.js
new file mode 100644
index 000000000..75a474c2b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-delete.js
@@ -0,0 +1,96 @@
+if (self.importScripts) {
+ importScripts('/resources/testharness.js');
+ importScripts('../resources/testharness-helpers.js');
+ importScripts('../resources/test-helpers.js');
+}
+
+var test_url = 'https://example.com/foo';
+
+// Construct a generic Request object. The URL is |test_url|. All other fields
+// are defaults.
+function new_test_request() {
+ return new Request(test_url);
+}
+
+// Construct a generic Response object.
+function new_test_response() {
+ return new Response('Hello world!', { status: 200 });
+}
+
+cache_test(function(cache) {
+ return assert_promise_rejects(
+ cache.delete(),
+ new TypeError(),
+ 'Cache.delete should reject with a TypeError when called with no ' +
+ 'arguments.');
+ }, 'Cache.delete with no arguments');
+
+cache_test(function(cache) {
+ return cache.put(new_test_request(), new_test_response())
+ .then(function() {
+ return cache.delete(test_url);
+ })
+ .then(function(result) {
+ assert_true(result,
+ 'Cache.delete should resolve with "true" if an entry ' +
+ 'was successfully deleted.');
+ return cache.match(test_url);
+ })
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.delete should remove matching entries from cache.');
+ });
+ }, 'Cache.delete called with a string URL');
+
+cache_test(function(cache) {
+ var request = new Request(test_url);
+ return cache.put(request, new_test_response())
+ .then(function() {
+ return cache.delete(request);
+ })
+ .then(function(result) {
+ assert_true(result,
+ 'Cache.delete should resolve with "true" if an entry ' +
+ 'was successfully deleted.');
+ });
+ }, 'Cache.delete called with a Request object');
+
+cache_test(function(cache) {
+ return cache.delete(test_url)
+ .then(function(result) {
+ assert_false(result,
+ 'Cache.delete should resolve with "false" if there ' +
+ 'are no matching entries.');
+ });
+ }, 'Cache.delete with a non-existent entry');
+
+var cache_entries = {
+ a: {
+ request: new Request('http://example.com/abc'),
+ response: new Response('')
+ },
+
+ b: {
+ request: new Request('http://example.com/b'),
+ response: new Response('')
+ },
+
+ a_with_query: {
+ request: new Request('http://example.com/abc?q=r'),
+ response: new Response('')
+ }
+};
+
+function prepopulated_cache_test(test_function, description) {
+ cache_test(function(cache) {
+ return Promise.all(Object.keys(cache_entries).map(function(k) {
+ return cache.put(cache_entries[k].request.clone(),
+ cache_entries[k].response.clone());
+ }))
+ .then(function() {
+ return test_function(cache);
+ });
+ }, description);
+}
+
+done();
diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-match.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-match.js
new file mode 100644
index 000000000..716bfe5a6
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-match.js
@@ -0,0 +1,193 @@
+if (self.importScripts) {
+ importScripts('/resources/testharness.js');
+ importScripts('../resources/testharness-helpers.js');
+ importScripts('../resources/test-helpers.js');
+}
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.match('not-present-in-the-cache')
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.match failures should resolve with undefined.');
+ });
+ }, 'Cache.match with no matching entries');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.match(entries.a.request.url)
+ .then(function(result) {
+ assert_response_equals(result, entries.a.response,
+ 'Cache.match should match by URL.');
+ });
+ }, 'Cache.match with URL');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.match(entries.a.request)
+ .then(function(result) {
+ assert_response_equals(result, entries.a.response,
+ 'Cache.match should match by Request.');
+ });
+ }, 'Cache.match with Request');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.match(new Request(entries.a.request.url))
+ .then(function(result) {
+ assert_response_equals(result, entries.a.response,
+ 'Cache.match should match by Request.');
+ });
+ }, 'Cache.match with new Request');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.match(entries.a.request,
+ {ignoreSearch: true})
+ .then(function(result) {
+ assert_response_in_array(
+ result,
+ [
+ entries.a.response,
+ entries.a_with_query.response
+ ],
+ 'Cache.match with ignoreSearch should ignore the ' +
+ 'search parameters of cached request.');
+ });
+ },
+ 'Cache.match with ignoreSearch option (request with no search ' +
+ 'parameters)');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.match(entries.a_with_query.request,
+ {ignoreSearch: true})
+ .then(function(result) {
+ assert_response_in_array(
+ result,
+ [
+ entries.a.response,
+ entries.a_with_query.response
+ ],
+ 'Cache.match with ignoreSearch should ignore the ' +
+ 'search parameters of request.');
+ });
+ },
+ 'Cache.match with ignoreSearch option (request with search parameter)');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.match(entries.cat.request.url + '#mouse')
+ .then(function(result) {
+ assert_response_equals(result, entries.cat.response,
+ 'Cache.match should ignore URL fragment.');
+ });
+ }, 'Cache.match with URL containing fragment');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.match('http')
+ .then(function(result) {
+ assert_equals(
+ result, undefined,
+ 'Cache.match should treat query as a URL and not ' +
+ 'just a string fragment.');
+ });
+ }, 'Cache.match with string fragment "http" as query');
+
+prepopulated_cache_test(vary_entries, function(cache, entries) {
+ return cache.match('http://example.com/c')
+ .then(function(result) {
+ assert_response_in_array(
+ result,
+ [
+ entries.vary_cookie_absent.response
+ ],
+ 'Cache.match should honor "Vary" header.');
+ });
+ }, 'Cache.match with responses containing "Vary" header');
+
+cache_test(function(cache) {
+ var request = new Request('http://example.com');
+ var response;
+ var request_url = new URL('../resources/simple.txt', location.href).href;
+ return fetch(request_url)
+ .then(function(fetch_result) {
+ response = fetch_result;
+ assert_equals(
+ response.url, request_url,
+ '[https://fetch.spec.whatwg.org/#dom-response-url] ' +
+ 'Reponse.url should return the URL of the response.');
+ return cache.put(request, response.clone());
+ })
+ .then(function() {
+ return cache.match(request.url);
+ })
+ .then(function(result) {
+ assert_response_equals(
+ result, response,
+ 'Cache.match should return a Response object that has the same ' +
+ 'properties as the stored response.');
+ return cache.match(response.url);
+ })
+ .then(function(result) {
+ assert_equals(
+ result, undefined,
+ 'Cache.match should not match cache entry based on response URL.');
+ });
+ }, 'Cache.match with Request and Response objects with different URLs');
+
+cache_test(function(cache) {
+ var request_url = new URL('../resources/simple.txt', location.href).href;
+ return fetch(request_url)
+ .then(function(fetch_result) {
+ return cache.put(new Request(request_url), fetch_result);
+ })
+ .then(function() {
+ return cache.match(request_url);
+ })
+ .then(function(result) {
+ return result.text();
+ })
+ .then(function(body_text) {
+ assert_equals(body_text, 'a simple text file\n',
+ 'Cache.match should return a Response object with a ' +
+ 'valid body.');
+ })
+ .then(function() {
+ return cache.match(request_url);
+ })
+ .then(function(result) {
+ return result.text();
+ })
+ .then(function(body_text) {
+ assert_equals(body_text, 'a simple text file\n',
+ 'Cache.match should return a Response object with a ' +
+ 'valid body each time it is called.');
+ });
+ }, 'Cache.match invoked multiple times for the same Request/Response');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ var request = new Request(entries.a.request.clone(), {method: 'POST'});
+ return cache.match(request)
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.match should not find a match');
+ });
+ }, 'Cache.match with POST Request');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ var response = entries.non_2xx_response.response;
+ return cache.match(entries.non_2xx_response.request.url)
+ .then(function(result) {
+ assert_response_equals(
+ result, entries.non_2xx_response.response,
+ 'Cache.match should return a Response object that has the ' +
+ 'same properties as a stored non-2xx response.');
+ });
+ }, 'Cache.match with a non-2xx Response');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ var response = entries.error_response.response;
+ return cache.match(entries.error_response.request.url)
+ .then(function(result) {
+ assert_response_equals(
+ result, entries.error_response.response,
+ 'Cache.match should return a Response object that has the ' +
+ 'same properties as a stored network error response.');
+ });
+ }, 'Cache.match with a network error Response');
+
+done();
diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-matchAll.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-matchAll.js
new file mode 100644
index 000000000..2bc661a5c
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-matchAll.js
@@ -0,0 +1,154 @@
+if (self.importScripts) {
+ importScripts('/resources/testharness.js');
+ importScripts('../resources/testharness-helpers.js');
+ importScripts('../resources/test-helpers.js');
+}
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.matchAll('not-present-in-the-cache')
+ .then(function(result) {
+ assert_response_array_equivalent(
+ result, [],
+ 'Cache.matchAll should resolve with an empty array on failure.');
+ });
+ }, 'Cache.matchAll with no matching entries');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.matchAll(entries.a.request.url)
+ .then(function(result) {
+ assert_response_array_equals(result, [entries.a.response],
+ 'Cache.matchAll should match by URL.');
+ });
+ }, 'Cache.matchAll with URL');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.matchAll(entries.a.request)
+ .then(function(result) {
+ assert_response_array_equals(
+ result, [entries.a.response],
+ 'Cache.matchAll should match by Request.');
+ });
+ }, 'Cache.matchAll with Request');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.matchAll(new Request(entries.a.request.url))
+ .then(function(result) {
+ assert_response_array_equals(
+ result, [entries.a.response],
+ 'Cache.matchAll should match by Request.');
+ });
+ }, 'Cache.matchAll with new Request');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.matchAll(entries.a.request,
+ {ignoreSearch: true})
+ .then(function(result) {
+ assert_response_array_equivalent(
+ result,
+ [
+ entries.a.response,
+ entries.a_with_query.response
+ ],
+ 'Cache.matchAll with ignoreSearch should ignore the ' +
+ 'search parameters of cached request.');
+ });
+ },
+ 'Cache.matchAll with ignoreSearch option (request with no search ' +
+ 'parameters)');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.matchAll(entries.a_with_query.request,
+ {ignoreSearch: true})
+ .then(function(result) {
+ assert_response_array_equivalent(
+ result,
+ [
+ entries.a.response,
+ entries.a_with_query.response
+ ],
+ 'Cache.matchAll with ignoreSearch should ignore the ' +
+ 'search parameters of request.');
+ });
+ },
+ 'Cache.matchAll with ignoreSearch option (request with search parameter)');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.matchAll(entries.cat.request.url + '#mouse')
+ .then(function(result) {
+ assert_response_array_equivalent(
+ result,
+ [
+ entries.cat.response,
+ ],
+ 'Cache.matchAll should ignore URL fragment.');
+ });
+ }, 'Cache.matchAll with URL containing fragment');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.matchAll('http')
+ .then(function(result) {
+ assert_response_array_equivalent(
+ result, [],
+ 'Cache.matchAll should treat query as a URL and not ' +
+ 'just a string fragment.');
+ });
+ }, 'Cache.matchAll with string fragment "http" as query');
+
+prepopulated_cache_test(vary_entries, function(cache, entries) {
+ return cache.matchAll('http://example.com/c')
+ .then(function(result) {
+ assert_response_array_equivalent(
+ result,
+ [
+ entries.vary_cookie_absent.response
+ ],
+ 'Cache.matchAll should exclude matches if a vary header is ' +
+ 'missing in the query request, but is present in the cached ' +
+ 'request.');
+ })
+
+ .then(function() {
+ return cache.matchAll(
+ new Request('http://example.com/c',
+ {headers: {'Cookies': 'none-of-the-above'}}));
+ })
+ .then(function(result) {
+ assert_response_array_equivalent(
+ result,
+ [
+ ],
+ 'Cache.matchAll should exclude matches if a vary header is ' +
+ 'missing in the cached request, but is present in the query ' +
+ 'request.');
+ })
+
+ .then(function() {
+ return cache.matchAll(
+ new Request('http://example.com/c',
+ {headers: {'Cookies': 'is-for-cookie'}}));
+ })
+ .then(function(result) {
+ assert_response_array_equivalent(
+ result,
+ [entries.vary_cookie_is_cookie.response],
+ 'Cache.matchAll should match the entire header if a vary header ' +
+ 'is present in both the query and cached requests.');
+ });
+ }, 'Cache.matchAll with responses containing "Vary" header');
+
+prepopulated_cache_test(vary_entries, function(cache, entries) {
+ return cache.matchAll('http://example.com/c',
+ {ignoreVary: true})
+ .then(function(result) {
+ assert_response_array_equivalent(
+ result,
+ [
+ entries.vary_cookie_is_cookie.response,
+ entries.vary_cookie_is_good.response,
+ entries.vary_cookie_absent.response
+ ],
+ 'Cache.matchAll should honor "ignoreVary" parameter.');
+ });
+ }, 'Cache.matchAll with "ignoreVary" parameter');
+
+done();
diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-put.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-put.js
new file mode 100644
index 000000000..bcc8541ad
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-put.js
@@ -0,0 +1,305 @@
+if (self.importScripts) {
+ importScripts('/resources/testharness.js');
+ importScripts('../resources/testharness-helpers.js');
+ importScripts('../resources/test-helpers.js');
+}
+
+var test_url = 'https://example.com/foo';
+var test_body = 'Hello world!';
+
+cache_test(function(cache) {
+ var request = new Request(test_url);
+ var response = new Response(test_body);
+ return cache.put(request, response)
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.put should resolve with undefined on success.');
+ });
+ }, 'Cache.put called with simple Request and Response');
+
+cache_test(function(cache) {
+ var test_url = new URL('../resources/simple.txt', location.href).href;
+ var request = new Request(test_url);
+ var response;
+ return fetch(test_url)
+ .then(function(fetch_result) {
+ response = fetch_result.clone();
+ return cache.put(request, fetch_result);
+ })
+ .then(function() {
+ return cache.match(test_url);
+ })
+ .then(function(result) {
+ assert_response_equals(result, response,
+ 'Cache.put should update the cache with ' +
+ 'new request and response.');
+ return result.text();
+ })
+ .then(function(body) {
+ assert_equals(body, 'a simple text file\n',
+ 'Cache.put should store response body.');
+ });
+ }, 'Cache.put called with Request and Response from fetch()');
+
+cache_test(function(cache) {
+ var request = new Request(test_url);
+ var response = new Response(test_body);
+ assert_false(request.bodyUsed,
+ '[https://fetch.spec.whatwg.org/#dom-body-bodyused] ' +
+ 'Request.bodyUsed should be initially false.');
+ return cache.put(request, response)
+ .then(function() {
+ assert_false(request.bodyUsed,
+ 'Cache.put should not mark empty request\'s body used');
+ });
+ }, 'Cache.put with Request without a body');
+
+cache_test(function(cache) {
+ var request = new Request(test_url);
+ var response = new Response();
+ assert_false(response.bodyUsed,
+ '[https://fetch.spec.whatwg.org/#dom-body-bodyused] ' +
+ 'Response.bodyUsed should be initially false.');
+ return cache.put(request, response)
+ .then(function() {
+ assert_false(response.bodyUsed,
+ 'Cache.put should not mark empty response\'s body used');
+ });
+ }, 'Cache.put with Response without a body');
+
+cache_test(function(cache) {
+ var request = new Request(test_url);
+ var response = new Response(test_body);
+ return cache.put(request, response.clone())
+ .then(function() {
+ return cache.match(test_url);
+ })
+ .then(function(result) {
+ assert_response_equals(result, response,
+ 'Cache.put should update the cache with ' +
+ 'new Request and Response.');
+ });
+ }, 'Cache.put with a Response containing an empty URL');
+
+cache_test(function(cache) {
+ var request = new Request(test_url);
+ var response = new Response('', {
+ status: 200,
+ headers: [['Content-Type', 'text/plain']]
+ });
+ return cache.put(request, response)
+ .then(function() {
+ return cache.match(test_url);
+ })
+ .then(function(result) {
+ assert_equals(result.status, 200, 'Cache.put should store status.');
+ assert_equals(result.headers.get('Content-Type'), 'text/plain',
+ 'Cache.put should store headers.');
+ return result.text();
+ })
+ .then(function(body) {
+ assert_equals(body, '',
+ 'Cache.put should store response body.');
+ });
+ }, 'Cache.put with an empty response body');
+
+cache_test(function(cache) {
+ var test_url = new URL('../resources/fetch-status.py?status=500', location.href).href;
+ var request = new Request(test_url);
+ var response;
+ return fetch(test_url)
+ .then(function(fetch_result) {
+ assert_equals(fetch_result.status, 500,
+ 'Test framework error: The status code should be 500.');
+ response = fetch_result.clone();
+ return cache.put(request, fetch_result);
+ })
+ .then(function() {
+ return cache.match(test_url);
+ })
+ .then(function(result) {
+ assert_response_equals(result, response,
+ 'Cache.put should update the cache with ' +
+ 'new request and response.');
+ return result.text();
+ })
+ .then(function(body) {
+ assert_equals(body, '',
+ 'Cache.put should store response body.');
+ });
+ }, 'Cache.put with HTTP 500 response');
+
+cache_test(function(cache) {
+ var alternate_response_body = 'New body';
+ var alternate_response = new Response(alternate_response_body,
+ { statusText: 'New status' });
+ return cache.put(new Request(test_url),
+ new Response('Old body', { statusText: 'Old status' }))
+ .then(function() {
+ return cache.put(new Request(test_url), alternate_response.clone());
+ })
+ .then(function() {
+ return cache.match(test_url);
+ })
+ .then(function(result) {
+ assert_response_equals(result, alternate_response,
+ 'Cache.put should replace existing ' +
+ 'response with new response.');
+ return result.text();
+ })
+ .then(function(body) {
+ assert_equals(body, alternate_response_body,
+ 'Cache put should store new response body.');
+ });
+ }, 'Cache.put called twice with matching Requests and different Responses');
+
+cache_test(function(cache) {
+ var first_url = test_url;
+ var second_url = first_url + '#(O_o)';
+ var alternate_response_body = 'New body';
+ var alternate_response = new Response(alternate_response_body,
+ { statusText: 'New status' });
+ return cache.put(new Request(first_url),
+ new Response('Old body', { statusText: 'Old status' }))
+ .then(function() {
+ return cache.put(new Request(second_url), alternate_response.clone());
+ })
+ .then(function() {
+ return cache.match(test_url);
+ })
+ .then(function(result) {
+ assert_response_equals(result, alternate_response,
+ 'Cache.put should replace existing ' +
+ 'response with new response.');
+ return result.text();
+ })
+ .then(function(body) {
+ assert_equals(body, alternate_response_body,
+ 'Cache put should store new response body.');
+ });
+ }, 'Cache.put called twice with request URLs that differ only by a fragment');
+
+cache_test(function(cache) {
+ var url = 'http://example.com/foo';
+ return cache.put(url, new Response('some body'))
+ .then(function() { return cache.match(url); })
+ .then(function(response) { return response.text(); })
+ .then(function(body) {
+ assert_equals(body, 'some body',
+ 'Cache.put should accept a string as request.');
+ });
+ }, 'Cache.put with a string request');
+
+cache_test(function(cache) {
+ return assert_promise_rejects(
+ cache.put(new Request(test_url), 'Hello world!'),
+ new TypeError(),
+ 'Cache.put should only accept a Response object as the response.');
+ }, 'Cache.put with an invalid response');
+
+cache_test(function(cache) {
+ return assert_promise_rejects(
+ cache.put(new Request('file:///etc/passwd'),
+ new Response(test_body)),
+ new TypeError(),
+ 'Cache.put should reject non-HTTP/HTTPS requests with a TypeError.');
+ }, 'Cache.put with a non-HTTP/HTTPS request');
+
+cache_test(function(cache) {
+ var response = new Response(test_body);
+ return cache.put(new Request('relative-url'), response.clone())
+ .then(function() {
+ return cache.match(new URL('relative-url', location.href).href);
+ })
+ .then(function(result) {
+ assert_response_equals(result, response,
+ 'Cache.put should accept a relative URL ' +
+ 'as the request.');
+ });
+ }, 'Cache.put with a relative URL');
+
+cache_test(function(cache) {
+ var request = new Request('http://example.com/foo', { method: 'HEAD' });
+ return assert_promise_rejects(
+ cache.put(request, new Response(test_body)),
+ new TypeError(),
+ 'Cache.put should throw a TypeError for non-GET requests.');
+ }, 'Cache.put with a non-GET request');
+
+cache_test(function(cache) {
+ return assert_promise_rejects(
+ cache.put(new Request(test_url), null),
+ new TypeError(),
+ 'Cache.put should throw a TypeError for a null response.');
+ }, 'Cache.put with a null response');
+
+cache_test(function(cache) {
+ var request = new Request(test_url, {method: 'POST', body: test_body});
+ return assert_promise_rejects(
+ cache.put(request, new Response(test_body)),
+ new TypeError(),
+ 'Cache.put should throw a TypeError for a POST request.');
+ }, 'Cache.put with a POST request');
+
+cache_test(function(cache) {
+ var response = new Response(test_body);
+ assert_false(response.bodyUsed,
+ '[https://fetch.spec.whatwg.org/#dom-body-bodyused] ' +
+ 'Response.bodyUsed should be initially false.');
+ return response.text().then(function() {
+ assert_true(
+ response.bodyUsed,
+ '[https://fetch.spec.whatwg.org/#concept-body-consume-body] ' +
+ 'The text() method should make the body disturbed.');
+ var request = new Request(test_url);
+ return cache.put(request, response).then(() => {
+ assert_unreached('cache.put should be rejected');
+ }, () => {});
+ });
+ }, 'Cache.put with a used response body');
+
+cache_test(function(cache) {
+ var response = new Response(test_body);
+ return cache.put(new Request(test_url), response)
+ .then(function() {
+ assert_throws(new TypeError(), () => response.body.getReader());
+ });
+ }, 'getReader() after Cache.put');
+
+cache_test(function(cache) {
+ return assert_promise_rejects(
+ cache.put(new Request(test_url),
+ new Response(test_body, { headers: { VARY: '*' }})),
+ new TypeError(),
+ 'Cache.put should reject VARY:* Responses with a TypeError.');
+ }, 'Cache.put with a VARY:* Response');
+
+cache_test(function(cache) {
+ return assert_promise_rejects(
+ cache.put(new Request(test_url),
+ new Response(test_body,
+ { headers: { VARY: 'Accept-Language,*' }})),
+ new TypeError(),
+ 'Cache.put should reject Responses with an embedded VARY:* with a ' +
+ 'TypeError.');
+ }, 'Cache.put with an embedded VARY:* Response');
+
+cache_test(function(cache) {
+ var url = 'foo.html';
+ var redirectURL = 'http://example.com/foo-bar.html';
+ var redirectResponse = Response.redirect(redirectURL);
+ assert_equals(redirectResponse.headers.get('Location'), redirectURL,
+ 'Response.redirect() should set Location header.');
+ return cache.put(url, redirectResponse.clone())
+ .then(function() {
+ return cache.match(url);
+ })
+ .then(function(response) {
+ assert_response_equals(response, redirectResponse,
+ 'Redirect response is reproduced by the Cache API');
+ assert_equals(response.headers.get('Location'), redirectURL,
+ 'Location header is preserved by Cache API.');
+ });
+ }, 'Cache.put should store Response.redirect() correctly');
+
+done();
diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-keys.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-keys.js
new file mode 100644
index 000000000..4d7bc623f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-keys.js
@@ -0,0 +1,36 @@
+if (self.importScripts) {
+ importScripts('/resources/testharness.js');
+ importScripts('../resources/testharness-helpers.js');
+ importScripts('../resources/test-helpers.js');
+}
+
+var test_cache_list =
+ ['', 'example', 'Another cache name', 'A', 'a', 'ex ample'];
+
+promise_test(function(test) {
+ return self.caches.keys()
+ .then(function(keys) {
+ assert_true(Array.isArray(keys),
+ 'CacheStorage.keys should return an Array.');
+ return Promise.all(keys.map(function(key) {
+ return self.caches.delete(key);
+ }));
+ })
+ .then(function() {
+ return Promise.all(test_cache_list.map(function(key) {
+ return self.caches.open(key);
+ }));
+ })
+
+ .then(function() { return self.caches.keys(); })
+ .then(function(keys) {
+ assert_true(Array.isArray(keys),
+ 'CacheStorage.keys should return an Array.');
+ assert_array_equals(keys,
+ test_cache_list,
+ 'CacheStorage.keys should only return ' +
+ 'existing caches.');
+ });
+ }, 'CacheStorage keys');
+
+done();
diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-match.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-match.js
new file mode 100644
index 000000000..0052e43ca
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-match.js
@@ -0,0 +1,124 @@
+if (self.importScripts) {
+ importScripts('/resources/testharness.js');
+ importScripts('../resources/testharness-helpers.js');
+ importScripts('../resources/test-helpers.js');
+}
+
+(function() {
+ var next_index = 1;
+
+ // Returns a transaction (request, response, and url) for a unique URL.
+ function create_unique_transaction(test) {
+ var uniquifier = String(next_index++);
+ var url = 'http://example.com/' + uniquifier;
+
+ return {
+ request: new Request(url),
+ response: new Response('hello'),
+ url: url
+ };
+ }
+
+ self.create_unique_transaction = create_unique_transaction;
+})();
+
+cache_test(function(cache) {
+ var transaction = create_unique_transaction();
+
+ return cache.put(transaction.request.clone(), transaction.response.clone())
+ .then(function() {
+ return self.caches.match(transaction.request);
+ })
+ .then(function(response) {
+ assert_response_equals(response, transaction.response,
+ 'The response should not have changed.');
+ });
+}, 'CacheStorageMatch with no cache name provided');
+
+cache_test(function(cache) {
+ var transaction = create_unique_transaction();
+
+ var test_cache_list = ['a', 'b', 'c'];
+ return cache.put(transaction.request.clone(), transaction.response.clone())
+ .then(function() {
+ return Promise.all(test_cache_list.map(function(key) {
+ return self.caches.open(key);
+ }));
+ })
+ .then(function() {
+ return self.caches.match(transaction.request);
+ })
+ .then(function(response) {
+ assert_response_equals(response, transaction.response,
+ 'The response should not have changed.');
+ });
+}, 'CacheStorageMatch from one of many caches');
+
+promise_test(function(test) {
+ var transaction = create_unique_transaction();
+
+ var test_cache_list = ['x', 'y', 'z'];
+ return Promise.all(test_cache_list.map(function(key) {
+ return self.caches.open(key);
+ }))
+ .then(function() { return caches.open('x'); })
+ .then(function(cache) {
+ return cache.put(transaction.request.clone(),
+ transaction.response.clone());
+ })
+ .then(function() {
+ return self.caches.match(transaction.request, {cacheName: 'x'});
+ })
+ .then(function(response) {
+ assert_response_equals(response, transaction.response,
+ 'The response should not have changed.');
+ })
+ .then(function() {
+ return self.caches.match(transaction.request, {cacheName: 'y'});
+ })
+ .then(function(response) {
+ assert_equals(response, undefined,
+ 'Cache y should not have a response for the request.');
+ });
+}, 'CacheStorageMatch from one of many caches by name');
+
+cache_test(function(cache) {
+ var transaction = create_unique_transaction();
+ return cache.put(transaction.url, transaction.response.clone())
+ .then(function() {
+ return self.caches.match(transaction.request);
+ })
+ .then(function(response) {
+ assert_response_equals(response, transaction.response,
+ 'The response should not have changed.');
+ });
+}, 'CacheStorageMatch a string request');
+
+promise_test(function(test) {
+ var transaction = create_unique_transaction();
+ return self.caches.match(transaction.request)
+ .then(function(response) {
+ assert_equals(response, undefined,
+ 'The response should not be found.');
+ })
+}, 'CacheStorageMatch with no cached entry');
+
+promise_test(function(test) {
+ var transaction = create_unique_transaction();
+ return self.caches.has('foo')
+ .then(function(has_foo) {
+ assert_false(has_foo, "The cache should not exist.");
+ return self.caches.match(transaction.request, {cacheName: 'foo'});
+ })
+ .then(function(response) {
+ assert_equals(response, undefined,
+ 'The match with bad cache name should resolve to ' +
+ 'undefined.');
+ return self.caches.has('foo');
+ })
+ .then(function(has_foo) {
+ assert_false(has_foo, "The cache should still not exist.");
+ })
+}, 'CacheStorageMatch with no caches available but name provided');
+
+done();
diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage.js
new file mode 100644
index 000000000..594b01b58
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage.js
@@ -0,0 +1,197 @@
+if (self.importScripts) {
+ importScripts('/resources/testharness.js');
+ importScripts('../resources/testharness-helpers.js');
+ importScripts('../resources/test-helpers.js');
+}
+
+promise_test(function(t) {
+ var cache_name = 'cache-storage/foo';
+ return self.caches.delete(cache_name)
+ .then(function() {
+ return self.caches.open(cache_name);
+ })
+ .then(function(cache) {
+ assert_true(cache instanceof Cache,
+ 'CacheStorage.open should return a Cache.');
+ });
+ }, 'CacheStorage.open');
+
+promise_test(function(t) {
+ // Note that this test may collide with other tests running in the same
+ // origin that also uses an empty cache name.
+ var cache_name = '';
+ return self.caches.delete(cache_name)
+ .then(function() {
+ return self.caches.open(cache_name);
+ })
+ .then(function(cache) {
+ assert_true(cache instanceof Cache,
+ 'CacheStorage.open should accept an empty name.');
+ });
+ }, 'CacheStorage.open with an empty name');
+
+promise_test(function(t) {
+ return assert_promise_rejects(
+ self.caches.open(),
+ new TypeError(),
+ 'CacheStorage.open should throw TypeError if called with no arguments.');
+ }, 'CacheStorage.open with no arguments');
+
+promise_test(function(t) {
+ var test_cases = [
+ {
+ name: 'cache-storage/lowercase',
+ should_not_match:
+ [
+ 'cache-storage/Lowercase',
+ ' cache-storage/lowercase',
+ 'cache-storage/lowercase '
+ ]
+ },
+ {
+ name: 'cache-storage/has a space',
+ should_not_match:
+ [
+ 'cache-storage/has'
+ ]
+ },
+ {
+ name: 'cache-storage/has\000_in_the_name',
+ should_not_match:
+ [
+ 'cache-storage/has',
+ 'cache-storage/has_in_the_name'
+ ]
+ }
+ ];
+ return Promise.all(test_cases.map(function(testcase) {
+ var cache_name = testcase.name;
+ return self.caches.delete(cache_name)
+ .then(function() {
+ return self.caches.open(cache_name);
+ })
+ .then(function() {
+ return self.caches.has(cache_name);
+ })
+ .then(function(result) {
+ assert_true(result,
+ 'CacheStorage.has should return true for existing ' +
+ 'cache.');
+ })
+ .then(function() {
+ return Promise.all(
+ testcase.should_not_match.map(function(cache_name) {
+ return self.caches.has(cache_name)
+ .then(function(result) {
+ assert_false(result,
+ 'CacheStorage.has should only perform ' +
+ 'exact matches on cache names.');
+ });
+ }));
+ })
+ .then(function() {
+ return self.caches.delete(cache_name);
+ });
+ }));
+ }, 'CacheStorage.has with existing cache');
+
+promise_test(function(t) {
+ return self.caches.has('cheezburger')
+ .then(function(result) {
+ assert_false(result,
+ 'CacheStorage.has should return false for ' +
+ 'nonexistent cache.');
+ });
+ }, 'CacheStorage.has with nonexistent cache');
+
+promise_test(function(t) {
+ var cache_name = 'cache-storage/open';
+ var cache;
+ return self.caches.delete(cache_name)
+ .then(function() {
+ return self.caches.open(cache_name);
+ })
+ .then(function(result) {
+ cache = result;
+ })
+ .then(function() {
+ return cache.add('../resources/simple.txt');
+ })
+ .then(function() {
+ return self.caches.open(cache_name);
+ })
+ .then(function(result) {
+ assert_true(result instanceof Cache,
+ 'CacheStorage.open should return a Cache object');
+ assert_not_equals(result, cache,
+ 'CacheStorage.open should return a new Cache ' +
+ 'object each time its called.');
+ return Promise.all([cache.keys(), result.keys()]);
+ })
+ .then(function(results) {
+ var expected_urls = results[0].map(function(r) { return r.url });
+ var actual_urls = results[1].map(function(r) { return r.url });
+ assert_array_equals(actual_urls, expected_urls,
+ 'CacheStorage.open should return a new Cache ' +
+ 'object for the same backing store.');
+ });
+ }, 'CacheStorage.open with existing cache');
+
+promise_test(function(t) {
+ var cache_name = 'cache-storage/delete';
+
+ return self.caches.delete(cache_name)
+ .then(function() {
+ return self.caches.open(cache_name);
+ })
+ .then(function() { return self.caches.delete(cache_name); })
+ .then(function(result) {
+ assert_true(result,
+ 'CacheStorage.delete should return true after ' +
+ 'deleting an existing cache.');
+ })
+
+ .then(function() { return self.caches.has(cache_name); })
+ .then(function(cache_exists) {
+ assert_false(cache_exists,
+ 'CacheStorage.has should return false after ' +
+ 'fulfillment of CacheStorage.delete promise.');
+ });
+ }, 'CacheStorage.delete with existing cache');
+
+promise_test(function(t) {
+ return self.caches.delete('cheezburger')
+ .then(function(result) {
+ assert_false(result,
+ 'CacheStorage.delete should return false for a ' +
+ 'nonexistent cache.');
+ });
+ }, 'CacheStorage.delete with nonexistent cache');
+
+promise_test(function(t) {
+ var bad_name = 'unpaired\uD800';
+ var converted_name = 'unpaired\uFFFD'; // Don't create cache with this name.
+ return self.caches.has(converted_name)
+ .then(function(cache_exists) {
+ assert_false(cache_exists,
+ 'Test setup failure: cache should not exist');
+ })
+ .then(function() { return self.caches.open(bad_name); })
+ .then(function() { return self.caches.keys(); })
+ .then(function(keys) {
+ assert_true(keys.indexOf(bad_name) !== -1,
+ 'keys should include cache with bad name');
+ })
+ .then(function() { return self.caches.has(bad_name); })
+ .then(function(cache_exists) {
+ assert_true(cache_exists,
+ 'CacheStorage names should be not be converted.');
+ })
+ .then(function() { return self.caches.has(converted_name); })
+ .then(function(cache_exists) {
+ assert_false(cache_exists,
+ 'CacheStorage names should be not be converted.');
+ });
+ }, 'CacheStorage names are DOMStrings not USVStrings');
+
+done();
diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-add.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-add.https.html
new file mode 100644
index 000000000..232de668e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-add.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Cache.add and Cache.addAll</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-add">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../service-worker/resources/test-helpers.sub.js"></script>
+<script>
+service_worker_test('../script-tests/cache-add.js');
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-delete.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-delete.https.html
new file mode 100644
index 000000000..6ff27be30
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-delete.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Cache.delete</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-delete">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../service-worker/resources/test-helpers.sub.js"></script>
+<script>
+service_worker_test('../script-tests/cache-delete.js');
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-match.https.html
new file mode 100644
index 000000000..81db70943
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-match.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Cache.match</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-match">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../service-worker/resources/test-helpers.sub.js"></script>
+<script>
+service_worker_test('../script-tests/cache-match.js');
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-matchAll.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-matchAll.https.html
new file mode 100644
index 000000000..f1a38ff07
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-matchAll.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Cache.matchAll</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-matchall">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../service-worker/resources/test-helpers.sub.js"></script>
+<script>
+service_worker_test('../script-tests/cache-matchAll.js');
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-put.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-put.https.html
new file mode 100644
index 000000000..961602386
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-put.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Cache.put</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-put">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../service-worker/resources/test-helpers.sub.js"></script>
+<script>
+service_worker_test('../script-tests/cache-put.js');
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-keys.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-keys.https.html
new file mode 100644
index 000000000..adde95eb8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-keys.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>CacheStorage.keys</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../service-worker/resources/test-helpers.sub.js"></script>
+<script>
+service_worker_test('../script-tests/cache-storage-keys.js');
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-match.https.html
new file mode 100644
index 000000000..fef14074d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-match.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>CacheStorage.match</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage-match">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../service-worker/resources/test-helpers.sub.js"></script>
+<script>
+service_worker_test('../script-tests/cache-storage-match.js');
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage.https.html
new file mode 100644
index 000000000..3934258f2
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>CacheStorage</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../service-worker/resources/test-helpers.sub.js"></script>
+<script>
+service_worker_test('../script-tests/cache-storage.js');
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/credentials.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/credentials.html
new file mode 100644
index 000000000..7bc494eb1
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/credentials.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Cache Storage: Verify credentials are respected by Cache operations</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../service-worker/resources/test-helpers.sub.js"></script>
+<style>iframe { display: none; }</style>
+<script>
+
+var worker = "../resources/credentials-worker.js";
+var scope = "../resources/credentials-iframe.html";
+promise_test(function(t) {
+ return self.caches.delete('credentials')
+ .then(function() {
+ return 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);
+ })
+ .then(function(frame) {
+ frame.contentWindow.postMessage([
+ {name: 'file.txt', username: 'aa', password: 'bb'},
+ {name: 'file.txt', username: 'cc', password: 'dd'},
+ {name: 'file.txt'}
+ ], '*');
+ return new Promise(function(resolve, reject) {
+ window.onmessage = t.step_func(function(e) {
+ resolve(e.data);
+ });
+ });
+ })
+ .then(function(data) {
+ assert_equals(data.length, 3, 'three entries should be present');
+ assert_equals(data.filter(function(url) { return /@/.test(url); }).length, 2,
+ 'two entries should contain credentials');
+ assert_true(data.some(function(url) { return /aa:bb@/.test(url); }),
+ 'entry with credentials aa:bb should be present');
+ assert_true(data.some(function(url) { return /cc:dd@/.test(url); }),
+ 'entry with credentials cc:dd should be present');
+ });
+}, "Cache API matching includes credentials");
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-add.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-add.https.html
new file mode 100644
index 000000000..d9945f9ae
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-add.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache Storage: Cache.add and Cache.addAll</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-add">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helpers.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<script src="../script-tests/cache-add.js"></script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-delete.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-delete.https.html
new file mode 100644
index 000000000..735d2cdb4
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-delete.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache Storage: Cache.delete</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-delete">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helpers.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<script src="../script-tests/cache-delete.js"></script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-match.https.html
new file mode 100644
index 000000000..0ff5c6799
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-match.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache Storage: Cache.match</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-match">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helpers.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<script src="../script-tests/cache-match.js"></script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-matchAll.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-matchAll.https.html
new file mode 100644
index 000000000..372e641a5
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-matchAll.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache.matchAll</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-matchall">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helpers.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<script src="../script-tests/cache-matchAll.js"></script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-put.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-put.https.html
new file mode 100644
index 000000000..1d9e02ba6
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-put.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache Storage: Cache.put</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-put">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helpers.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<script src="../script-tests/cache-put.js"></script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-keys.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-keys.https.html
new file mode 100644
index 000000000..b4f94bade
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-keys.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache Storage: CacheStorage.keys</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helpers.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<script src="../script-tests/cache-storage-keys.js"></script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-match.https.html
new file mode 100644
index 000000000..72005f43a
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-match.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache Storage: CacheStorage.match</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage-match">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helpers.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<script src="../script-tests/cache-storage-match.js"></script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage.https.html
new file mode 100644
index 000000000..6c4f4af7f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache Storage: CacheStorage</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helpers.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<script src="../script-tests/cache-storage.js"></script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/sandboxed-iframes.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/sandboxed-iframes.https.html
new file mode 100644
index 000000000..061858521
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/window/sandboxed-iframes.https.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>Cache Storage: Verify access in sandboxed iframes</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helpers.js"></script>
+<script>
+
+function load_iframe(src, sandbox) {
+ return new Promise(function(resolve, reject) {
+ var iframe = document.createElement('iframe');
+ iframe.onload = function() { resolve(iframe); };
+
+ iframe.sandbox = sandbox;
+ iframe.src = src;
+
+ document.documentElement.appendChild(iframe);
+ });
+}
+
+function wait_for_message(id) {
+ return new Promise(function(resolve) {
+ self.addEventListener('message', function listener(e) {
+ if (e.data.id === id) {
+ resolve(e.data);
+ self.removeEventListener('message', listener);
+ }
+ });
+ });
+}
+
+var counter = 0;
+
+promise_test(function(t) {
+ return load_iframe('../resources/iframe.html',
+ 'allow-scripts allow-same-origin')
+ .then(function(iframe) {
+ var id = ++counter;
+ iframe.contentWindow.postMessage({id: id}, '*');
+ return wait_for_message(id);
+ })
+ .then(function(message) {
+ assert_equals(
+ message.result, 'allowed',
+ 'Access should be allowed if sandbox has allow-same-origin');
+ });
+}, 'Sandboxed iframe with allow-same-origin is allowed access');
+
+promise_test(function(t) {
+ return load_iframe('../resources/iframe.html',
+ 'allow-scripts')
+ .then(function(iframe) {
+ var id = ++counter;
+ iframe.contentWindow.postMessage({id: id}, '*');
+ return wait_for_message(id);
+ })
+ .then(function(message) {
+ assert_equals(
+ message.result, 'denied',
+ 'Access should be denied if sandbox lacks allow-same-origin');
+ assert_equals(message.name, 'SecurityError',
+ 'Failure should be a SecurityError');
+ });
+}, 'Sandboxed iframe without allow-same-origin is denied access');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-add.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-add.https.html
new file mode 100644
index 000000000..2658e1e50
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-add.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache.add and Cache.addAll</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-add">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker('../script-tests/cache-add.js'));
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-delete.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-delete.https.html
new file mode 100644
index 000000000..3d63a2f7f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-delete.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache.delete</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-delete">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker('../script-tests/cache-delete.js'));
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-match.https.html
new file mode 100644
index 000000000..479a29d1e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-match.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache.match</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-match">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker('../script-tests/cache-match.js'));
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-matchAll.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-matchAll.https.html
new file mode 100644
index 000000000..c7e893a23
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-matchAll.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache.matchAll</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-matchall">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker('../script-tests/cache-matchAll.js'));
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-put.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-put.https.html
new file mode 100644
index 000000000..20aeb2351
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-put.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache.put</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-put">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker('../script-tests/cache-put.js'));
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-keys.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-keys.https.html
new file mode 100644
index 000000000..71d995bc9
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-keys.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>CacheStorage.keys</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker('../script-tests/cache-storage-keys.js'));
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-match.https.html
new file mode 100644
index 000000000..cd93410d2
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-match.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>CacheStorage.match</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage-match">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker('../script-tests/cache-storage-match.js'));
+</script>
diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage.https.html
new file mode 100644
index 000000000..0899609e4
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>CacheStorage</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker('../script-tests/cache-storage.js'));
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html
new file mode 100644
index 000000000..f0dad6a7b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>ServiceWorkerGlobalScope: registration</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../resources/test-helpers.sub.js'></script>
+<script>
+
+promise_test(function(t) {
+ var script = 'resources/registration-attribute-worker.js';
+ var scope = 'resources/scope/registration-attribute';
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(frame) {
+ var expected_events_seen = [
+ 'updatefound',
+ 'install',
+ 'statechange(installed)',
+ 'statechange(activating)',
+ 'activate',
+ 'statechange(activated)',
+ 'fetch',
+ ];
+
+ assert_equals(
+ frame.contentDocument.body.textContent,
+ expected_events_seen.toString(),
+ 'Service Worker should respond to fetch');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Verify registration attribute on ServiceWorkerGlobalScope');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-worker.js b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-worker.js
new file mode 100644
index 000000000..c98acbcfb
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-worker.js
@@ -0,0 +1,132 @@
+importScripts('../../resources/test-helpers.sub.js');
+importScripts('../../resources/worker-testharness.js');
+
+var events_seen = [];
+
+assert_equals(
+ self.registration.scope,
+ normalizeURL('scope/registration-attribute'),
+ 'On worker script evaluation, registration attribute should be set');
+assert_equals(
+ self.registration.installing,
+ null,
+ 'On worker script evaluation, installing worker should be null');
+assert_equals(
+ self.registration.waiting,
+ null,
+ 'On worker script evaluation, waiting worker should be null');
+assert_equals(
+ self.registration.active,
+ null,
+ 'On worker script evaluation, active worker should be null');
+
+self.registration.addEventListener('updatefound', function() {
+ events_seen.push('updatefound');
+
+ assert_equals(
+ self.registration.scope,
+ normalizeURL('scope/registration-attribute'),
+ 'On updatefound event, registration attribute should be set');
+ assert_equals(
+ self.registration.installing.scriptURL,
+ normalizeURL('registration-attribute-worker.js'),
+ 'On updatefound event, installing worker should be set');
+ assert_equals(
+ self.registration.waiting,
+ null,
+ 'On updatefound event, waiting worker should be null');
+ assert_equals(
+ self.registration.active,
+ null,
+ 'On updatefound event, active worker should be null');
+
+ assert_equals(
+ self.registration.installing.state,
+ 'installing',
+ 'On updatefound event, worker should be in the installing state');
+
+ var worker = self.registration.installing;
+ self.registration.installing.addEventListener('statechange', function() {
+ events_seen.push('statechange(' + worker.state + ')');
+ });
+ });
+
+self.addEventListener('install', function(e) {
+ events_seen.push('install');
+
+ assert_equals(
+ self.registration.scope,
+ normalizeURL('scope/registration-attribute'),
+ 'On install event, registration attribute should be set');
+ assert_equals(
+ self.registration.installing.scriptURL,
+ normalizeURL('registration-attribute-worker.js'),
+ 'On install event, installing worker should be set');
+ assert_equals(
+ self.registration.waiting,
+ null,
+ 'On install event, waiting worker should be null');
+ assert_equals(
+ self.registration.active,
+ null,
+ 'On install event, active worker should be null');
+
+ assert_equals(
+ self.registration.installing.state,
+ 'installing',
+ 'On install event, worker should be in the installing state');
+ });
+
+self.addEventListener('activate', function(e) {
+ events_seen.push('activate');
+
+ assert_equals(
+ self.registration.scope,
+ normalizeURL('scope/registration-attribute'),
+ 'On activate event, registration attribute should be set');
+ assert_equals(
+ self.registration.installing,
+ null,
+ 'On activate event, installing worker should be null');
+ assert_equals(
+ self.registration.waiting,
+ null,
+ 'On activate event, waiting worker should be null');
+ assert_equals(
+ self.registration.active.scriptURL,
+ normalizeURL('registration-attribute-worker.js'),
+ 'On activate event, active worker should be set');
+
+ assert_equals(
+ self.registration.active.state,
+ 'activating',
+ 'On activate event, worker should be in the activating state');
+ });
+
+self.addEventListener('fetch', function(e) {
+ events_seen.push('fetch');
+
+ assert_equals(
+ self.registration.scope,
+ normalizeURL('scope/registration-attribute'),
+ 'On fetch event, registration attribute should be set');
+ assert_equals(
+ self.registration.installing,
+ null,
+ 'On fetch event, installing worker should be null');
+ assert_equals(
+ self.registration.waiting,
+ null,
+ 'On fetch event, waiting worker should be null');
+ assert_equals(
+ self.registration.active.scriptURL,
+ normalizeURL('registration-attribute-worker.js'),
+ 'On fetch event, active worker should be set');
+
+ assert_equals(
+ self.registration.active.state,
+ 'activated',
+ 'On fetch event, worker should be in the activated state');
+
+ e.respondWith(new Response(events_seen));
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-controlling-worker.html b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-controlling-worker.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-controlling-worker.html
diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-worker.js b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-worker.js
new file mode 100644
index 000000000..6cee53654
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-worker.js
@@ -0,0 +1,23 @@
+function matchQuery(query) {
+ return self.location.href.indexOf(query) != -1;
+}
+
+if (matchQuery('?evaluation'))
+ self.registration.unregister();
+
+self.addEventListener('install', function(e) {
+ if (matchQuery('?install'))
+ self.registration.unregister();
+ });
+
+self.addEventListener('activate', function(e) {
+ if (matchQuery('?activate'))
+ self.registration.unregister();
+ });
+
+self.addEventListener('message', function(e) {
+ self.registration.unregister()
+ .then(function(result) {
+ e.data.port.postMessage({result: result});
+ });
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js
new file mode 100644
index 000000000..63c4534b7
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js
@@ -0,0 +1,25 @@
+importScripts('../../resources/test-helpers.sub.js');
+importScripts('../../resources/worker-testharness.js');
+
+var events_seen = [];
+
+self.registration.addEventListener('updatefound', function() {
+ events_seen.push('updatefound');
+ });
+
+self.addEventListener('activate', function(e) {
+ events_seen.push('activate');
+ });
+
+self.addEventListener('fetch', function(e) {
+ events_seen.push('fetch');
+ e.respondWith(new Response(events_seen));
+ });
+
+self.addEventListener('message', function(e) {
+ events_seen.push('message');
+ self.registration.update();
+ });
+
+// update() during the script evaluation should be ignored.
+self.registration.update();
diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py
new file mode 100644
index 000000000..5158bf251
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py
@@ -0,0 +1,14 @@
+import os
+import time
+
+def main(request, response):
+ # update() does not bypass cache so set the max-age to 0 such that update()
+ # can find a new version in the network.
+ headers = [('Cache-Control', 'max-age: 0'),
+ ('Content-Type', 'application/javascript')]
+ with open(os.path.join(os.path.dirname(__file__),
+ 'update-worker.js'), 'r') as file:
+ script = file.read()
+ # Return a different script for each access.
+ return headers, '// %s\n%s' % (time.time(), script)
+
diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/unregister.https.html b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/unregister.https.html
new file mode 100644
index 000000000..313309188
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/unregister.https.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<title>ServiceWorkerGlobalScope: unregister</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../resources/test-helpers.sub.js'></script>
+<script>
+
+promise_test(function(t) {
+ var script = 'resources/unregister-worker.js?evaluation';
+ var scope = 'resources/scope/unregister-on-script-evaluation';
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'redundant');
+ })
+ .then(function() {
+ return navigator.serviceWorker.getRegistration(scope);
+ })
+ .then(function(result) {
+ assert_equals(
+ result,
+ undefined,
+ 'After unregister(), the registration should not found');
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Unregister on script evaluation');
+
+promise_test(function(t) {
+ var script = 'resources/unregister-worker.js?install';
+ var scope = 'resources/scope/unregister-on-install-event';
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'redundant');
+ })
+ .then(function() {
+ return navigator.serviceWorker.getRegistration(scope);
+ })
+ .then(function(result) {
+ assert_equals(
+ result,
+ undefined,
+ 'After unregister(), the registration should not found');
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Unregister on installing event');
+
+promise_test(function(t) {
+ var script = 'resources/unregister-worker.js?activate';
+ var scope = 'resources/scope/unregister-on-activate-event';
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'redundant');
+ })
+ .then(function() {
+ return navigator.serviceWorker.getRegistration(scope);
+ })
+ .then(function(result) {
+ assert_equals(
+ result,
+ undefined,
+ 'After unregister(), the registration should not found');
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Unregister on activate event');
+
+promise_test(function(t) {
+ var script = 'resources/unregister-worker.js';
+ var scope = 'resources/unregister-controlling-worker.html';
+
+ var controller;
+ var frame;
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame = f;
+ controller = frame.contentWindow.navigator.serviceWorker.controller;
+
+ assert_equals(
+ controller.scriptURL,
+ normalizeURL(script),
+ 'Service worker should control a new document')
+
+ // Wait for the completion of unregister() on the worker.
+ var channel = new MessageChannel();
+ var promise = new Promise(function(resolve) {
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_true(e.data.result,
+ 'unregister() should successfully finish');
+ resolve();
+ });
+ });
+ controller.postMessage({port: channel.port2}, [channel.port2]);
+ return promise;
+ })
+ .then(function() {
+ return navigator.serviceWorker.getRegistration(scope);
+ })
+ .then(function(result) {
+ assert_equals(
+ result,
+ undefined,
+ 'After unregister(), the registration should not found');
+ assert_equals(
+ frame.contentWindow.navigator.serviceWorker.controller,
+ controller,
+ 'After unregister(), the worker should still control the document');
+ return with_iframe(scope);
+ })
+ .then(function(new_frame) {
+ assert_equals(
+ new_frame.contentWindow.navigator.serviceWorker.controller,
+ null,
+ 'After unregister(), the worker should not control a new document');
+
+ frame.remove();
+ new_frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Unregister controlling service worker');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html
new file mode 100644
index 000000000..a9285a1c9
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>ServiceWorkerGlobalScope: update</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../resources/test-helpers.sub.js'></script>
+<script>
+
+promise_test(function(t) {
+ var script = 'resources/update-worker.py';
+ var scope = 'resources/scope/update';
+ var registration;
+ var frame1;
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame1 = f;
+ registration.active.postMessage('update');
+ return wait_for_update(t, registration);
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(frame2) {
+ var expected_events_seen = [
+ 'updatefound', // by register().
+ 'activate',
+ 'fetch',
+ 'message',
+ 'updatefound', // by update() in the message handler.
+ 'fetch',
+ ];
+ assert_equals(
+ frame2.contentDocument.body.textContent,
+ expected_events_seen.toString(),
+ 'events seen by the worker');
+ frame1.remove();
+ frame2.remove();
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Update a registration on ServiceWorkerGlobalScope');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html b/testing/web-platform/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html
new file mode 100644
index 000000000..57fccf137
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Service Worker: registration events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/blank.html';
+ var registration;
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ var sw = registration.installing;
+
+ return new Promise(t.step_func(function(resolve) {
+ sw.onstatechange = t.step_func(function() {
+ if (sw.state === 'installed') {
+ assert_equals(registration.active, null,
+ 'installed event should be fired before activating service worker');
+ resolve();
+ }
+ });
+ }));
+ })
+ .then(function() {
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'installed event should be fired before activating service worker');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/activation-after-registration.https.html b/testing/web-platform/tests/service-workers/service-worker/activation-after-registration.https.html
new file mode 100644
index 000000000..ff0990df6
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/activation-after-registration.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Service Worker: Activation occurs after registration</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+var t = async_test('activation occurs after registration');
+t.step(function() {
+ var scope = 'resources/blank.html';
+ var registration;
+
+ service_worker_unregister_and_register(
+ t, 'resources/empty-worker.js', scope)
+ .then(function(r) {
+ registration = r;
+ assert_equals(
+ r.installing.state,
+ 'installing',
+ 'worker should be in the "installing" state upon registration');
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/activation.https.html b/testing/web-platform/tests/service-workers/service-worker/activation.https.html
new file mode 100644
index 000000000..75fdff813
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/activation.https.html
@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>service worker: activation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+// Registers, waits for activation, then unregisters on a dummy scope.
+//
+// This helper can be used in tests that assert that activation doesn't happen.
+// It would not be sufficient to check the .waiting/.active properties once,
+// since activation could be scheduled and just hasn't happened yet. Since this
+// helper shows that activation of another registration completed, we can be
+// sure that activation really will not happen.
+function wait_for_activation_on_dummy_scope(t) {
+ var dummy_scope = 'resources/there/is/no/there/there';
+ var registration;
+ return navigator.serviceWorker.register('resources/empty-worker.js',
+ { scope: dummy_scope })
+ .then(r => {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(() => registration.unregister());
+}
+// Returns {registration, iframe}, where |registration| has an active and
+// waiting worker. The active worker controls |iframe| and has an inflight
+// message event that can be finished by calling
+// |registration.active.postMessage('go')|.
+function setup_activation_test(t, scope, worker_url) {
+ var registration;
+ var iframe;
+ return navigator.serviceWorker.getRegistration(scope)
+ .then(r => {
+ if (r)
+ return r.unregister();
+ })
+ .then(() => {
+ // Create an in-scope iframe. Do this prior to registration to avoid
+ // racing between an update triggered by navigation and the update()
+ // call below.
+ return with_iframe(scope);
+ })
+ .then(f => {
+ iframe = f;
+ // Create an active worker.
+ return navigator.serviceWorker.register(worker_url, { scope: scope });
+ })
+ .then(r => {
+ registration = r;
+ add_result_callback(() => registration.unregister());
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(() => {
+ // Check that the frame was claimed.
+ assert_not_equals(
+ iframe.contentWindow.navigator.serviceWorker.controller, null);
+ // Create an in-flight request.
+ registration.active.postMessage('wait');
+ // Now there is both a controllee and an in-flight request.
+ // Initiate an update.
+ return registration.update();
+ })
+ .then(() => {
+ // Wait for a waiting worker.
+ return wait_for_state(t, registration.installing, 'installed');
+ })
+ .then(() => {
+ return wait_for_activation_on_dummy_scope(t);
+ })
+ .then(() => {
+ assert_not_equals(registration.waiting, null);
+ assert_not_equals(registration.active, null);
+ return Promise.resolve({registration: registration, iframe: iframe});
+ });
+}
+promise_test(t => {
+ var scope = 'resources/no-controllee';
+ var worker_url = 'resources/mint-new-worker.py';
+ var registration;
+ var iframe;
+ var new_worker;
+ return setup_activation_test(t, scope, worker_url)
+ .then(result => {
+ registration = result.registration;
+ iframe = result.iframe;
+ // Finish the in-flight request.
+ registration.active.postMessage('go');
+ return wait_for_activation_on_dummy_scope(t);
+ })
+ .then(() => {
+ // The new worker is still waiting. Remove the frame and it should
+ // activate.
+ new_worker = registration.waiting;
+ assert_equals(new_worker.state, 'installed');
+ var reached_active = wait_for_state(t, new_worker, 'activating');
+ iframe.remove();
+ return reached_active;
+ })
+ .then(() => {
+ assert_equals(new_worker, registration.active);
+ });
+ }, 'loss of controllees triggers activation');
+promise_test(t => {
+ var scope = 'resources/no-request';
+ var worker_url = 'resources/mint-new-worker.py';
+ var registration;
+ var iframe;
+ var new_worker;
+ return setup_activation_test(t, scope, worker_url)
+ .then(result => {
+ registration = result.registration;
+ iframe = result.iframe;
+ // Remove the iframe.
+ iframe.remove();
+ return new Promise(resolve => setTimeout(resolve, 0));
+ })
+ .then(() => {
+ // Finish the request.
+ new_worker = registration.waiting;
+ var reached_active = wait_for_state(t, new_worker, 'activating');
+ registration.active.postMessage('go');
+ return reached_active;
+ })
+ .then(() => {
+ assert_equals(registration.active, new_worker);
+ });
+ }, 'finishing a request triggers activation');
+promise_test(t => {
+ var scope = 'resources/skip-waiting';
+ var worker_url = 'resources/mint-new-worker.py?skip-waiting';
+ var registration;
+ var new_worker;
+ return setup_activation_test(t, scope, worker_url)
+ .then(result => {
+ registration = result.registration;
+ // Finish the request. The iframe does not need to be removed because
+ // skipWaiting() was called.
+ new_worker = registration.waiting;
+ var reached_active = wait_for_state(t, new_worker, 'activating');
+ registration.active.postMessage('go');
+ return reached_active;
+ })
+ .then(() => {
+ assert_equals(registration.active, new_worker);
+ });
+ }, 'skipWaiting bypasses no controllee requirement');
+
+// This test is not really about activation, but otherwise is very
+// similar to the other tests here.
+promise_test(t => {
+ var scope = 'resources/unregister';
+ var worker_url = 'resources/mint-new-worker.py';
+ var registration;
+ var iframe;
+ var new_worker;
+ return setup_activation_test(t, scope, worker_url)
+ .then(result => {
+ registration = result.registration;
+ iframe = result.iframe;
+ // Remove the iframe.
+ iframe.remove();
+ return registration.unregister();
+ })
+ .then(() => {
+ // The unregister operation should wait for the active worker to
+ // finish processing its events before clearing the registration.
+ new_worker = registration.waiting;
+ var reached_redundant = wait_for_state(t, new_worker, 'redundant');
+ registration.active.postMessage('go');
+ return reached_redundant;
+ })
+ .then(() => {
+ assert_equals(registration.installing, null);
+ assert_equals(registration.waiting, null);
+ assert_equals(registration.active, null);
+ });
+ }, 'finishing a request triggers unregister');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/active.https.html b/testing/web-platform/tests/service-workers/service-worker/active.https.html
new file mode 100644
index 000000000..deee6a50e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/active.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>ServiceWorker: navigator.serviceWorker.active</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+// "active" is set
+async_test(function(t) {
+ var step = t.step_func.bind(t);
+ var url = 'resources/empty-worker.js';
+ var scope = 'resources/blank.html';
+ var frame;
+ var registration;
+
+ service_worker_unregister(t, scope)
+ .then(step(function() { return with_iframe(scope); }))
+ .then(step(function(f) {
+ frame = f;
+ return navigator.serviceWorker.register(url, {scope: scope});
+ }))
+ .then(step(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activating');
+ }))
+ .then(step(function() {
+ var container = frame.contentWindow.navigator.serviceWorker;
+ assert_equals(
+ container.controller,
+ null,
+ 'On activating state a document should not have a controller');
+ assert_equals(
+ registration.active.scriptURL,
+ normalizeURL(url),
+ 'On activating state a document should have an active worker ');
+ assert_equals(
+ registration.waiting,
+ null,
+ 'On activating state a document should not have a waiting worker');
+ assert_equals(
+ registration.installing,
+ null,
+ 'On activating state a document should not have an installing ' +
+ 'worker');
+
+ // FIXME: Add a test for a frame created after installation.
+ // Should the existing frame ("frame") block activation?
+ }))
+ .then(step(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ }))
+ .catch(unreached_rejection(t));
+ }, 'active is set');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/appcache-ordering-main.https.html b/testing/web-platform/tests/service-workers/service-worker/appcache-ordering-main.https.html
new file mode 100644
index 000000000..609d67e45
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/appcache-ordering-main.https.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+var INSTALL_APPCACHE_URL = "resources/appcache-ordering.install.html";
+var IS_APPCACHED_URL = "resources/appcache-ordering.is-appcached.html";
+var SERVICE_WORKER_SCOPE = "resources/appcache-ordering";
+var SERVICE_WORKER_SCRIPT = "resources/empty-worker.js";
+
+var resolve_install_appcache = undefined;
+var reject_install_appcache = undefined;
+
+var frames = [];
+
+// Called by the INSTALL_APPCACHE_URL child frame.
+function notify_appcache_installed(success) {
+ if (success)
+ resolve_install_appcache();
+ else
+ reject_install_appcache();
+}
+
+function install_appcache() {
+ return new Promise(function(resolve, reject) {
+ var frame = document.createElement('iframe');
+ frames.push(frame);
+ frame.src = INSTALL_APPCACHE_URL;
+ document.body.appendChild(frame);
+ resolve_install_appcache = function() {
+ document.body.removeChild(frame);
+ resolve();
+ };
+ reject_install_appcache = function() {
+ document.body.removeChild(frame);
+ reject();
+ };
+ });
+}
+
+var resolve_is_appcached = undefined;
+
+// Called by the IS_APPCACHED_URL child frame.
+function notify_is_appcached(is) {
+ resolve_is_appcached(is);
+}
+
+function is_appcached() {
+ return new Promise(function(resolve) {
+ var frame = document.createElement('iframe');
+ frames.push(frame);
+ frame.src = IS_APPCACHED_URL;
+ document.body.appendChild(frame);
+ resolve_is_appcached = function(is) {
+ document.body.removeChild(frame);
+ resolve(is);
+ };
+ });
+}
+
+async_test(function(t) {
+ service_worker_unregister(t, SERVICE_WORKER_SCOPE)
+ .then(function() {
+ return install_appcache();
+ })
+ .then(function() {
+ return is_appcached();
+ })
+ .then(function(result) {
+ assert_true(result, 'appcache should initially be utilized');
+ return service_worker_unregister_and_register(
+ t, SERVICE_WORKER_SCRIPT, SERVICE_WORKER_SCOPE);
+ })
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return is_appcached();
+ })
+ .then(function(result) {
+ assert_false(result, 'but serviceworkers should take priority');
+ frames.forEach(function(f) { f.remove(); });
+ service_worker_unregister_and_done(t, SERVICE_WORKER_SCOPE);
+ })
+ .catch(unreached_rejection(t));
+ }, 'serviceworkers take priority over appcaches');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/claim-not-using-registration.https.html b/testing/web-platform/tests/service-workers/service-worker/claim-not-using-registration.https.html
new file mode 100644
index 000000000..e18e061cd
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/claim-not-using-registration.https.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<title>Service Worker: claim client not using registration</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+promise_test(function(t) {
+ var init_scope = 'resources/blank.html?not-using-init';
+ var claim_scope = 'resources/blank.html?not-using';
+ var init_worker_url = 'resources/empty.js';
+ var claim_worker_url = 'resources/claim-worker.js';
+ var claim_worker, claim_registration, frame1, frame2;
+ return service_worker_unregister_and_register(
+ t, init_worker_url, init_scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return Promise.all(
+ [with_iframe(init_scope), with_iframe(claim_scope)]);
+ })
+ .then(function(frames) {
+ frame1 = frames[0];
+ frame2 = frames[1];
+ assert_equals(
+ frame1.contentWindow.navigator.serviceWorker.controller.scriptURL,
+ normalizeURL(init_worker_url),
+ 'Frame1 controller should not be null');
+ assert_equals(
+ frame2.contentWindow.navigator.serviceWorker.controller, null,
+ 'Frame2 controller should be null');
+ return navigator.serviceWorker.register(claim_worker_url,
+ {scope: claim_scope});
+ })
+ .then(function(registration) {
+ claim_worker = registration.installing;
+ claim_registration = registration;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ var saw_controllerchanged = new Promise(function(resolve) {
+ frame2.contentWindow.navigator.serviceWorker.oncontrollerchange =
+ function() { resolve(); }
+ });
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data, 'PASS',
+ 'Worker call to claim() should fulfill.');
+ resolve();
+ });
+ });
+ claim_worker.postMessage({port: channel.port2}, [channel.port2]);
+ return Promise.all([saw_controllerchanged, saw_message]);
+ })
+ .then(function() {
+ assert_equals(
+ frame1.contentWindow.navigator.serviceWorker.controller.scriptURL,
+ normalizeURL(init_worker_url),
+ 'Frame1 should not be influenced');
+ assert_equals(
+ frame2.contentWindow.navigator.serviceWorker.controller.scriptURL,
+ normalizeURL(claim_worker_url),
+ 'Frame2 should be controlled by the new registration');
+ frame1.remove();
+ frame2.remove();
+ return claim_registration.unregister();
+ })
+ .then(function() {
+ return service_worker_unregister_and_done(t, init_scope);
+ });
+ }, 'Test claim client which is not using registration');
+
+promise_test(function(t) {
+ var scope = 'resources/blank.html?longer-matched';
+ var claim_scope = 'resources/blank.html?longer';
+ var claim_worker_url = 'resources/claim-worker.js';
+ var installing_worker_url = 'resources/empty-worker.js';
+ var frame, claim_worker;
+ return with_iframe(scope)
+ .then(function(f) {
+ frame = f;
+ return navigator.serviceWorker.register(
+ claim_worker_url, {scope: claim_scope});
+ })
+ .then(function(registration) {
+ claim_worker = registration.installing;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return navigator.serviceWorker.register(
+ installing_worker_url, {scope: scope});
+ })
+ .then(function() {
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data, 'PASS',
+ 'Worker call to claim() should fulfill.');
+ resolve();
+ });
+ });
+ claim_worker.postMessage({port: channel.port2}, [channel.port2]);
+ return saw_message;
+ })
+ .then(function() {
+ assert_equals(
+ frame.contentWindow.navigator.serviceWorker.controller, null,
+ 'Frame should not be claimed when a longer-matched ' +
+ 'registration exists');
+ frame.remove();
+ return service_worker_unregister(t, claim_scope);
+ })
+ .then(function() {
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Test claim client when there\'s a longer-matched registration not ' +
+ 'already used by the page');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/claim-using-registration.https.html b/testing/web-platform/tests/service-workers/service-worker/claim-using-registration.https.html
new file mode 100644
index 000000000..640b0be3e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/claim-using-registration.https.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<title>Service Worker: claim client using registration</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(function(t) {
+ var scope = 'resources/';
+ var frame_url = 'resources/blank.html?using-different-registration';
+ var url1 = 'resources/empty.js';
+ var url2 = 'resources/claim-worker.js';
+ var worker, sw_registration, frame;
+ return service_worker_unregister_and_register(t, url1, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(frame_url);
+ })
+ .then(function(f) {
+ frame = f;
+ return navigator.serviceWorker.register(url2, {scope: frame_url});
+ })
+ .then(function(registration) {
+ worker = registration.installing;
+ sw_registration = registration;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ var saw_controllerchanged = new Promise(function(resolve) {
+ frame.contentWindow.navigator.serviceWorker.oncontrollerchange =
+ function() { resolve(); }
+ });
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data, 'PASS',
+ 'Worker call to claim() should fulfill.');
+ resolve();
+ });
+ });
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+ return Promise.all([saw_controllerchanged, saw_message]);
+ })
+ .then(function() {
+ assert_equals(
+ frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
+ normalizeURL(url2),
+ 'Frame1 controller scriptURL should be changed to url2');
+ frame.remove();
+ return sw_registration.unregister();
+ })
+ .then(function() {
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Test worker claims client which is using another registration');
+
+promise_test(function(t) {
+ var scope = 'resources/blank.html?using-same-registration';
+ var url1 = 'resources/empty.js';
+ var url2 = 'resources/claim-worker.js';
+ var frame, worker;
+ return service_worker_unregister_and_register(t, url1, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ return navigator.serviceWorker.register(url2, {scope: scope});
+ })
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, registration.installing, 'installed');
+ })
+ .then(function() {
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data, 'FAIL: exception: InvalidStateError',
+ 'Worker call to claim() should reject with ' +
+ 'InvalidStateError');
+ resolve();
+ });
+ });
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+ return saw_message;
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Test for the waiting worker claims a client which is using the the ' +
+ 'same registration');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/client-navigate.https.html b/testing/web-platform/tests/service-workers/service-worker/client-navigate.https.html
new file mode 100644
index 000000000..a505e8722
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/client-navigate.https.html
@@ -0,0 +1,117 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Service Worker: WindowClient.navigate</title>
+<script src=/resources/testharness.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>
+<script>
+ function wait_for_message(msg) {
+ return new Promise(function(resolve, reject) {
+ var get_message_data = function get_message_data(e) {
+ window.removeEventListener("message", get_message_data);
+ resolve(e.data);
+ }
+ window.addEventListener("message", get_message_data, false);
+ });
+ }
+
+ function run_test(controller, clientId, test) {
+ return new Promise(function(resolve, reject) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function(e) {
+ resolve(e.data);
+ };
+ var message = {
+ port: channel.port2,
+ test: test,
+ clientId: clientId,
+ };
+ controller.postMessage(
+ message, [channel.port2]);
+ });
+ }
+
+ promise_test(function(t) {
+ var worker = "resources/client-navigate-worker.js";
+ var scope = "resources/client-navigate-frame.html";
+ var controller, frame, clientId;
+
+ return service_worker_unregister_and_register(t, worker, scope)
+ .then(reg => wait_for_state(t, reg.installing, "activated"))
+ .then(___ => with_iframe(scope))
+ .then(f => {
+ frame = f;
+ controller = frame.contentWindow.navigator.serviceWorker.controller;
+ fetch_tests_from_worker(controller);
+ return wait_for_message()
+ })
+ .then(({id}) => clientId = id)
+ .then(___ => run_test(controller, clientId, "test_client_navigate_success"))
+ .then(({result, url}) => {
+ assert_equals(result, "test_client_navigate_success");
+ assert_equals(
+ url, new URL("resources/client-navigated-frame.html",
+ location).toString());
+ assert_equals(
+ frame.contentWindow.location.href,
+ new URL("resources/client-navigated-frame.html",
+ location).toString());
+ })
+ .catch(unreached_rejection(t))
+ .then(___ => service_worker_unregister(t, scope));
+ }, "Frame location should update on successful navigation");
+
+ promise_test(function(t) {
+ var worker = "resources/client-navigate-worker.js";
+ var scope = "resources/client-navigate-frame.html";
+ var controller, frame, clientId;
+
+ return service_worker_unregister_and_register(t, worker, scope)
+ .then(reg => wait_for_state(t, reg.installing, "activated"))
+ .then(___ => with_iframe(scope))
+ .then(f => {
+ frame = f;
+ controller = frame.contentWindow.navigator.serviceWorker.controller;
+ fetch_tests_from_worker(controller);
+ return wait_for_message()
+ })
+ .then(({id}) => clientId = id)
+ .then(___ => run_test(controller, clientId, "test_client_navigate_redirect"))
+ .then(({result, url}) => {
+ assert_equals(result, "test_client_navigate_redirect");
+ assert_equals(url, "");
+ assert_throws(null, function() { return frame.contentWindow.location.href });
+ })
+ .catch(unreached_rejection(t))
+ .then(___ => service_worker_unregister(t, scope));
+ }, "Frame location should not be accessible after redirect");
+
+ promise_test(function(t) {
+ var worker = "resources/client-navigate-worker.js";
+ var scope = "resources/client-navigate-frame.html";
+ var controller, frame, clientId;
+
+ return service_worker_unregister_and_register(t, worker, scope)
+ .then(reg => wait_for_state(t, reg.installing, "activated"))
+ .then(___ => with_iframe(scope))
+ .then(f => {
+ frame = f;
+ controller = frame.contentWindow.navigator.serviceWorker.controller;
+ fetch_tests_from_worker(controller);
+ return wait_for_message()
+ })
+ .then(({id}) => clientId = id)
+ .then(___ => run_test(controller, clientId, "test_client_navigate_failure"))
+ .then(({result, url}) => {
+ assert_equals(result, "test_client_navigate_failure");
+ assert_equals(
+ frame.contentWindow.location.href,
+ new URL("resources/client-navigate-frame.html",
+ location).toString());
+ frame.contentWindow.document.body.style = "background-color: green"
+ })
+ .catch(unreached_rejection(t))
+ .then(___ => service_worker_unregister(t, scope));
+ }, "Frame location should not update on failed navigation");
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/clients-get-cross-origin.https.html b/testing/web-platform/tests/service-workers/service-worker/clients-get-cross-origin.https.html
new file mode 100644
index 000000000..3413acbf9
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/clients-get-cross-origin.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Service Worker: Clients.get across origins</title>
+<script src="/resources/testharness.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>
+<script>
+var host_info = get_host_info();
+
+var scope = 'resources/blank.html?clients-get';
+var t = async_test('Test Clients.get() cross origin');
+var other_origin_iframe = host_info['HTTPS_REMOTE_ORIGIN'] + base_path() +
+ 'resources/clients-get-other-origin.html';
+var myOriginClientId;
+t.step(function() {
+ service_worker_unregister_and_register(
+ t, 'resources/clients-get-worker.js', scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame1) {
+ myOriginClientId = frame1.contentDocument.body.textContent;
+ return with_iframe(other_origin_iframe);
+ })
+ .then(function(frame2) {
+ window.addEventListener('message', on_message_other_origin, false);
+ frame2.contentWindow.postMessage(
+ {clientId: myOriginClientId,
+ message: 'get_client_id'},
+ host_info['HTTPS_REMOTE_ORIGIN']);
+ })
+ .catch(unreached_rejection(t));
+ });
+
+function on_message_other_origin(e) {
+ assert_equals(e.data.result, undefined);
+ t.done();
+}
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/clients-get.https.html b/testing/web-platform/tests/service-workers/service-worker/clients-get.https.html
new file mode 100644
index 000000000..af38502dd
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/clients-get.https.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<title>Service Worker: Clients.get</title>
+<script src="/resources/testharness.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>
+<script>
+var host_info = get_host_info();
+
+var scope = 'resources/clients-get-frame.html';
+var t = async_test('Test Clients.get()');
+var clientIds = [];
+var frame;
+t.step(function() {
+ service_worker_unregister_and_register(
+ t, 'resources/clients-get-worker.js', scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope + '#1');
+ })
+ .then(function(frame1) {
+ frame1.focus();
+ return wait_for_clientId();
+ })
+ .then(function(clientId) {
+ clientIds.push(clientId);
+ return with_iframe(scope + '#2');
+ })
+ .then(function(frame2) {
+ frame = frame2;
+ return wait_for_clientId();
+ })
+ .then(function(clientId) {
+ clientIds.push(clientId);
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(on_message);
+ frame.contentWindow.navigator.serviceWorker.controller.postMessage(
+ {port:channel.port2, clientIds:clientIds,
+ message: 'get_client_ids'}, [channel.port2]);
+ })
+ .catch(unreached_rejection(t));
+ });
+
+function wait_for_clientId() {
+ return new Promise(function(resolve, reject) {
+ function get_client_id(e) {
+ window.removeEventListener("message", get_client_id);
+ resolve(e.data.clientId);
+ }
+ window.addEventListener("message", get_client_id, false);
+ });
+}
+
+var expected = [
+ /* visibilityState, focused, url, frameType */
+ ['visible', true, new URL(scope + '#1', location).toString(), 'nested'],
+ ['visible', false, new URL(scope + '#2', location).toString(), 'nested'],
+ undefined
+];
+
+function on_message(e) {
+ assert_equals(e.data.length, 3);
+ assert_array_equals(e.data[0], expected[0]);
+ assert_array_equals(e.data[1], expected[1]);
+ assert_equals(e.data[2], expected[2]);
+ service_worker_unregister_and_done(t, scope);
+}
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/clients-matchall-client-types.https.html b/testing/web-platform/tests/service-workers/service-worker/clients-matchall-client-types.https.html
new file mode 100644
index 000000000..3645e8635
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/clients-matchall-client-types.https.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<title>Service Worker: Clients.matchAll with various clientTypes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var scope = 'resources/clients-matchall-client-types';
+var iframe_url = scope + '-iframe.html';
+var shared_worker_url = scope + '-shared-worker.js';
+
+/* visibilityState, focused, url, frameType */
+var expected_without_type = [
+ ['visible', true, new URL(iframe_url, location).href, 'nested']
+];
+var expected_with_window = [
+ ['visible', true, new URL(iframe_url, location).href, 'nested']
+];
+var expected_with_shared_worker = [
+ [,,new URL(shared_worker_url, location).href, 'none']
+];
+var expected_with_all = [
+ ['visible', true, new URL(iframe_url, location).href, 'nested'],
+ [,,new URL(shared_worker_url, location).href, 'none']
+];
+
+function test_matchall(frame, expected, query_options) {
+ // Make sure the frame gets focus.
+ frame.focus();
+ expected.sort(function(a, b) { return a[2] > b[2] ? 1 : -1; });
+ return new Promise(function(resolve, reject) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function(e) {
+ assert_equals(e.data.length, expected.length);
+ for (var i = 0; i < e.data.length; i++)
+ assert_array_equals(e.data[i], expected[i]);
+ resolve();
+ };
+ frame.contentWindow.navigator.serviceWorker.controller.postMessage(
+ {port:channel.port2, options:query_options},
+ [channel.port2]);
+ });
+}
+
+promise_test(function(t) {
+ var frame;
+ return service_worker_unregister_and_register(
+ t, 'resources/clients-matchall-worker.js', scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(iframe_url); })
+ .then(function(f) {
+ frame = f;
+ return new Promise(function(resolve, reject) {
+ var w = new SharedWorker(shared_worker_url);
+ w.port.onmessage = resolve;
+ });
+ })
+ .then(function() {
+ return test_matchall(frame, expected_without_type, {});
+ })
+ .then(function() {
+ return test_matchall(frame, expected_with_window, {type:'window'});
+ })
+ //.then(function() {
+ // return test_matchall(frame, expected_with_shared_worker,
+ // {type:'sharedworker'});
+ // })
+ //.then(function() {
+ // return test_matchall(frame, expected_with_all, {type:'all'});
+ // })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Verify matchAll() with various client types');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html b/testing/web-platform/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html
new file mode 100644
index 000000000..9285aef97
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<title>Service Worker: Clients.matchAll with includeUncontrolled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var base_url = 'resources/blank.html'; // This is out-of-scope.
+var scope = base_url + '?clients-matchAll-includeUncontrolled';
+var frames = [];
+
+// Creates 3 iframes, 2 for in-scope and 1 for out-of-scope.
+// The frame opened for scope + '#2' is returned via a promise.
+function create_iframes(scope) {
+ return with_iframe(base_url)
+ .then(function(frame0) {
+ frames.push(frame0);
+ return with_iframe(scope + '#1');
+ })
+ .then(function(frame1) {
+ frames.push(frame1);
+ return with_iframe(scope + '#2');
+ })
+ .then(function(frame2) {
+ frames.push(frame2);
+ return frame2;
+ })
+}
+
+var expected_without_include_uncontrolled = [
+ /* visibilityState, focused, url, frameType */
+ ['visible', false, new URL(scope + '#1', location).toString(), 'nested'],
+ ['visible', true, new URL(scope + '#2', location).toString(), 'nested']
+];
+
+var expected_with_include_uncontrolled = [
+ /* visibilityState, focused, url, frameType */
+ ['visible', true, location.href, 'top-level'],
+ ['visible', false, new URL(scope + '#1', location).toString(), 'nested'],
+ ['visible', true, new URL(scope + '#2', location).toString(), 'nested'],
+ ['visible', false, new URL(base_url, location).toString(), 'nested']
+];
+
+function test_matchall(frame, expected, query_options) {
+ // Make sure we have focus for '#2' frame and its parent window.
+ frame.focus();
+ frame.contentWindow.focus();
+ expected.sort(function(a, b) { return a[2] > b[2] ? 1 : -1; });
+ return new Promise(function(resolve, reject) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function(e) {
+ // Ignore hidden clients which may be coming from background tabs, or
+ // clients unrelated to this test.
+ var data = e.data.filter(function(info) {
+ return info[0] == 'visible' &&
+ info[2].indexOf('service-worker') > -1;
+ });
+ data.sort(function(a, b) { return a[2] > b[2] ? 1 : -1; });
+ assert_equals(data.length, expected.length);
+ for (var i = 0; i < data.length; i++)
+ assert_array_equals(data[i], expected[i]);
+ resolve(frame);
+ };
+ frame.contentWindow.navigator.serviceWorker.controller.postMessage(
+ {port:channel.port2, options:query_options},
+ [channel.port2]);
+ });
+}
+
+// Run clients.matchAll without and with includeUncontrolled=true.
+// (We want to run the two tests sequentially in the same async_test
+// so that we can use the same set of iframes without intefering each other.
+async_test(function(t) {
+ service_worker_unregister_and_register(
+ t, 'resources/clients-matchall-worker.js', scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return create_iframes(scope); })
+ .then(function(frame) {
+ return test_matchall(frame, expected_without_include_uncontrolled);
+ })
+ .then(function(frame) {
+ return test_matchall(frame, expected_with_include_uncontrolled,
+ {includeUncontrolled:true});
+ })
+ .then(function() {
+ frames.forEach(function(f) { f.remove() });
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify matchAll() respect includeUncontrolled');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/clients-matchall.https.html b/testing/web-platform/tests/service-workers/service-worker/clients-matchall.https.html
new file mode 100644
index 000000000..12e3da4e6
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/clients-matchall.https.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Service Worker: Clients.matchAll</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var scope = 'resources/blank.html?clients-matchAll';
+var t = async_test('Test Clients.matchAll()');
+var frames = [];
+t.step(function() {
+ service_worker_unregister_and_register(
+ t, 'resources/clients-matchall-worker.js', scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(scope + '#1'); })
+ .then(function(frame1) {
+ frames.push(frame1);
+ frame1.focus();
+ return with_iframe(scope + '#2');
+ })
+ .then(function(frame2) {
+ frames.push(frame2);
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(onMessage);
+ frame2.contentWindow.navigator.serviceWorker.controller.postMessage(
+ {port:channel.port2}, [channel.port2]);
+ })
+ .catch(unreached_rejection(t));
+ });
+
+var expected = [
+ /* visibilityState, focused, url, frameType */
+ ['visible', true, new URL(scope + '#1', location).toString(), 'nested'],
+ ['visible', false, new URL(scope + '#2', location).toString(), 'nested']
+];
+
+function onMessage(e) {
+ assert_equals(e.data.length, 2);
+ assert_array_equals(e.data[0], expected[0]);
+ assert_array_equals(e.data[1], expected[1]);
+ frames.forEach(function(f) { f.remove(); });
+ service_worker_unregister_and_done(t, scope);
+}
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/controller-on-disconnect.https.html b/testing/web-platform/tests/service-workers/service-worker/controller-on-disconnect.https.html
new file mode 100644
index 000000000..920d190e3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/controller-on-disconnect.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>Service Worker: Controller on load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+var t = async_test('controller is cleared on disconnected window');
+t.step(function() {
+ var url = 'resources/empty-worker.js';
+ var scope = 'resources/blank.html';
+ var registration;
+ var controller;
+ var frame;
+ service_worker_unregister_and_register(t, url, scope)
+ .then(t.step_func(function(swr) {
+ registration = swr;
+ return wait_for_state(t, registration.installing, 'activated');
+ }))
+ .then(t.step_func(function() {
+ return with_iframe(scope)
+ }))
+ .then(t.step_func(function(f) {
+ frame = f;
+ var w = frame.contentWindow;
+ var swc = w.navigator.serviceWorker;
+ assert_true(swc.controller instanceof w.ServiceWorker,
+ 'controller should be a ServiceWorker object');
+
+ frame.remove();
+
+ assert_equals(swc.controller, null,
+ 'disconnected frame should not be controlled');
+
+ service_worker_unregister_and_done(t, scope);
+ }))
+ .catch(unreached_rejection(t));
+ });
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/controller-on-load.https.html b/testing/web-platform/tests/service-workers/service-worker/controller-on-load.https.html
new file mode 100644
index 000000000..ff3b7ce04
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/controller-on-load.https.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Service Worker: Controller on load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+var t = async_test('controller is set for a controlled document');
+t.step(function() {
+ var url = 'resources/empty-worker.js';
+ var scope = 'resources/blank.html';
+ var registration;
+ var controller;
+ var frame;
+ service_worker_unregister_and_register(t, url, scope)
+ .then(t.step_func(function(swr) {
+ registration = swr;
+ return wait_for_state(t, registration.installing, 'activated');
+ }))
+ .then(t.step_func(function() {
+ return with_iframe(scope)
+ }))
+ .then(t.step_func(function(f) {
+ frame = f;
+ var w = frame.contentWindow;
+ controller = w.navigator.serviceWorker.controller;
+ assert_true(controller instanceof w.ServiceWorker,
+ 'controller should be a ServiceWorker object');
+ assert_equals(controller.scriptURL, normalizeURL(url));
+
+ // objects from different windows should not be equal
+ assert_not_equals(controller, registration.active);
+
+ return w.navigator.serviceWorker.getRegistration();
+ }))
+ .then(t.step_func(function(frameRegistration) {
+ // SW objects from same window should be equal
+ assert_equals(frameRegistration.active, controller);
+ frame.remove();
+ service_worker_unregister_and_done(t, scope);
+ }))
+ .catch(unreached_rejection(t));
+ });
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/controller-on-reload.https.html b/testing/web-platform/tests/service-workers/service-worker/controller-on-reload.https.html
new file mode 100644
index 000000000..4490c7079
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/controller-on-reload.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Service Worker: Controller on reload</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(function(t) {
+ var scope = 'resources/blank.html';
+ var frame;
+ var registration;
+ var controller;
+ return service_worker_unregister(t, scope)
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ return frame.contentWindow.navigator.serviceWorker.register(
+ 'resources/empty-worker.js', {scope: scope});
+ })
+ .then(function(swr) {
+ registration = swr;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ var w = frame.contentWindow;
+ assert_equals(w.navigator.serviceWorker.controller, null,
+ 'controller should be null until the document is ' +
+ 'reloaded');
+ return new Promise(function(resolve) {
+ frame.onload = function() { resolve(); }
+ w.location.reload();
+ });
+ })
+ .then(function() {
+ var w = frame.contentWindow;
+ controller = w.navigator.serviceWorker.controller;
+ assert_true(controller instanceof w.ServiceWorker,
+ 'controller should be a ServiceWorker object upon reload');
+
+ // objects from separate windows should not be equal
+ assert_not_equals(controller, registration.active);
+
+ return w.navigator.serviceWorker.getRegistration();
+ })
+ .then(function(frameRegistration) {
+ assert_equals(frameRegistration.active, controller);
+ frame.remove();
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'controller is set upon reload after registration');
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/extendable-event-async-waituntil.https.html b/testing/web-platform/tests/service-workers/service-worker/extendable-event-async-waituntil.https.html
new file mode 100644
index 000000000..c06bf84ab
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/extendable-event-async-waituntil.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+promise_test(function(t) {
+ var script = 'resources/extendable-event-async-waituntil.js';
+ var scope = 'resources/async-waituntil';
+ var worker;
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() {
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = function(e) { resolve(e.data); }
+ });
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+ return saw_message;
+ })
+ .then(function(message) {
+ assert_equals(message, 'PASS');
+ return service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Calling waitUntil asynchronously throws an exception');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/extendable-event-waituntil.https.html b/testing/web-platform/tests/service-workers/service-worker/extendable-event-waituntil.https.html
new file mode 100644
index 000000000..003e703b1
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/extendable-event-waituntil.https.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<title>ExtendableEvent: waitUntil</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+function runTest(test, scope, onRegister) {
+ var script = 'resources/extendable-event-waituntil.js?' + scope;
+ service_worker_unregister_and_register(test, script, scope)
+ .then(function(registration) {
+ onRegister(registration.installing);
+ });
+}
+
+// Sends a SYN to the worker and asynchronously listens for an ACK; sets
+// |obj.synced| to true once ack'd.
+function syncWorker(test, worker, obj) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = test.step_func(function(e) {
+ var message = e.data;
+ assert_equals(message, 'SYNC',
+ 'Should receive sync message from worker.');
+ obj.synced = true;
+ channel.port1.postMessage('ACK');
+ });
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+}
+
+async_test(function(t) {
+ // Passing scope as the test switch for worker script.
+ var scope = 'resources/install-fulfilled';
+ var onRegister = function(worker) {
+ var obj = {};
+ wait_for_state(t, worker, 'installed')
+ .then(function() {
+ assert_true(
+ obj.synced,
+ 'state should be "installed" after the waitUntil promise ' +
+ 'for "oninstall" is fulfilled.');
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ syncWorker(t, worker, obj);
+ };
+ runTest(t, scope, onRegister);
+ }, 'Test install event waitUntil fulfilled');
+
+async_test(function(t) {
+ var scope = 'resources/install-multiple-fulfilled';
+ var onRegister = function(worker) {
+ var obj1 = {};
+ var obj2 = {};
+ wait_for_state(t, worker, 'installed')
+ .then(function() {
+ assert_true(
+ obj1.synced && obj2.synced,
+ 'state should be "installed" after all waitUntil promises ' +
+ 'for "oninstall" are fulfilled.');
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ syncWorker(t, worker, obj1);
+ syncWorker(t, worker, obj2);
+ };
+ runTest(t, scope, onRegister);
+ }, 'Test ExtendableEvent multiple waitUntil fulfilled.');
+
+async_test(function(t) {
+ var scope = 'resources/install-reject-precedence';
+ var onRegister = function(worker) {
+ wait_for_state(t, worker, 'redundant')
+ .then(function() {
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ };
+ runTest(t, scope, onRegister);
+ }, 'Test ExtendableEvent waitUntil reject precedence.');
+
+async_test(function(t) {
+ var scope = 'resources/activate-fulfilled';
+ var onRegister = function(worker) {
+ var obj = {};
+ wait_for_state(t, worker, 'activating')
+ .then(function() {
+ syncWorker(t, worker, obj);
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() {
+ assert_true(
+ obj.synced,
+ 'state should be "activated" after the waitUntil promise ' +
+ 'for "onactivate" is fulfilled.');
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ };
+ runTest(t, scope, onRegister);
+ }, 'Test activate event waitUntil fulfilled');
+
+async_test(function(t) {
+ var scope = 'resources/install-rejected';
+ var onRegister = function(worker) {
+ wait_for_state(t, worker, 'redundant')
+ .then(function() {
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ };
+ runTest(t, scope, onRegister);
+ }, 'Test install event waitUntil rejected');
+
+async_test(function(t) {
+ var scope = 'resources/activate-rejected';
+ var onRegister = function(worker) {
+ wait_for_state(t, worker, 'activated')
+ .then(function() {
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ };
+ runTest(t, scope, onRegister);
+ }, 'Test activate event waitUntil rejected.');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting-cache.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting-cache.https.html
new file mode 100644
index 000000000..fdb64e15d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting-cache.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Service Worker: canvas tainting of the fetched image using cached responses</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<body>
+<script>
+async_test(function(t) {
+ var SCOPE = 'resources/fetch-canvas-tainting-iframe.html?cache';
+ var SCRIPT = 'resources/fetch-rewrite-worker.js';
+ var host_info = get_host_info();
+
+ login_https(t)
+ .then(function() {
+ return service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ })
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(frame) {
+ return new Promise(function(resolve, reject) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data.results, 'finish');
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ });
+ frame.contentWindow.postMessage({},
+ host_info['HTTPS_ORIGIN'],
+ [channel.port2]);
+ });
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify canvas tainting of fetched image in a Service Worker');
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting.https.html
new file mode 100644
index 000000000..bb7547392
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Service Worker: canvas tainting of the fetched image</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<body>
+<script>
+async_test(function(t) {
+ var SCOPE = 'resources/fetch-canvas-tainting-iframe.html';
+ var SCRIPT = 'resources/fetch-rewrite-worker.js';
+ var host_info = get_host_info();
+
+ login_https(t)
+ .then(function() {
+ return service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ })
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(frame) {
+ return new Promise(function(resolve, reject) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data.results, 'finish');
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ });
+ frame.contentWindow.postMessage({},
+ host_info['HTTPS_ORIGIN'],
+ [channel.port2]);
+ });
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify canvas tainting of fetched image in a Service Worker');
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-cors-xhr.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-cors-xhr.https.html
new file mode 100644
index 000000000..3b1f1d6bc
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-cors-xhr.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Service Worker: CORS XHR of fetch()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<body>
+<script>
+async_test(function(t) {
+ var SCOPE = 'resources/fetch-cors-xhr-iframe.html';
+ var SCRIPT = 'resources/fetch-rewrite-worker.js';
+ var host_info = get_host_info();
+
+ login_https(t)
+ .then(function() {
+ return service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ })
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(frame) {
+ return new Promise(function(resolve, reject) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data.results, 'finish');
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ });
+ frame.contentWindow.postMessage({},
+ host_info['HTTPS_ORIGIN'],
+ [channel.port2]);
+ });
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify CORS XHR of fetch() in a Service Worker');
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-csp.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-csp.https.html
new file mode 100644
index 000000000..bdb56c213
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-csp.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Service Worker: CSP control of fetch()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+async_test(function(t) {
+ var SCOPE = 'resources/fetch-csp-iframe.html';
+ var SCRIPT = 'resources/fetch-rewrite-worker.js';
+ var host_info = get_host_info();
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(frame) {
+ return new Promise(function(resolve, reject) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data.results, 'finish');
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ });
+ frame.contentWindow.postMessage({},
+ host_info['HTTPS_ORIGIN'],
+ [channel.port2]);
+ });
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify CSP control of fetch() in a Service Worker');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event-after-navigation-within-page.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event-after-navigation-within-page.https.html
new file mode 100644
index 000000000..dce1f794f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event-after-navigation-within-page.https.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<title>ServiceWorker: navigator.serviceWorker.waiting</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+promise_test(function(t) {
+ var scope =
+ 'resources/fetch-event-after-navigation-within-page-iframe.html' +
+ '?hashchange';
+ var worker = 'resources/simple-intercept-worker.js';
+ var frame;
+
+ return 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); })
+ .then(function(f) {
+ frame = f;
+ return frame.contentWindow.fetch_url('simple.txt');
+ })
+ .then(function(response) {
+ assert_equals(response, 'intercepted by service worker');
+ frame.contentWindow.location.hash = 'foo';
+ return frame.contentWindow.fetch_url('simple.txt');
+ })
+ .then(function(response) {
+ assert_equals(response, 'intercepted by service worker');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Service Worker should respond to fetch event after the hash changes');
+
+promise_test(function(t) {
+ var scope =
+ 'resources/fetch-event-after-navigation-within-page-iframe.html' +
+ '?pushState';
+ var worker = 'resources/simple-intercept-worker.js';
+ var frame;
+
+ return 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); })
+ .then(function(f) {
+ frame = f;
+ return frame.contentWindow.fetch_url('simple.txt');
+ })
+ .then(function(response) {
+ assert_equals(response, 'intercepted by service worker');
+ frame.contentWindow.history.pushState('', '', 'bar');
+ return frame.contentWindow.fetch_url('simple.txt');
+ })
+ .then(function(response) {
+ assert_equals(response, 'intercepted by service worker');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Service Worker should respond to fetch event after the pushState');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event-async-respond-with.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event-async-respond-with.https.html
new file mode 100644
index 000000000..912e709ca
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event-async-respond-with.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+promise_test(function(t) {
+ var script = 'resources/fetch-event-async-respond-with-worker.js';
+ var scope = 'resources/simple.html';
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = function(e) { resolve(e.data); }
+ });
+ var worker = frame.contentWindow.navigator.serviceWorker.controller;
+
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+ frame.remove();
+ return saw_message;
+ })
+ .then(function(message) {
+ assert_equals(message, 'PASS');
+ return service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Calling respondWith asynchronously throws an exception');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event-network-error.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event-network-error.https.html
new file mode 100644
index 000000000..f6a3d0776
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event-network-error.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Service Worker: Fetch event network error</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var resolve_test_done;
+
+var test_done_promise = new Promise(function(resolve) {
+ resolve_test_done = resolve;
+ });
+
+// Called by the child frame.
+function notify_test_done(result) {
+ resolve_test_done(result);
+}
+
+promise_test(function(t) {
+ var scope = 'resources/fetch-event-network-error-controllee-iframe.html';
+ var script = 'resources/fetch-event-network-error-worker.js';
+ var frame;
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ return test_done_promise;
+ })
+ .then(function(result) {
+ frame.remove();
+ assert_equals(result, 'PASS');
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Rejecting the fetch event or using preventDefault() causes a network ' +
+ 'error');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event-redirect.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event-redirect.https.html
new file mode 100644
index 000000000..17f4cb15e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event-redirect.https.html
@@ -0,0 +1,1102 @@
+<!DOCTYPE html>
+<title>Service Worker: Fetch Event Redirect Handling</title>
+<meta name=timeout content=long>
+<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 expectedRedirectedParam = test['expected_redirected']
+ ? '&expected_redirected=' + test['expected_redirected']
+ : '';
+
+ var url = scope +
+ '?url=' + encodeURIComponent(redirect + dest) +
+ expectedTypeParam + expectedRedirectedParam
+
+ var p = new Promise(function(resolve, reject) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function(e) {
+ frame.remove();
+ if (e.data.result === 'reject') {
+ reject(e.data.detail);
+ } else if (e.data.result === 'success') {
+ resolve(e.data.result);
+ } else {
+ resolve(e.data.detail);
+ }
+ };
+ frame.contentWindow.postMessage({
+ url: url,
+ request_init: test.request_init,
+ redirect_dest: test.redirect_dest,
+ expected_type: test.expected_type,
+ expected_redirected: test.expected_redirected,
+ }, '*', [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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ request_init: {
+ redirect: 'manual',
+ mode: 'no-cors'
+ },
+ should_reject: false
+ });
+}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
+ 'no-cors without credentials should succeed interception ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ request_init: {
+ redirect: 'manual',
+ mode: 'no-cors'
+ },
+ should_reject: false
+ });
+}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
+ 'cors without credentials should succeed interception ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ request_init: {
+ redirect: 'manual',
+ mode: 'no-cors'
+ },
+ should_reject: false
+ });
+}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
+ 'no-cors with credentials should succeed interception ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ request_init: {
+ redirect: 'manual',
+ mode: 'no-cors'
+ },
+ should_reject: false
+ });
+}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
+ 'cors with credentials should succeed interception ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: true,
+ 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 ' +
+ 'and response should be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: true,
+ request_init: {
+ redirect: 'follow',
+ mode: 'cors'
+ },
+ should_reject: false
+ });
+}, 'Non-navigation, follow redirect, cors mode Request redirected to ' +
+ 'cors without credentials should succeed interception ' +
+ 'and response should be redirected');
+
+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',
+ expected_redirected: true,
+ 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 ' +
+ 'and response should be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: true,
+ 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 ' +
+ 'and response should be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: true,
+ 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 ' +
+ 'and response should be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: true,
+ 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 ' +
+ 'and response should be redirected');
+
+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',
+ expected_redirected: true,
+ 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 ' +
+ 'and response should be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: true,
+ 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 ' +
+ 'and response should be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 ' +
+ 'and response should not be redirected');
+
+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',
+ expected_redirected: false,
+ 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 and response should not ' +
+ 'be redirected');
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html
new file mode 100644
index 000000000..5d3346e7b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+promise_test(function(t) {
+ var script =
+ 'resources/fetch-event-respond-with-stops-propagation-worker.js';
+ var scope = 'resources/simple.html';
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = function(e) { resolve(e.data); }
+ });
+ var worker = frame.contentWindow.navigator.serviceWorker.controller;
+
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+ frame.remove();
+ return saw_message;
+ })
+ .then(function(message) {
+ assert_equals(message, 'PASS');
+ return service_worker_unregister_and_done(t, scope);
+ })
+ }, 'respondWith() invokes stopImmediatePropagation()');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event.https.html
new file mode 100644
index 000000000..dfa96ebb5
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event.https.html
@@ -0,0 +1,575 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.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>
+var worker = 'resources/fetch-event-test-worker.js';
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?string';
+ 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); })
+ .then(function(frame) {
+ assert_equals(
+ frame.contentDocument.body.textContent,
+ 'Test string',
+ 'Service Worker should respond to fetch with a test string');
+ assert_equals(
+ frame.contentDocument.contentType,
+ 'text/plain',
+ 'The content type of the response created with a string should be text/plain');
+ assert_equals(
+ frame.contentDocument.characterSet,
+ 'UTF-8',
+ 'The character set of the response created with a string should be UTF-8');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker responds to fetch event with string');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?blob';
+ 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); })
+ .then(function(frame) {
+ assert_equals(
+ frame.contentDocument.body.textContent,
+ 'Test blob',
+ 'Service Worker should respond to fetch with a test string');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker responds to fetch event with blob body');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?referrer';
+ 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); })
+ .then(function(frame) {
+ assert_equals(
+ frame.contentDocument.body.textContent,
+ 'Referrer: ' + document.location.href,
+ 'Service Worker should respond to fetch with the referrer URL');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker responds to fetch event with the referrer URL');
+
+function run_referrer_policy_tests(frame, referrer, href, origin) {
+ return frame.contentWindow.fetch('resources/simple.html?referrerFull',
+ {method: "GET", referrer: referrer})
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: ' + href + '\n' +
+ 'ReferrerPolicy: no-referrer-when-downgrade',
+ 'Service Worker should respond to fetch with the referrer URL when a member of RequestInit is present');
+ var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() +
+ '/resources/simple.html?referrerFull';
+ return frame.contentWindow.fetch(http_url,
+ {method: "GET", referrer: referrer});
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: about:client\n' +
+ 'ReferrerPolicy: no-referrer-when-downgrade',
+ 'Service Worker should respond to fetch with no referrer when a member of RequestInit is present with an HTTP request');
+ return frame.contentWindow.fetch('resources/simple.html?referrerFull',
+ {referrerPolicy: "", referrer: referrer});
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: ' + href + '\n' +
+ 'ReferrerPolicy: no-referrer-when-downgrade',
+ 'Service Worker should respond to fetch with the referrer with ""');
+ var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() +
+ '/resources/simple.html?referrerFull';
+ return frame.contentWindow.fetch(http_url,
+ {referrerPolicy: "", referrer: referrer});
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: about:client\n' +
+ 'ReferrerPolicy: no-referrer-when-downgrade',
+ 'Service Worker should respond to fetch with no referrer with ""');
+ return frame.contentWindow.fetch('resources/simple.html?referrerFull',
+ {referrerPolicy: "origin", referrer: referrer});
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: ' + origin + '/' + '\n' +
+ 'ReferrerPolicy: origin',
+ 'Service Worker should respond to fetch with the referrer origin with "origin" and a same origin request');
+ var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() +
+ '/resources/simple.html?referrerFull';
+ return frame.contentWindow.fetch(http_url,
+ {referrerPolicy: "origin", referrer: referrer});
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: ' + origin + '/' + '\n' +
+ 'ReferrerPolicy: origin',
+ 'Service Worker should respond to fetch with the referrer origin with "origin" and a cross origin request');
+ return frame.contentWindow.fetch('resources/simple.html?referrerFull',
+ {referrerPolicy: "origin-when-cross-origin", referrer: referrer});
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: ' + href + '\n' +
+ 'ReferrerPolicy: origin-when-cross-origin',
+ 'Service Worker should respond to fetch with the referrer URL with "origin-when-cross-origin" and a same origin request');
+ var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() +
+ '/resources/simple.html?referrerFull';
+ return frame.contentWindow.fetch(http_url,
+ {referrerPolicy: "origin-when-cross-origin", referrer: referrer});
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: ' + origin + '/' + '\n' +
+ 'ReferrerPolicy: origin-when-cross-origin',
+ 'Service Worker should respond to fetch with the referrer origin with "origin-when-cross-origin" and a cross origin request');
+ return frame.contentWindow.fetch('resources/simple.html?referrerFull',
+ {referrerPolicy: "no-referrer-when-downgrade", referrer: referrer});
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: ' + href + '\n' +
+ 'ReferrerPolicy: no-referrer-when-downgrade',
+ 'Service Worker should respond to fetch with no referrer with "no-referrer-when-downgrade" and a same origin request');
+ var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() +
+ '/resources/simple.html?referrerFull';
+ return frame.contentWindow.fetch(http_url,
+ {referrerPolicy: "no-referrer-when-downgrade", referrer: referrer});
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: about:client\n' +
+ 'ReferrerPolicy: no-referrer-when-downgrade',
+ 'Service Worker should respond to fetch with no referrer with "no-referrer-when-downgrade" and an HTTP request');
+ var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() +
+ '/resources/simple.html?referrerFull';
+ return frame.contentWindow.fetch(http_url, {referrerPolicy: "unsafe-url", referrer: referrer});
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: ' + href + '\n' +
+ 'ReferrerPolicy: unsafe-url',
+ 'Service Worker should respond to fetch with no referrer with "unsafe-url"');
+ return frame.contentWindow.fetch('resources/simple.html?referrerFull',
+ {referrerPolicy: "no-referrer", referrer: referrer});
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ assert_equals(
+ response_text,
+ 'Referrer: about:client\n' +
+ 'ReferrerPolicy: no-referrer',
+ 'Service Worker should respond to fetch with no referrer URL with "no-referrer"');
+ });
+}
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?referrerPolicy';
+ var frame;
+ 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); })
+ .then(function(f) {
+ frame = f;
+ assert_equals(
+ frame.contentDocument.body.textContent,
+ 'ReferrerPolicy: no-referrer-when-downgrade',
+ 'Service Worker should respond to fetch with the default referrer policy');
+ // First, run the referrer policy tests without passing a referrer in RequestInit.
+ return run_referrer_policy_tests(frame, undefined, frame.contentDocument.location.href,
+ frame.contentDocument.location.origin);
+ })
+ .then(function() {
+ // Now, run the referrer policy tests while passing a referrer in RequestInit.
+ var referrer = get_host_info()['HTTPS_ORIGIN'] + base_path() + 'fake-referrer';
+ return run_referrer_policy_tests(frame, 'fake-referrer', referrer,
+ frame.contentDocument.location.origin);
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker responds to fetch event with the referrer URL');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?clientId';
+ var frame;
+ 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); })
+ .then(function(f) {
+ frame = f;
+ assert_equals(
+ frame.contentDocument.body.textContent,
+ 'Client ID Not Found',
+ 'Service Worker should respond to fetch with a client id');
+ return frame.contentWindow.fetch('resources/other.html?clientId');
+ })
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ var new_client_id = response_text.substr(17);
+ assert_equals(
+ response_text.substr(0, 15),
+ 'Client ID Found',
+ 'Service Worker should respond to fetch with an existing client id');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker responds to fetch event with an existing client id');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?ignore';
+ 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); })
+ .then(function(frame) {
+ assert_equals(frame.contentDocument.body.textContent,
+ 'Here\'s a simple html file.\n',
+ 'Response should come from fallback to native fetch');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker does not respond to fetch event');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?null';
+ 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); })
+ .then(function(frame) {
+ assert_equals(frame.contentDocument.body.textContent,
+ '',
+ 'Response should be the empty string');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker responds to fetch event with null response body');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?fetch';
+ 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); })
+ .then(function(frame) {
+ assert_equals(frame.contentDocument.body.textContent,
+ 'Here\'s an other html file.\n',
+ 'Response should come from fetched other file');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker fetches other file in fetch event');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?form-post';
+ var frame_name = 'xhr-post-frame';
+ service_worker_unregister_and_register(t, worker, scope)
+ .then(function(reg) {
+ return wait_for_state(t, reg.installing, 'activated');
+ })
+ .then(function(sw) {
+ return new Promise(function(resolve) {
+ var frame = document.createElement('iframe');
+ frame.name = frame_name;
+ document.body.appendChild(frame);
+ var form = document.createElement('form');
+ form.target = frame_name;
+ form.action = scope;
+ form.method = 'post';
+ var input1 = document.createElement('input');
+ input1.type = 'text';
+ input1.value = 'testValue1';
+ input1.name = 'testName1'
+ form.appendChild(input1);
+ var input2 = document.createElement('input');
+ input2.type = 'text';
+ input2.value = 'testValue2';
+ input2.name = 'testName2'
+ form.appendChild(input2);
+ document.body.appendChild(form);
+ frame.onload = function() {
+ document.body.removeChild(form);
+ resolve(frame);
+ };
+ form.submit();
+ });
+ })
+ .then(function(frame) {
+ assert_equals(frame.contentDocument.body.textContent,
+ 'POST:application/x-www-form-urlencoded:' +
+ 'testName1=testValue1&testName2=testValue2');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker responds to fetch event with POST form');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?multiple-respond-with';
+ 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); })
+ .then(function(frame) {
+ assert_equals(
+ frame.contentDocument.body.textContent,
+ '(0)(1)[InvalidStateError](2)[InvalidStateError]',
+ 'Multiple calls of respondWith must throw InvalidStateErrors.');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Multiple calls of respondWith must throw InvalidStateErrors');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?used-check';
+ var first_frame;
+ 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); })
+ .then(function(frame) {
+ assert_equals(frame.contentDocument.body.textContent,
+ 'Here\'s an other html file.\n',
+ 'Response should come from fetched other file');
+ first_frame = frame;
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ // When we access to the scope in the second time, the content of the
+ // response is generated inside the ServiceWorker. The body contains
+ // the value of bodyUsed of the first response which is already
+ // consumed by FetchEvent.respondWith method.
+ assert_equals(
+ frame.contentDocument.body.textContent,
+ 'bodyUsed: true',
+ 'event.respondWith must set the used flag.');
+ first_frame.remove();
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker event.respondWith must set the used flag');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?fragment-check';
+ var fragment = '#/some/fragment';
+ var first_frame;
+ 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 + fragment); })
+ .then(function(frame) {
+ assert_equals(
+ frame.contentDocument.body.textContent,
+ 'Fragment Found :' + fragment,
+ 'Service worker should expose URL fragments in request.');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker must not expose FetchEvent URL fragments.');
+async_test(function(t) {
+ var scope = 'resources/simple.html?cache';
+ var frame;
+ var cacheTypes = [
+ undefined, 'default', 'no-store', 'reload', 'no-cache', 'force-cache', 'only-if-cached'
+ ];
+ 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); })
+ .then(function(f) {
+ frame = f;
+ assert_equals(frame.contentWindow.document.body.textContent, 'default');
+ var tests = cacheTypes.map(function(type) {
+ return new Promise(function(resolve, reject) {
+ var init = {cache: type};
+ if (type === 'only-if-cached') {
+ // For privacy reasons, for the time being, only-if-cached
+ // requires the mode to be same-origin.
+ init.mode = 'same-origin';
+ }
+ return frame.contentWindow.fetch(scope + '=' + type, init)
+ .then(function(response) { return response.text(); })
+ .then(function(response_text) {
+ var expected = (type === undefined) ? 'default' : type;
+ assert_equals(response_text, expected,
+ 'Service Worker should respond to fetch with the correct type');
+ })
+ .then(resolve)
+ .catch(reject);
+ });
+ });
+ })
+ .then(function() {
+ return new Promise(function(resolve, reject) {
+ frame.addEventListener('load', function onLoad() {
+ frame.removeEventListener('load', onLoad);
+ try {
+ assert_equals(frame.contentWindow.document.body.textContent,
+ 'no-cache');
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ });
+ frame.contentWindow.location.reload();
+ });
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker responds to fetch event with the correct cache types');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?eventsource';
+ var frame;
+
+ function test_eventsource(opts) {
+ return new Promise(function(resolve, reject) {
+ var eventSource = new frame.contentWindow.EventSource(scope, opts);
+ eventSource.addEventListener('message', function(msg) {
+ eventSource.close();
+ try {
+ var data = JSON.parse(msg.data);
+ assert_equals(data.mode, 'cors',
+ 'EventSource should make CORS requests.');
+ assert_equals(data.cache, 'no-store',
+ 'EventSource should bypass the http cache.');
+ var expectedCredentials = opts.withCredentials ? 'include'
+ : 'same-origin';
+ assert_equals(data.credentials, expectedCredentials,
+ 'EventSource should pass correct credentials mode.');
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ });
+ eventSource.addEventListener('error', function(e) {
+ eventSource.close();
+ reject('The EventSource fired an error event.');
+ });
+ });
+ }
+
+ 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); })
+ .then(function(f) {
+ frame = f;
+ return test_eventsource({ withCredentials: false });
+ })
+ .then(function() {
+ return test_eventsource({ withCredentials: true });
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker should intercept EventSource');
+
+async_test(function(t) {
+ var scope = 'resources/simple.html?integrity';
+ var frame;
+ var integrity_metadata = 'gs0nqru8KbsrIt5YToQqS9fYao4GQJXtcId610g7cCU=';
+
+ 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); })
+ .then(function(f) {
+ frame = f;
+ // A request has associated integrity metadata (a string).
+ // Unless stated otherwise, it is the empty string.
+ assert_equals(
+ frame.contentDocument.body.textContent, '');
+
+ return new Promise(function(resolve, reject) {
+ return frame.contentWindow.fetch(scope,
+ {'integrity': integrity_metadata})
+ .then(function(response) {
+ return response.text();
+ })
+ .then(function(response_text) {
+ // Should get the same integrity metadata.
+ assert_equals(response_text, integrity_metadata,
+ 'Service Worker should respond to fetch with the correct integrity');
+ })
+ .then(resolve())
+ .catch(reject());
+ });
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Service Worker responds to fetch event with the correct integrity_metadata');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-frame-resource.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-frame-resource.https.html
new file mode 100644
index 000000000..cc1dac472
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-frame-resource.https.html
@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+<title>Service Worker: Fetch for the frame loading.</title>
+<meta name=timeout content=long>
+<script src="/resources/testharness.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>
+var worker = 'resources/fetch-rewrite-worker.js';
+var path = base_path() + 'resources/fetch-access-control.py';
+var host_info = get_host_info();
+
+if (window.testRunner) {
+ testRunner.setCanOpenWindows();
+}
+
+function getLoadedObject(win, contentFunc, closeFunc) {
+ return new Promise(function(resolve) {
+ function done(contentString) {
+ var result = null;
+ // fetch-access-control.py returns a string like "report( <json> )".
+ // Eval the returned string with a report functionto get the json
+ // object.
+ try {
+ function report(obj) { result = obj };
+ eval(contentString);
+ } catch(e) {
+ // just resolve null if we get unexpected page content
+ }
+ closeFunc(win);
+ resolve(result);
+ }
+
+ // We can't catch the network error on window. So we use the timer.
+ var timeout = setTimeout(function() {
+ // Failure pages are considered cross-origin in some browsers. This
+ // means you cannot even .resolve() the window because the check for
+ // the .then property will throw. Instead, treat cross-origin
+ // failure pages as the empty string which will fail to parse as the
+ // expected json result.
+ var content = '';
+ try {
+ content = contentFunc(win);
+ } catch(e) {
+ // use default empty string for cross-domain window
+ }
+ done(content);
+ }, 10000);
+
+ win.onload = function() {
+ clearTimeout(timeout);
+ var content = contentFunc(win);
+ done(content);
+ };
+ });
+}
+
+function getLoadedFrameAsObject(frame) {
+ return getLoadedObject(frame, function(f) {
+ return f.contentDocument.body.textContent;
+ }, function(f) {
+ f.parentNode.removeChild(f);
+ });
+}
+
+function getLoadedWindowAsObject(win) {
+ return getLoadedObject(win, function(w) {
+ return w.document.body.textContent
+ }, function(w) {
+ w.close();
+ });
+}
+
+async_test(function(t) {
+ var scope = 'resources/fetch-frame-resource/frame-basic';
+ var frame;
+ service_worker_unregister_and_register(t, worker, scope)
+ .then(function(reg) {
+ return wait_for_state(t, reg.installing, 'activated');
+ })
+ .then(function() {
+ frame = document.createElement('iframe');
+ frame.src =
+ scope + '?url=' +
+ encodeURIComponent(host_info['HTTPS_ORIGIN'] + path);
+ document.body.appendChild(frame);
+ return getLoadedFrameAsObject(frame);
+ })
+ .then(function(result) {
+ assert_equals(
+ result.jsonpResult,
+ 'success',
+ 'Basic type response could be loaded in the iframe.');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Basic type response could be loaded in the iframe.');
+
+async_test(function(t) {
+ var scope = 'resources/fetch-frame-resource/frame-cors';
+ var frame;
+ service_worker_unregister_and_register(t, worker, scope)
+ .then(function(reg) {
+ return wait_for_state(t, reg.installing, 'activated');
+ })
+ .then(function() {
+ frame = document.createElement('iframe');
+ frame.src =
+ scope + '?mode=cors&url=' +
+ encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + path +
+ '?ACAOrigin=' + host_info['HTTPS_ORIGIN']);
+ document.body.appendChild(frame);
+ return getLoadedFrameAsObject(frame);
+ })
+ .then(function(result) {
+ assert_equals(
+ result.jsonpResult,
+ 'success',
+ 'CORS type response could be loaded in the iframe.');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'CORS type response could be loaded in the iframe.');
+
+async_test(function(t) {
+ var scope = 'resources/fetch-frame-resource/frame-opaque';
+ var frame;
+ service_worker_unregister_and_register(t, worker, scope)
+ .then(function(reg) {
+ return wait_for_state(t, reg.installing, 'activated');
+ })
+ .then(function() {
+ frame = document.createElement('iframe');
+ frame.src =
+ scope + '?mode=no-cors&url=' +
+ encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + path);
+ document.body.appendChild(frame);
+ return getLoadedFrameAsObject(frame);
+ })
+ .then(function(result) {
+ assert_equals(
+ result,
+ null,
+ 'Opaque type response could not be loaded in the iframe.');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Opaque type response could not be loaded in the iframe.');
+
+async_test(function(t) {
+ var scope = 'resources/fetch-frame-resource/window-basic';
+ service_worker_unregister_and_register(t, worker, scope)
+ .then(function(reg) {
+ return wait_for_state(t, reg.installing, 'activated');
+ })
+ .then(function() {
+ var win = window.open(
+ scope + '?url=' +
+ encodeURIComponent(host_info['HTTPS_ORIGIN'] + path));
+ return getLoadedWindowAsObject(win);
+ })
+ .then(function(result) {
+ assert_equals(
+ result.jsonpResult,
+ 'success',
+ 'Basic type response could be loaded in the new window.');
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Basic type response could be loaded in the new window.');
+
+async_test(function(t) {
+ var scope = 'resources/fetch-frame-resource/window-cors';
+ service_worker_unregister_and_register(t, worker, scope)
+ .then(function(reg) {
+ return wait_for_state(t, reg.installing, 'activated');
+ })
+ .then(function() {
+ var win = window.open(
+ scope + '?mode=cors&url=' +
+ encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + path +
+ '?ACAOrigin=' + host_info['HTTPS_ORIGIN']));
+ return getLoadedWindowAsObject(win);
+ })
+ .then(function(result) {
+ assert_equals(
+ result.jsonpResult,
+ 'success',
+ 'CORS type response could be loaded in the new window.');
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'CORS type response could be loaded in the new window.');
+
+async_test(function(t) {
+ var scope = 'resources/fetch-frame-resource/window-opaque';
+ service_worker_unregister_and_register(t, worker, scope)
+ .then(function(reg) {
+ return wait_for_state(t, reg.installing, 'activated');
+ })
+ .then(function() {
+ var win = window.open(
+ scope + '?mode=no-cors&url=' +
+ encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + path));
+ return getLoadedWindowAsObject(win);
+ })
+ .then(function(result) {
+ assert_equals(
+ result,
+ null,
+ 'Opaque type response could not be loaded in the new window.');
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Opaque type response could not be loaded in the new window.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-header-visibility.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-header-visibility.https.html
new file mode 100644
index 000000000..36bf16f32
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-header-visibility.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Service Worker: Visibility of headers during fetch.</title>
+<script src="/resources/testharness.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>
+ var worker = 'resources/fetch-rewrite-worker.js';
+ var path = base_path() + 'resources/fetch-access-control.py';
+ var host_info = get_host_info();
+ var frame;
+
+ async_test(function(t) {
+ var scope = 'resources/fetch-header-visibility-iframe.html';
+ service_worker_unregister_and_register(t, worker, scope)
+ .then(function(reg) {
+ return wait_for_state(t, reg.installing, 'activated');
+ })
+ .then(function() {
+ frame = document.createElement('iframe');
+ frame.src = scope;
+ document.body.appendChild(frame);
+
+ // Resolve a promise when we recieve 2 success messages
+ return new Promise(function(resolve, reject) {
+ var remaining = 4;
+ function onMessage(e) {
+ if (e.data == 'PASS') {
+ remaining--;
+ if (remaining == 0) {
+ resolve();
+ } else {
+ return;
+ }
+ } else {
+ reject(e.data);
+ }
+
+ window.removeEventListener('message', onMessage);
+ }
+ window.addEventListener('message', onMessage);
+ });
+ })
+ .then(function(result) {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Visibility of defaulted headers during interception');
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html
new file mode 100644
index 000000000..fb9fa9b7b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Service Worker: Mixed content of fetch()</title>
+<meta name=timeout content=long>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<body></body>
+<script>
+if (window.testRunner) {
+ // In Chromium we need to change the setting to disallow displaying insecure
+ // contents.
+ testRunner.overridePreference('WebKitAllowDisplayingInsecureContent', false);
+}
+
+async_test(function(t) {
+ var host_info = get_host_info();
+ window.addEventListener('message', t.step_func(on_message), false);
+ with_iframe(
+ host_info['HTTPS_ORIGIN'] + base_path() +
+ 'resources/fetch-mixed-content-iframe.html?target=inscope');
+ function on_message(e) {
+ assert_equals(e.data.results, 'finish');
+ t.done();
+ }
+ }, 'Verify Mixed content of fetch() in a Service Worker');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html
new file mode 100644
index 000000000..cee89ce16
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Service Worker: Mixed content of fetch()</title>
+<meta name=timeout content=long>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<body></body>
+<script>
+if (window.testRunner) {
+ // In Chromium we need to change the setting to disallow displaying insecure
+ // contents.
+ testRunner.overridePreference('WebKitAllowDisplayingInsecureContent', false);
+}
+
+async_test(function(t) {
+ var host_info = get_host_info();
+ window.addEventListener('message', t.step_func(on_message), false);
+ with_iframe(
+ host_info['HTTPS_ORIGIN'] + base_path() +
+ 'resources/fetch-mixed-content-iframe.html?target=outscope');
+ function on_message(e) {
+ assert_equals(e.data.results, 'finish');
+ t.done();
+ }
+ }, 'Verify Mixed content of fetch() in a Service Worker');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-base-url.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-base-url.https.html
new file mode 100644
index 000000000..0405ed709
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-base-url.https.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>Service Worker: CSS's base URL must be the request URL even when fetched from other URL</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+async_test(function(t) {
+ var SCOPE = 'resources/fetch-request-css-base-url-iframe.html';
+ var SCRIPT = 'resources/fetch-request-css-base-url-worker.js';
+ var worker;
+ var testDonePromise;
+
+ return service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() {
+ return new Promise(function(resolve) {
+ var channel = new MessageChannel();
+ testDonePromise = new Promise(function(resolveTestDone) {
+ channel.port1.onmessage = t.step_func(function(msg) {
+ if (msg.data.ready) {
+ resolve();
+ return;
+ }
+ var result = msg.data;
+ var base = get_host_info()['HTTPS_ORIGIN'] + base_path();
+ assert_equals(
+ result.url,
+ base + 'resources/dummy.png',
+ 'The base URL while loading the images referred from CSS ' +
+ 'must be the request URL of CSS.');
+ assert_equals(
+ result.referrer,
+ base + 'resources/fetch-request-css-base-url-style.css',
+ 'While loading the image defined in CSS the referrer must ' +
+ 'be the request URL of CSS.');
+ resolveTestDone();
+ });
+ });
+ worker.postMessage(
+ {port: channel.port2}, [channel.port2]);
+ });
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(f) {
+ return testDonePromise.then(function() {
+ f.remove();
+ return service_worker_unregister_and_done(t, SCOPE);
+ });
+ })
+ .catch(unreached_rejection(t));
+ }, 'CSS\'s base URL must be the request URL even when fetched from other URL.');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-images.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-images.https.html
new file mode 100644
index 000000000..777308241
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-images.https.html
@@ -0,0 +1,176 @@
+<!DOCTYPE html>
+<title>Service Worker: FetchEvent for css image</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+var SCOPE = 'resources/fetch-request-resources-iframe.https.html';
+var SCRIPT = 'resources/fetch-request-resources-worker.js';
+var host_info = get_host_info();
+var LOCAL_URL =
+ host_info['HTTPS_ORIGIN'] + base_path() + 'resources/dummy?test';
+var REMOTE_URL =
+ host_info['HTTPS_REMOTE_ORIGIN'] + base_path() + 'resources/dummy?test';
+
+function css_image_test(expected_results, frame, url, type,
+ expected_mode, expected_credentials) {
+ expected_results[url] = {
+ url: url,
+ mode: expected_mode,
+ credentials: expected_credentials,
+ message: 'CSSImage load (url:' + url + ' type:' + type + ')'
+ };
+ return frame.contentWindow.load_css_image(url, type);
+}
+
+function css_image_set_test(expected_results, frame, url, type,
+ expected_mode, expected_credentials) {
+ expected_results[url] = {
+ url: url,
+ mode: expected_mode,
+ credentials: expected_credentials,
+ message: 'CSSImageSet load (url:' + url + ' type:' + type + ')'
+ };
+ return frame.contentWindow.load_css_image_set(url, type);
+}
+
+function create_message_promise(t, expected_results, frame,
+ worker, test_count, scope) {
+ return new Promise(function(resolve) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(msg) {
+ if (msg.data.ready) {
+ resolve();
+ return;
+ }
+ var result = msg.data;
+ var expected = expected_results[result.url];
+ if (!expected) {
+ return;
+ }
+ assert_equals(
+ result.mode, expected.mode,
+ 'mode of ' + expected.message + ' must be ' +
+ expected.mode + '.');
+ assert_equals(
+ result.credentials, expected.credentials,
+ 'credentials of ' + expected.message + ' must be ' +
+ expected.credentials + '.');
+ --test_count;
+ delete expected_results[result.url];
+ if (test_count == 0) {
+ frame.remove();
+ service_worker_unregister_and_done(t, scope);
+ }
+ });
+ worker.postMessage(
+ {port: channel.port2}, [channel.port2]);
+ });
+}
+
+async_test(function(t) {
+ var scope = SCOPE + "?img=backgroundImage";
+ var test_count = 2;
+ var expected_results = {};
+ var worker;
+ var frame;
+ service_worker_unregister_and_register(t, SCRIPT, scope)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame = f;
+ return create_message_promise(t, expected_results, frame,
+ worker, test_count, scope);
+ })
+ .then(function() {
+ css_image_test(expected_results, frame, LOCAL_URL + Date.now(),
+ 'backgroundImage', 'no-cors', 'include');
+ css_image_test(expected_results, frame, REMOTE_URL + Date.now(),
+ 'backgroundImage', 'no-cors', 'include');
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify FetchEvent for css image (backgroundImage).');
+
+async_test(function(t) {
+ var scope = SCOPE + "?img=shapeOutside";
+ var test_count = 2;
+ var expected_results = {};
+ var worker;
+ var frame;
+ service_worker_unregister_and_register(t, SCRIPT, scope)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame = f;
+ return create_message_promise(t, expected_results, frame,
+ worker, test_count, scope);
+ })
+ .then(function() {
+ css_image_test(expected_results, frame, LOCAL_URL + Date.now(),
+ 'shapeOutside', 'cors', 'same-origin');
+ css_image_test(expected_results, frame, REMOTE_URL + Date.now(),
+ 'shapeOutside', 'cors', 'same-origin');
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify FetchEvent for css image (shapeOutside).');
+
+async_test(function(t) {
+ var scope = SCOPE + "?img_set=backgroundImage";
+ var test_count = 2;
+ var expected_results = {};
+ var worker;
+ var frame;
+ service_worker_unregister_and_register(t, SCRIPT, scope)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame = f;
+ return create_message_promise(t, expected_results, frame,
+ worker, test_count, scope);
+ })
+ .then(function() {
+ css_image_set_test(expected_results, frame, LOCAL_URL + Date.now(),
+ 'backgroundImage', 'no-cors', 'include');
+ css_image_set_test(expected_results, frame, REMOTE_URL + Date.now(),
+ 'backgroundImage', 'no-cors', 'include');
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify FetchEvent for css image-set (backgroundImage).');
+
+async_test(function(t) {
+ var scope = SCOPE + "?img_set=shapeOutside";
+ var test_count = 2;
+ var expected_results = {};
+ var worker;
+ var frame;
+ service_worker_unregister_and_register(t, SCRIPT, scope)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame = f;
+ return create_message_promise(t, expected_results, frame,
+ worker, test_count, scope);
+ })
+ .then(function() {
+ css_image_set_test(expected_results, frame, LOCAL_URL + Date.now(),
+ 'shapeOutside', 'cors', 'same-origin');
+ css_image_set_test(expected_results, frame, REMOTE_URL + Date.now(),
+ 'shapeOutside', 'cors', 'same-origin');
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify FetchEvent for css image-set (shapeOutside).');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-fallback.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-fallback.https.html
new file mode 100644
index 000000000..8290e21dd
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-fallback.https.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<title>Service Worker: the fallback behavior of FetchEvent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+var expected_urls = [];
+
+function xhr_fail_test(frame, url) {
+ expected_urls.push(url);
+ return new Promise(function(resolve, reject) {
+ frame.contentWindow.xhr(url)
+ .then(function(){
+ reject(url + ' should fail.');
+ })
+ .catch(function(){
+ resolve();
+ });
+ });
+}
+
+function xhr_succeed_test(frame, url) {
+ expected_urls.push(url);
+ return new Promise(function(resolve, reject) {
+ frame.contentWindow.xhr(url)
+ .then(function(){
+ resolve();
+ })
+ .catch(function(){
+ reject(url + ' should succeed.');
+ });
+ });
+}
+
+async_test(function(t) {
+ var path = new URL(".", window.location).pathname;
+ var SCOPE = 'resources/fetch-request-fallback-iframe.html';
+ var SCRIPT = 'resources/fetch-request-fallback-worker.js';
+ var host_info = get_host_info();
+ var BASE_URL = host_info['HTTPS_ORIGIN'] +
+ path + 'resources/fetch-access-control.py?';
+ var OTHER_BASE_URL = host_info['HTTPS_REMOTE_ORIGIN'] +
+ path + 'resources/fetch-access-control.py?';
+ var REDIRECT_URL = host_info['HTTPS_ORIGIN'] +
+ path + 'resources/redirect.py?Redirect=';
+ var frame;
+ var worker;
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(f) {
+ frame = f;
+ return xhr_succeed_test(frame, BASE_URL);
+ })
+ .then(function(f) {
+ return xhr_fail_test(frame, OTHER_BASE_URL);
+ })
+ .then(function(f) {
+ return xhr_succeed_test(frame, OTHER_BASE_URL + 'ACAOrigin=*');
+ })
+ .then(function(f) {
+ return xhr_succeed_test(frame,
+ REDIRECT_URL + encodeURIComponent(BASE_URL));
+ })
+ .then(function() {
+ return xhr_fail_test(
+ frame,
+ REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL));
+ })
+ .then(function() {
+ return xhr_succeed_test(
+ frame,
+ REDIRECT_URL +
+ encodeURIComponent(OTHER_BASE_URL + 'ACAOrigin=*'));
+ })
+ .then(function() {
+ return new Promise(function(resolve) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(msg) {
+ frame.remove();
+ resolve(msg);
+ });
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+ });
+ })
+ .then(function(msg) {
+ var requests = msg.data.requests;
+ assert_equals(requests.length, expected_urls.length + 1,
+ 'The count of the requests which are passed to the ' +
+ 'ServiceWorker must be correct.');
+ assert_equals(requests[0].url, new URL(SCOPE, location).toString(),
+ 'The first request to the SW must be the request for ' +
+ 'the page.');
+ assert_equals(requests[0].mode, 'navigate',
+ 'The mode of the first request to the SW must be ' +
+ 'navigate');
+ for (var i = 0; i < expected_urls.length; ++i) {
+ assert_equals(requests[i + 1].url, expected_urls[i],
+ 'The URL of the request which was passed from XHR ' +
+ 'to the ServiceWorker must be correct.');
+ assert_equals(requests[i + 1].mode, 'cors',
+ 'The mode of the request which was passed from XHR ' +
+ 'to the ServiceWorker must be cors.');
+ }
+ service_worker_unregister_and_done(t, SCOPE);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify the fallback behavior of FetchEvent');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-no-freshness-headers.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-no-freshness-headers.https.html
new file mode 100644
index 000000000..829b0cf25
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-no-freshness-headers.https.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<title>Service Worker: the headers of FetchEvent shouldn't contain freshness headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+async_test(function(t) {
+ var SCOPE = 'resources/fetch-request-no-freshness-headers-iframe.html';
+ var SCRIPT = 'resources/fetch-request-no-freshness-headers-worker.js';
+ var worker;
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(frame) {
+ return new Promise(function(resolve) {
+ frame.onload = function() {
+ resolve(frame);
+ };
+ frame.contentWindow.location.reload();
+ });
+ })
+ .then(function(frame) {
+ return new Promise(function(resolve) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(msg) {
+ frame.remove();
+ resolve(msg);
+ });
+ worker.postMessage(
+ {port: channel.port2}, [channel.port2]);
+ });
+ })
+ .then(function(msg) {
+ var freshness_headers = {
+ 'if-none-match': true,
+ 'if-modified-since': true
+ };
+ msg.data.requests.forEach(t.step_func(function(request) {
+ request.headers.forEach(t.step_func(function(header) {
+ assert_false(
+ !!freshness_headers[header[0]],
+ header[0] + ' header must not be set in the ' +
+ 'FetchEvent\'s request. (url = ' + request.url + ')');
+ }));
+ }))
+ service_worker_unregister_and_done(t, SCOPE);
+ })
+ .catch(unreached_rejection(t));
+ }, 'The headers of FetchEvent shouldn\'t contain freshness headers.');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-redirect.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-redirect.https.html
new file mode 100644
index 000000000..25d4a6ef3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-redirect.https.html
@@ -0,0 +1,372 @@
+<!DOCTYPE html>
+<title>Service Worker: FetchEvent for resources</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.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>
+<script>
+
+function assert_resolves(promise, description) {
+ return promise.catch(function(reason) {
+ throw new Error(description + ' - ' + reason.message);
+ });
+}
+
+function assert_rejects(promise, description) {
+ return promise.then(
+ function() { throw new Error(description); },
+ function() {});
+}
+
+function iframe_test(url, timeout_enabled) {
+ return new Promise(function(resolve, reject) {
+ var frame = document.createElement('iframe');
+ frame.src = url;
+ if (timeout_enabled) {
+ // We can't catch the network error on iframe. So we use the timer for
+ // failure detection.
+ var timer = setTimeout(function() {
+ reject(new Error('iframe load timeout'));
+ frame.remove();
+ }, 10000);
+ }
+ frame.onload = function() {
+ if (timeout_enabled)
+ clearTimeout(timer);
+ if (frame.contentDocument.body.textContent == 'Hello world\n')
+ resolve();
+ else
+ reject(new Error('content mismatch'));
+ frame.remove();
+ };
+ document.body.appendChild(frame);
+ });
+}
+
+promise_test(function(t) {
+ var SCOPE = 'resources/fetch-request-redirect-iframe.html';
+ var SCRIPT = 'resources/fetch-rewrite-worker.js';
+ var REDIRECT_URL = base_path() + 'resources/redirect.py?Redirect=';
+ var IMAGE_URL = base_path() + 'resources/square.png';
+ var AUDIO_URL = base_path() + 'resources/silence.oga';
+ var XHR_URL = base_path() + 'resources/simple.txt';
+ var HTML_URL = base_path() + 'resources/dummy.html';
+
+ var REDIRECT_TO_IMAGE_URL = REDIRECT_URL + encodeURIComponent(IMAGE_URL);
+ var REDIRECT_TO_AUDIO_URL = REDIRECT_URL + encodeURIComponent(AUDIO_URL);
+ var REDIRECT_TO_XHR_URL = REDIRECT_URL + encodeURIComponent(XHR_URL);
+ var REDIRECT_TO_HTML_URL = REDIRECT_URL + encodeURIComponent(HTML_URL);
+
+ var worker;
+ var frame;
+ return service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(f) {
+ frame = f;
+ return Promise.all([
+ // XMLHttpRequest tests.
+ assert_resolves(frame.contentWindow.xhr(XHR_URL),
+ 'Normal XHR should succeed.'),
+ assert_resolves(frame.contentWindow.xhr(REDIRECT_TO_XHR_URL),
+ 'Redirected XHR should succeed.'),
+ assert_resolves(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
+ '&redirect-mode=follow'),
+ 'Redirected XHR with Request.redirect=follow should succeed.'),
+ assert_rejects(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
+ '&redirect-mode=error'),
+ 'Redirected XHR with Request.redirect=error should fail.'),
+ assert_rejects(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
+ '&redirect-mode=manual'),
+ 'Redirected XHR with Request.redirect=manual should fail.'),
+
+ // Image loading tests.
+ assert_resolves(frame.contentWindow.load_image(IMAGE_URL),
+ 'Normal image resource should be loaded.'),
+ assert_resolves(
+ frame.contentWindow.load_image(REDIRECT_TO_IMAGE_URL),
+ 'Redirected image resource should be loaded.'),
+ assert_resolves(
+ frame.contentWindow.load_image(
+ './?url=' + encodeURIComponent(REDIRECT_TO_IMAGE_URL) +
+ '&redirect-mode=follow'),
+ 'Loading redirected image with Request.redirect=follow should' +
+ ' succeed.'),
+ assert_rejects(
+ frame.contentWindow.load_image(
+ './?url=' + encodeURIComponent(REDIRECT_TO_IMAGE_URL) +
+ '&redirect-mode=error'),
+ 'Loading redirected image with Request.redirect=error should ' +
+ 'fail.'),
+ assert_rejects(
+ frame.contentWindow.load_image(
+ './?url=' + encodeURIComponent(REDIRECT_TO_IMAGE_URL) +
+ '&redirect-mode=manual'),
+ 'Loading redirected image with Request.redirect=manual should' +
+ ' fail.'),
+
+ // Audio loading tests.
+ assert_resolves(frame.contentWindow.load_audio(AUDIO_URL),
+ 'Normal audio resource should be loaded.'),
+ assert_resolves(
+ frame.contentWindow.load_audio(REDIRECT_TO_AUDIO_URL),
+ 'Redirected audio resource should be loaded.'),
+ assert_resolves(
+ frame.contentWindow.load_audio(
+ './?url=' + encodeURIComponent(REDIRECT_TO_AUDIO_URL) +
+ '&redirect-mode=follow'),
+ 'Loading redirected audio with Request.redirect=follow should' +
+ ' succeed.'),
+ assert_rejects(
+ frame.contentWindow.load_audio(
+ './?url=' + encodeURIComponent(REDIRECT_TO_AUDIO_URL) +
+ '&redirect-mode=error'),
+ 'Loading redirected audio with Request.redirect=error should ' +
+ 'fail.'),
+ assert_rejects(
+ frame.contentWindow.load_audio(
+ './?url=' + encodeURIComponent(REDIRECT_TO_AUDIO_URL) +
+ '&redirect-mode=manual'),
+ 'Loading redirected audio with Request.redirect=manual should' +
+ ' fail.'),
+
+ // Iframe tests.
+ assert_resolves(iframe_test(HTML_URL),
+ 'Normal iframe loading should succeed.'),
+ assert_resolves(
+ iframe_test(REDIRECT_TO_HTML_URL),
+ 'Normal redirected iframe loading should succeed.'),
+ assert_rejects(
+ iframe_test(SCOPE + '?url=' +
+ encodeURIComponent(REDIRECT_TO_HTML_URL) +
+ '&redirect-mode=follow',
+ true /* timeout_enabled */),
+ 'Redirected iframe loading with Request.redirect=follow should'+
+ ' fail.'),
+ assert_rejects(
+ iframe_test(SCOPE + '?url=' +
+ encodeURIComponent(REDIRECT_TO_HTML_URL) +
+ '&redirect-mode=error',
+ true /* timeout_enabled */),
+ 'Redirected iframe loading with Request.redirect=error should '+
+ 'fail.'),
+ assert_resolves(
+ iframe_test(SCOPE + '?url=' +
+ encodeURIComponent(REDIRECT_TO_HTML_URL) +
+ '&redirect-mode=manual',
+ true /* timeout_enabled */),
+ 'Redirected iframe loading with Request.redirect=manual should'+
+ ' succeed.'),
+ ]);
+ })
+ .then(function() {
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ });
+ }, 'Verify redirect mode of Fetch API and ServiceWorker FetchEvent.');
+
+// test for reponse.redirected
+promise_test(function(t) {
+ var SCOPE = 'resources/fetch-request-redirect-iframe.html';
+ var SCRIPT = 'resources/fetch-rewrite-worker.js';
+ var REDIRECT_URL = base_path() + 'resources/redirect.py?Redirect=';
+ var XHR_URL = base_path() + 'resources/simple.txt';
+ var IMAGE_URL = base_path() + 'resources/square.png';
+
+ var REDIRECT_TO_XHR_URL = REDIRECT_URL + encodeURIComponent(XHR_URL);
+
+ var host_info = get_host_info();
+
+ var CROSS_ORIGIN_URL = host_info['HTTPS_REMOTE_ORIGIN'] + IMAGE_URL;
+
+ var REDIRECT_TO_CROSS_ORIGIN = REDIRECT_URL +
+ encodeURIComponent(CROSS_ORIGIN_URL + '?ACAOrigin=*');
+
+ var worker;
+ var frame;
+ return service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(f) {
+ frame = f;
+ return Promise.all([
+ // XMLHttpRequest tests.
+ assert_resolves(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(XHR_URL) +
+ '&expected_redirected=false' +
+ '&expected_resolves=true'),
+ 'Normal XHR should be resolved and response should not be ' +
+ 'redirected.'),
+ assert_resolves(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
+ '&expected_redirected=true' +
+ '&expected_resolves=true'),
+ 'Redirected XHR should be resolved and response should be ' +
+ 'redirected.'),
+
+ // tests for request's mode = cors
+ assert_resolves(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(XHR_URL) +
+ '&mode=cors' +
+ '&expected_redirected=false' +
+ '&expected_resolves=true'),
+ 'Normal XHR should be resolved and response should not be ' +
+ 'redirected even with CORS mode.'),
+ assert_resolves(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
+ '&mode=cors' +
+ '&redirect-mode=follow' +
+ '&expected_redirected=true' +
+ '&expected_resolves=true'),
+ 'Redirected XHR should be resolved and response.redirected ' +
+ 'should be redirected with CORS mode.'),
+
+ // tests for request's mode = no-cors
+ // The response.redirect should be false since we will not add
+ // redirected url list when redirect-mode is not follow.
+ assert_rejects(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
+ '&mode=no-cors' +
+ '&redirect-mode=manual' +
+ '&expected_redirected=false' +
+ '&expected_resolves=false'),
+ 'Redirected XHR should be reject and response should be ' +
+ 'redirected with NO-CORS mode and redirect-mode=manual.'),
+
+ // tests for redirecting to a cors
+ assert_resolves(
+ frame.contentWindow.load_image(
+ './?url=' + encodeURIComponent(REDIRECT_TO_CROSS_ORIGIN) +
+ '&mode=no-cors' +
+ '&redirect-mode=follow' +
+ '&expected_redirected=false' +
+ '&expected_resolves=true'),
+ 'Redirected COS image should be reject and response should ' +
+ 'not be redirected with NO-CORS mode.'),
+ ]);
+ })
+ .then(function() {
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ });
+ }, 'Verify redirected of Response(Fetch API) and ServiceWorker FetchEvent.');
+
+// test for reponse.redirected after cached
+promise_test(function(t) {
+ var SCOPE = 'resources/fetch-request-redirect-iframe.html';
+ var SCRIPT = 'resources/fetch-rewrite-worker.js';
+ var REDIRECT_URL = base_path() + 'resources/redirect.py?Redirect=';
+ var XHR_URL = base_path() + 'resources/simple.txt';
+ var IMAGE_URL = base_path() + 'resources/square.png';
+
+ var REDIRECT_TO_XHR_URL = REDIRECT_URL + encodeURIComponent(XHR_URL);
+
+ var host_info = get_host_info();
+
+ var CROSS_ORIGIN_URL = host_info['HTTPS_REMOTE_ORIGIN'] + IMAGE_URL;
+
+ var REDIRECT_TO_CROSS_ORIGIN = REDIRECT_URL +
+ encodeURIComponent(CROSS_ORIGIN_URL + '?ACAOrigin=*');
+
+ var worker;
+ var frame;
+ return service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(f) {
+ frame = f;
+ return Promise.all([
+ // XMLHttpRequest tests.
+ assert_resolves(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(XHR_URL) +
+ '&expected_redirected=false' +
+ '&expected_resolves=true' +
+ '&cache'),
+ 'Normal XHR should be resolved and response should not be ' +
+ 'redirected.'),
+ assert_resolves(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
+ '&expected_redirected=true' +
+ '&expected_resolves=true' +
+ '&cache'),
+ 'Redirected XHR should be resolved and response should be ' +
+ 'redirected.'),
+
+ // tests for request's mode = cors
+ assert_resolves(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(XHR_URL) +
+ '&mode=cors' +
+ '&expected_redirected=false' +
+ '&expected_resolves=true' +
+ '&cache'),
+ 'Normal XHR should be resolved and response should not be ' +
+ 'redirected even with CORS mode.'),
+ assert_resolves(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
+ '&mode=cors' +
+ '&redirect-mode=follow' +
+ '&expected_redirected=true' +
+ '&expected_resolves=true' +
+ '&cache'),
+ 'Redirected XHR should be resolved and response.redirected ' +
+ 'should be redirected with CORS mode.'),
+
+ // tests for request's mode = no-cors
+ // The response.redirect should be false since we will not add
+ // redirected url list when redirect-mode is not follow.
+ assert_rejects(
+ frame.contentWindow.xhr(
+ './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
+ '&mode=no-cors' +
+ '&redirect-mode=manual' +
+ '&expected_redirected=false' +
+ '&expected_resolves=false' +
+ '&cache'),
+ 'Redirected XHR should be reject and response should be ' +
+ 'redirected with NO-CORS mode and redirect-mode=manual.'),
+
+ // tests for redirecting to a cors
+ assert_resolves(
+ frame.contentWindow.load_image(
+ './?url=' + encodeURIComponent(REDIRECT_TO_CROSS_ORIGIN) +
+ '&mode=no-cors' +
+ '&redirect-mode=follow' +
+ '&expected_redirected=false' +
+ '&expected_resolves=true' +
+ '&cache'),
+ 'Redirected COS image should be reject and response should ' +
+ 'not be redirected with NO-CORS mode.'),
+ ]);
+ })
+ .then(function() {
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ });
+ }, 'Verify redirected of Response(Fetch API), Cache API and ServiceWorker ' +
+ 'FetchEvent.');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-resources.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-resources.https.html
new file mode 100644
index 000000000..7a64e9444
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-resources.https.html
@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<title>Service Worker: FetchEvent for resources</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+var url_count = 0;
+var expected_results = {};
+
+function image_test(frame, url, cross_origin, expected_mode,
+ expected_credentials) {
+ var actual_url = url + (++url_count);
+ expected_results[actual_url] = {
+ cross_origin: cross_origin,
+ mode: expected_mode,
+ credentials: expected_credentials,
+ integrity: '',
+ message: 'Image load (url:' +
+ actual_url + ' cross_origin:' + cross_origin + ')'
+ };
+ return frame.contentWindow.load_image(actual_url, cross_origin);
+}
+
+function script_test(frame, url, cross_origin, expected_mode,
+ expected_credentials) {
+ var actual_url = url + (++url_count);
+ expected_results[actual_url] = {
+ cross_origin: cross_origin,
+ mode: expected_mode,
+ credentials: expected_credentials,
+ integrity: '',
+ message: 'Script load (url:' +
+ actual_url + ' cross_origin:' + cross_origin + ')'
+ };
+ return frame.contentWindow.load_script(actual_url, cross_origin);
+}
+
+function css_test(frame, url, cross_origin, expected_mode,
+ expected_credentials) {
+ var actual_url = url + (++url_count);
+ expected_results[actual_url] = {
+ cross_origin: cross_origin,
+ mode: expected_mode,
+ credentials: expected_credentials,
+ integrity: '',
+ message: 'CSS load (url:' +
+ actual_url + ' cross_origin:' + cross_origin + ')'
+ };
+ return frame.contentWindow.load_css(actual_url, cross_origin);
+}
+
+function font_face_test(frame, url, expected_mode, expected_credentials) {
+ var actual_url = url + (++url_count);
+ expected_results[actual_url] = {
+ url: actual_url,
+ mode: expected_mode,
+ credentials: expected_credentials,
+ integrity: '',
+ message: 'FontFace load (url:' + actual_url + ')'
+ };
+ return frame.contentWindow.load_font(actual_url);
+}
+
+function script_integrity_test(frame, url, integrity, expected_integrity) {
+ var actual_url = url + (++url_count);
+ expected_results[actual_url] = {
+ url: actual_url,
+ mode: 'no-cors',
+ credentials: 'include',
+ integrity: expected_integrity,
+ message: 'Script load (url:' + actual_url + ')'
+ };
+ return frame.contentWindow.load_script_with_integrity(actual_url, integrity);
+}
+
+function css_integrity_test(frame, url, integrity, expected_integrity) {
+ var actual_url = url + (++url_count);
+ expected_results[actual_url] = {
+ url: actual_url,
+ mode: 'no-cors',
+ credentials: 'include',
+ integrity: expected_integrity,
+ message: 'CSS load (url:' + actual_url + ')'
+ };
+ return frame.contentWindow.load_css_with_integrity(actual_url, integrity);
+}
+
+async_test(function(t) {
+ var SCOPE = 'resources/fetch-request-resources-iframe.https.html';
+ var SCRIPT = 'resources/fetch-request-resources-worker.js';
+ var host_info = get_host_info();
+ var LOCAL_URL =
+ host_info['HTTPS_ORIGIN'] + base_path() + 'resources/dummy?test';
+ var REMOTE_URL =
+ host_info['HTTPS_REMOTE_ORIGIN'] + base_path() + 'resources/dummy?test';
+ var worker;
+ var frame;
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ worker = registration.installing;
+ return wait_for_state(t, worker, 'activated');
+ })
+ .then(function() {
+ return new Promise(function(resolve) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(msg) {
+ if (msg.data.ready) {
+ resolve();
+ return;
+ }
+ var result = msg.data;
+ var expected = expected_results[result.url];
+ if (!expected) {
+ return;
+ }
+ assert_equals(
+ result.mode, expected.mode,
+ 'mode of ' + expected.message + ' must be ' +
+ expected.mode + '.');
+ assert_equals(
+ result.credentials, expected.credentials,
+ 'credentials of ' + expected.message + ' must be ' +
+ expected.credentials + '.');
+ assert_equals(
+ result.integrity, expected.integrity,
+ 'integrity of ' + expected.message + ' must be ' +
+ expected.integrity + '.');
+ --url_count;
+ delete expected_results[result.url];
+ if (url_count == 0) {
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ }
+ });
+ worker.postMessage(
+ {port: channel.port2}, [channel.port2]);
+ });
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(f) {
+ frame = f;
+
+ image_test(f, LOCAL_URL, '', 'no-cors', 'include');
+ image_test(f, REMOTE_URL, '', 'no-cors', 'include');
+ css_test(f, LOCAL_URL, '', 'no-cors', 'include');
+ css_test(f, REMOTE_URL, '', 'no-cors', 'include');
+
+ image_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
+ image_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
+ image_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
+ image_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
+
+ script_test(f, LOCAL_URL, '', 'no-cors', 'include');
+ script_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
+ script_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
+ script_test(f, REMOTE_URL, '', 'no-cors', 'include');
+ script_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
+ script_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
+
+ css_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
+ css_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
+ css_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
+ css_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
+
+ font_face_test(f, LOCAL_URL, 'cors', 'same-origin');
+ font_face_test(f, REMOTE_URL, 'cors', 'same-origin');
+
+ script_integrity_test(f, LOCAL_URL, ' ', ' ');
+ script_integrity_test(f, LOCAL_URL,
+ 'This is not a valid integrity because it has no dashes',
+ 'This is not a valid integrity because it has no dashes');
+ script_integrity_test(f, LOCAL_URL, 'sha256-', 'sha256-');
+ script_integrity_test(f, LOCAL_URL, 'sha256-foo?123', 'sha256-foo?123');
+ script_integrity_test(f, LOCAL_URL, 'sha256-foo sha384-abc ', 'sha256-foo sha384-abc ');
+ script_integrity_test(f, LOCAL_URL, 'sha256-foo sha256-abc', 'sha256-foo sha256-abc');
+
+ css_integrity_test(f, LOCAL_URL, ' ', ' ');
+ css_integrity_test(f, LOCAL_URL,
+ 'This is not a valid integrity because it has no dashes',
+ 'This is not a valid integrity because it has no dashes');
+ css_integrity_test(f, LOCAL_URL, 'sha256-', 'sha256-');
+ css_integrity_test(f, LOCAL_URL, 'sha256-foo?123', 'sha256-foo?123');
+ css_integrity_test(f, LOCAL_URL, 'sha256-foo sha384-abc ', 'sha256-foo sha384-abc ');
+ css_integrity_test(f, LOCAL_URL, 'sha256-foo sha256-abc', 'sha256-foo sha256-abc');
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify FetchEvent for resources.');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-xhr.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-xhr.https.html
new file mode 100644
index 000000000..1b80985a3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-xhr.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Service Worker: the body of FetchEvent using XMLHttpRequest</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+async_test(function(t) {
+ var SCOPE = 'resources/fetch-request-xhr-iframe.https.html';
+ var SCRIPT = 'resources/fetch-request-xhr-worker.js';
+ var host_info = get_host_info();
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(frame) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(e) {
+ if (e.data.results === 'finish') {
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ } else if (e.data.results == 'equals') {
+ assert_equals(e.data.got, e.data.expected);
+ }
+ });
+ frame.contentWindow.postMessage({},
+ host_info['HTTPS_ORIGIN'],
+ [channel.port2]);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify the body of FetchEvent using XMLHttpRequest');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-response-xhr.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-response-xhr.https.html
new file mode 100644
index 000000000..8b66c6051
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-response-xhr.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Service Worker: the response of FetchEvent using XMLHttpRequest</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+async_test(function(t) {
+ var SCOPE = 'resources/fetch-response-xhr-iframe.https.html';
+ var SCRIPT = 'resources/fetch-response-xhr-worker.js';
+ var host_info = get_host_info();
+
+ window.addEventListener('message', t.step_func(on_message), false);
+ function on_message(e) {
+ assert_equals(e.data.results, 'foo, bar');
+ t.done();
+ }
+
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(frame) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data.results, 'finish');
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ });
+ frame.contentWindow.postMessage({},
+ host_info['HTTPS_ORIGIN'],
+ [channel.port2]);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify the response of FetchEvent using XMLHttpRequest');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-waits-for-activate.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-waits-for-activate.https.html
new file mode 100644
index 000000000..304680c97
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/fetch-waits-for-activate.https.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<title>Service Worker: Fetch Event Waits for Activate Event</title>
+<meta name=timeout content=long>
+<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>
+
+var worker = 'resources/fetch-waits-for-activate-worker.js';
+var expected_url = normalizeURL(worker);
+var scope = 'resources/fetch-waits-for-activate/';
+
+async_test(function(t) {
+ var registration;
+ var frameLoadPromise;
+ var frame;
+ service_worker_unregister_and_register(t, worker, scope).then(function(reg) {
+ registration = reg;
+ return wait_for_state(t, reg.installing, 'activating');
+ }).then(function() {
+ assert_equals(registration.active.scriptURL, expected_url,
+ 'active worker should be present');
+ assert_equals(registration.active.state, 'activating',
+ 'active worker should be in activating state');
+
+ // This should block until we message the worker to tell it to complete
+ // the activate event.
+ frameLoadPromise = with_iframe(scope).then(function(f) {
+ frame = f;
+ });
+
+ // Wait some time to allow frame loading to proceed. It should not,
+ // however, if the fetch event is blocked on the activate. I don't
+ // see any way to force this race without a timeout, unfortunately.
+ return new Promise(function(resolve) {
+ setTimeout(resolve, 1000);
+ });
+ }).then(function() {
+ assert_equals(frame, undefined, 'frame should not be loaded');
+ assert_equals(registration.active.scriptURL, expected_url,
+ 'active worker should be present');
+ assert_equals(registration.active.state, 'activating',
+ 'active worker should be in activating state');
+
+ // This signals the activate event to complete. The frame should now
+ // load.
+ registration.active.postMessage('GO');
+ return frameLoadPromise;
+ }).then(function() {
+ assert_equals(frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
+ expected_url, 'frame should now be loaded and controlled');
+ assert_equals(registration.active.state, 'activated',
+ 'active worker should be in activated state');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ }).catch(unreached_rejection(t));
+}, 'Fetch events should wait for the activate event to complete.');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/getregistration.https.html b/testing/web-platform/tests/service-workers/service-worker/getregistration.https.html
new file mode 100644
index 000000000..c86fda50d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/getregistration.https.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+async_test(function(t) {
+ var documentURL = 'no-such-worker';
+ navigator.serviceWorker.getRegistration(documentURL)
+ .then(function(value) {
+ assert_equals(value, undefined,
+ 'getRegistration should resolve with undefined');
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'getRegistration');
+
+async_test(function(t) {
+ var scope = 'resources/scope/getregistration/normal';
+ var registration;
+ service_worker_unregister_and_register(t, 'resources/empty-worker.js',
+ scope)
+ .then(function(r) {
+ registration = r;
+ return navigator.serviceWorker.getRegistration(scope);
+ })
+ .then(function(value) {
+ assert_equals(
+ value, registration,
+ 'getRegistration should resolve to the same registration object');
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Register then getRegistration');
+
+async_test(function(t) {
+ var scope = 'resources/scope/getregistration/url-with-fragment';
+ var documentURL = scope + '#ref';
+ var registration;
+ service_worker_unregister_and_register(t, 'resources/empty-worker.js',
+ scope)
+ .then(function(r) {
+ registration = r;
+ return navigator.serviceWorker.getRegistration(documentURL);
+ })
+ .then(function(value) {
+ assert_equals(
+ value, registration,
+ 'getRegistration should resolve to the same registration object');
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Register then getRegistration with a URL having a fragment');
+
+async_test(function(t) {
+ var documentURL = 'http://example.com/';
+ navigator.serviceWorker.getRegistration(documentURL)
+ .then(function() {
+ assert_unreached(
+ 'getRegistration with an out of origin URL should fail');
+ }, function(reason) {
+ assert_equals(
+ reason.name, 'SecurityError',
+ 'getRegistration with an out of origin URL should fail');
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'getRegistration with a cross origin URL');
+
+async_test(function(t) {
+ var scope = 'resources/scope/getregistration/register-unregister';
+ service_worker_unregister_and_register(t, 'resources/empty-worker.js',
+ scope)
+ .then(function(registration) {
+ return registration.unregister();
+ })
+ .then(function() {
+ return navigator.serviceWorker.getRegistration(scope);
+ })
+ .then(function(value) {
+ assert_equals(value, undefined,
+ 'getRegistration should resolve with undefined');
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Register then Unregister then getRegistration');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/getregistrations.https.html b/testing/web-platform/tests/service-workers/service-worker/getregistrations.https.html
new file mode 100644
index 000000000..b65a97869
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/getregistrations.https.html
@@ -0,0 +1,191 @@
+<!DOCTYPE html>
+<title>Service Worker: getRegistrations()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="../fetch/resources/fetch-test-helpers.sub.js"></script>
+<script>
+// Purge the existing registrations for the origin.
+// getRegistrations() is used in order to avoid adding additional complexity
+// e.g. adding an internal function.
+promise_test(function(t) {
+ return navigator.serviceWorker.getRegistrations()
+ .then(function(registrations) {
+ return registrations.reduce(function(sequence, registration) {
+ return sequence.then(function() {
+ return registration.unregister();
+ });
+ }, Promise.resolve());
+ });
+ }, 'Purge the existing registrations.');
+
+promise_test(function(t) {
+ return navigator.serviceWorker.getRegistrations()
+ .then(function(value) {
+ assert_array_equals(
+ value,
+ [],
+ 'getRegistrations should resolve with an empty array.');
+ });
+ }, 'getRegistrations');
+
+promise_test(function(t) {
+ var scope = 'resources/scope/getregistrations/normal';
+ var script = 'resources/empty-worker.js';
+ var registrations = [];
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(r) {
+ registrations.push(r);
+ return navigator.serviceWorker.getRegistrations();
+ })
+ .then(function(value) {
+ assert_array_equals(
+ value,
+ registrations,
+ 'getRegistrations should resolve with array of registrations.');
+ return service_worker_unregister(t, scope);
+ });
+ }, 'Register then getRegistrations');
+
+promise_test(function(t) {
+ var scope1 = 'resources/scope/getregistrations/scope1';
+ var scope2 = 'resources/scope/getregistrations/scope2';
+ var script = 'resources/empty-worker.js';
+ var registrations = [];
+ return service_worker_unregister_and_register(t, script, scope1)
+ .then(function(r) {
+ registrations.push(r);
+ return service_worker_unregister_and_register(t, script, scope2);
+ })
+ .then(function(r) {
+ registrations.push(r);
+ return navigator.serviceWorker.getRegistrations();
+ })
+ .then(function(value) {
+ assert_array_equals(
+ value,
+ registrations,
+ 'getRegistrations should resolve with array of registrations.');
+ return service_worker_unregister(t, scope1);
+ })
+ .then(function() {
+ return service_worker_unregister(t, scope2);
+ });
+ }, 'Register multiple times then getRegistrations');
+
+promise_test(function(t) {
+ var scope = 'resources/scope/getregistrations/register-unregister';
+ var script = 'resources/empty-worker.js';
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return registration.unregister();
+ })
+ .then(function() {
+ return navigator.serviceWorker.getRegistrations();
+ })
+ .then(function(value) {
+ assert_array_equals(
+ value,
+ [],
+ 'getRegistrations should resolve with an empty array.');
+ });
+ }, 'Register then Unregister then getRegistrations');
+
+promise_test(function(t) {
+ var scope = 'resources/scope/getregistrations/register-unregister-controlled';
+ var script = 'resources/empty-worker.js';
+ var registrations;
+ var frame;
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ return registration.unregister();
+ })
+ .then(function() {
+ return navigator.serviceWorker.getRegistrations();
+ })
+ .then(function(value) {
+ assert_array_equals(
+ value,
+ [],
+ 'getRegistrations should resolve with an empty array.');
+ assert_equals(registration.installing, null);
+ assert_equals(registration.waiting, null);
+ assert_equals(registration.active.state, 'activated');
+ frame.remove();
+ });
+ }, 'Register then Unregister with controlled frame then getRegistrations');
+
+promise_test(function(t) {
+ var host_info = get_host_info();
+ // Rewrite the url to point to remote origin.
+ var frame_same_origin_url = new URL("resources/frame-for-getregistrations.html", window.location);
+ var frame_url = host_info['HTTPS_REMOTE_ORIGIN'] + frame_same_origin_url.pathname;
+ var scope = 'resources/scope-for-getregistrations';
+ var script = 'resources/empty-worker.js';
+ var frame;
+ var registrations = [];
+
+ // Loads an iframe and waits for 'ready' message from it to resolve promise.
+ // Caller is responsible for removing frame.
+ function with_iframe_ready(url) {
+ return new Promise(function(resolve) {
+ var frame = document.createElement('iframe');
+ frame.src = url;
+ window.addEventListener('message', function onMessage(e) {
+ window.removeEventListener('message', onMessage);
+ if (e.data == 'ready') {
+ resolve(frame);
+ }
+ });
+ document.body.appendChild(frame);
+ });
+ }
+
+ // We need this special frame loading function because the frame is going
+ // to register it's own service worker and there is the possibility that that
+ // register() finishes after the register() for the same domain later in the
+ // test. So we have to wait until the cross origin register() is done, and not
+ // just until the frame loads.
+ return with_iframe_ready(frame_url)
+ .then(function(f) {
+ frame = f;
+ return service_worker_unregister_and_register(t, script, scope);
+ })
+ .then(function(r) {
+ registrations.push(r);
+ return navigator.serviceWorker.getRegistrations();
+ })
+ .then(function(value) {
+ assert_array_equals(
+ value,
+ registrations,
+ 'getRegistrations should only return same origin registrations.');
+
+ var channel = new MessageChannel();
+ var resolve;
+ var p = new Promise(function(r) { resolve = r; });
+
+ channel.port1.onmessage = function(e) {
+ if (e.data == 'unregistered')
+ resolve();
+ };
+ frame.contentWindow.postMessage('unregister', '*', [channel.port2]);
+ return p;
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister(t, scope);
+ });
+ }, 'getRegistrations promise resolves only with same origin registrations.');
+
+done();
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/indexeddb.https.html b/testing/web-platform/tests/service-workers/service-worker/indexeddb.https.html
new file mode 100644
index 000000000..4de2bc43e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/indexeddb.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Service Worker: Indexed DB</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+async_test(function(t) {
+ var scope = 'resources/blank.html';
+ service_worker_unregister_and_register(
+ t, 'resources/indexeddb-worker.js', scope)
+ .then(function(registration) {
+ var sw = registration.installing;
+ var messageChannel = new MessageChannel();
+ messageChannel.port1.onmessage = t.step_func(onMessage);
+ sw.postMessage({port: messageChannel.port2}, [messageChannel.port2]);
+ })
+ .catch(unreached_rejection(t));
+
+ function onMessage() {
+ var openRequest = indexedDB.open('db');
+ openRequest.onsuccess = t.step_func(function() {
+ var db = openRequest.result;
+ var tx = db.transaction('store');
+ var store = tx.objectStore('store');
+ var getRequest = store.get('key');
+ getRequest.onsuccess = t.step_func(function() {
+ assert_equals(
+ getRequest.result, 'value',
+ 'The get() result should match what the worker put().');
+ service_worker_unregister_and_done(t, scope);
+ });
+ });
+ }
+ }, 'Verify Indexed DB operation in a Service Worker');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/install-event-type.https.html b/testing/web-platform/tests/service-workers/service-worker/install-event-type.https.html
new file mode 100644
index 000000000..7e74af85c
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/install-event-type.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+function wait_for_install_event(worker) {
+ return new Promise(function(resolve) {
+ worker.addEventListener('statechange', function(event) {
+ if (worker.state == 'installed')
+ resolve(true);
+ else if (worker.state == 'redundant')
+ resolve(false);
+ });
+ });
+}
+
+promise_test(function(t) {
+ var script = 'resources/install-event-type-worker.js';
+ var scope = 'resources/install-event-type';
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return wait_for_install_event(registration.installing);
+ })
+ .then(function(did_install) {
+ assert_true(did_install, 'The worker was installed');
+ })
+ }, 'install event type');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/installing.https.html b/testing/web-platform/tests/service-workers/service-worker/installing.https.html
new file mode 100644
index 000000000..57d878111
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/installing.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>ServiceWorker: navigator.serviceWorker.installing</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+// "installing" is set
+async_test(function(t) {
+ var step = t.step_func.bind(t);
+ var url = 'resources/empty-worker.js';
+ var scope = 'resources/blank.html';
+ var frame;
+
+ service_worker_unregister(t, scope)
+ .then(step(function() { return with_iframe(scope); }))
+ .then(step(function(f) {
+ frame = f;
+ return navigator.serviceWorker.register(url, {scope: scope});
+ }))
+ .then(step(function(registration) {
+ var container = frame.contentWindow.navigator.serviceWorker;
+ assert_equals(container.controller, null);
+ assert_equals(registration.active, null);
+ assert_equals(registration.waiting, null);
+ assert_equals(registration.installing.scriptURL, normalizeURL(url));
+
+ // FIXME: Add a test for a frame created after installation.
+ // Should the existing frame ("frame") block activation?
+ }))
+ .then(step(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ }))
+ .catch(unreached_rejection(t));
+}, 'installing is set');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/interfaces.https.html b/testing/web-platform/tests/service-workers/service-worker/interfaces.https.html
new file mode 100644
index 000000000..403a00534
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/interfaces.https.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>Service Worker: Interfaces</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/interfaces.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+test(function() {
+ var EVENT_HANDLER = 'object';
+ verify_interface(
+ 'ServiceWorkerContainer', navigator.serviceWorker,
+ {
+ register: 'function',
+ getRegistration: 'function',
+ oncontrollerchange: EVENT_HANDLER
+ });
+ }, 'Interfaces and attributes of ServiceWorkerContainer');
+
+async_test(function(t) {
+ var EVENT_HANDLER = 'object';
+ var scope = 'resources/scope/interfaces-and-attributes';
+
+ service_worker_unregister_and_register(
+ t, 'resources/empty-worker.js', scope)
+ .then(function(registration) {
+ verify_interface(
+ 'ServiceWorkerRegistration', registration,
+ {
+ installing: 'object',
+ waiting: 'object',
+ active: 'object',
+ scope: 'string',
+ unregister: 'function',
+ onupdatefound: EVENT_HANDLER
+ });
+ verify_interface(
+ 'ServiceWorker', registration.installing,
+ {
+ scriptURL: 'string',
+ state: 'string',
+ onstatechange: EVENT_HANDLER
+ });
+ return registration.unregister();
+ })
+ .then(function() {
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Interfaces and attributes of ServiceWorker');
+
+service_worker_test(
+ 'resources/interfaces-worker.sub.js',
+ 'Interfaces and attributes in ServiceWorkerGlobalScope');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/invalid-blobtype.https.html b/testing/web-platform/tests/service-workers/service-worker/invalid-blobtype.https.html
new file mode 100644
index 000000000..f1f2d1bdc
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/invalid-blobtype.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Service Worker: respondWith with header value containing a null byte</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+async_test(function(t) {
+ var SCOPE = 'resources/invalid-blobtype-iframe.https.html';
+ var SCRIPT = 'resources/invalid-blobtype-worker.js';
+ var host_info = get_host_info();
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(frame) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data.results, 'finish');
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ });
+ frame.contentWindow.postMessage({},
+ host_info['HTTPS_ORIGIN'],
+ [channel.port2]);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify the response of FetchEvent using XMLHttpRequest');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/invalid-header.https.html b/testing/web-platform/tests/service-workers/service-worker/invalid-header.https.html
new file mode 100644
index 000000000..794695264
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/invalid-header.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Service Worker: respondWith with header value containing a null byte</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+async_test(function(t) {
+ var SCOPE = 'resources/invalid-header-iframe.https.html';
+ var SCRIPT = 'resources/invalid-header-worker.js';
+ var host_info = get_host_info();
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(frame) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data.results, 'finish');
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ });
+ frame.contentWindow.postMessage({},
+ host_info['HTTPS_ORIGIN'],
+ [channel.port2]);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify the response of FetchEvent using XMLHttpRequest');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/multiple-register.https.html b/testing/web-platform/tests/service-workers/service-worker/multiple-register.https.html
new file mode 100644
index 000000000..1089cffda
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/multiple-register.https.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+var worker_url = 'resources/empty-worker.js';
+
+async_test(function(t) {
+ var scope = 'resources/scope/subsequent-register-from-same-window';
+ var registration;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return navigator.serviceWorker.register(worker_url, { scope: scope });
+ })
+ .then(function(new_registration) {
+ assert_equals(new_registration, registration,
+ 'register should resolve to the same registration');
+ assert_equals(new_registration.active, registration.active,
+ 'register should resolve to the same worker');
+ assert_equals(new_registration.active.state, 'activated',
+ 'the worker should be in state "activated"');
+ return registration.unregister();
+ })
+ .then(function() { t.done(); })
+ .catch(unreached_rejection(t));
+}, 'Subsequent registrations resolve to the same registration object');
+
+async_test(function(t) {
+ var scope = 'resources/scope/subsequent-register-from-different-iframe';
+ var frame;
+ var registration;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() { return with_iframe('resources/404.py'); })
+ .then(function(f) {
+ frame = f;
+ return frame.contentWindow.navigator.serviceWorker.register(
+ worker_url, { scope: scope });
+ })
+ .then(function(new_registration) {
+ assert_not_equals(
+ registration, new_registration,
+ 'register should resolve to a different registration');
+ assert_equals(
+ registration.scope, new_registration.scope,
+ 'registrations should have the same scope');
+
+ assert_equals(
+ registration.installing, null,
+ 'installing worker should be null');
+ assert_equals(
+ new_registration.installing, null,
+ 'installing worker should be null');
+ assert_equals(
+ registration.waiting, null,
+ 'waiting worker should be null')
+ assert_equals(
+ new_registration.waiting, null,
+ 'waiting worker should be null')
+
+ assert_not_equals(
+ registration.active, new_registration.active,
+ 'registration should have the different active worker');
+ assert_equals(
+ registration.active.scriptURL,
+ new_registration.active.scriptURL,
+ 'active workers should have the same script URL');
+ assert_equals(
+ registration.active.state,
+ new_registration.active.state,
+ 'active workers should be in the same state');
+
+ frame.remove();
+ return registration.unregister();
+ })
+ .then(function() { t.done(); })
+ .catch(unreached_rejection(t));
+}, 'Subsequent registrations from a different iframe resolve to the ' +
+ 'different registration object but they refer to the same ' +
+ 'registration and workers');
+
+async_test(function(t) {
+ var scope = 'resources/scope/concurrent-register';
+
+ service_worker_unregister(t, scope)
+ .then(function() {
+ var promises = [];
+ for (var i = 0; i < 10; ++i) {
+ promises.push(navigator.serviceWorker.register(worker_url,
+ { scope: scope }));
+ }
+ return Promise.all(promises);
+ })
+ .then(function(registrations) {
+ registrations.forEach(function(registration) {
+ assert_equals(registration, registrations[0],
+ 'register should resolve to the same registration');
+ });
+ return registrations[0].unregister();
+ })
+ .then(function() { t.done(); })
+ .catch(unreached_rejection(t));
+}, 'Concurrent registrations resolve to the same registration object');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/multiple-update.https.html b/testing/web-platform/tests/service-workers/service-worker/multiple-update.https.html
new file mode 100644
index 000000000..84aac9583
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/multiple-update.https.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<!-- In Bug 1217367, we will try to merge update events for same registration
+ if possible. This testcase is used to make sure the optimization algorithm
+ doesn't go wrong. -->
+<title>Service Worker: Trigger multiple updates</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+promise_test(function(t) {
+ var script = 'resources/update-nocookie-worker.py';
+ var scope = 'resources/scope/update';
+ var expected_url = normalizeURL(script);
+ var registration;
+
+ return service_worker_unregister_and_register(t, expected_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ // Test single update works before triggering multiple update events
+ 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() {
+ // Test triggering multiple update events at the same time.
+ var promiseList = [];
+ const burstUpdateCount = 10;
+ for (var i = 0; i < burstUpdateCount; i++) {
+ promiseList.push(registration.update());
+ }
+ promiseList.push(wait_for_update(t, registration));
+ return Promise.all(promiseList);
+ })
+ .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() {
+ // Test update still works after handling update event burst.
+ 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.');
+
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Trigger multiple updates.');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/navigate-window.https.html b/testing/web-platform/tests/service-workers/service-worker/navigate-window.https.html
new file mode 100644
index 000000000..e3aaf4c5c
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/navigate-window.https.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<title>Service Worker: Navigate a Window</title>
+<script src="/resources/testharness.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>
+var host_info = get_host_info();
+var BASE_URL = host_info['HTTPS_ORIGIN'] + base_path();
+
+function wait_for_message(msg) {
+ return new Promise(function(resolve, reject) {
+ window.addEventListener('message', function onMsg(evt) {
+ if (evt.data.type === msg) {
+ resolve();
+ }
+ });
+ });
+}
+
+function with_window(url) {
+ var win = window.open(url);
+ return wait_for_message('LOADED').then(_ => win);
+}
+
+function navigate_window(win, url) {
+ win.location = url;
+ return wait_for_message('LOADED').then(_ => win);
+}
+
+function reload_window(win) {
+ win.location.reload();
+ return wait_for_message('LOADED').then(_ => win);
+}
+
+function go_back(win) {
+ win.history.back();
+ return wait_for_message('PAGESHOW').then(_ => win);
+}
+
+function go_forward(win) {
+ win.history.forward();
+ return wait_for_message('PAGESHOW').then(_ => win);
+}
+
+function get_clients(win, sw, opts) {
+ return new Promise((resolve, reject) => {
+ win.navigator.serviceWorker.addEventListener('message', function onMsg(evt) {
+ win.navigator.serviceWorker.removeEventListener('message', onMsg);
+ if (evt.data.type === 'success') {
+ resolve(evt.data.detail);
+ } else {
+ reject(evt.data.detail);
+ }
+ });
+ sw.postMessage({ type: 'GET_CLIENTS', opts: (opts || {}) });
+ });
+}
+
+function validate_window(win, url, opts) {
+ return win.navigator.serviceWorker.getRegistration(url)
+ .then(reg => {
+ // In order to compare service worker instances we need to
+ // make sure the DOM object is owned by the same global; the
+ // opened window in this case.
+ assert_equals(win.navigator.serviceWorker.controller, reg.active,
+ 'window should be controlled by service worker');
+ return get_clients(win, reg.active, opts);
+ })
+ .then(resultList => {
+ // We should always see our controlled window.
+ var expected = [
+ { url: url, frameType: 'auxiliary' }
+ ];
+ // If we are including uncontrolled windows, then we might see the
+ // test window itself and the test harness.
+ if (opts.includeUncontrolled) {
+ expected.push({ url: BASE_URL + 'navigate-window.https.html',
+ frameType: 'auxiliary' });
+ expected.push({ url: host_info['HTTPS_ORIGIN'] + '/testharness_runner.html',
+ frameType: 'top-level' });
+ }
+ assert_equals(resultList.length, expected.length,
+ 'expected number of clients');
+ for (var i = 0; i < resultList.length; ++i) {
+ assert_equals(resultList[i].url, expected[i].url,
+ 'client should have expected url');
+ assert_equals(resultList[i].frameType, expected[i].frameType,
+ ' client should have expected frame type');
+ }
+ return win;
+ })
+}
+
+promise_test(function(t) {
+ var worker = BASE_URL + 'resources/navigate-window-worker.js';
+ var scope = BASE_URL + 'resources/loaded.html?navigate-window-controlled';
+ var url1 = scope + '&q=1';
+ var url2 = scope + '&q=2';
+ return service_worker_unregister_and_register(t, worker, scope)
+ .then(reg => wait_for_state(t, reg.installing, 'activated') )
+ .then(___ => with_window(url1))
+ .then(win => validate_window(win, url1, { includeUncontrolled: false }))
+ .then(win => navigate_window(win, url2))
+ .then(win => validate_window(win, url2, { includeUncontrolled: false }))
+ .then(win => go_back(win))
+ .then(win => validate_window(win, url1, { includeUncontrolled: false }))
+ .then(win => go_forward(win))
+ .then(win => validate_window(win, url2, { includeUncontrolled: false }))
+ .then(win => reload_window(win))
+ .then(win => validate_window(win, url2, { includeUncontrolled: false }))
+ .then(win => win.close())
+ .catch(unreached_rejection(t))
+ .then(___ => service_worker_unregister(t, scope))
+ }, 'Clients.matchAll() should not show an old window as controlled after ' +
+ 'it navigates.');
+
+promise_test(function(t) {
+ var worker = BASE_URL + 'resources/navigate-window-worker.js';
+ var scope = BASE_URL + 'resources/loaded.html?navigate-window-uncontrolled';
+ var url1 = scope + '&q=1';
+ var url2 = scope + '&q=2';
+ return service_worker_unregister_and_register(t, worker, scope)
+ .then(reg => wait_for_state(t, reg.installing, 'activated') )
+ .then(___ => with_window(url1))
+ .then(win => validate_window(win, url1, { includeUncontrolled: true }))
+ .then(win => navigate_window(win, url2))
+ .then(win => validate_window(win, url2, { includeUncontrolled: true }))
+ .then(win => go_back(win))
+ .then(win => validate_window(win, url1, { includeUncontrolled: true }))
+ .then(win => go_forward(win))
+ .then(win => validate_window(win, url2, { includeUncontrolled: true }))
+ .then(win => reload_window(win))
+ .then(win => validate_window(win, url2, { includeUncontrolled: true }))
+ .then(win => win.close())
+ .catch(unreached_rejection(t))
+ .then(___ => service_worker_unregister(t, scope))
+ }, 'Clients.matchAll() should not show an old window after it navigates.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html
new file mode 100644
index 000000000..7b606cf0c
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html
@@ -0,0 +1,449 @@
+<!DOCTYPE html>
+<title>Service Worker: Navigation redirection</title>
+<script src="/resources/testharness.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>
+var host_info = get_host_info();
+
+// This test registers three Service Workers at SCOPE1, SCOPE2 and
+// OTHER_ORIGIN_SCOPE. And checks the redirected page's URL and the requests
+// which are intercepted by Service Worker while loading redirect page.
+var BASE_URL = host_info['HTTPS_ORIGIN'] + base_path();
+var OTHER_BASE_URL = host_info['HTTPS_REMOTE_ORIGIN'] + base_path();
+
+var SCOPE1 = BASE_URL + 'resources/navigation-redirect-scope1.py?';
+var SCOPE2 = BASE_URL + 'resources/navigation-redirect-scope2.py?';
+var OUT_SCOPE = BASE_URL + 'resources/navigation-redirect-out-scope.py?';
+var SCRIPT = 'resources/navigation-redirect-worker.js';
+
+var OTHER_ORIGIN_IFRAME_URL =
+ OTHER_BASE_URL + 'resources/navigation-redirect-other-origin.html';
+var OTHER_ORIGIN_SCOPE =
+ OTHER_BASE_URL + 'resources/navigation-redirect-scope1.py?';
+var OTHER_ORIGIN_OUT_SCOPE =
+ OTHER_BASE_URL + 'resources/navigation-redirect-out-scope.py?';
+
+var workers;
+var other_origin_frame;
+var setup_environment_promise;
+var message_resolvers = {};
+var next_message_id = 0;
+
+function setup_environment(t) {
+ if (setup_environment_promise)
+ return setup_environment_promise;
+ setup_environment_promise =
+ with_iframe(OTHER_ORIGIN_IFRAME_URL)
+ .then(function(f) {
+ // In this frame we register a Service Worker at OTHER_ORIGIN_SCOPE.
+ // And will use this frame to communicate with the worker.
+ other_origin_frame = f;
+ return Promise.all(
+ [service_worker_unregister_and_register(t, SCRIPT, SCOPE1),
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE2)]);
+ })
+ .then(function(registrations) {
+ add_completion_callback(function() {
+ registrations[0].unregister();
+ registrations[1].unregister();
+ send_to_iframe(other_origin_frame, 'unregister')
+ .then(function() { other_origin_frame.remove(); });
+ });
+ workers = registrations.map(get_effective_worker);
+ return Promise.all([
+ wait_for_state(t, workers[0], 'activated'),
+ wait_for_state(t, workers[1], 'activated'),
+ // This promise will resolve when |wait_for_worker_promise|
+ // in OTHER_ORIGIN_IFRAME_URL resolves.
+ send_to_iframe(other_origin_frame, 'wait_for_worker')]);
+ });
+ return setup_environment_promise;
+}
+
+function get_effective_worker(registration) {
+ if (registration.active)
+ return registration.active;
+ if (registration.waiting)
+ return registration.waiting;
+ if (registration.installing)
+ return registration.installing;
+}
+
+function check_all_intercepted_urls(expected_urls) {
+ var urls = [];
+ return get_intercepted_urls(workers[0])
+ .then(function(url) {
+ urls.push(url);
+ return get_intercepted_urls(workers[1]);
+ }).then(function(url) {
+ urls.push(url);
+ // Gets the request URLs which are intercepted by OTHER_ORIGIN_SCOPE's
+ // SW. This promise will resolve when get_intercepted_urls() in
+ // OTHER_ORIGIN_IFRAME_URL resolves.
+ return send_to_iframe(other_origin_frame, 'get_intercepted_urls');
+ }).then(function(url) {
+ urls.push(url);
+ return urls;
+ }).then(function(urls) {
+ assert_object_equals(
+ urls, expected_urls,
+ 'Intercepted URLs should match.');
+ });
+}
+
+function test_redirect(url, expected_last_url,
+ expected_intercepted_urls) {
+ var message_promise = new Promise(function(resolve) {
+ // A message which ID is 'last_url' will be sent from the iframe.
+ message_resolvers['last_url'] = resolve;
+ });
+ return with_iframe(url)
+ .then(function(f) {
+ f.remove();
+ return check_all_intercepted_urls(expected_intercepted_urls);
+ })
+ .then(function() { return message_promise; })
+ .then(function(last_url) {
+ assert_equals(
+ last_url, expected_last_url,
+ 'Last URL should match.');
+ });
+}
+
+window.addEventListener('message', on_message, false);
+
+function on_message(e) {
+ if (e.origin != host_info['HTTPS_REMOTE_ORIGIN'] &&
+ e.origin != host_info['HTTPS_ORIGIN'] ) {
+ console.error('invalid origin: ' + e.origin);
+ return;
+ }
+ var resolve = message_resolvers[e.data.id];
+ delete message_resolvers[e.data.id];
+ resolve(e.data.result);
+}
+
+function send_to_iframe(frame, message) {
+ var message_id = next_message_id++;
+ return new Promise(function(resolve) {
+ message_resolvers[message_id] = resolve;
+ frame.contentWindow.postMessage(
+ {id: message_id, message: message},
+ host_info['HTTPS_REMOTE_ORIGIN']);
+ });
+}
+
+function get_intercepted_urls(worker) {
+ return new Promise(function(resolve) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function(msg) { resolve(msg.data.urls); };
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+ });
+}
+
+// Normal redirect.
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ OUT_SCOPE + 'url=' + encodeURIComponent(SCOPE1),
+ SCOPE1,
+ [[SCOPE1], [], []]);
+ });
+ }, 'Normal redirect to same-origin scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ OUT_SCOPE + 'url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE),
+ OTHER_ORIGIN_SCOPE,
+ [[], [], [OTHER_ORIGIN_SCOPE]]);
+ });
+ }, 'Normal redirect to other-origin scope.');
+
+// SW fallbacked redirect. SW doesn't handle the fetch request.
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'url=' + encodeURIComponent(OUT_SCOPE),
+ OUT_SCOPE,
+ [[SCOPE1 + 'url=' + encodeURIComponent(OUT_SCOPE)], [], []]);
+ });
+ }, 'SW-fallbacked redirect to same-origin out-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'url=' + encodeURIComponent(SCOPE1),
+ SCOPE1,
+ [[SCOPE1 + 'url=' + encodeURIComponent(SCOPE1)], [], []]);
+ });
+ }, 'SW-fallbacked redirect to same-origin same-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'url=' + encodeURIComponent(SCOPE2),
+ SCOPE2,
+ [[SCOPE1 + 'url=' + encodeURIComponent(SCOPE2)], [], []]);
+ });
+ }, 'SW-fallbacked redirect to same-origin other-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE),
+ OTHER_ORIGIN_OUT_SCOPE,
+ [[SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE)],
+ [],
+ []]);
+ });
+ }, 'SW-fallbacked redirect to other-origin out-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE),
+ OTHER_ORIGIN_SCOPE,
+ [[SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE)],
+ [],
+ []]);
+ });
+ }, 'SW-fallbacked redirect to other-origin in-scope.');
+
+// SW generated redirect.
+// SW: event.respondWith(Response.redirect(params['url']));
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE),
+ OUT_SCOPE,
+ [[SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE)], [], []]);
+ });
+ }, 'SW-generated redirect to same-origin out-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE1),
+ SCOPE1,
+ [[SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE1), SCOPE1],
+ [],
+ []]);
+ });
+ }, 'SW-generated redirect to same-origin same-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE2),
+ SCOPE2,
+ [[SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE2)],
+ [SCOPE2],
+ []]);
+ });
+ }, 'SW-generated redirect to same-origin other-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE),
+ OTHER_ORIGIN_OUT_SCOPE,
+ [[SCOPE1 + 'sw=gen&url=' +
+ encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE)],
+ [],
+ []]);
+ });
+ }, 'SW-generated redirect to other-origin out-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE),
+ OTHER_ORIGIN_SCOPE,
+ [[SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE)],
+ [],
+ [OTHER_ORIGIN_SCOPE]]);
+ });
+ }, 'SW-generated redirect to other-origin in-scope.');
+
+// SW fetched redirect.
+// SW: event.respondWith(fetch(event.request));
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OUT_SCOPE),
+ OUT_SCOPE,
+ [[SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OUT_SCOPE)],
+ [],
+ []]);
+ });
+ }, 'SW-fetched redirect to same-origin out-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE1),
+ SCOPE1,
+ [[SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE1), SCOPE1],
+ [],
+ []]);
+ });
+ }, 'SW-fetched redirect to same-origin same-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE2),
+ SCOPE2,
+ [[SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE2)],
+ [SCOPE2],
+ []]);
+ });
+ }, 'SW-fetched redirect to same-origin other-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=fetch&url=' +
+ encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE),
+ OTHER_ORIGIN_OUT_SCOPE,
+ [[SCOPE1 + 'sw=fetch&url=' +
+ encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE)],
+ [],
+ []]);
+ });
+ }, 'SW-fetched redirect to other-origin out-scope.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE),
+ OTHER_ORIGIN_SCOPE,
+ [[SCOPE1 + 'sw=fetch&url=' +
+ encodeURIComponent(OTHER_ORIGIN_SCOPE)],
+ [],
+ [OTHER_ORIGIN_SCOPE]]);
+ });
+ }, 'SW-fetched redirect to other-origin in-scope.');
+
+// Opaque redirect.
+// SW: event.respondWith(fetch(
+// new Request(event.request.url, {redirect: 'manual'})));
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(OUT_SCOPE),
+ OUT_SCOPE,
+ [[SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(OUT_SCOPE)],
+ [],
+ []]);
+ });
+ }, 'Redirect to same-origin out-scope with opaque redirect response.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(SCOPE1),
+ SCOPE1,
+ [[SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(SCOPE1), SCOPE1],
+ [],
+ []]);
+ });
+ }, 'Redirect to same-origin same-scope with opaque redirect response.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(SCOPE2),
+ SCOPE2,
+ [[SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(SCOPE2)],
+ [SCOPE2],
+ []]);
+ });
+ }, 'Redirect to same-origin other-scope with opaque redirect response.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=opaque&url=' +
+ encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE),
+ OTHER_ORIGIN_OUT_SCOPE,
+ [[SCOPE1 + 'sw=opaque&url=' +
+ encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE)],
+ [],
+ []]);
+ });
+ }, 'Redirect to other-origin out-scope with opaque redirect response.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE),
+ OTHER_ORIGIN_SCOPE,
+ [[SCOPE1 + 'sw=opaque&url=' +
+ encodeURIComponent(OTHER_ORIGIN_SCOPE)],
+ [],
+ [OTHER_ORIGIN_SCOPE]]);
+ });
+ }, 'Redirect to other-origin in-scope with opaque redirect response.');
+
+// Opaque redirect passed through Cache.
+// SW responds with an opaque redirectresponse from the Cache API.
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=opaqueThroughCache&url=' +
+ encodeURIComponent(OUT_SCOPE),
+ OUT_SCOPE,
+ [[SCOPE1 + 'sw=opaqueThroughCache&url=' +
+ encodeURIComponent(OUT_SCOPE)],
+ [],
+ []]);
+ });
+ },
+ 'Redirect to same-origin out-scope with opaque redirect response which ' +
+ 'is passed through Cache.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=opaqueThroughCache&url=' +
+ encodeURIComponent(SCOPE1),
+ SCOPE1,
+ [[SCOPE1 + 'sw=opaqueThroughCache&url=' +
+ encodeURIComponent(SCOPE1), SCOPE1],
+ [],
+ []]);
+ });
+ },
+ 'Redirect to same-origin same-scope with opaque redirect response which ' +
+ 'is passed through Cache.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=opaqueThroughCache&url=' +
+ encodeURIComponent(SCOPE2),
+ SCOPE2,
+ [[SCOPE1 + 'sw=opaqueThroughCache&url=' +
+ encodeURIComponent(SCOPE2)],
+ [SCOPE2],
+ []]);
+ });
+ },
+ 'Redirect to same-origin other-scope with opaque redirect response which ' +
+ 'is passed through Cache.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=opaqueThroughCache&url=' +
+ encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE),
+ OTHER_ORIGIN_OUT_SCOPE,
+ [[SCOPE1 + 'sw=opaqueThroughCache&url=' +
+ encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE)],
+ [],
+ []]);
+ });
+ },
+ 'Redirect to other-origin out-scope with opaque redirect response which ' +
+ 'is passed through Cache.');
+promise_test(function(t) {
+ return setup_environment(t).then(function() {
+ return test_redirect(
+ SCOPE1 + 'sw=opaqueThroughCache&url=' +
+ encodeURIComponent(OTHER_ORIGIN_SCOPE),
+ OTHER_ORIGIN_SCOPE,
+ [[SCOPE1 + 'sw=opaqueThroughCache&url=' +
+ encodeURIComponent(OTHER_ORIGIN_SCOPE)],
+ [],
+ [OTHER_ORIGIN_SCOPE]]);
+ });
+ },
+ 'Redirect to other-origin in-scope with opaque redirect response which ' +
+ 'is passed through Cache.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/onactivate-script-error.https.html b/testing/web-platform/tests/service-workers/service-worker/onactivate-script-error.https.html
new file mode 100644
index 000000000..23a7f2c86
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/onactivate-script-error.https.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+function wait_for_install(worker) {
+ return new Promise(function(resolve, reject) {
+ worker.addEventListener('statechange', function(event) {
+ if (worker.state == 'installed')
+ resolve();
+ else if (worker.state == 'redundant')
+ reject();
+ });
+ });
+}
+
+function wait_for_activate(worker) {
+ return new Promise(function(resolve, reject) {
+ worker.addEventListener('statechange', function(event) {
+ if (worker.state == 'activated')
+ resolve();
+ else if (worker.state == 'redundant')
+ reject();
+ });
+ });
+}
+
+function make_test(name, script) {
+ promise_test(function(t) {
+ var scope = script;
+ var registration;
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_install(registration.installing);
+ })
+ .then(function() {
+ // Activate should succeed regardless of script errors.
+ if (registration.active && registration.active.state == 'activated') {
+ return Promise.resolve();
+ } else if (registration.active) {
+ return wait_for_activate(registration.active);
+ }
+
+ return wait_for_activate(registration.waiting);
+ });
+ }, name);
+}
+
+[
+ {
+ name: 'activate handler throws an error',
+ script: 'resources/onactivate-throw-error-worker.js',
+ },
+ {
+ name: 'activate handler throws an error, error handler does not cancel',
+ script: 'resources/onactivate-throw-error-with-empty-onerror-worker.js',
+ },
+ {
+ name: 'activate handler dispatches an event that throws an error',
+ script: 'resources/onactivate-throw-error-from-nested-event-worker.js',
+ },
+ {
+ name: 'activate handler throws an error that is cancelled',
+ script: 'resources/onactivate-throw-error-then-cancel-worker.js',
+ },
+ {
+ name: 'activate handler throws an error and prevents default',
+ script: 'resources/onactivate-throw-error-then-prevent-default-worker.js',
+ }
+].forEach(function(test_case) {
+ make_test(test_case.name, test_case.script);
+ });
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/oninstall-script-error.https.html b/testing/web-platform/tests/service-workers/service-worker/oninstall-script-error.https.html
new file mode 100644
index 000000000..a9ca19cab
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/oninstall-script-error.https.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+function wait_for_install_event(worker) {
+ return new Promise(function(resolve) {
+ worker.addEventListener('statechange', function(event) {
+ if (worker.state == 'installed')
+ resolve(true);
+ else if (worker.state == 'redundant')
+ resolve(false);
+ });
+ });
+}
+
+function make_test(name, script, expect_install) {
+ promise_test(function(t) {
+ var scope = script;
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return wait_for_install_event(registration.installing);
+ })
+ .then(function(did_install) {
+ assert_equals(did_install, expect_install,
+ 'The worker was installed');
+ })
+ }, name);
+}
+
+[
+ {
+ name: 'install handler throws an error',
+ script: 'resources/oninstall-throw-error-worker.js',
+ expect_install: false
+ },
+ {
+ name: 'install handler throws an error, error handler does not cancel',
+ script: 'resources/oninstall-throw-error-with-empty-onerror-worker.js',
+ expect_install: false
+ },
+ {
+ name: 'install handler dispatches an event that throws an error',
+ script: 'resources/oninstall-throw-error-from-nested-event-worker.js',
+ expect_install: true
+ },
+
+ // The following two cases test what happens when the ServiceWorkerGlobalScope
+ // 'error' event handler cancels the resulting error event. Since the
+ // original 'install' event handler through, the installation should still
+ // be stopped in this case. See:
+ // https://github.com/slightlyoff/ServiceWorker/issues/778
+ {
+ name: 'install handler throws an error that is cancelled',
+ script: 'resources/oninstall-throw-error-then-cancel-worker.js',
+ expect_install: false
+ },
+ {
+ name: 'install handler throws an error and prevents default',
+ script: 'resources/oninstall-throw-error-then-prevent-default-worker.js',
+ expect_install: false
+ }
+].forEach(function(test_case) {
+ make_test(test_case.name, test_case.script, test_case.expect_install);
+ });
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/performance-timeline.https.html b/testing/web-platform/tests/service-workers/service-worker/performance-timeline.https.html
new file mode 100644
index 000000000..182076baa
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/performance-timeline.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+service_worker_test(
+ 'resources/performance-timeline-worker.js',
+ 'Test Performance Timeline API in Service Worker');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/postmessage-msgport-to-client.https.html b/testing/web-platform/tests/service-workers/service-worker/postmessage-msgport-to-client.https.html
new file mode 100644
index 000000000..38b4f56e7
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/postmessage-msgport-to-client.https.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Service Worker: postMessage to Client</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var frame;
+var t = async_test('postMessage MessagePorts from ServiceWorker to Client');
+t.step(function() {
+ var scope = 'resources/blank.html'
+ service_worker_unregister_and_register(
+ t, 'resources/postmessage-msgport-to-client-worker.js', scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame = f;
+ var w = frame.contentWindow;
+ w.navigator.serviceWorker.onmessage = t.step_func(onMessage);
+ w.navigator.serviceWorker.controller.postMessage('ping');
+ })
+ .catch(unreached_rejection(t));
+
+ var result = [];
+ var expected = [
+ 'Acking value: 1',
+ 'Acking value: 2',
+ ];
+
+ function onMessage(e) {
+ var message = e.data;
+ if ('port' in message) {
+ var port = message.port;
+ port.postMessage({value: 1});
+ port.postMessage({value: 2});
+ port.postMessage({done: true});
+ } else if ('ack' in message) {
+ result.push(message.ack);
+ } else if ('done' in message) {
+ assert_array_equals(
+ result, expected,
+ 'Worker should post back expected values via MessagePort.');
+ frame.remove();
+ service_worker_unregister_and_done(t, scope);
+ } else {
+ assert_unreached('Got unexpected message from ServiceWorker');
+ }
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/postmessage-to-client.https.html b/testing/web-platform/tests/service-workers/service-worker/postmessage-to-client.https.html
new file mode 100644
index 000000000..a031ee2ed
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/postmessage-to-client.https.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>Service Worker: postMessage to Client</title>
+<script src="/resources/testharness.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>
+<script>
+var frame;
+var t = async_test('postMessage from ServiceWorker to Client');
+t.step(function() {
+ var scope = 'resources/blank.html';
+ var host_info = get_host_info();
+ var sw;
+ service_worker_unregister_and_register(
+ t, 'resources/postmessage-to-client-worker.js', scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame = f;
+ sw = frame.contentWindow.navigator.serviceWorker;
+ sw.onmessage = t.step_func(onMessage);
+ sw.controller.postMessage('ping');
+ })
+ .catch(unreached_rejection(t));
+
+ var result = [];
+ var expected = ['Sending message via clients'];
+
+ function onMessage(e) {
+ assert_equals(e.bubbles, false, 'message events should not bubble.');
+ assert_equals(e.cancelable, false, 'message events should not be cancelable.');
+ assert_equals(e.origin, host_info['HTTPS_ORIGIN'], 'message event\'s origin should be set correctly.');
+// XXXkhuey fixme!
+// assert_equals(e.source, sw.controller, 'source should be ServiceWorker.');
+
+ var message = e.data;
+ if (message === 'quit') {
+ assert_array_equals(result, expected,
+ 'Worker should post back expected messages.');
+ frame.remove();
+ service_worker_unregister_and_done(t, scope);
+ } else {
+ result.push(message);
+ }
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/postmessage.https.html b/testing/web-platform/tests/service-workers/service-worker/postmessage.https.html
new file mode 100644
index 000000000..a6f665179
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/postmessage.https.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Service Worker: postMessage</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+async_test(function(t) {
+ var scope = 'resources/blank.html';
+ var registration;
+ var worker;
+ service_worker_unregister_and_register(
+ t, 'resources/postmessage-worker.js', scope)
+ .then(function(r) {
+ registration = r;
+ worker = registration.installing;
+ var messageChannel = new MessageChannel();
+ messageChannel.port1.onmessage = t.step_func(onMessage);
+ worker.postMessage({port: messageChannel.port2},
+ [messageChannel.port2]);
+ worker.postMessage({value: 1});
+ worker.postMessage({value: 2});
+ worker.postMessage({done: true});
+ })
+ .catch(unreached_rejection(t));
+
+ var result = [];
+ var expected = [
+ 'Acking value: 1',
+ 'Acking value: 2',
+ ];
+
+ function onMessage(e) {
+ var message = e.data;
+ if (message === 'quit') {
+ assert_array_equals(result, expected,
+ 'Worker should post back expected values.');
+ postMessageToRedundantWorker();
+ } else {
+ result.push(message);
+ }
+ };
+
+ function postMessageToRedundantWorker() {
+ registration.unregister(scope)
+ .then(function() {
+ return wait_for_state(t, worker, 'redundant');
+ })
+ .then(function() {
+ assert_equals(worker.state, 'redundant');
+ assert_throws(
+ {name:'InvalidStateError'},
+ function() { worker.postMessage(''); },
+ 'Calling postMessage on a redundant ServiceWorker should ' +
+ 'throw InvalidStateError.');
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }
+ }, 'postMessage to a ServiceWorker (and back via MessagePort)');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/ready.https.html b/testing/web-platform/tests/service-workers/service-worker/ready.https.html
new file mode 100644
index 000000000..ee6a97ca8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/ready.https.html
@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<title>Service Worker: navigator.serviceWorker.ready</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+test(function() {
+ var promise = navigator.serviceWorker.ready;
+ assert_equals(promise, navigator.serviceWorker.ready,
+ 'repeated access to ready without intervening ' +
+ 'registrations should return the same Promise object');
+ }, 'ready returns the same Promise object');
+
+async_test(function(t) {
+ with_iframe('resources/blank.html?uncontrolled')
+ .then(t.step_func(function(frame) {
+ var promise = frame.contentWindow.navigator.serviceWorker.ready;
+ assert_equals(Object.getPrototypeOf(promise),
+ frame.contentWindow.Promise.prototype,
+ 'the Promise should be in the context of the ' +
+ 'related document');
+ frame.remove();
+ t.done();
+ }));
+ }, 'ready returns a Promise object in the context of the related document');
+
+async_test(function(t) {
+ var url = 'resources/empty-worker.js';
+ var scope = 'resources/blank.html?ready-controlled';
+ var expected_url = normalizeURL(url);
+ var frame;
+
+ service_worker_unregister_and_register(t, url, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame = f;
+ return frame.contentWindow.navigator.serviceWorker.ready;
+ })
+ .then(function(registration) {
+ assert_equals(registration.installing, null,
+ 'installing should be null');
+ assert_equals(registration.waiting, null,
+ 'waiting should be null');
+ assert_equals(registration.active.scriptURL, expected_url,
+ 'active after ready should not be null');
+ assert_equals(
+ frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
+ expected_url,
+ 'controlled document should have a controller');
+
+ frame.remove();
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'ready on a controlled document');
+
+async_test(function(t) {
+ var url = 'resources/empty-worker.js';
+ var scope = 'resources/blank.html?ready-potential-controlled';
+ var expected_url = normalizeURL(url);
+ var frame;
+
+ with_iframe(scope)
+ .then(function(f) {
+ frame = f;
+ return navigator.serviceWorker.register(url, {scope:scope});
+ })
+ .then(function() {
+ return frame.contentWindow.navigator.serviceWorker.ready;
+ })
+ .then(function(registration) {
+ assert_equals(registration.installing, null,
+ 'installing should be null');
+ assert_equals(registration.waiting, null,
+ 'waiting should be null.')
+ assert_equals(registration.active.scriptURL, expected_url,
+ 'active after ready should not be null');
+ assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
+ null,
+ 'uncontrolled document should not have a controller');
+
+ frame.remove();
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'ready on a potential controlled document');
+
+async_test(function(t) {
+ var url = 'resources/empty-worker.js';
+ var matched_scope = 'resources/blank.html?ready-after-match';
+ var longer_matched_scope = 'resources/blank.html?ready-after-match-longer';
+ var frame, registration;
+
+ Promise.all([service_worker_unregister(t, matched_scope),
+ service_worker_unregister(t, longer_matched_scope)])
+ .then(function() {
+ return with_iframe(longer_matched_scope);
+ })
+ .then(function(f) {
+ frame = f;
+ return navigator.serviceWorker.register(url, {scope: matched_scope});
+ })
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return navigator.serviceWorker.register(
+ url, {scope: longer_matched_scope});
+ })
+ .then(function() {
+ return frame.contentWindow.navigator.serviceWorker.ready;
+ })
+ .then(function(r) {
+ assert_equals(r.scope, normalizeURL(longer_matched_scope),
+ 'longer matched registration should be returned');
+ assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
+ null, 'controller should be null');
+ return registration.unregister();
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, longer_matched_scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'ready after a longer matched registration registered');
+
+async_test(function(t) {
+ var url = 'resources/empty-worker.js';
+ var matched_scope = 'resources/blank.html?ready-after-resolve';
+ var longer_matched_scope =
+ 'resources/blank.html?ready-after-resolve-longer';
+ var frame, registration;
+
+ service_worker_unregister_and_register(t, url, matched_scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(longer_matched_scope);
+ })
+ .then(function(f) {
+ frame = f;
+ return f.contentWindow.navigator.serviceWorker.ready;
+ })
+ .then(function(r) {
+ assert_equals(r.scope, normalizeURL(matched_scope),
+ 'matched registration should be returned');
+ return navigator.serviceWorker.register(
+ url, {scope: longer_matched_scope});
+ })
+ .then(function() {
+ return frame.contentWindow.navigator.serviceWorker.ready;
+ })
+ .then(function(r) {
+ assert_equals(r.scope, normalizeURL(matched_scope),
+ 'ready should only be resolved once');
+ return registration.unregister();
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, longer_matched_scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'access ready after it has been resolved');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/referer.https.html b/testing/web-platform/tests/service-workers/service-worker/referer.https.html
new file mode 100644
index 000000000..9b3565329
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/referer.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Service Worker: check referer of fetch()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+async_test(function(t) {
+ var SCOPE = 'resources/referer-iframe.html';
+ var SCRIPT = 'resources/fetch-rewrite-worker.js';
+ var host_info = get_host_info();
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(SCOPE); })
+ .then(function(frame) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data.results, 'finish');
+ frame.remove();
+ service_worker_unregister_and_done(t, SCOPE);
+ });
+ frame.contentWindow.postMessage({},
+ host_info['HTTPS_ORIGIN'],
+ [channel.port2]);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify the referer');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/register-closed-window.https.html b/testing/web-platform/tests/service-workers/service-worker/register-closed-window.https.html
new file mode 100644
index 000000000..221356716
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/register-closed-window.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Service Worker: Register() on Closed Window</title>
+<meta name=timeout content=long>
+<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>
+
+var host_info = get_host_info();
+var frameURL = host_info['HTTPS_ORIGIN'] + base_path() +
+ 'resources/register-closed-window-iframe.html';
+
+async_test(function(t) {
+ var frame;
+ with_iframe(frameURL).then(function(f) {
+ frame = f;
+ return new Promise(function(resolve) {
+ window.addEventListener('message', function messageHandler(evt) {
+ window.removeEventListener('message', messageHandler);
+ resolve(evt.data);
+ });
+ frame.contentWindow.postMessage('START', '*');
+ });
+ }).then(function(result) {
+ assert_equals(result, 'OK', 'frame should complete without crashing');
+ frame.remove();
+ t.done();
+ }).catch(unreached_rejection(t));
+}, 'Call register() on ServiceWorkerContainer owned by closed window.');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/register-default-scope.https.html b/testing/web-platform/tests/service-workers/service-worker/register-default-scope.https.html
new file mode 100644
index 000000000..dc136d4e8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/register-default-scope.https.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<title>register() and scope</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var script_url = new URL(script, location.href);
+ var expected_scope = new URL('./', script_url).href;
+ return service_worker_unregister(t, expected_scope)
+ .then(function() {
+ return navigator.serviceWorker.register('resources/empty-worker.js');
+ }).then(function(registration) {
+ assert_equals(registration.scope, expected_scope,
+ 'The default scope should be URL("./", script_url)');
+ return registration.unregister();
+ }).then(function() {
+ t.done();
+ });
+ }, 'default scope');
+
+promise_test(function(t) {
+ // This script must be different than the 'default scope' test, or else
+ // the scopes will collide.
+ var script = 'resources/empty.js';
+ var script_url = new URL(script, location.href);
+ var expected_scope = new URL('./', script_url).href;
+ return service_worker_unregister(t, expected_scope)
+ .then(function() {
+ return navigator.serviceWorker.register('resources/empty.js',
+ { scope: undefined });
+ }).then(function(registration) {
+ assert_equals(registration.scope, expected_scope,
+ 'The default scope should be URL("./", script_url)');
+ return registration.unregister();
+ }).then(function() {
+ t.done();
+ });
+ }, 'undefined scope');
+
+promise_test(function(t) {
+ var script = 'resources/simple-fetch-worker.js';
+ var script_url = new URL(script, location.href);
+ var expected_scope = new URL('./', script_url).href;
+ return service_worker_unregister(t, expected_scope)
+ .then(function() {
+ return navigator.serviceWorker.register('resources/empty.js',
+ { scope: null });
+ })
+ .then(
+ function(registration) {
+ assert_unreached('register should fail');
+ service_worker_unregister_and_done(t, registration.scope);
+ },
+ function(error) {
+ assert_equals(error.name, 'SecurityError',
+ 'passing a null scope should be interpreted as ' +
+ 'scope="null" which violates the path restriction');
+ t.done();
+ });
+ }, 'null scope');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/register-same-scope-different-script-url.https.html b/testing/web-platform/tests/service-workers/service-worker/register-same-scope-different-script-url.https.html
new file mode 100644
index 000000000..445be7409
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/register-same-scope-different-script-url.https.html
@@ -0,0 +1,240 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var script1 = normalizeURL('resources/empty-worker.js');
+var script2 = normalizeURL('resources/empty-worker.js?new');
+
+async_test(function(t) {
+ var scope = 'resources/scope/register-new-script-concurrently';
+ var register_promise1;
+ var register_promise2;
+
+ service_worker_unregister(t, scope)
+ .then(function() {
+ register_promise1 = navigator.serviceWorker.register(script1,
+ {scope: scope});
+ register_promise2 = navigator.serviceWorker.register(script2,
+ {scope: scope});
+ return register_promise1;
+ })
+ .then(function(registration) {
+ assert_equals(registration.installing.scriptURL, script1,
+ 'on first register, first script should be installing');
+ assert_equals(registration.waiting, null,
+ 'on first register, waiting should be null');
+ assert_equals(registration.active, null,
+ 'on first register, active should be null');
+ return register_promise2;
+ })
+ .then(function(registration) {
+ assert_equals(
+ registration.installing.scriptURL, script2,
+ 'on second register, second script should be installing');
+ // Spec allows racing: the first register may have finished
+ // or the second one could have terminated the installing worker.
+ assert_true(registration.waiting == null ||
+ registration.waiting.scriptURL == script1,
+ 'on second register, .waiting should be null or the ' +
+ 'first script');
+ assert_true(registration.active == null ||
+ (registration.waiting == null &&
+ registration.active.scriptURL == script1),
+ 'on second register, .active should be null or the ' +
+ 'first script');
+ return registration.unregister();
+ })
+ .then(function() {
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Register different scripts concurrently');
+
+async_test(function(t) {
+ var scope = 'resources/scope/register-then-register-new-script';
+ var registration;
+
+ service_worker_unregister_and_register(t, script1, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ assert_equals(registration.installing, null,
+ 'on activated, installing should be null');
+ assert_equals(registration.waiting, null,
+ 'on activated, waiting should be null');
+ assert_equals(registration.active.scriptURL, script1,
+ 'on activated, the first script should be active');
+ return navigator.serviceWorker.register(script2, {scope:scope});
+ })
+ .then(function(r) {
+ registration = r;
+ assert_equals(registration.installing.scriptURL, script2,
+ 'on second register, the second script should be ' +
+ 'installing');
+ assert_equals(registration.waiting, null,
+ 'on second register, waiting should be null');
+ assert_equals(registration.active.scriptURL, script1,
+ 'on second register, the first script should be ' +
+ 'active');
+ return wait_for_state(t, registration.installing, 'installed');
+ })
+ .then(function() {
+ assert_equals(registration.installing, null,
+ 'on installed, installing should be null');
+ // Since the registration is not controlling any document, the new
+ // worker can immediately transition to active.
+ if (registration.waiting) {
+ assert_equals(registration.waiting.scriptURL, script2,
+ 'on installed, the second script may still be waiting');
+ assert_equals(registration.active.scriptURL, script1,
+ 'on installed, the first script may be active');
+ } else {
+ assert_equals(registration.active.scriptURL, script2,
+ 'on installed, the second script may be active');
+ }
+ return registration.unregister();
+ })
+ .then(function() {
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Register then register new script URL');
+
+async_test(function(t) {
+ var scope = 'resources/scope/register-then-register-new-script-404';
+ var registration;
+
+ service_worker_unregister_and_register(t, script1, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ assert_equals(registration.installing, null,
+ 'on activated, installing should be null');
+ assert_equals(registration.waiting, null,
+ 'on activated, waiting should be null');
+ assert_equals(registration.active.scriptURL, script1,
+ 'on activated, the first script should be active');
+ return navigator.serviceWorker.register('this-will-404.js',
+ {scope:scope});
+ })
+ .then(
+ function() { assert_unreached('register should reject'); },
+ function(error) {
+ assert_equals(registration.installing, null,
+ 'on rejected, installing should be null');
+ assert_equals(registration.waiting, null,
+ 'on rejected, waiting should be null');
+ assert_equals(registration.active.scriptURL, script1,
+ 'on rejected, the first script should be active');
+ return registration.unregister();
+ })
+ .then(function() {
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Register then register new script URL that 404s');
+
+async_test(function(t) {
+ var scope = 'resources/scope/register-then-register-new-script-reject-install';
+ var reject_script = normalizeURL('resources/reject-install-worker.js');
+ var registration;
+
+ service_worker_unregister_and_register(t, script1, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ assert_equals(registration.installing, null,
+ 'on activated, installing should be null');
+ assert_equals(registration.waiting, null,
+ 'on activated, waiting should be null');
+ assert_equals(registration.active.scriptURL, script1,
+ 'on activated, the first script should be active');
+ return navigator.serviceWorker.register(reject_script, {scope:scope});
+ })
+ .then(function(r) {
+ registration = r;
+ assert_equals(registration.installing.scriptURL, reject_script,
+ 'on update, the second script should be installing');
+ assert_equals(registration.waiting, null,
+ 'on update, waiting should be null');
+ assert_equals(registration.active.scriptURL, script1,
+ 'on update, the first script should be active');
+ return wait_for_state(t, registration.installing, 'redundant');
+ })
+ .then(function() {
+ assert_equals(registration.installing, null,
+ 'on redundant, installing should be null');
+ assert_equals(registration.waiting, null,
+ 'on redundant, waiting should be null');
+ assert_equals(registration.active.scriptURL, script1,
+ 'on redundant, the first script should be active');
+ return registration.unregister();
+ })
+ .then(function() {
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Register then register new script that does not install');
+
+async_test(function(t) {
+ var scope = 'resources/scope/register-new-script-controller';
+ var iframe;
+ var registration;
+
+ service_worker_unregister_and_register(t, script1, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ iframe = frame;
+ return navigator.serviceWorker.register(script2, { scope: scope })
+ })
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'installed');
+ })
+ .then(function() {
+ var sw_container = iframe.contentWindow.navigator.serviceWorker;
+ assert_equals(sw_container.controller.scriptURL, script1,
+ 'the old version should control the old doc');
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ var sw_container = frame.contentWindow.navigator.serviceWorker;
+ assert_equals(sw_container.controller.scriptURL, script1,
+ 'the old version should control a new doc');
+ var onactivated_promise = wait_for_state(t,
+ registration.waiting,
+ 'activated');
+ frame.remove();
+ iframe.remove();
+ return onactivated_promise;
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ var sw_container = frame.contentWindow.navigator.serviceWorker;
+ assert_equals(sw_container.controller.scriptURL, script2,
+ 'the new version should control a new doc');
+ frame.remove();
+ return registration.unregister();
+ })
+ .then(function() {
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Register same-scope new script url effect on controller');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/register-wait-forever-in-install-worker.https.html b/testing/web-platform/tests/service-workers/service-worker/register-wait-forever-in-install-worker.https.html
new file mode 100644
index 000000000..e23f9f4fc
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/register-wait-forever-in-install-worker.https.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>Service Worker: Register wait-forever-in-install-worker</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(function(t) {
+ var bad_script = 'resources/wait-forever-in-install-worker.js';
+ var good_script = 'resources/empty-worker.js';
+ var scope = 'resources/wait-forever-in-install-worker';
+ var other_scope = 'resources/wait-forever-in-install-worker-other';
+ var registration;
+ var registerPromise;
+
+ return navigator.serviceWorker.register(bad_script, {scope: scope})
+ .then(function(r) {
+ registration = r;
+ assert_equals(registration.installing.scriptURL,
+ normalizeURL(bad_script));
+
+ // This register job should not start until the first
+ // register for the same scope completes.
+ registerPromise =
+ navigator.serviceWorker.register(good_script, {scope: scope});
+
+ // In order to test that the above register does not complete
+ // we will perform a register() on a different scope. The
+ // assumption here is that the previous register call would
+ // have completed in the same timeframe if it was able to do
+ // so.
+ return navigator.serviceWorker.register(good_script,
+ {scope: other_scope});
+ })
+ .then(function(swr) {
+ return swr.unregister();
+ })
+ .then(function() {
+ assert_equals(registration.installing.scriptURL,
+ normalizeURL(bad_script));
+ registration.installing.postMessage('STOP_WAITING');
+ return registerPromise;
+ })
+ .then(function(swr) {
+ assert_equals(registration.installing.scriptURL,
+ normalizeURL(good_script));
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return service_worker_unregister_and_done(t, scope);
+ })
+ }, 'register worker that calls waitUntil with a promise that never ' +
+ 'resolves in oninstall');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/registration-end-to-end.https.html b/testing/web-platform/tests/service-workers/service-worker/registration-end-to-end.https.html
new file mode 100644
index 000000000..e92b6502f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/registration-end-to-end.https.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<title>Service Worker: registration end-to-end</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var t = async_test('Registration: end-to-end');
+t.step(function() {
+
+ var scope = 'resources/in-scope/';
+ var serviceWorkerStates = [];
+ var lastServiceWorkerState = '';
+ var receivedMessageFromPort = '';
+ var currentChangeCount = 0;
+
+ assert_true(navigator.serviceWorker instanceof ServiceWorkerContainer);
+ assert_equals(typeof navigator.serviceWorker.register, 'function');
+ assert_equals(typeof navigator.serviceWorker.getRegistration, 'function');
+
+ navigator.serviceWorker.oncurrentchange = function() {
+ ++currentChangeCount;
+ };
+
+ service_worker_unregister_and_register(
+ t, 'resources/end-to-end-worker.js', scope)
+ .then(onRegister)
+ .catch(unreached_rejection(t));
+
+ function sendMessagePort(worker, from) {
+ var messageChannel = new MessageChannel();
+ worker.postMessage({from:from, port:messageChannel.port2}, [messageChannel.port2]);
+ return messageChannel.port1;
+ }
+
+ function onRegister(registration) {
+ var sw = registration.installing;
+ serviceWorkerStates.push(sw.state);
+ lastServiceWorkerState = sw.state;
+
+ var sawMessage = new Promise(t.step_func(function(resolve) {
+ sendMessagePort(sw, 'registering doc').onmessage = t.step_func(function (e) {
+ receivedMessageFromPort = e.data;
+ resolve();
+ });
+ }));
+
+ var sawActive = new Promise(t.step_func(function(resolve) {
+ sw.onstatechange = t.step_func(function() {
+ serviceWorkerStates.push(sw.state);
+
+ switch (sw.state) {
+ case 'installed':
+ assert_equals(lastServiceWorkerState, 'installing');
+ break;
+ case 'activating':
+ assert_equals(lastServiceWorkerState, 'installed');
+ break;
+ case 'activated':
+ assert_equals(lastServiceWorkerState, 'activating');
+ break;
+ default:
+ // We won't see 'redundant' because onstatechange is
+ // overwritten before calling unregister.
+ assert_unreached('Unexpected state: ' + sw.state);
+ }
+
+ lastServiceWorkerState = sw.state;
+ if (sw.state === 'activated')
+ resolve();
+ });
+ }));
+
+ Promise.all([sawMessage, sawActive]).then(t.step_func(function() {
+ assert_array_equals(serviceWorkerStates,
+ ['installing', 'installed', 'activating', 'activated'],
+ 'Service worker should pass through all states');
+
+ assert_equals(currentChangeCount, 0,
+ 'Should not see current changes since document is out of scope');
+
+ assert_equals(receivedMessageFromPort, 'Ack for: registering doc');
+
+ var sawRedundant = new Promise(t.step_func(function(resolve) {
+ sw.onstatechange = t.step_func(function() {
+ assert_equals(sw.state, 'redundant');
+ resolve();
+ });
+ }));
+ registration.unregister();
+ sawRedundant.then(t.step_func(function() {
+ t.done();
+ }));
+ }));
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/registration-events.https.html b/testing/web-platform/tests/service-workers/service-worker/registration-events.https.html
new file mode 100644
index 000000000..972ce7410
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/registration-events.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Service Worker: registration events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var t = async_test('Registration: events');
+t.step(function() {
+ var scope = 'resources/in-scope/';
+ service_worker_unregister_and_register(
+ t, 'resources/events-worker.js', scope)
+ .then(t.step_func(function(registration) {
+ onRegister(registration.installing);
+ }))
+ .catch(unreached_rejection(t));
+
+ function sendMessagePort(worker, from) {
+ var messageChannel = new MessageChannel();
+ worker.postMessage({from:from, port:messageChannel.port2}, [messageChannel.port2]);
+ return messageChannel.port1;
+ }
+
+ function onRegister(sw) {
+ sw.onstatechange = t.step_func(function() {
+ if (sw.state !== 'activated')
+ return;
+
+ sendMessagePort(sw, 'registering doc').onmessage = t.step_func(function (e) {
+ assert_array_equals(e.data.events,
+ ['install', 'activate'],
+ 'Worker should see install then activate events');
+ service_worker_unregister_and_done(t, scope);
+ });
+ });
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/registration-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/registration-iframe.https.html
new file mode 100644
index 000000000..fb60afe84
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/registration-iframe.https.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Service Worker: Registration for iframe</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+// Set script url and scope url relative to the calling frame's document's url.
+// Assert the implementation parses the urls against the calling frame's
+// document's url.
+async_test(function(t) {
+ var url = 'resources/blank.html';
+ var scope = 'resources/registration-for-iframe-from-calling-frame';
+ var parsed_scope = normalizeURL(scope);
+ var script = 'resources/empty-worker.js';
+ var parsed_script = normalizeURL(script);
+ var frame;
+ var registration;
+
+ service_worker_unregister(t, scope)
+ .then(function() { return with_iframe(url); })
+ .then(function(f) {
+ frame = f;
+ return frame.contentWindow.navigator.serviceWorker.register(
+ script,
+ { scope: scope });
+ })
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ assert_equals(
+ registration.scope, parsed_scope,
+ 'registration\'s scope must be the scope parsed against calling ' +
+ 'document\'s url');
+ assert_equals(
+ registration.active.scriptURL, parsed_script,
+ 'worker\'s script must be the url parsed against calling ' +
+ 'document\'s url');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Subframe\'s container\'s register method should use calling frame\'s ' +
+ 'document\'s url as a base url for parsing its script url and scope url ' +
+ '- normal case');
+
+// Set script url and scope url relative to the iframe's document's url.
+// Assert the implementation throws a NetworkError exception.
+async_test(function(t) {
+ var url = 'resources/blank.html';
+ var scope = 'registration-for-iframe-from-calling-frame';
+ var script = 'empty-worker.js';
+ var frame;
+ var registration;
+
+ service_worker_unregister(t, scope)
+ .then(function() { return with_iframe(url); })
+ .then(function(f) {
+ frame = f;
+ return frame.contentWindow.navigator.serviceWorker.register(
+ script,
+ { scope: scope });
+ })
+ .then(
+ function() {
+ assert_unreached('register() should reject');
+ },
+ function(e) {
+ assert_equals(e.name, 'TypeError');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Subframe\'s container\'s register method should use calling frame\'s ' +
+ 'document\'s url as a base url for parsing its script url and scope url ' +
+ '- error case');
+
+// Set the scope url to a non-subdirectory of the script url.
+// Assert the implementation throws a SecurityError exception.
+async_test(function(t) {
+ var url = 'resources/blank.html';
+ var scope = 'registration-for-iframe-from-calling-frame';
+ var script = 'resources/empty-worker.js';
+ var frame;
+ var registration;
+
+ service_worker_unregister(t, scope)
+ .then(function() { return with_iframe(url); })
+ .then(function(f) {
+ frame = f;
+ return frame.contentWindow.navigator.serviceWorker.register(
+ script,
+ { scope: scope });
+ })
+ .then(
+ function() {
+ assert_unreached('register() should reject');
+ },
+ function(e) {
+ assert_equals(e.name, 'SecurityError');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'A scope url should start with the given script url');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/registration-service-worker-attributes.https.html b/testing/web-platform/tests/service-workers/service-worker/registration-service-worker-attributes.https.html
new file mode 100644
index 000000000..a0dea5428
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/registration-service-worker-attributes.https.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+async_test(function(t) {
+ var scope = 'resources/scope/installing-waiting-active-after-registration';
+ var worker_url = 'resources/empty-worker.js';
+ var expected_url = normalizeURL(worker_url);
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ assert_equals(registration.installing.scriptURL, expected_url,
+ 'installing before updatefound');
+ assert_equals(registration.waiting, null,
+ 'waiting before updatefound');
+ assert_equals(registration.active, null,
+ 'active before updatefound');
+ return wait_for_update(t, registration);
+ })
+ .then(function(worker) {
+ assert_equals(registration.installing.scriptURL, expected_url,
+ 'installing after updatefound');
+ assert_equals(registration.waiting, null,
+ 'waiting after updatefound');
+ assert_equals(registration.active, null,
+ 'active after updatefound');
+ return wait_for_state(t, registration.installing, 'installed');
+ })
+ .then(function() {
+ assert_equals(registration.installing, null,
+ 'installing after installed');
+ var newest = registration.waiting || registration.active;
+ assert_equals(newest.scriptURL, expected_url,
+ 'waiting or active after installed');
+ if (registration.waiting) {
+ return wait_for_state(t, registration.waiting, 'activated')
+ .then(function() {
+ assert_equals(registration.installing, null,
+ 'installing after activated');
+ assert_equals(registration.waiting, null,
+ 'waiting after activated');
+ assert_equals(registration.active.scriptURL, expected_url,
+ 'active after activated');
+ });
+ }
+ })
+ .then(function() {
+ return Promise.all([
+ wait_for_state(t, registration.active, 'redundant'),
+ registration.unregister()
+ ]);
+ })
+ .then(function() {
+ assert_equals(registration.installing, null,
+ 'installing after redundant');
+ assert_equals(registration.waiting, null,
+ 'waiting after redundant');
+ // According to spec, Clear Registration runs Update State which is
+ // immediately followed by setting active to null, which means by the
+ // time the event loop turns and the Promise for statechange is
+ // resolved, this will be gone.
+ assert_equals(registration.active, null,
+ 'active should be null after redundant');
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'installing/waiting/active after registration');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/registration.https.html b/testing/web-platform/tests/service-workers/service-worker/registration.https.html
new file mode 100644
index 000000000..ae9f85fb2
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/registration.https.html
@@ -0,0 +1,368 @@
+<!DOCTYPE html>
+<title>Service Worker: Registration</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'resources/registration/normal';
+ return navigator.serviceWorker.register(script, {scope: scope})
+ .then(function(registration) {
+ assert_true(registration instanceof ServiceWorkerRegistration,
+ 'Successfully registered.');
+ service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Registering normal scope');
+
+promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'resources/registration/scope-with-fragment#ref';
+ return navigator.serviceWorker.register(script, {scope: scope})
+ .then(function(registration) {
+ assert_true(
+ registration instanceof ServiceWorkerRegistration,
+ 'Successfully registered.');
+ assert_equals(
+ registration.scope,
+ normalizeURL('resources/registration/scope-with-fragment'),
+ 'A fragment should be removed from scope')
+ service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Registering scope with fragment');
+
+promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'resources/';
+ return navigator.serviceWorker.register(script, {scope: scope})
+ .then(function(registration) {
+ assert_true(registration instanceof ServiceWorkerRegistration,
+ 'Successfully registered.');
+ service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Registering same scope as the script directory');
+
+promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'resources';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ 'SecurityError',
+ 'Registering same scope as the script directory without the last ' +
+ 'slash should fail with SecurityError.');
+ }, 'Registering same scope as the script directory without the last slash');
+
+promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'different-directory/';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ 'SecurityError',
+ 'Registration scope outside the script directory should fail ' +
+ 'with SecurityError.');
+ }, 'Registration scope outside the script directory');
+
+promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'http://example.com/';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ 'SecurityError',
+ 'Registration scope outside domain should fail with SecurityError.');
+ }, 'Registering scope outside domain');
+
+promise_test(function(t) {
+ var script = 'http://example.com/worker.js';
+ var scope = 'http://example.com/scope/';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ 'SecurityError',
+ 'Registration script outside domain should fail with SecurityError.');
+ }, 'Registering script outside domain');
+
+promise_test(function(t) {
+ var script = 'resources/no-such-worker.js';
+ var scope = 'resources/scope/no-such-worker';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'Registration of non-existent script should fail.');
+ }, 'Registering non-existent script');
+
+promise_test(function(t) {
+ var script = 'resources/invalid-chunked-encoding.py';
+ var scope = 'resources/scope/invalid-chunked-encoding/';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'Registration of invalid chunked encoding script should fail.');
+ }, 'Registering invalid chunked encoding script');
+
+promise_test(function(t) {
+ var script = 'resources/invalid-chunked-encoding-with-flush.py';
+ var scope = 'resources/scope/invalid-chunked-encoding-with-flush/';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'Registration of invalid chunked encoding script should fail.');
+ }, 'Registering invalid chunked encoding script with flush');
+
+promise_test(function(t) {
+ var script = 'resources/mime-type-worker.py';
+ var scope = 'resources/scope/no-mime-type-worker/';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ 'SecurityError',
+ 'Registration of no MIME type script should fail.');
+ }, 'Registering script with no MIME type');
+
+promise_test(function(t) {
+ var script = 'resources/mime-type-worker.py?mime=text/plain';
+ var scope = 'resources/scope/bad-mime-type-worker/';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ 'SecurityError',
+ 'Registration of plain text script should fail.');
+ }, 'Registering script with bad MIME type');
+
+promise_test(function(t) {
+ var script = 'resources/redirect.py?Redirect=' +
+ encodeURIComponent('/resources/registration-worker.js');
+ var scope = 'resources/scope/redirect/';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ 'SecurityError',
+ 'Registration of redirected script should fail.');
+ }, 'Registering redirected script');
+
+promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?parse-error';
+ var scope = 'resources/scope/parse-error';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'Registration of script including parse error should fail.');
+ }, 'Registering script including parse error');
+
+promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?undefined-error';
+ var scope = 'resources/scope/undefined-error';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'Registration of script including undefined error should fail.');
+ }, 'Registering script including undefined error');
+
+promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?uncaught-exception';
+ var scope = 'resources/scope/uncaught-exception';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'Registration of script including uncaught exception should fail.');
+ }, 'Registering script including uncaught exception');
+
+promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?caught-exception';
+ var scope = 'resources/scope/caught-exception';
+ return navigator.serviceWorker.register(script, {scope: scope})
+ .then(function(registration) {
+ assert_true(registration instanceof ServiceWorkerRegistration,
+ 'Successfully registered.');
+ service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Registering script including caught exception');
+
+promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?import-malformed-script';
+ var scope = 'resources/scope/import-malformed-script';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'Registration of script importing malformed script should fail.');
+ }, 'Registering script importing malformed script');
+
+promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?import-no-such-script';
+ var scope = 'resources/scope/import-no-such-script';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'Registration of script importing non-existent script should fail.');
+ }, 'Registering script importing non-existent script');
+
+promise_test(function(t) {
+ // URL-encoded full-width 'scope'.
+ var name = '%ef%bd%93%ef%bd%83%ef%bd%8f%ef%bd%90%ef%bd%85';
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/' + name + '/escaped-multibyte-character-scope';
+ return navigator.serviceWorker.register(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ registration.scope,
+ normalizeURL(scope),
+ 'URL-encoded multibyte characters should be available.');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Scope including URL-encoded multibyte characters');
+
+promise_test(function(t) {
+ // Non-URL-encoded full-width "scope".
+ var name = String.fromCodePoint(0xff53, 0xff43, 0xff4f, 0xff50, 0xff45);
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/' + name + '/non-escaped-multibyte-character-scope';
+ return navigator.serviceWorker.register(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ registration.scope,
+ normalizeURL(scope),
+ 'Non-URL-encoded multibyte characters should be available.');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Scope including non-escaped multibyte characters');
+
+promise_test(function(t) {
+ var script = 'resources%2fempty-worker.js';
+ var scope = 'resources/scope/encoded-slash-in-script-url';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'URL-encoded slash in the script URL should be rejected.');
+ }, 'Script URL including URL-encoded slash');
+
+promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/scope%2fencoded-slash-in-scope';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'URL-encoded slash in the scope should be rejected.');
+ }, 'Scope including URL-encoded slash');
+
+promise_test(function(t) {
+ var script = 'resources%5cempty-worker.js';
+ var scope = 'resources/scope/encoded-slash-in-script-url';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'URL-encoded backslash in the script URL should be rejected.');
+ }, 'Script URL including URL-encoded backslash');
+
+promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/scope%5cencoded-slash-in-scope';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ new TypeError(),
+ 'URL-encoded backslash in the scope should be rejected.');
+ }, 'Scope including URL-encoded backslash');
+
+promise_test(function(t) {
+ var script = 'resources/././empty-worker.js';
+ var scope = 'resources/scope/parent-reference-in-script-url';
+ return navigator.serviceWorker.register(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ registration.installing.scriptURL,
+ normalizeURL('resources/empty-worker.js'),
+ 'Script URL including self-reference should be normalized.');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Script URL including self-reference');
+
+promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/././scope/self-reference-in-scope';
+ return navigator.serviceWorker.register(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ registration.scope,
+ normalizeURL('resources/scope/self-reference-in-scope'),
+ 'Scope including self-reference should be normalized.');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Scope including self-reference');
+
+promise_test(function(t) {
+ var script = 'resources/../resources/empty-worker.js';
+ var scope = 'resources/scope/parent-reference-in-script-url';
+ return navigator.serviceWorker.register(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ registration.installing.scriptURL,
+ normalizeURL('resources/empty-worker.js'),
+ 'Script URL including parent-reference should be normalized.');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Script URL including parent-reference');
+
+promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/../resources/scope/parent-reference-in-scope';
+ return navigator.serviceWorker.register(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ registration.scope,
+ normalizeURL('resources/scope/parent-reference-in-scope'),
+ 'Scope including parent-reference should be normalized.');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Scope including parent-reference');
+
+promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/../scope/parent-reference-in-scope';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ 'SecurityError',
+ 'Scope not under the script directory should be rejected.');
+ }, 'Scope including parent-reference and not under the script directory');
+
+promise_test(function(t) {
+ var script = 'resources////empty-worker.js';
+ var scope = 'resources/scope/consecutive-slashes-in-script-url';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ 'SecurityError',
+ 'Consecutive slashes in the script url should not be unified.');
+ }, 'Script URL including consecutive slashes');
+
+promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/scope////consecutive-slashes-in-scope';
+ return navigator.serviceWorker.register(script, {scope: scope})
+ .then(function(registration) {
+ // Although consecutive slashes in the scope are not unified, the
+ // scope is under the script directory and registration should
+ // succeed.
+ assert_equals(
+ registration.scope,
+ normalizeURL(scope),
+ 'Should successfully be registered.');
+ service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Scope including consecutive slashes');
+
+promise_test(function(t) {
+ var script = 'filesystem:' + normalizeURL('resources/empty-worker.js');
+ var scope = 'resources/scope/filesystem-script-url';
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ 'SecurityError',
+ 'Registering a script which has same-origin filesystem: URL should ' +
+ 'fail with SecurityError.');
+ }, 'Script URL is same-origin filesystem: URL');
+
+promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'filesystem:' + normalizeURL('resources/scope/filesystem-scope-url');
+ return assert_promise_rejects(
+ navigator.serviceWorker.register(script, {scope: scope}),
+ 'SecurityError',
+ 'Registering with the scope that has same-origin filesystem: URL ' +
+ 'should fail with SecurityError.');
+ }, 'Scope URL is same-origin filesystem: URL');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/rejections.https.html b/testing/web-platform/tests/service-workers/service-worker/rejections.https.html
new file mode 100644
index 000000000..8002ad9a8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/rejections.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Service Worker: Rejection Types</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+(function() {
+ var t = async_test('Rejections are DOMExceptions');
+ t.step(function() {
+
+ navigator.serviceWorker.register('http://example.com').then(
+ t.step_func(function() { assert_unreached('Registration should fail'); }),
+ t.step_func(function(reason) {
+ assert_true(reason instanceof DOMException);
+ assert_true(reason instanceof Error);
+ t.done();
+ }));
+ });
+}());
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/request-end-to-end.https.html b/testing/web-platform/tests/service-workers/service-worker/request-end-to-end.https.html
new file mode 100644
index 000000000..c9c3b3046
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/request-end-to-end.https.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<title>Service Worker: Request end-to-end</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var t = async_test('Request: end-to-end');
+t.step(function() {
+ var url = 'resources/request-end-to-end-worker.js';
+ var scope = 'resources/blank.html';
+ var frames = [];
+
+ service_worker_unregister_and_register(t, url, scope)
+ .then(onRegister)
+ .catch(unreached_rejection(t));
+
+ function sendMessagePort(worker) {
+ var messageChannel = new MessageChannel();
+ worker.postMessage({port:messageChannel.port2}, [messageChannel.port2]);
+ return messageChannel.port1;
+ }
+
+ function onRegister(registration) {
+ var sw = registration.installing;
+ var port = sendMessagePort(sw);
+ port.addEventListener('message', t.step_func(function(event) {
+ onMessage(event);
+ }), false);
+ port.start();
+ sw.addEventListener('statechange', t.step_func(function(event) {
+ if (event.target.state == 'activated')
+ onActive();
+ }));
+ }
+
+ function onActive() {
+ with_iframe(scope).then(function(f) { frames.push(f); });
+ }
+
+ function onMessage(event) {
+ assert_equals(
+ event.data.url,
+ location.href.substring(0, location.href.lastIndexOf('/') + 1) +
+ scope,
+ 'request.url should be passed to onfetch event.');
+ assert_equals(event.data.mode, 'navigate',
+ 'request.mode should be passed to onfetch event.');
+ assert_equals(event.data.method, 'GET',
+ 'request.method should be passed to onfetch event.');
+ assert_equals(event.data.referrer, location.href,
+ 'request.referrer should be passed to onfetch event.');
+ assert_equals(event.data.headers['user-agent'], undefined,
+ 'Default User-Agent header should not be passed to onfetch event.')
+ assert_equals(event.data.errorNameWhileAppendingHeader, 'TypeError',
+ 'Appending a new header to the request must throw a ' +
+ 'TypeError.')
+ frames.forEach(function(f) { f.remove(); });
+ service_worker_unregister_and_done(t, scope);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html b/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html
new file mode 100644
index 000000000..f33c41d71
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.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>
+<script>
+function resourceUrl(path) {
+ return get_host_info()['HTTP_ORIGIN'] + base_path() + path;
+}
+
+function verify(performance, resource, description) {
+ var entry = performance.getEntriesByName(resourceUrl(resource))[0];
+ assert_greater_than(entry.workerStart, 0, description);
+ assert_greater_than_equal(entry.workerStart, entry.startTime, description);
+ assert_less_than_equal(entry.workerStart, entry.fetchStart, description);
+ if (resource.indexOf('redirect.py') != -1) {
+ assert_less_than_equal(entry.workerStart, entry.redirectStart,
+ description);
+ } else {
+ assert_equals(entry.redirectStart, 0, description);
+ }
+}
+
+async_test(function(t) {
+ var worker_url = 'resources/resource-timing-worker.js';
+ var scope = 'resources/resource-timing-iframe.html';
+ var registration;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ var performance = frame.contentWindow.performance;
+ verify(performance, 'resources/dummy.js', 'Generated response');
+ verify(performance, 'resources/empty.js', 'Network fallback');
+ verify(performance, 'resources/redirect.py?Redirect=empty.js',
+ 'Redirect');
+ frame.remove();
+ return registration.unregister();
+ })
+ .then(function() {
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+}, 'Controlled resource loads');
+
+test(function() {
+ var url = resourceUrl('resources/test-helpers.sub.js');
+ var entry = window.performance.getEntriesByName(url)[0];
+ assert_equals(entry.workerStart, 0, 'Non-controlled');
+}, 'Non-controlled resource loads');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/404.py b/testing/web-platform/tests/service-workers/service-worker/resources/404.py
new file mode 100644
index 000000000..235a3d4ff
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/404.py
@@ -0,0 +1,5 @@
+# iframe does not fire onload event if the response's content-type is not
+# text/plain or text/html so this script exists if you want to test a 404 load
+# in an iframe.
+def main(req, res):
+ return 404, [('Content-Type', 'text/plain')], "Page not found"
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.install.html b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.install.html
new file mode 100644
index 000000000..428ad92c7
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.install.html
@@ -0,0 +1,26 @@
+<html manifest="appcache-ordering.manifest">
+<script>
+var handled = false;
+
+function installComplete() {
+ if (handled)
+ return;
+ handled = true;
+ window.parent.notify_appcache_installed(true);
+}
+
+function installFailed() {
+ if (handled)
+ return;
+ handled = true;
+ window.parent.notify_appcache_installed(false);
+}
+
+applicationCache.oncached = installComplete;
+applicationCache.onnoupdate = installComplete;
+applicationCache.onupdateready = installFailed;
+applicationCache.onerror = installFailed;
+applicationCache.onobsolete = installFailed;
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.html b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.html
new file mode 100644
index 000000000..485ab1771
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.html
@@ -0,0 +1,13 @@
+<html> <!-- Intentionally does NOT include a manifest attribute -->
+<body>
+<!-- This should FALLBACK to ordering.is_appcached.js as specified in manifest
+ when the appcache is present -->
+<script src="appcache-ordering.is-appcached404.js"></script>
+<script>
+
+// If the script of the fallback resource loaded, is_appcached will be defined.
+window.parent.notify_is_appcached(typeof is_appcached != 'undefined');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.js b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.js
new file mode 100644
index 000000000..a562b6f1c
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.js
@@ -0,0 +1 @@
+var is_appcached = true;
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.manifest b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.manifest
new file mode 100644
index 000000000..0deed0e91
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.manifest
@@ -0,0 +1,7 @@
+CACHE MANIFEST
+
+appcache-ordering.is-appcached.html
+
+FALLBACK:
+appcache-ordering.is-appcached404.js appcache-ordering.is-appcached.js
+
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/blank.html b/testing/web-platform/tests/service-workers/service-worker/resources/blank.html
new file mode 100644
index 000000000..a3c3a4689
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/blank.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<title>Empty doc</title>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/claim-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/claim-worker.js
new file mode 100644
index 000000000..317feb1a0
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/claim-worker.js
@@ -0,0 +1,14 @@
+self.addEventListener('message', function(event) {
+ self.clients.claim()
+ .then(function(result) {
+ if (result !== undefined) {
+ event.data.port.postMessage(
+ 'FAIL: claim() should be resolved with undefined');
+ return;
+ }
+ event.data.port.postMessage('PASS');
+ })
+ .catch(function(error) {
+ event.data.port.postMessage('FAIL: exception: ' + error.name);
+ });
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-frame.html b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-frame.html
new file mode 100644
index 000000000..7e186f8ee
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-frame.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script>
+ fetch("clientId")
+ .then(function(response) {
+ return response.text();
+ })
+ .then(function(text) {
+ parent.postMessage({id: text}, "*");
+ });
+</script>
+<body style="background-color: red;"></body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-worker.js
new file mode 100644
index 000000000..3f2424078
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-worker.js
@@ -0,0 +1,71 @@
+importScripts("worker-testharness.js");
+importScripts("test-helpers.sub.js");
+importScripts("get-host-info.sub.js")
+importScripts("testharness-helpers.js")
+
+self.onfetch = function(e) {
+ if (e.request.url.indexOf("client-navigate-frame.html") >= 0) {
+ if (e.clientId === null) {
+ e.respondWith(fetch(e.request));
+ } else {
+ e.respondWith(Response.error());
+ }
+ return;
+ }
+ e.respondWith(new Response(e.clientId));
+};
+
+function pass(test, url) {
+ return { result: test,
+ url: url };
+}
+
+function fail(test, reason) {
+ return { result: "FAILED " + test + " " + reason }
+}
+
+self.onmessage = function(e) {
+ var port = e.data.port;
+ var test = e.data.test;
+ var clientId = e.data.clientId;
+ var clientUrl = "";
+ if (test === "test_client_navigate_success") {
+ promise_test(function(t) {
+ this.add_cleanup(() => port.postMessage(pass(test, clientUrl)));
+ return self.clients.get(clientId)
+ .then(client => client.navigate("client-navigated-frame.html"))
+ .then(client => {
+ clientUrl = client.url;
+ assert_true(client instanceof WindowClient);
+ })
+ .catch(unreached_rejection(t));
+ }, "Return value should be instance of WindowClient");
+ } else if (test === "test_client_navigate_failure") {
+ promise_test(function(t) {
+ return self.clients.get(clientId)
+ .then(client => assert_promise_rejects(client.navigate("http://example.com")))
+ .catch(unreached_rejection(t));
+ }, "Navigating to different origin should reject");
+
+ promise_test(function(t) {
+ this.add_cleanup(function() { port.postMessage(pass(test, "")); });
+ return self.clients.get(clientId)
+ .then(client => promise_rejects(t, new TypeError(), client.navigate("about:blank")))
+ .catch(unreached_rejection(t));
+ }, "Navigating to about:blank should reject with TypeError")
+ } else if (test === "test_client_navigate_redirect") {
+ var host_info = get_host_info();
+ var url = new URL(host_info['HTTPS_REMOTE_ORIGIN']).toString() +
+ new URL("client-navigated-frame.html", location).pathname.substring(1);
+ promise_test(function(t) {
+ this.add_cleanup(() => port.postMessage(pass(test, clientUrl)));
+ return self.clients.get(clientId)
+ .then(client => client.navigate("redirect.py?Redirect=" + url))
+ .then(client => {
+ clientUrl = (client && client.url) || ""
+ assert_true(client === null);
+ })
+ .catch(unreached_rejection(t));
+ }, "Redirecting to another origin should resolve with null");
+ }
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/client-navigated-frame.html b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigated-frame.html
new file mode 100644
index 000000000..307f7f9ac
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigated-frame.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<body style="background-color: green;"></body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-frame.html b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-frame.html
new file mode 100644
index 000000000..27143d4b9
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-frame.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+
+ fetch("clientId")
+ .then(function(response) {
+ return response.text();
+ })
+ .then(function(text) {
+ parent.postMessage({clientId: text}, "*");
+ });
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-other-origin.html b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-other-origin.html
new file mode 100644
index 000000000..cbd3dcc6f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-other-origin.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<script src="get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js"></script>
+<script>
+var host_info = get_host_info();
+var SCOPE = 'blank.html?clients-get';
+var SCRIPT = 'clients-get-worker.js';
+
+var registration;
+var worker;
+var wait_for_worker_promise = navigator.serviceWorker.getRegistration(SCOPE)
+ .then(function(reg) {
+ if (reg)
+ return reg.unregister();
+ })
+ .then(function() {
+ return navigator.serviceWorker.register(SCRIPT, {scope: SCOPE});
+ })
+ .then(function(reg) {
+ registration = reg;
+ worker = reg.installing;
+ return new Promise(function(resolve) {
+ worker.addEventListener('statechange', function() {
+ if (worker.state == 'activated')
+ resolve();
+ });
+ });
+ });
+
+function send_result(result) {
+ window.parent.postMessage(
+ {result: result},
+ host_info['HTTPS_ORIGIN']);
+}
+
+window.addEventListener("message", on_message, false);
+
+function on_message(e) {
+ if (e.origin != host_info['HTTPS_ORIGIN']) {
+ console.error('invalid origin: ' + e.origin);
+ return;
+ }
+ if (e.data.message == 'get_client_id') {
+ var otherOriginClientId = e.data.clientId;
+ wait_for_worker_promise
+ .then(function() {
+ return with_iframe(SCOPE);
+ })
+ .then(function(iframe) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function(e) {
+ navigator.serviceWorker.getRegistration(SCOPE)
+ .then(function(reg) {
+ reg.unregister();
+ send_result(e.data);
+ });
+ };
+ iframe.contentWindow.navigator.serviceWorker.controller.postMessage(
+ {port:channel.port2, clientId: otherOriginClientId,
+ message: 'get_other_client_id'}, [channel.port2]);
+ })
+ }
+}
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-worker.js
new file mode 100644
index 000000000..9ac2c2264
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-worker.js
@@ -0,0 +1,53 @@
+self.onfetch = function(e) {
+ if (e.request.url.indexOf("clients-get-frame.html") >= 0) {
+ if (e.clientId === null) {
+ e.respondWith(fetch(e.request));
+ } else {
+ e.respondWith(Response.error());
+ }
+ return;
+ }
+ e.respondWith(new Response(e.clientId));
+};
+
+self.onmessage = function(e) {
+ var port = e.data.port;
+ if (e.data.message == 'get_client_ids') {
+ var clientIds = e.data.clientIds;
+ var message = [];
+
+ Promise.all(
+ clientIds.map(function(clientId) {
+ return self.clients.get(clientId);
+ }).concat(self.clients.get("invalid-id"))
+ ).then(function(clients) {
+ clients.forEach(function(client) {
+ if (client instanceof Client) {
+ message.push([client.visibilityState,
+ client.focused,
+ client.url,
+ client.frameType]);
+ } else {
+ message.push(client);
+ }
+ });
+ port.postMessage(message);
+ });
+ } else if (e.data.message == 'get_other_client_id') {
+ var clientId = e.data.clientId;
+ var message;
+
+ self.clients.get(clientId)
+ .then(function(client) {
+ if (client instanceof Client) {
+ message = [client.visibilityState,
+ client.focused,
+ client.url,
+ client.frameType];
+ } else {
+ message = client;
+ }
+ port.postMessage(message);
+ });
+ }
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html
new file mode 100644
index 000000000..51b4dca03
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<title>Empty doc</title>
+<!--
+ Change the page URL using the History API to ensure that ServiceWorkerClient
+ uses the creation URL.
+-->
+<body onload="history.pushState({}, 'title', 'new-url.html')">
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-shared-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-shared-worker.js
new file mode 100644
index 000000000..1ae72fb89
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-shared-worker.js
@@ -0,0 +1,4 @@
+onconnect = function(e) {
+ var port = e.ports[0];
+ port.postMessage('started');
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-worker.js
new file mode 100644
index 000000000..f0ae90d81
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-worker.js
@@ -0,0 +1,24 @@
+self.onmessage = function(e) {
+ var port = e.data.port;
+ var options = e.data.options;
+
+ self.clients.matchAll(options).then(function(clients) {
+ var message = [];
+ clients.forEach(function(client) {
+ var frame_type = client.frameType;
+ if (client.url.indexOf('clients-matchall-include-uncontrolled.https.html') > -1 &&
+ client.frameType == 'auxiliary') {
+ // The test tab might be opened using window.open() by the test framework.
+ // In that case, just pretend it's top-level!
+ frame_type = 'top-level';
+ }
+ message.push([client.visibilityState,
+ client.focused,
+ client.url,
+ frame_type]);
+ });
+ // Sort by url
+ message.sort(function(a, b) { return a[2] > b[2] ? 1 : -1; });
+ port.postMessage(message);
+ });
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js
new file mode 100644
index 000000000..82b24459b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js
@@ -0,0 +1,7 @@
+var worker_text = 'onconnect = function(e) { e.ports[0].postMessage("worker loading intercepted by service worker"); };';
+
+self.onfetch = function(event) {
+ if (event.request.url.indexOf('dummy-shared-worker.js') != -1)
+ event.respondWith(new Response(worker_text));
+};
+
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-interceptor.js b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-interceptor.js
new file mode 100644
index 000000000..43244e1d9
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-interceptor.js
@@ -0,0 +1,21 @@
+importScripts('get-host-info.sub.js');
+
+var worker_text = 'postMessage("worker loading intercepted by service worker"); ';
+
+self.onfetch = function(event) {
+ if (event.request.url.indexOf('synthesized') != -1) {
+ event.respondWith(new Response(worker_text));
+ } else if (event.request.url.indexOf('same-origin') != -1) {
+ event.respondWith(fetch('dummy-worker-script.py'));
+ } else if (event.request.url.indexOf('cors') != -1) {
+ var path = (new URL('dummy-worker-script.py', self.location)).pathname;
+ var url = get_host_info()['HTTPS_REMOTE_ORIGIN'] + path;
+ var mode = "no-cors";
+ if (event.request.url.indexOf('no-cors') == -1) {
+ url += '?ACAOrigin=*';
+ mode = "cors";
+ }
+ event.respondWith(fetch(url, { mode: mode }));
+ }
+};
+
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-script.py b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-script.py
new file mode 100644
index 000000000..6f40b5ed6
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-script.py
@@ -0,0 +1,9 @@
+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, "postMessage('dummy-worker-script loaded');"
+
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/dummy.html b/testing/web-platform/tests/service-workers/service-worker/resources/dummy.html
new file mode 100644
index 000000000..12a179980
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/dummy.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<body>Hello world
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/dummy.txt b/testing/web-platform/tests/service-workers/service-worker/resources/dummy.txt
new file mode 100644
index 000000000..802992c42
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/dummy.txt
@@ -0,0 +1 @@
+Hello world
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/empty-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/empty-worker.js
new file mode 100644
index 000000000..49ceb2648
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/empty-worker.js
@@ -0,0 +1 @@
+// Do nothing.
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/empty.js b/testing/web-platform/tests/service-workers/service-worker/resources/empty.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/empty.js
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/end-to-end-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/end-to-end-worker.js
new file mode 100644
index 000000000..d45a50556
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/end-to-end-worker.js
@@ -0,0 +1,7 @@
+onmessage = function(e) {
+ var message = e.data;
+ if (typeof message === 'object' && 'port' in message) {
+ var response = 'Ack for: ' + message.from;
+ message.port.postMessage(response);
+ }
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/events-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/events-worker.js
new file mode 100644
index 000000000..80a218867
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/events-worker.js
@@ -0,0 +1,12 @@
+var eventsSeen = [];
+
+function handler(event) { eventsSeen.push(event.type); }
+
+['activate', 'install'].forEach(function(type) {
+ self.addEventListener(type, handler);
+ });
+
+onmessage = function(e) {
+ var message = e.data;
+ message.port.postMessage({events: eventsSeen});
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-async-waituntil.js b/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-async-waituntil.js
new file mode 100644
index 000000000..d77238d93
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-async-waituntil.js
@@ -0,0 +1,20 @@
+var result = 'FAIL: did not throw.';
+
+self.addEventListener('message', function(event) {
+ event.data.port.postMessage(result);
+ });
+
+self.addEventListener('install', function(event) {
+ self.installEvent = event;
+ });
+
+self.addEventListener('activate', function(event) {
+ try {
+ self.installEvent.waitUntil(new Promise(function(){}));
+ } catch (error) {
+ if (error.name == 'InvalidStateError')
+ result = 'PASS';
+ else
+ result = 'FAIL: unexpected exception: ' + error;
+ }
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-waituntil.js b/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-waituntil.js
new file mode 100644
index 000000000..48fdf1b99
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-waituntil.js
@@ -0,0 +1,75 @@
+var pendingPorts = [];
+var portResolves = [];
+
+onmessage = function(e) {
+ var message = e.data;
+ if ('port' in message) {
+ var resolve = self.portResolves.shift();
+ if (resolve)
+ resolve(message.port);
+ else
+ self.pendingPorts.push(message.port);
+ }
+};
+
+function fulfillPromise() {
+ return new Promise(function(resolve) {
+ // Make sure the oninstall/onactivate callback returns first.
+ Promise.resolve().then(function() {
+ var port = self.pendingPorts.shift();
+ if (port)
+ resolve(port);
+ else
+ self.portResolves.push(resolve);
+ });
+ }).then(function(port) {
+ port.postMessage('SYNC');
+ return new Promise(function(resolve) {
+ port.onmessage = function(e) {
+ if (e.data == 'ACK')
+ resolve();
+ };
+ });
+ });
+}
+
+function rejectPromise() {
+ return new Promise(function(resolve, reject) {
+ // Make sure the oninstall/onactivate callback returns first.
+ Promise.resolve().then(reject);
+ });
+}
+
+function stripScopeName(url) {
+ return url.split('/').slice(-1)[0];
+}
+
+oninstall = function(e) {
+ switch (stripScopeName(self.location.href)) {
+ case 'install-fulfilled':
+ e.waitUntil(fulfillPromise());
+ break;
+ case 'install-rejected':
+ e.waitUntil(rejectPromise());
+ break;
+ case 'install-multiple-fulfilled':
+ e.waitUntil(fulfillPromise());
+ e.waitUntil(fulfillPromise());
+ break;
+ case 'install-reject-precedence':
+ e.waitUntil(fulfillPromise());
+ e.waitUntil(rejectPromise());
+ break;
+ }
+};
+
+onactivate = function(e) {
+ switch (stripScopeName(self.location.href)) {
+ case 'activate-fulfilled':
+ e.waitUntil(fulfillPromise());
+ break;
+ case 'activate-rejected':
+ e.waitUntil(rejectPromise());
+ break;
+ }
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fail-on-fetch-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fail-on-fetch-worker.js
new file mode 100644
index 000000000..517f289fb
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fail-on-fetch-worker.js
@@ -0,0 +1,5 @@
+importScripts('worker-testharness.js');
+
+this.addEventListener('fetch', function(event) {
+ event.respondWith(new Response('ERROR'));
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control-login.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control-login.html
new file mode 100644
index 000000000..0ffab1af5
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control-login.html
@@ -0,0 +1,16 @@
+<script>
+// Set authentication info
+window.addEventListener("message", function(evt) {
+ var port = evt.ports[0];
+ document.cookie = 'cookie=' + evt.data.cookie;
+ var xhr = new XMLHttpRequest();
+ xhr.addEventListener('load', function() {
+ port.postMessage({msg: 'LOGIN FINISHED'});
+ }, false);
+ xhr.open('GET',
+ './fetch-access-control.py?Auth',
+ true,
+ evt.data.username, evt.data.password);
+ xhr.send();
+ }, false);
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control.py b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control.py
new file mode 100644
index 000000000..862718ad0
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control.py
@@ -0,0 +1,69 @@
+import base64
+import json
+
+def main(request, response):
+ headers = []
+ headers.append(('X-ServiceWorker-ServerHeader', 'SetInTheServer'))
+
+ if "ACAOrigin" in request.GET:
+ for item in request.GET["ACAOrigin"].split(","):
+ headers.append(("Access-Control-Allow-Origin", item))
+
+ for suffix in ["Headers", "Methods", "Credentials"]:
+ query = "ACA%s" % suffix
+ header = "Access-Control-Allow-%s" % suffix
+ if query in request.GET:
+ headers.append((header, request.GET[query]))
+
+ if "ACEHeaders" in request.GET:
+ headers.append(("Access-Control-Expose-Headers", request.GET[query]))
+
+ if ("Auth" in request.GET and not request.auth.username) or "AuthFail" in request.GET:
+ status = 401
+ headers.append(('WWW-Authenticate', 'Basic realm="Restricted"'))
+ body = 'Authentication canceled'
+ return status, headers, body
+
+ if "PNGIMAGE" in request.GET:
+ headers.append(("Content-Type", "image/png"))
+ body = base64.decodestring("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1B"
+ "AACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAhSURBVDhPY3wro/KfgQLABKXJBqMG"
+ "jBoAAqMGDLwBDAwAEsoCTFWunmQAAAAASUVORK5CYII=")
+ return headers, body
+
+
+ username = request.auth.username if request.auth.username else "undefined"
+ password = request.auth.password if request.auth.username else "undefined"
+ cookie = request.cookies['cookie'].value if 'cookie' in request.cookies else "undefined"
+
+ files = []
+ for key, values in request.POST.iteritems():
+ assert len(values) == 1
+ value = values[0]
+ if not hasattr(value, "file"):
+ continue
+ data = value.file.read()
+ files.append({"key": key,
+ "name": value.file.name,
+ "type": value.type,
+ "error": 0, #TODO,
+ "size": len(data),
+ "content": data})
+
+ get_data = {key:request.GET[key] for key,value in request.GET.iteritems()}
+ post_data = {key:request.POST[key] for key,value in request.POST.iteritems()
+ if not hasattr(request.POST[key], "file")}
+ headers_data = {key:request.headers[key] for key,value in request.headers.iteritems()}
+
+ data = {"jsonpResult": "success",
+ "method": request.method,
+ "headers": headers_data,
+ "body": request.body,
+ "files": files,
+ "GET": get_data,
+ "POST": post_data,
+ "username": username,
+ "password": password,
+ "cookie": cookie}
+
+ return headers, "report( %s )" % json.dumps(data)
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html
new file mode 100644
index 000000000..3822971e8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html
@@ -0,0 +1,294 @@
+<script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+var image_path = base_path() + 'fetch-access-control.py?PNGIMAGE';
+var host_info = get_host_info();
+var params = get_query_params(location.href);
+
+var NOT_TAINTED = 'NOT_TAINTED';
+var TAINTED = 'TAINTED';
+var LOAD_ERROR = 'LOAD_ERROR';
+
+function get_query_params(url) {
+ var search = (new URL(url)).search;
+ if (!search) {
+ return {};
+ }
+ var ret = {};
+ var params = search.substring(1).split('&');
+ params.forEach(function(param) {
+ var element = param.split('=');
+ ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]);
+ });
+ return ret;
+}
+
+function create_test_case_promise(url, cross_origin) {
+ return new Promise(function(resolve) {
+ var img = new Image();
+ if (cross_origin != '') {
+ img.crossOrigin = cross_origin;
+ }
+ img.onload = function() {
+ try {
+ var canvas = document.createElement('canvas');
+ canvas.width = 100;
+ canvas.height = 100;
+ var context = canvas.getContext('2d');
+ context.drawImage(img, 0, 0);
+ context.getImageData(0, 0, 100, 100);
+ resolve(NOT_TAINTED);
+ } catch (e) {
+ resolve(TAINTED);
+ }
+ };
+ img.onerror = function() {
+ resolve(LOAD_ERROR);
+ }
+ img.src = url;
+ });
+}
+
+function create_test_promise(url, cross_origin, expected_result) {
+ if (params['cache']) {
+ url += "&cache";
+ }
+
+ return new Promise(function(resolve, reject) {
+ create_test_case_promise(url, cross_origin)
+ .then(function(result) {
+ if (result == expected_result) {
+ resolve();
+ } else {
+ reject('Result of url:' + url + ' ' +
+ ' cross_origin: ' + cross_origin + ' must be ' +
+ expected_result + ' but ' + result);
+ }
+ })
+ });
+}
+
+window.addEventListener('message', function(evt) {
+ var port = evt.ports[0];
+ var image_url = host_info['HTTPS_ORIGIN'] + image_path;
+ var remote_image_url = host_info['HTTPS_REMOTE_ORIGIN'] + image_path;
+ Promise.all([
+ // Reject tests
+ create_test_promise(image_url + '&reject', '', LOAD_ERROR),
+ create_test_promise(image_url + '&reject', 'anonymous', LOAD_ERROR),
+ create_test_promise(
+ image_url + '&reject', 'use-credentials', LOAD_ERROR),
+ // Fallback tests
+ create_test_promise(
+ image_url + '&ignore',
+ '',
+ NOT_TAINTED),
+ create_test_promise(
+ remote_image_url + '&ignore',
+ '',
+ TAINTED),
+ create_test_promise(
+ remote_image_url + '&ignore',
+ 'anonymous',
+ LOAD_ERROR),
+ create_test_promise(
+ remote_image_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+ '&ignore',
+ 'anonymous',
+ NOT_TAINTED),
+ create_test_promise(
+ remote_image_url + '&ignore',
+ 'use-credentials',
+ LOAD_ERROR),
+ create_test_promise(
+ remote_image_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+ '&ignore',
+ 'use-credentials',
+ LOAD_ERROR),
+ create_test_promise(
+ remote_image_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+ '&ACACredentials=true&ignore',
+ 'use-credentials',
+ NOT_TAINTED),
+
+ // Credential test (fallback)
+ create_test_promise(
+ image_url + '&Auth&ignore',
+ '',
+ NOT_TAINTED),
+ create_test_promise(
+ remote_image_url + '&Auth&ignore',
+ '',
+ TAINTED),
+ create_test_promise(
+ remote_image_url + '&Auth&ignore',
+ 'anonymous',
+ LOAD_ERROR),
+ create_test_promise(
+ remote_image_url + '&Auth&ignore',
+ 'use-credentials',
+ LOAD_ERROR),
+ create_test_promise(
+ remote_image_url + '&Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+ '&ignore',
+ 'use-credentials',
+ LOAD_ERROR),
+ create_test_promise(
+ remote_image_url + '&Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+ '&ACACredentials=true&ignore',
+ 'use-credentials',
+ NOT_TAINTED),
+
+ // Basic response
+ create_test_promise(
+ image_url +
+ '&mode=same-origin&url=' + encodeURIComponent(image_url),
+ '',
+ NOT_TAINTED),
+ create_test_promise(
+ image_url +
+ '&mode=same-origin&url=' + encodeURIComponent(image_url),
+ 'anonymous',
+ NOT_TAINTED),
+ create_test_promise(
+ image_url +
+ '&mode=same-origin&url=' + encodeURIComponent(image_url),
+ 'use-credentials',
+ NOT_TAINTED),
+ create_test_promise(
+ remote_image_url +
+ '&mode=same-origin&url=' + encodeURIComponent(image_url),
+ '',
+ TAINTED),
+ create_test_promise(
+ remote_image_url +
+ '&mode=same-origin&url=' + encodeURIComponent(image_url),
+ 'anonymous',
+ NOT_TAINTED),
+ create_test_promise(
+ remote_image_url +
+ '&mode=same-origin&url=' + encodeURIComponent(image_url),
+ 'use-credentials',
+ NOT_TAINTED),
+
+ // Opaque response
+ create_test_promise(
+ image_url +
+ '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
+ '',
+ TAINTED),
+ create_test_promise(
+ image_url +
+ '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
+ 'anonymous',
+ LOAD_ERROR),
+ create_test_promise(
+ image_url +
+ '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
+ 'use-credentials',
+ LOAD_ERROR),
+ create_test_promise(
+ remote_image_url +
+ '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
+ '',
+ TAINTED),
+ create_test_promise(
+ remote_image_url +
+ '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
+ 'anonymous',
+ LOAD_ERROR),
+ create_test_promise(
+ remote_image_url +
+ '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
+ 'use-credentials',
+ LOAD_ERROR),
+
+ // CORS response
+ create_test_promise(
+ image_url +
+ '&mode=cors&url=' +
+ encodeURIComponent(remote_image_url +
+ '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+ '',
+ LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond
+ // with an Access-Control-Allow-Credentials header.
+ create_test_promise(
+ image_url +
+ '&mode=cors&credentials=same-origin&url=' +
+ encodeURIComponent(remote_image_url +
+ '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+ '',
+ NOT_TAINTED),
+ create_test_promise(
+ image_url +
+ '&mode=cors&url=' +
+ encodeURIComponent(remote_image_url +
+ '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+ 'anonymous',
+ NOT_TAINTED),
+ create_test_promise(
+ image_url +
+ '&mode=cors&url=' +
+ encodeURIComponent(remote_image_url +
+ '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+ 'use-credentials',
+ LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond
+ // with an Access-Control-Allow-Credentials header.
+ create_test_promise(
+ image_url +
+ '&mode=cors&url=' +
+ encodeURIComponent(
+ remote_image_url +
+ '&ACACredentials=true&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+ 'use-credentials',
+ NOT_TAINTED),
+ create_test_promise(
+ remote_image_url +
+ '&mode=cors&url=' +
+ encodeURIComponent(remote_image_url +
+ '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+ '',
+ LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond
+ // with an Access-Control-Allow-Credentials header.
+ create_test_promise(
+ remote_image_url +
+ '&mode=cors&credentials=same-origin&url=' +
+ encodeURIComponent(remote_image_url +
+ '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+ '',
+ TAINTED), // The cross-origin no-cors request is immediately tainted.
+ // Since this happens before the service worker interception,
+ // it does not matter what kind of response it returns.
+ // The result will always be tainted.
+ create_test_promise(
+ remote_image_url +
+ '&mode=cors&url=' +
+ encodeURIComponent(remote_image_url +
+ '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+ 'anonymous',
+ NOT_TAINTED),
+ create_test_promise(
+ remote_image_url +
+ '&mode=cors&url=' +
+ encodeURIComponent(remote_image_url +
+ '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+ 'use-credentials',
+ LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond
+ // with an Access-Control-Allow-Credentials header.
+ create_test_promise(
+ remote_image_url +
+ '&mode=cors&url=' +
+ encodeURIComponent(
+ remote_image_url +
+ '&ACACredentials=true&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+ 'use-credentials',
+ NOT_TAINTED)
+ ])
+ .then(function() {
+ port.postMessage({results: 'finish'});
+ })
+ .catch(function(e) {
+ port.postMessage({results: 'failure:' + e});
+ });
+ }, false);
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html
new file mode 100644
index 000000000..48f618397
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html
@@ -0,0 +1,210 @@
+<script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+var path = base_path() + 'fetch-access-control.py';
+var host_info = get_host_info();
+var SUCCESS = 'SUCCESS';
+var FAIL = 'FAIL';
+
+function create_test_case_promise(url, with_credentials) {
+ return new Promise(function(resolve) {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function() {
+ if (xhr.status == 200) {
+ resolve(SUCCESS);
+ } else {
+ resolve("STATUS" + xhr.status);
+ }
+ }
+ xhr.onerror = function() {
+ resolve(FAIL);
+ }
+ xhr.responseType = 'text';
+ xhr.withCredentials = with_credentials;
+ xhr.open('GET', url, true);
+ xhr.send();
+ });
+}
+
+
+function create_test_promise(url, with_credentials, expected_result) {
+ return new Promise(function(resolve, reject) {
+ create_test_case_promise(url, with_credentials)
+ .then(function(result) {
+ if (result == expected_result) {
+ resolve();
+ } else {
+ reject('Result of url:' + url + ' ' +
+ ' with_credentials: ' + with_credentials + ' must be ' +
+ expected_result + ' but ' + result);
+ }
+ })
+ });
+}
+
+function create_serial_promise(test_cases) {
+ var promise = Promise.resolve();
+ test_cases.forEach(function(test_case) {
+ promise = promise.then(function() {
+ return create_test_promise(test_case[0], test_case[1], test_case[2]);
+ });
+ });
+ return promise;
+}
+
+window.addEventListener('message', function(evt) {
+ var port = evt.ports[0];
+ var url = host_info['HTTPS_ORIGIN'] + path;
+ var remote_url = host_info['HTTPS_REMOTE_ORIGIN'] + path;
+ // If the 4th value of the item of TEST_CASES is true, the test case outputs
+ // warning messages. So such tests must be executed in serial to match the
+ // expected output text.
+ var TEST_CASES = [
+ // Reject tests
+ [url + '?reject', false, FAIL],
+ [url + '?reject', true, FAIL],
+ [remote_url + '?reject', false, FAIL],
+ [remote_url + '?reject', true, FAIL],
+ // Event handler exception tests
+ [url + '?throw', false, FAIL],
+ [url + '?throw', true, FAIL],
+ [remote_url + '?throw', false, FAIL],
+ [remote_url + '?throw', true, FAIL],
+ // Reject(resolve-null) tests
+ [url + '?resolve-null', false, FAIL],
+ [url + '?resolve-null', true, FAIL],
+ [remote_url + '?resolve-null', false, FAIL],
+ [remote_url + '?resolve-null', true, FAIL],
+ // Fallback tests
+ [url + '?ignore', false, SUCCESS],
+ [url + '?ignore', true, SUCCESS],
+ [remote_url + '?ignore', false, FAIL, true], // Executed in serial.
+ [remote_url + '?ignore', true, FAIL, true], // Executed in serial.
+ [
+ remote_url + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore',
+ false, SUCCESS
+ ],
+ [
+ remote_url + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore',
+ true, FAIL, true // Executed in serial.
+ ],
+ [
+ remote_url + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+ '&ACACredentials=true&ignore',
+ true, SUCCESS
+ ],
+ // Credential test (fallback)
+ [url + '?Auth&ignore', false, SUCCESS],
+ [url + '?Auth&ignore', true, SUCCESS],
+ [remote_url + '?Auth&ignore', false, FAIL, true], // Executed in serial.
+ [remote_url + '?Auth&ignore', true, FAIL, true], // Executed in serial.
+ [
+ remote_url + '?Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore',
+ false, 'STATUS401'
+ ],
+ [
+ remote_url + '?Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore',
+ true, FAIL, true // Executed in serial.
+ ],
+ [
+ remote_url + '?Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+ '&ACACredentials=true&ignore',
+ true, SUCCESS
+ ],
+ // Basic response
+ [
+ url + '?mode=same-origin&url=' + encodeURIComponent(url),
+ false, SUCCESS
+ ],
+ [
+ url + '?mode=same-origin&url=' + encodeURIComponent(url),
+ false, SUCCESS
+ ],
+ [
+ remote_url + '?mode=same-origin&url=' + encodeURIComponent(url),
+ false, SUCCESS
+ ],
+ [
+ remote_url + '?mode=same-origin&url=' + encodeURIComponent(url),
+ false, SUCCESS
+ ],
+ // Opaque response
+ [
+ url + '?mode=no-cors&url=' + encodeURIComponent(remote_url),
+ false, FAIL
+ ],
+ [
+ url + '?mode=no-cors&url=' + encodeURIComponent(remote_url),
+ false, FAIL
+ ],
+ [
+ remote_url + '?mode=no-cors&url=' + encodeURIComponent(remote_url),
+ false, FAIL
+ ],
+ [
+ remote_url + '?mode=no-cors&url=' + encodeURIComponent(remote_url),
+ false, FAIL
+ ],
+ // CORS response
+ [
+ url + '?mode=cors&url=' +
+ encodeURIComponent(remote_url + '?ACAOrigin=' +
+ host_info['HTTPS_ORIGIN']),
+ false, SUCCESS
+ ],
+ [
+ url + '?mode=cors&url=' +
+ encodeURIComponent(remote_url + '?ACAOrigin=' +
+ host_info['HTTPS_ORIGIN']),
+ true, FAIL
+ ],
+ [
+ url + '?mode=cors&url=' +
+ encodeURIComponent(remote_url + '?ACAOrigin=' +
+ host_info['HTTPS_ORIGIN'] +
+ '&ACACredentials=true'),
+ true, SUCCESS
+ ],
+ [
+ remote_url + '?mode=cors&url=' +
+ encodeURIComponent(remote_url + '?ACAOrigin=' +
+ host_info['HTTPS_ORIGIN']),
+ false, SUCCESS
+ ],
+ [
+ remote_url +
+ '?mode=cors&url=' +
+ encodeURIComponent(remote_url + '?ACAOrigin=' +
+ host_info['HTTPS_ORIGIN']),
+ true, FAIL
+ ],
+ [
+ remote_url +
+ '?mode=cors&url=' +
+ encodeURIComponent(remote_url + '?ACAOrigin=' +
+ host_info['HTTPS_ORIGIN'] +
+ '&ACACredentials=true'),
+ true, SUCCESS
+ ]
+ ];
+ var promises = [];
+ var serial_tests = [];
+ for (var i = 0; i < TEST_CASES.length ; ++i) {
+ if (!TEST_CASES[i][3]) {
+ promises.push(create_test_promise(TEST_CASES[i][0],
+ TEST_CASES[i][1],
+ TEST_CASES[i][2]));
+ } else {
+ serial_tests.push(TEST_CASES[i]);
+ }
+ }
+ promises.push(create_serial_promise(serial_tests));
+ Promise.all(promises)
+ .then(function() {
+ port.postMessage({results: 'finish'});
+ })
+ .catch(function(e) {
+ port.postMessage({results: 'failure:' + e});
+ });
+ }, false);
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html
new file mode 100644
index 000000000..cf7175bc8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html
@@ -0,0 +1,72 @@
+<script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+var image_path = base_path() + 'fetch-access-control.py?PNGIMAGE';
+var host_info = get_host_info();
+var results = '';
+var port = undefined;
+
+function test1() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ test2();
+ };
+ img.onerror = function() {
+ results += 'FAIL(1)';
+ test2();
+ };
+ img.src = host_info['HTTPS_ORIGIN'] + image_path;
+}
+
+function test2() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ results += 'FAIL(2)';
+ test3();
+ };
+ img.onerror = function() {
+ test3();
+ };
+ img.src = host_info['HTTPS_REMOTE_ORIGIN'] + image_path;
+}
+
+function test3() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ test4();
+ };
+ img.onerror = function() {
+ results += 'FAIL(3)';
+ test4();
+ };
+ img.src = './dummy?url=' +
+ encodeURIComponent(host_info['HTTPS_ORIGIN'] + image_path);
+}
+
+function test4() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ results += 'FAIL(4)';
+ finish();
+ };
+ img.onerror = function() {
+ finish();
+ };
+ img.src = './dummy?mode=no-cors&url=' +
+ encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + image_path);
+}
+
+function finish() {
+ results += 'finish';
+ port.postMessage({results: results});
+}
+
+window.addEventListener('message', function(evt) {
+ port = evt.ports[0];
+ test1();
+ }, false);
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html.sub.headers b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html.sub.headers
new file mode 100644
index 000000000..300efe049
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html.sub.headers
@@ -0,0 +1 @@
+Content-Security-Policy: img-src https://{{host}}:{{ports[https][0]}}
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-after-navigation-within-page-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-after-navigation-within-page-iframe.html
new file mode 100644
index 000000000..bf8a6d5ce
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-after-navigation-within-page-iframe.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script>
+function fetch_url(url) {
+ return new Promise(function(resolve, reject) {
+ var request = new XMLHttpRequest();
+ request.addEventListener('load', function(event) {
+ if (request.status == 200)
+ resolve(request.response);
+ else
+ reject(new Error('fetch_url: ' + request.statusText + " : " + url));
+ });
+ request.addEventListener('error', function(event) {
+ reject(new Error('fetch_url encountered an error: ' + url));
+ });
+ request.addEventListener('abort', function(event) {
+ reject(new Error('fetch_url was aborted: ' + url));
+ });
+ request.open('GET', url);
+ request.send();
+ });
+}
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js
new file mode 100644
index 000000000..7f66d20df
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js
@@ -0,0 +1,19 @@
+var result;
+
+self.addEventListener('message', function(event) {
+ event.data.port.postMessage(result);
+ });
+
+self.addEventListener('fetch', function(event) {
+ setTimeout(function() {
+ try {
+ event.respondWith(new Response());
+ result = 'FAIL: did not throw';
+ } catch (error) {
+ if (error.name == 'InvalidStateError')
+ result = 'PASS';
+ else
+ result = 'FAIL: Unexpected exception: ' + error;
+ }
+ }, 0);
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html
new file mode 100644
index 000000000..a4c9307e7
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<script>
+function fetch_url(url) {
+ return new Promise(function(resolve, reject) {
+ var request = new XMLHttpRequest();
+ request.addEventListener('load', function(event) {
+ resolve();
+ });
+ request.addEventListener('error', function(event) {
+ reject();
+ });
+ request.open('GET', url);
+ request.send();
+ });
+}
+
+function make_test(testcase) {
+ var name = testcase.name;
+ return fetch_url(window.location.href + '?' + name)
+ .then(
+ function() {
+ if (testcase.expect_load)
+ return Promise.resolve();
+ return Promise.reject(new Error(
+ name + ': expected network error but loaded'));
+ },
+ function() {
+ if (!testcase.expect_load)
+ return Promise.resolve();
+ return Promise.reject(new Error(
+ name + ': expected to load but got network error'));
+ });
+}
+
+function run_tests() {
+ var tests = [
+ { name: 'prevent-default-and-respond-with', expect_load: true },
+ { name: 'prevent-default', expect_load: false },
+ { name: 'reject', expect_load: false },
+ { name: 'unused-body', expect_load: true },
+ { name: 'used-body', expect_load: false },
+ { name: 'unused-fetched-body', expect_load: true },
+ { name: 'used-fetched-body', expect_load: false }
+ ].map(make_test);
+
+ Promise.all(tests)
+ .then(function() {
+ window.parent.notify_test_done('PASS');
+ })
+ .catch(function(error) {
+ window.parent.notify_test_done('FAIL: ' + error.message);
+ });
+}
+
+if (!navigator.serviceWorker.controller)
+ window.parent.notify_done('FAIL: no controller');
+else
+ run_tests();
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-worker.js
new file mode 100644
index 000000000..52d4c8e86
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-worker.js
@@ -0,0 +1,46 @@
+// Test that multiple fetch handlers do not confuse the implementation.
+self.addEventListener('fetch', function(event) {});
+
+self.addEventListener('fetch', function(event) {
+ var testcase = new URL(event.request.url).search;
+ switch (testcase) {
+ case '?reject':
+ event.respondWith(Promise.reject());
+ break;
+ case '?prevent-default':
+ event.preventDefault();
+ break;
+ case '?prevent-default-and-respond-with':
+ event.preventDefault();
+ break;
+ case '?unused-body':
+ event.respondWith(new Response('body'));
+ break;
+ case '?used-body':
+ var res = new Response('body');
+ res.text();
+ event.respondWith(res);
+ break;
+ case '?unused-fetched-body':
+ event.respondWith(fetch('other.html').then(function(res){
+ return res;
+ }));
+ break;
+ case '?used-fetched-body':
+ event.respondWith(fetch('other.html').then(function(res){
+ res.text();
+ return res;
+ }));
+ break;
+ }
+ });
+
+self.addEventListener('fetch', function(event) {});
+
+self.addEventListener('fetch', function(event) {
+ var testcase = new URL(event.request.url).search;
+ if (testcase == '?prevent-default-and-respond-with')
+ event.respondWith(new Response('responding!'));
+ });
+
+self.addEventListener('fetch', function(event) {});
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html
new file mode 100644
index 000000000..7548d8af7
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html
@@ -0,0 +1,25 @@
+<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) {
+ if (data.request_init.mode === 'no-cors' && data.redirect_dest != 'same-origin') {
+ if (response.type === data.expected_type &&
+ (response.type === 'opaque' || response.type === 'opaqueredirect') &&
+ response.redirected === data.expected_redirected) {
+ return {result: 'success', detail: ''};
+ } else {
+ return {result: 'failure',
+ detail: 'expected type ' + data.expected_type +
+ ', expected redirected ' + data.expected_redirected +
+ ' 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>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-respond-with-stops-propagation-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-respond-with-stops-propagation-worker.js
new file mode 100644
index 000000000..18da049d6
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-respond-with-stops-propagation-worker.js
@@ -0,0 +1,15 @@
+var result = null;
+
+self.addEventListener('message', function(event) {
+ event.data.port.postMessage(result);
+ });
+
+self.addEventListener('fetch', function(event) {
+ if (!result)
+ result = 'PASS';
+ event.respondWith(new Response());
+ });
+
+self.addEventListener('fetch', function(event) {
+ result = 'FAIL: fetch event propagated';
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-test-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-test-worker.js
new file mode 100644
index 000000000..55ba4ab4d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-test-worker.js
@@ -0,0 +1,151 @@
+function handleString(event) {
+ event.respondWith(new Response('Test string'));
+}
+
+function handleBlob(event) {
+ event.respondWith(new Response(new Blob(['Test blob'])));
+}
+
+function handleReferrer(event) {
+ event.respondWith(new Response(new Blob(
+ ['Referrer: ' + event.request.referrer])));
+}
+
+function handleReferrerPolicy(event) {
+ event.respondWith(new Response(new Blob(
+ ['ReferrerPolicy: ' + event.request.referrerPolicy])));
+}
+
+function handleReferrerFull(event) {
+ event.respondWith(new Response(new Blob(
+ ['Referrer: ' + event.request.referrer + '\n' +
+ 'ReferrerPolicy: ' + event.request.referrerPolicy])));
+}
+
+function handleClientId(event) {
+ var body;
+ if (event.clientId !== null) {
+ body = 'Client ID Found: ' + event.clientId;
+ } else {
+ body = 'Client ID Not Found';
+ }
+ event.respondWith(new Response(body));
+}
+
+function handleNullBody(event) {
+ event.respondWith(new Response());
+}
+
+function handleFetch(event) {
+ event.respondWith(fetch('other.html'));
+}
+
+function handleFormPost(event) {
+ event.respondWith(new Promise(function(resolve) {
+ event.request.text()
+ .then(function(result) {
+ resolve(new Response(event.request.method + ':' +
+ event.request.headers.get('Content-Type') + ':' +
+ result));
+ });
+ }));
+}
+
+function handleMultipleRespondWith(event) {
+ var logForMultipleRespondWith = '';
+ for (var i = 0; i < 3; ++i) {
+ logForMultipleRespondWith += '(' + i + ')';
+ try {
+ event.respondWith(new Promise(function(resolve) {
+ setTimeout(function() {
+ resolve(new Response(logForMultipleRespondWith));
+ }, 0);
+ }));
+ } catch (e) {
+ logForMultipleRespondWith += '[' + e.name + ']';
+ }
+ }
+}
+
+var lastResponseForUsedCheck = undefined;
+
+function handleUsedCheck(event) {
+ if (!lastResponseForUsedCheck) {
+ event.respondWith(fetch('other.html').then(function(response) {
+ lastResponseForUsedCheck = response;
+ return response;
+ }));
+ } else {
+ event.respondWith(new Response(
+ 'bodyUsed: ' + lastResponseForUsedCheck.bodyUsed));
+ }
+}
+function handleFragmentCheck(event) {
+ var body;
+ if (event.request.url.indexOf('#') === -1) {
+ body = 'Fragment Not Found';
+ } else {
+ body = 'Fragment Found :' +
+ event.request.url.substring(event.request.url.indexOf('#'));
+ }
+ event.respondWith(new Response(body));
+}
+function handleCache(event) {
+ event.respondWith(new Response(event.request.cache));
+}
+function handleEventSource(event) {
+ if (event.request.mode === 'navigate') {
+ return;
+ }
+ var data = {
+ mode: event.request.mode,
+ cache: event.request.cache,
+ credentials: event.request.credentials
+ };
+ var body = 'data:' + JSON.stringify(data) + '\n\n';
+ event.respondWith(new Response(body, {
+ headers: { 'Content-Type': 'text/event-stream' }
+ }
+ ));
+}
+
+function handleIntegrity(event) {
+ event.respondWith(new Response(event.request.integrity));
+}
+
+self.addEventListener('fetch', function(event) {
+ var url = event.request.url;
+ var handlers = [
+ { pattern: '?string', fn: handleString },
+ { pattern: '?blob', fn: handleBlob },
+ { pattern: '?referrerFull', fn: handleReferrerFull },
+ { pattern: '?referrerPolicy', fn: handleReferrerPolicy },
+ { pattern: '?referrer', fn: handleReferrer },
+ { pattern: '?clientId', fn: handleClientId },
+ { pattern: '?ignore', fn: function() {} },
+ { pattern: '?null', fn: handleNullBody },
+ { pattern: '?fetch', fn: handleFetch },
+ { pattern: '?form-post', fn: handleFormPost },
+ { pattern: '?multiple-respond-with', fn: handleMultipleRespondWith },
+ { pattern: '?used-check', fn: handleUsedCheck },
+ { pattern: '?fragment-check', fn: handleFragmentCheck },
+ { pattern: '?cache', fn: handleCache },
+ { pattern: '?eventsource', fn: handleEventSource },
+ { pattern: '?integrity', fn: handleIntegrity },
+ ];
+
+ var handler = null;
+ for (var i = 0; i < handlers.length; ++i) {
+ if (url.indexOf(handlers[i].pattern) != -1) {
+ handler = handlers[i];
+ break;
+ }
+ }
+
+ if (handler) {
+ handler.fn(event);
+ } else {
+ event.respondWith(new Response(new Blob(
+ ['Service Worker got an unexpected request: ' + url])));
+ }
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-header-visibility-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-header-visibility-iframe.html
new file mode 100644
index 000000000..e0f32f754
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-header-visibility-iframe.html
@@ -0,0 +1,66 @@
+<script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+ var host_info = get_host_info();
+ var uri = document.location + '?check-ua-header';
+
+ var headers = new Headers();
+ headers.set('User-Agent', 'custom_ua');
+
+ // Check the custom UA case
+ fetch(uri, { headers: headers }).then(function(response) {
+ return response.text();
+ }).then(function(text) {
+ if (text == 'custom_ua') {
+ parent.postMessage('PASS', '*');
+ } else {
+ parent.postMessage('withUA FAIL - expected "custom_ua", got "' + text + '"', '*');
+ }
+ }).catch(function(err) {
+ parent.postMessage('withUA FAIL - unexpected error: ' + err, '*');
+ });
+
+ // Check the default UA case
+ fetch(uri, {}).then(function(response) {
+ return response.text();
+ }).then(function(text) {
+ if (text == 'NO_UA') {
+ parent.postMessage('PASS', '*');
+ } else {
+ parent.postMessage('noUA FAIL - expected "NO_UA", got "' + text + '"', '*');
+ }
+ }).catch(function(err) {
+ parent.postMessage('noUA FAIL - unexpected error: ' + err, '*');
+ });
+
+ var uri = document.location + '?check-accept-header';
+ var headers = new Headers();
+ headers.set('Accept', 'hmm');
+
+ // Check for custom accept header
+ fetch(uri, { headers: headers }).then(function(response) {
+ return response.text();
+ }).then(function(text) {
+ if (text === headers.get('Accept')) {
+ parent.postMessage('PASS', '*');
+ } else {
+ parent.postMessage('custom accept FAIL - expected ' + headers.get('Accept') +
+ ' got "' + text + '"', '*');
+ }
+ }).catch(function(err) {
+ parent.postMessage('custom accept FAIL - unexpected error: ' + err, '*');
+ });
+
+ // Check for default accept header
+ fetch(uri).then(function(response) {
+ return response.text();
+ }).then(function(text) {
+ if (text === '*/*') {
+ parent.postMessage('PASS', '*');
+ } else {
+ parent.postMessage('accept FAIL - expected */* got "' + text + '"', '*');
+ }
+ }).catch(function(err) {
+ parent.postMessage('accept FAIL - unexpected error: ' + err, '*');
+ });
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-inscope.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-inscope.html
new file mode 100644
index 000000000..a7db229ce
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-inscope.html
@@ -0,0 +1,71 @@
+<script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+var image_path = base_path() + 'fetch-access-control.py?PNGIMAGE';
+var host_info = get_host_info();
+var results = '';
+
+function test1() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ test2();
+ };
+ img.onerror = function() {
+ results += 'FAIL(1)';
+ test2();
+ };
+ img.src = './dummy?url=' +
+ encodeURIComponent(host_info['HTTPS_ORIGIN'] + image_path);
+}
+
+function test2() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ test3();
+ };
+ img.onerror = function() {
+ results += 'FAIL(2)';
+ test3();
+ };
+ img.src = './dummy?mode=no-cors&url=' +
+ encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + image_path);
+}
+
+function test3() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ results += 'FAIL(3)';
+ test4();
+ };
+ img.onerror = function() {
+ test4();
+ };
+ img.src = './dummy?mode=no-cors&url=' +
+ encodeURIComponent(host_info['HTTP_ORIGIN'] + image_path);
+}
+
+function test4() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ results += 'FAIL(4)';
+ finish();
+ };
+ img.onerror = function() {
+ finish();
+ };
+ img.src = './dummy?mode=no-cors&url=' +
+ encodeURIComponent(host_info['HTTP_REMOTE_ORIGIN'] + image_path);
+}
+
+function finish() {
+ results += 'finish';
+ window.parent.postMessage({results: results}, host_info['HTTPS_ORIGIN']);
+}
+</script>
+
+<body onload='test1();'>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-outscope.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-outscope.html
new file mode 100644
index 000000000..cec00cb25
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-outscope.html
@@ -0,0 +1,80 @@
+<script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+var image_path = base_path() + 'fetch-access-control.py?PNGIMAGE';
+var host_info = get_host_info();
+var results = '';
+
+function test1() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ test2();
+ };
+ img.onerror = function() {
+ results += 'FAIL(1)';
+ test2();
+ };
+ img.src = host_info['HTTPS_ORIGIN'] + image_path;
+}
+
+function test2() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ test3();
+ };
+ img.onerror = function() {
+ results += 'FAIL(2)';
+ test3();
+ };
+ img.src = host_info['HTTPS_REMOTE_ORIGIN'] + image_path;
+}
+
+function test3() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ results += 'FAIL(3)';
+ test4();
+ };
+ img.onerror = function() {
+ test4();
+ };
+ img.src = host_info['HTTP_ORIGIN'] + image_path;
+}
+
+function test4() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ results += 'FAIL(4)';
+ test5();
+ };
+ img.onerror = function() {
+ test5();
+ };
+ img.src = host_info['HTTP_REMOTE_ORIGIN'] + image_path;
+}
+
+function test5() {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = function() {
+ finish();
+ };
+ img.onerror = function() {
+ results += 'FAIL(5)';
+ finish();
+ };
+ img.src = './dummy?generate-png';
+}
+
+function finish() {
+ results += 'finish';
+ window.parent.postMessage({results: results}, host_info['HTTPS_ORIGIN']);
+}
+</script>
+
+<body onload='test1();'>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe.html
new file mode 100644
index 000000000..961fd2ab6
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+var params = get_query_params(location.href);
+var SCOPE = 'fetch-mixed-content-iframe-inscope-to-' + params['target'] + '.html';
+var URL = 'fetch-rewrite-worker.js';
+var host_info = get_host_info();
+
+window.addEventListener('message', on_message, false);
+
+navigator.serviceWorker.getRegistration(SCOPE)
+ .then(function(registration) {
+ if (registration)
+ return registration.unregister();
+ })
+ .then(function() {
+ return navigator.serviceWorker.register(URL, {scope: SCOPE});
+ })
+ .then(function(registration) {
+ return new Promise(function(resolve) {
+ registration.addEventListener('updatefound', function() {
+ resolve(registration.installing);
+ });
+ });
+ })
+ .then(function(worker) {
+ worker.addEventListener('statechange', on_state_change);
+ })
+ .catch(function(reason) {
+ window.parent.postMessage({results: 'FAILURE: ' + reason.message},
+ host_info['HTTPS_ORIGIN']);
+ });
+
+function on_state_change(event) {
+ if (event.target.state != 'activated')
+ return;
+ var frame = document.createElement('iframe');
+ frame.src = SCOPE;
+ document.body.appendChild(frame);
+}
+
+function on_message(e) {
+ navigator.serviceWorker.getRegistration(SCOPE)
+ .then(function(registration) {
+ if (registration)
+ return registration.unregister();
+ })
+ .then(function() {
+ window.parent.postMessage(e.data, host_info['HTTPS_ORIGIN']);
+ })
+ .catch(function(reason) {
+ window.parent.postMessage({results: 'FAILURE: ' + reason.message},
+ host_info['HTTPS_ORIGIN']);
+ });
+}
+
+function get_query_params(url) {
+ var search = (new URL(url)).search;
+ if (!search) {
+ return {};
+ }
+ var ret = {};
+ var params = search.substring(1).split('&');
+ params.forEach(function(param) {
+ var element = param.split('=');
+ ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]);
+ });
+ return ret;
+}
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-iframe.html
new file mode 100644
index 000000000..0edf2e7f9
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-iframe.html
@@ -0,0 +1 @@
+<link href="./fetch-request-css-base-url-style.css" rel="stylesheet" type="text/css">
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-style.css b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-style.css
new file mode 100644
index 000000000..7643f667a
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-style.css
@@ -0,0 +1 @@
+body { background-image: url("./dummy.png");}
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-worker.js
new file mode 100644
index 000000000..0d9244ec7
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-worker.js
@@ -0,0 +1,27 @@
+importScripts('../resources/get-host-info.sub.js');
+importScripts('test-helpers.sub.js');
+
+var port = undefined;
+
+self.onmessage = function(e) {
+ var message = e.data;
+ if ('port' in message) {
+ port = message.port;
+ port.postMessage({ready: true});
+ }
+};
+
+self.addEventListener('fetch', function(event) {
+ var url = event.request.url;
+ if (url.indexOf('fetch-request-css-base-url-style.css') != -1) {
+ event.respondWith(fetch(
+ get_host_info()['HTTPS_REMOTE_ORIGIN'] + base_path() +
+ 'fetch-request-css-base-url-style.css',
+ {mode: 'no-cors'}));
+ } else if (url.indexOf('dummy.png') != -1) {
+ port.postMessage({
+ url: event.request.url,
+ referrer: event.request.referrer
+ });
+ }
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-iframe.html
new file mode 100644
index 000000000..f00d24e37
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-iframe.html
@@ -0,0 +1,15 @@
+<script>
+function xhr(url) {
+ return new Promise(function(resolve, reject) {
+ var request = new XMLHttpRequest();
+ request.addEventListener(
+ 'error',
+ function(event) { reject(event); });
+ request.addEventListener(
+ 'load',
+ function(event) { resolve(request.response); });
+ request.open('GET', url);
+ request.send();
+ });
+}
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js
new file mode 100644
index 000000000..3b028b24b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js
@@ -0,0 +1,13 @@
+var requests = [];
+
+self.addEventListener('message', function(event) {
+ event.data.port.postMessage({requests: requests});
+ requests = [];
+ });
+
+self.addEventListener('fetch', function(event) {
+ requests.push({
+ url: event.request.url,
+ mode: event.request.mode
+ });
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html
new file mode 100644
index 000000000..68a1a8687
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html
@@ -0,0 +1 @@
+<script src="empty.js"></script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-worker.js
new file mode 100644
index 000000000..2bd59d739
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-worker.js
@@ -0,0 +1,18 @@
+var requests = [];
+
+self.addEventListener('message', function(event) {
+ event.data.port.postMessage({requests: requests});
+ });
+
+self.addEventListener('fetch', function(event) {
+ var url = event.request.url;
+ var headers = [];
+ for (var header of event.request.headers) {
+ headers.push(header);
+ }
+ requests.push({
+ url: url,
+ headers: headers
+ });
+ event.respondWith(fetch(event.request));
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html
new file mode 100644
index 000000000..ffd76bfc4
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html
@@ -0,0 +1,35 @@
+<script>
+function xhr(url) {
+ return new Promise(function(resolve, reject) {
+ var request = new XMLHttpRequest();
+ request.addEventListener(
+ 'error',
+ function(event) { reject(event); });
+ request.addEventListener(
+ 'load',
+ function(event) { resolve(request.response); });
+ request.open('GET', url);
+ request.send();
+ });
+}
+
+function load_image(url) {
+ return new Promise(function(resolve, reject) {
+ var img = document.createElement('img');
+ document.body.appendChild(img);
+ img.onload = resolve;
+ img.onerror = reject;
+ img.src = url;
+ });
+}
+
+function load_audio(url) {
+ return new Promise(function(resolve, reject) {
+ var audio = document.createElement('audio');
+ document.body.appendChild(audio);
+ audio.oncanplay = resolve;
+ audio.onerror = reject;
+ audio.src = url;
+ });
+}
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html
new file mode 100644
index 000000000..93b038dd6
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html
@@ -0,0 +1,67 @@
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<body>
+<script>
+
+function load_image(url, cross_origin) {
+ var img = document.createElement('img');
+ if (cross_origin != '') {
+ img.crossOrigin = cross_origin;
+ }
+ img.src = url;
+}
+
+function load_script(url, cross_origin) {
+ var script = document.createElement('script');
+ script.src = url;
+ if (cross_origin != '') {
+ script.crossOrigin = cross_origin;
+ }
+ document.body.appendChild(script);
+}
+
+function load_css(url, cross_origin) {
+ var link = document.createElement('link');
+ link.rel = 'stylesheet'
+ link.href = url;
+ link.type = 'text/css';
+ if (cross_origin != '') {
+ link.crossOrigin = cross_origin;
+ }
+ document.body.appendChild(link);
+}
+
+function load_font(url) {
+ var fontFace = new FontFace('test', 'url(' + url + ')');
+ fontFace.load();
+}
+
+function load_css_image(url, type) {
+ var div = document.createElement('div');
+ document.body.appendChild(div);
+ div.style[type] = 'url(' + url + ')';
+}
+
+function load_css_image_set(url, type) {
+ var div = document.createElement('div');
+ document.body.appendChild(div);
+ div.style[type] = '-webkit-image-set(url(' + url + ') 1x)';
+}
+
+function load_script_with_integrity(url, integrity) {
+ var script = document.createElement('script');
+ script.src = url;
+ script.integrity = integrity;
+ document.body.appendChild(script);
+}
+
+function load_css_with_integrity(url, integrity) {
+ var link = document.createElement('link');
+ link.rel = 'stylesheet'
+ link.href = url;
+ link.type = 'text/css';
+ link.integrity = integrity;
+ document.body.appendChild(link);
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-worker.js
new file mode 100644
index 000000000..900b63c62
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-worker.js
@@ -0,0 +1,24 @@
+var requests = [];
+var port = undefined;
+
+self.onmessage = function(e) {
+ var message = e.data;
+ if ('port' in message) {
+ port = message.port;
+ port.postMessage({ready: true});
+ }
+};
+
+self.addEventListener('fetch', function(event) {
+ var url = event.request.url;
+ if (url.indexOf('dummy?test') == -1) {
+ return;
+ }
+ port.postMessage({
+ url: url,
+ mode: event.request.mode,
+ credentials: event.request.credentials,
+ integrity: event.request.integrity
+ });
+ event.respondWith(Promise.reject());
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html
new file mode 100644
index 000000000..26c6b7344
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html
@@ -0,0 +1,179 @@
+<script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+var port;
+var host_info = get_host_info();
+
+function assert_equals(a, b) {
+ port.postMessage({results: 'equals', got: a, expected: b});
+}
+
+function get_boundary(headers) {
+ var reg = new RegExp('multipart\/form-data; boundary=(.*)');
+ for (var i = 0; i < headers.length; ++i) {
+ if (headers[i][0] != 'content-type') {
+ continue;
+ }
+ var regResult = reg.exec(headers[i][1]);
+ if (!regResult) {
+ continue;
+ }
+ return regResult[1];
+ }
+ return '';
+}
+
+function create_file_system_file(file_name, data) {
+ return new Promise(function(resolve, reject) {
+ webkitRequestFileSystem(TEMPORARY, 1024, function(fs) {
+ fs.root.getFile(
+ file_name, {create: true, exclusive: true},
+ function(fileEntry) {
+ fileEntry.createWriter(function(fileWriter) {
+ fileWriter.onwriteend = function(e) {
+ fileEntry.file(function(file) { resolve(file); });
+ };
+ var blob = new Blob([data], {type: 'text/plain'});
+ fileWriter.write(blob);
+ });
+ }, function(e) { reject(e); });
+ }, function(e) { reject(e); });
+ });
+}
+
+function xhr_send(url_base, method, data, with_credentials) {
+ return new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function() {
+ resolve(JSON.parse(xhr.response));
+ };
+ xhr.onerror = function() {
+ reject('XHR should succeed.');
+ };
+ xhr.responseType = 'text';
+ if (with_credentials) {
+ xhr.withCredentials = true;
+ }
+ xhr.open(method, url_base + '/dummy?test', true);
+ xhr.send(data);
+ });
+}
+
+function string_test() {
+ return xhr_send(host_info['HTTPS_ORIGIN'], 'POST', 'test string', false)
+ .then(function(response) {
+ assert_equals(response.method, 'POST');
+ assert_equals(response.body, 'test string');
+ });
+}
+
+function blob_test() {
+ return xhr_send(host_info['HTTPS_ORIGIN'], 'POST', new Blob(['test blob']),
+ false)
+ .then(function(response) {
+ assert_equals(response.method, 'POST');
+ assert_equals(response.body, 'test blob');
+ });
+}
+
+function custom_method_test() {
+ return xhr_send(host_info['HTTPS_ORIGIN'], 'XXX', 'test string xxx', false)
+ .then(function(response) {
+ assert_equals(response.method, 'XXX');
+ assert_equals(response.body, 'test string xxx');
+ });
+}
+
+function options_method_test() {
+ return xhr_send(host_info['HTTPS_ORIGIN'], 'OPTIONS', 'test string xxx', false)
+ .then(function(response) {
+ assert_equals(response.method, 'OPTIONS');
+ assert_equals(response.body, 'test string xxx');
+ });
+}
+
+function form_data_test() {
+ var formData = new FormData();
+ formData.append('sample string', '1234567890');
+ formData.append('sample blob', new Blob(['blob content']));
+ formData.append('sample file', new File(['file content'], 'file.dat'));
+ return xhr_send(host_info['HTTPS_ORIGIN'], 'POST', formData, false)
+ .then(function(response) {
+ assert_equals(response.method, 'POST');
+ var boundary = get_boundary(response.headers);
+ var expected_body =
+ '--' + boundary + '\r\n' +
+ 'Content-Disposition: form-data; name="sample string"\r\n' +
+ '\r\n' +
+ '1234567890\r\n' +
+ '--' + boundary + '\r\n' +
+ 'Content-Disposition: form-data; name="sample blob"; ' +
+ 'filename="blob"\r\n' +
+ 'Content-Type: application/octet-stream\r\n' +
+ '\r\n' +
+ 'blob content\r\n' +
+ '--' + boundary + '\r\n' +
+ 'Content-Disposition: form-data; name="sample file"; ' +
+ 'filename="file.dat"\r\n' +
+ 'Content-Type: application/octet-stream\r\n' +
+ '\r\n' +
+ 'file content\r\n' +
+ '--' + boundary + '--\r\n';
+ assert_equals(response.body, expected_body);
+ });
+}
+
+function mode_credentials_test() {
+ return xhr_send(host_info['HTTPS_ORIGIN'], 'GET', '', false)
+ .then(function(response){
+ assert_equals(response.mode, 'cors');
+ assert_equals(response.credentials, 'same-origin');
+ return xhr_send(host_info['HTTPS_ORIGIN'], 'GET', '', true);
+ })
+ .then(function(response){
+ assert_equals(response.mode, 'cors');
+ assert_equals(response.credentials, 'include');
+ return xhr_send(host_info['HTTPS_REMOTE_ORIGIN'], 'GET', '', false);
+ })
+ .then(function(response){
+ assert_equals(response.mode, 'cors');
+ assert_equals(response.credentials, 'same-origin');
+ return xhr_send(host_info['HTTPS_REMOTE_ORIGIN'], 'GET', '', true);
+ })
+ .then(function(response){
+ assert_equals(response.mode, 'cors');
+ assert_equals(response.credentials, 'include');
+ });
+}
+
+function data_url_test() {
+ return new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ xhr.onerror = function() {
+ reject('XHR should succeed.');
+ };
+ xhr.responseType = 'text';
+ xhr.open('GET', 'data:text/html,Foobar', true);
+ xhr.send();
+ })
+ .then(function(data) {
+ assert_equals(data, 'Foobar');
+ });
+}
+
+window.addEventListener('message', function(evt) {
+ port = evt.ports[0];
+ string_test()
+ .then(blob_test)
+ .then(custom_method_test)
+ .then(options_method_test)
+ .then(form_data_test)
+ .then(mode_credentials_test)
+ .then(data_url_test)
+ .then(function() { port.postMessage({results: 'finish'}); })
+ .catch(function(e) { port.postMessage({results: 'failure:' + e}); });
+ });
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-worker.js
new file mode 100644
index 000000000..91b3abb14
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-worker.js
@@ -0,0 +1,22 @@
+self.addEventListener('fetch', function(event) {
+ var url = event.request.url;
+ if (url.indexOf('dummy?test') == -1) {
+ return;
+ }
+ event.respondWith(new Promise(function(resolve) {
+ var headers = [];
+ for (var header of event.request.headers) {
+ headers.push(header);
+ }
+ event.request.text()
+ .then(function(result) {
+ resolve(new Response(JSON.stringify({
+ method: event.request.method,
+ mode: event.request.mode,
+ credentials: event.request.credentials,
+ headers: headers,
+ body: result
+ })));
+ });
+ }));
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-iframe.https.html
new file mode 100644
index 000000000..3391381e3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-iframe.https.html
@@ -0,0 +1,35 @@
+<script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+var host_info = get_host_info();
+
+function xhr_send(method, data) {
+ return new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function() {
+ resolve(xhr);
+ };
+ xhr.onerror = function() {
+ reject('XHR should succeed.');
+ };
+ xhr.responseType = 'text';
+ xhr.open(method, './dummy?test', true);
+ xhr.send(data);
+ });
+}
+
+function coalesce_headers_test() {
+ return xhr_send('POST', 'test string')
+ .then(function(xhr) {
+ window.parent.postMessage({results: xhr.getResponseHeader('foo')},
+ host_info['HTTPS_ORIGIN']);
+ });
+}
+
+window.addEventListener('message', function(evt) {
+ var port = evt.ports[0];
+ coalesce_headers_test()
+ .then(function() { port.postMessage({results: 'finish'}); })
+ .catch(function(e) { port.postMessage({results: 'failure:' + e}); });
+ });
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-worker.js
new file mode 100644
index 000000000..465fbc91b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-worker.js
@@ -0,0 +1,12 @@
+self.addEventListener('fetch', function(event) {
+ var url = event.request.url;
+ if (url.indexOf('dummy?test') == -1) {
+ return;
+ }
+ event.respondWith(new Promise(function(resolve) {
+ var headers = new Headers;
+ headers.append('foo', 'foo');
+ headers.append('foo', 'bar');
+ resolve(new Response('hello world', {'headers': headers}));
+ }));
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js
new file mode 100644
index 000000000..9806f2b5b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js
@@ -0,0 +1,149 @@
+function get_query_params(url) {
+ var search = (new URL(url)).search;
+ if (!search) {
+ return {};
+ }
+ var ret = {};
+ 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(base, params) {
+ var init = {};
+ init['method'] = params['method'] || base['method'];
+ init['mode'] = params['mode'] || base['mode'];
+ if (init['mode'] == 'navigate') {
+ init['mode'] = 'same-origin';
+ }
+ init['credentials'] = params['credentials'] || base['credentials'];
+ init['redirect'] = params['redirect-mode'] || base['redirect'];
+ return init;
+}
+
+self.addEventListener('fetch', function(event) {
+ var params = get_query_params(event.request.url);
+ var init = get_request_init(event.request, params);
+ var url = params['url'];
+ if (params['ignore']) {
+ return;
+ }
+ if (params['throw']) {
+ throw new Error('boom');
+ }
+ if (params['reject']) {
+ event.respondWith(new Promise(function(resolve, reject) {
+ reject();
+ }));
+ return;
+ }
+ if (params['resolve-null']) {
+ event.respondWith(new Promise(function(resolve) {
+ resolve(null);
+ }));
+ return;
+ }
+ if (params['generate-png']) {
+ var binary = atob(
+ 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAA' +
+ 'RnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAhSURBVDhPY3wro/Kf' +
+ 'gQLABKXJBqMGjBoAAqMGDLwBDAwAEsoCTFWunmQAAAAASUVORK5CYII=');
+ var array = new Uint8Array(binary.length);
+ for(var i = 0; i < binary.length; i++) {
+ array[i] = binary.charCodeAt(i);
+ };
+ event.respondWith(new Response(new Blob([array], {type: 'image/png'})));
+ return;
+ }
+ if (params['check-ua-header']) {
+ var ua = event.request.headers.get('User-Agent');
+ if (ua) {
+ // We have a user agent!
+ event.respondWith(new Response(new Blob([ua])));
+ } else {
+ // We don't have a user-agent!
+ event.respondWith(new Response(new Blob(["NO_UA"])));
+ }
+ return;
+ }
+ if (params['check-accept-header']) {
+ var accept = event.request.headers.get('Accept');
+ if (accept) {
+ event.respondWith(new Response(accept));
+ } else {
+ event.respondWith(new Response('NO_ACCEPT'));
+ }
+ return;
+ }
+ event.respondWith(new Promise(function(resolve, reject) {
+ var request = event.request;
+ if (url) {
+ request = new Request(url, init);
+ }
+ 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
+ })));
+ }
+
+ var expectedRedirected = params['expected_redirected'];
+ if (typeof expectedRedirected !== 'undefined') {
+ var expected_redirected = (expectedRedirected === 'true');
+ if(response.redirected !== expected_redirected) {
+ // This is simply determining how to pass an error to the outer
+ // test case(fetch-request-redirect.https.html).
+ var execptedResolves = params['expected_resolves'];
+ if (execptedResolves === 'true') {
+ // Reject a JSON object with a failure since promise is expected
+ // to be resolved.
+ reject(new Response(JSON.stringify({
+ result: 'failure',
+ detail: 'got '+ response.redirected +
+ ' Response.redirected instead of ' +
+ expectedRedirected
+ })));
+ } else {
+ // Resolve a JSON object with a failure since promise is
+ // expected to be rejected.
+ resolve(new Response(JSON.stringify({
+ result: 'failure',
+ detail: 'got '+ response.redirected +
+ ' Response.redirected instead of ' +
+ expectedRedirected
+ })));
+ }
+ }
+ }
+
+ if (params['cache']) {
+ var cacheName = "cached-fetches-" + performance.now() + "-" +
+ event.request.url;
+ var cache;
+ var cachedResponse;
+ return self.caches.open(cacheName).then(function(opened) {
+ cache = opened;
+ return cache.put(request, response);
+ }).then(function() {
+ return cache.match(request);
+ }).then(function(cached) {
+ cachedResponse = cached;
+ return self.caches.delete(cacheName);
+ }).then(function() {
+ resolve(cachedResponse);
+ });
+ } else {
+ resolve(response);
+ }
+ }, reject)
+ }));
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js
new file mode 100644
index 000000000..66f3e5936
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js
@@ -0,0 +1,17 @@
+var activatePromiseResolve;
+
+addEventListener('activate', function(evt) {
+ evt.waitUntil(new Promise(function(resolve) {
+ activatePromiseResolve = resolve;
+ }));
+});
+
+addEventListener('message', function(evt) {
+ if (typeof activatePromiseResolve === 'function') {
+ activatePromiseResolve();
+ }
+});
+
+addEventListener('fetch', function(evt) {
+ evt.respondWith(new Response('Hello world'));
+});
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/frame-for-getregistrations.html b/testing/web-platform/tests/service-workers/service-worker/resources/frame-for-getregistrations.html
new file mode 100644
index 000000000..7fc35f189
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/frame-for-getregistrations.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Service Worker: frame for getRegistrations()</title>
+<script>
+var scope = 'scope-for-getregistrations';
+var script = 'empty-worker.js';
+var registration;
+
+navigator.serviceWorker.register(script, { scope: scope })
+ .then(function(r) { registration = r; window.parent.postMessage('ready', '*'); })
+
+self.onmessage = function(e) {
+ if (e.data == 'unregister') {
+ registration.unregister()
+ .then(function() {
+ e.ports[0].postMessage('unregistered');
+ });
+ }
+};
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/get-host-info.sub.js b/testing/web-platform/tests/service-workers/service-worker/resources/get-host-info.sub.js
new file mode 100644
index 000000000..b64334df6
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/get-host-info.sub.js
@@ -0,0 +1,26 @@
+function get_host_info() {
+ var ORIGINAL_HOST = '127.0.0.1';
+ var REMOTE_HOST = 'localhost';
+ var UNAUTHENTICATED_HOST = 'example.test';
+ var HTTP_PORT = 8000;
+ var HTTPS_PORT = 8443;
+ try {
+ // In W3C test, we can get the hostname and port number in config.json
+ // using wptserve's built-in pipe.
+ // http://wptserve.readthedocs.org/en/latest/pipes.html#built-in-pipes
+ HTTP_PORT = eval('{{ports[http][0]}}');
+ 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
+ };
+}
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/indexeddb-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/indexeddb-worker.js
new file mode 100644
index 000000000..ef89550b3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/indexeddb-worker.js
@@ -0,0 +1,26 @@
+var port;
+self.addEventListener('message', function(e) {
+ var message = e.data;
+ if ('port' in message)
+ doIndexedDBTest(message.port);
+ });
+
+function doIndexedDBTest(port) {
+ var delete_request = indexedDB.deleteDatabase('db');
+ delete_request.onsuccess = function() {
+ var open_request = indexedDB.open('db');
+ open_request.onupgradeneeded = function() {
+ var db = open_request.result;
+ db.createObjectStore('store');
+ };
+ open_request.onsuccess = function() {
+ var db = open_request.result;
+ var tx = db.transaction('store', 'readwrite');
+ var store = tx.objectStore('store');
+ store.put('value', 'key');
+ tx.oncomplete = function() {
+ port.postMessage('done');
+ };
+ };
+ };
+}
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/install-event-type-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/install-event-type-worker.js
new file mode 100644
index 000000000..d729afa09
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/install-event-type-worker.js
@@ -0,0 +1,8 @@
+importScripts('worker-testharness.js');
+
+self.oninstall = function(event) {
+ assert_true(event instanceof ExtendableEvent);
+ assert_equals(event.type, 'install');
+ assert_false(event.cancelable);
+ assert_false(event.bubbles);
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/interfaces-worker.sub.js b/testing/web-platform/tests/service-workers/service-worker/resources/interfaces-worker.sub.js
new file mode 100644
index 000000000..e5ed36fce
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/interfaces-worker.sub.js
@@ -0,0 +1,107 @@
+importScripts('interfaces.js');
+importScripts('worker-testharness.js');
+importScripts('../resources/testharness-helpers.js');
+
+var EVENT_HANDLER = 'object';
+
+test(function() {
+ verify_interface('ServiceWorkerGlobalScope',
+ self,
+ {
+ clients: 'object',
+ registration: 'object',
+ skipWaiting: 'function',
+
+ onactivate: EVENT_HANDLER,
+ onfetch: EVENT_HANDLER,
+ oninstall: EVENT_HANDLER,
+ onmessage: EVENT_HANDLER
+ });
+ }, 'ServiceWorkerGlobalScope');
+
+test(function() {
+ verify_interface('Clients',
+ self.clients,
+ {
+ claim: 'function',
+ matchAll: 'function'
+ });
+ }, 'Clients');
+
+test(function() {
+ verify_interface('Client');
+ // FIXME: Get an instance and test it, or ensure property exists on
+ // prototype.
+ }, 'Client');
+
+test(function() {
+ verify_interface('WindowClient');
+ // FIXME: Get an instance and test it, or ensure property exists on
+ // prototype.
+ }, 'WindowClient');
+
+test(function() {
+ verify_interface('CacheStorage',
+ self.caches,
+ {
+ match: 'function',
+ has: 'function',
+ open: 'function',
+ delete: 'function',
+ keys: 'function'
+ });
+ }, 'CacheStorage');
+
+promise_test(function(t) {
+ return create_temporary_cache(t)
+ .then(function(cache) {
+ verify_interface('Cache',
+ cache,
+ {
+ match: 'function',
+ matchAll: 'function',
+ add: 'function',
+ addAll: 'function',
+ put: 'function',
+ delete: 'function',
+ keys: 'function'
+ });
+ });
+ }, 'Cache');
+
+test(function() {
+ var req = new Request('http://{{host}}/',
+ {method: 'POST',
+ headers: [['Content-Type', 'Text/Html']]});
+ assert_equals(
+ new ExtendableEvent('ExtendableEvent').type,
+ 'ExtendableEvent', 'Type of ExtendableEvent should be ExtendableEvent');
+ assert_equals(
+ new FetchEvent('FetchEvent', {request: req}).type,
+ 'FetchEvent', 'Type of FetchEvent should be FetchEvent');
+ assert_equals(
+ new FetchEvent('FetchEvent', {request: req}).cancelable,
+ false, 'Default FetchEvent.cancelable should be false');
+ assert_equals(
+ new FetchEvent('FetchEvent', {request: req}).bubbles,
+ false, 'Default FetchEvent.bubbles should be false');
+ assert_equals(
+ new FetchEvent('FetchEvent', {request: req}).clientId,
+ null, 'Default FetchEvent.clientId should be null');
+ assert_equals(
+ new FetchEvent('FetchEvent', {request: req}).isReload,
+ false, 'Default FetchEvent.isReload should be false');
+ assert_equals(
+ new FetchEvent('FetchEvent', {request: req, cancelable: false}).cancelable,
+ false, 'FetchEvent.cancelable should be false');
+ assert_equals(
+ new FetchEvent('FetchEvent', {request: req, clientId : 'test-client-id'}).clientId, 'test-client-id',
+ 'FetchEvent.clientId with option {clientId : "test-client-id"} should be "test-client-id"');
+ assert_equals(
+ new FetchEvent('FetchEvent', {request: req, isReload : true}).isReload, true,
+ 'FetchEvent.isReload with option {isReload : true} should be true');
+ assert_equals(
+ new FetchEvent('FetchEvent', {request : req, isReload : true}).request.url,
+ 'http://{{host}}/',
+ 'FetchEvent.request.url should return the value it was initialized to');
+ }, 'Event constructors');
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/interfaces.js b/testing/web-platform/tests/service-workers/service-worker/resources/interfaces.js
new file mode 100644
index 000000000..eb00df65f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/interfaces.js
@@ -0,0 +1,15 @@
+function verify_interface(name, instance, attributes) {
+ assert_true(name in self,
+ name + ' should be an defined type');
+ if (instance) {
+ assert_true(instance instanceof self[name],
+ instance + ' should be an instance of ' + name);
+ Object.keys(attributes || {}).forEach(function(attribute) {
+ var type = attributes[attribute];
+ assert_true(attribute in instance,
+ attribute + ' should be an attribute of ' + name);
+ assert_equals(typeof instance[attribute], type,
+ attribute + ' should be of type ' + type);
+ });
+ }
+}
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-iframe.https.html
new file mode 100644
index 000000000..f111bd924
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-iframe.https.html
@@ -0,0 +1,29 @@
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+
+function xhr_send(method, data) {
+ return new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function() {
+ if (xhr.getResponseHeader('Content-Type') !== null) {
+ reject('Content-Type must be null.');
+ }
+ resolve();
+ };
+ xhr.onerror = function() {
+ reject('XHR must succeed.');
+ };
+ xhr.responseType = 'text';
+ xhr.open(method, './dummy?test', true);
+ xhr.send(data);
+ });
+}
+
+
+window.addEventListener('message', function(evt) {
+ var port = evt.ports[0];
+ xhr_send('POST', 'test string')
+ .then(function() { port.postMessage({results: 'finish'}); })
+ .catch(function(e) { port.postMessage({results: 'failure:' + e}); });
+ });
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js
new file mode 100644
index 000000000..93f496ef4
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js
@@ -0,0 +1,10 @@
+self.addEventListener('fetch', function(event) {
+ var url = event.request.url;
+ if (url.indexOf('dummy?test') == -1) {
+ return;
+ }
+ event.respondWith(new Promise(function(resolve) {
+ // null byte in blob type
+ resolve(new Response(new Blob([],{type: 'a\0b'})));
+ }));
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding-with-flush.py b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding-with-flush.py
new file mode 100644
index 000000000..c91250a9d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding-with-flush.py
@@ -0,0 +1,11 @@
+import time
+def main(request, response):
+ response.headers.set("Content-Type", "application/javascript")
+ response.headers.set("Transfer-encoding", "chunked")
+ response.write_status_headers()
+
+ time.sleep(1)
+ response.explicit_flush = True
+
+ response.writer.write("XX\r\n\r\n")
+ response.writer.flush()
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding.py b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding.py
new file mode 100644
index 000000000..ae2c1f21b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding.py
@@ -0,0 +1,2 @@
+def main(request, response):
+ return [("Content-Type", "application/javascript"), ("Transfer-encoding", "chunked")], "XX\r\n\r\n"
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-iframe.https.html
new file mode 100644
index 000000000..19f302c35
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-iframe.https.html
@@ -0,0 +1,26 @@
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+
+function xhr_send(method, data) {
+ return new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function() {
+ reject('XHR must fail.');
+ };
+ xhr.onerror = function() {
+ resolve();
+ };
+ xhr.responseType = 'text';
+ xhr.open(method, './dummy?test', true);
+ xhr.send(data);
+ });
+}
+
+
+window.addEventListener('message', function(evt) {
+ var port = evt.ports[0];
+ xhr_send('POST', 'test string')
+ .then(function() { port.postMessage({results: 'finish'}); })
+ .catch(function(e) { port.postMessage({results: 'failure:' + e}); });
+ });
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-worker.js
new file mode 100644
index 000000000..31e7f29d0
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-worker.js
@@ -0,0 +1,12 @@
+self.addEventListener('fetch', function(event) {
+ var url = event.request.url;
+ if (url.indexOf('dummy?test') == -1) {
+ return;
+ }
+ event.respondWith(new Promise(function(resolve) {
+ var headers = new Headers;
+ headers.append('foo', 'foo');
+ headers.append('foo', 'b\0r'); // header value with a null byte
+ resolve(new Response('hello world', {'headers': headers}));
+ }));
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/load_worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/load_worker.js
new file mode 100644
index 000000000..2c80f25a3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/load_worker.js
@@ -0,0 +1,29 @@
+self.onmessage = function (evt) {
+ if (evt.data == "xhr") {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "synthesized-response.txt", true);
+ xhr.responseType = "text";
+ xhr.send();
+ xhr.onload = function (evt) {
+ postMessage(xhr.responseText);
+ };
+ xhr.onerror = function() {
+ postMessage("XHR failed!");
+ };
+ } else if (evt.data == "fetch") {
+ fetch("synthesized-response.txt")
+ .then(function(response) {
+ return response.text();
+ })
+ .then(function(data) {
+ postMessage(data);
+ })
+ .catch(function(error) {
+ postMessage("Fetch failed!");
+ });
+ } else if (evt.data == "importScripts") {
+ importScripts("synthesized-response.js");
+ } else {
+ throw "Unexpected message! " + evt.data;
+ }
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/loaded.html b/testing/web-platform/tests/service-workers/service-worker/resources/loaded.html
new file mode 100644
index 000000000..0cabce69f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/loaded.html
@@ -0,0 +1,9 @@
+<script>
+addEventListener('load', function() {
+ opener.postMessage({ type: 'LOADED' }, '*');
+});
+
+addEventListener('pageshow', function() {
+ opener.postMessage({ type: 'PAGESHOW' }, '*');
+});
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/malformed-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/malformed-worker.py
new file mode 100644
index 000000000..501521ff3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/malformed-worker.py
@@ -0,0 +1,10 @@
+def main(request, response):
+ headers = [("Content-Type", "application/javascript")]
+
+ body = {'parse-error': 'var foo = function() {;',
+ 'undefined-error': 'foo.bar = 42;',
+ 'uncaught-exception': 'throw new DOMException("AbortError");',
+ 'caught-exception': 'try { throw new Error; } catch(e) {}',
+ 'import-malformed-script': 'importScripts("malformed-worker.py?parse-error");',
+ 'import-no-such-script': 'importScripts("no-such-script.js");'}[request.url_parts.query]
+ return headers, body
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/mime-type-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/mime-type-worker.py
new file mode 100644
index 000000000..a16684de5
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/mime-type-worker.py
@@ -0,0 +1,4 @@
+def main(request, response):
+ if 'mime' in request.GET:
+ return [('Content-Type', request.GET['mime'])], ""
+ return [], ""
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/mint-new-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/mint-new-worker.py
new file mode 100644
index 000000000..cbe70304d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/mint-new-worker.py
@@ -0,0 +1,25 @@
+import time
+
+body = '''
+onactivate = (e) => e.waitUntil(clients.claim());
+var resolve_wait_until;
+var wait_until = new Promise(resolve => {
+ resolve_wait_until = resolve;
+ });
+onmessage = (e) => {
+ if (e.data == 'wait')
+ e.waitUntil(wait_until);
+ if (e.data == 'go')
+ resolve_wait_until();
+ };'''
+
+def main(request, response):
+ headers = [('Cache-Control', 'no-cache, must-revalidate'),
+ ('Pragma', 'no-cache'),
+ ('Content-Type', 'application/javascript')]
+
+ skipWaiting = ''
+ if 'skip-waiting' in request.GET:
+ skipWaiting = 'skipWaiting();'
+
+ return headers, '/* %s %s */ %s %s' % (time.time(), time.clock(), skipWaiting, body)
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigate-window-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/navigate-window-worker.js
new file mode 100644
index 000000000..f9617439f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigate-window-worker.js
@@ -0,0 +1,21 @@
+addEventListener('message', function(evt) {
+ if (evt.data.type === 'GET_CLIENTS') {
+ clients.matchAll(evt.data.opts).then(function(clientList) {
+ var resultList = clientList.map(function(c) {
+ return { url: c.url, frameType: c.frameType, id: c.id };
+ });
+ evt.source.postMessage({ type: 'success', detail: resultList });
+ }).catch(function(err) {
+ evt.source.postMessage({
+ type: 'failure',
+ detail: 'matchAll() rejected with "' + err + '"'
+ });
+ });
+ return;
+ }
+
+ evt.source.postMessage({
+ type: 'failure',
+ detail: 'Unexpected message type "' + evt.data.type + '"'
+ });
+});
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html
new file mode 100644
index 000000000..c1441ba68
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<script src="get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js"></script>
+<script>
+var host_info = get_host_info();
+var SCOPE = 'navigation-redirect-scope1.py';
+var SCRIPT = 'navigation-redirect-worker.js';
+
+var registration;
+var worker;
+var wait_for_worker_promise = navigator.serviceWorker.getRegistration(SCOPE)
+ .then(function(reg) {
+ if (reg)
+ return reg.unregister();
+ })
+ .then(function() {
+ return navigator.serviceWorker.register(SCRIPT, {scope: SCOPE});
+ })
+ .then(function(reg) {
+ registration = reg;
+ worker = reg.installing;
+ return new Promise(function(resolve) {
+ worker.addEventListener('statechange', function() {
+ if (worker.state == 'activated')
+ resolve();
+ });
+ });
+ });
+
+function send_result(message_id, result) {
+ window.parent.postMessage(
+ {id: message_id, result: result},
+ host_info['HTTPS_ORIGIN']);
+}
+
+function get_intercepted_urls(worker) {
+ return new Promise(function(resolve) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function(msg) { resolve(msg.data.urls); };
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+ });
+}
+
+window.addEventListener('message', on_message, false);
+
+function on_message(e) {
+ if (e.origin != host_info['HTTPS_ORIGIN']) {
+ console.error('invalid origin: ' + e.origin);
+ return;
+ }
+ if (e.data.message == 'wait_for_worker') {
+ wait_for_worker_promise.then(function() { send_result(e.data.id, 'ok'); });
+ } else if (e.data.message == 'get_intercepted_urls') {
+ get_intercepted_urls(worker)
+ .then(function(urls) {
+ send_result(e.data.id, urls);
+ });
+ } else if (e.data.message == 'unregister') {
+ registration.unregister()
+ .then(function() {
+ send_result(e.data.id, 'ok');
+ });
+ }
+}
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py
new file mode 100644
index 000000000..4b40762d8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py
@@ -0,0 +1,15 @@
+def main(request, response):
+ if "url" in request.GET:
+ headers = [("Location", request.GET["url"])]
+ return 302, headers, ''
+
+ return [], '''
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage(
+ {
+ id: 'last_url',
+ result: location.href
+ }, '*');
+</script>
+'''
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py
new file mode 100644
index 000000000..4b40762d8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py
@@ -0,0 +1,15 @@
+def main(request, response):
+ if "url" in request.GET:
+ headers = [("Location", request.GET["url"])]
+ return 302, headers, ''
+
+ return [], '''
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage(
+ {
+ id: 'last_url',
+ result: location.href
+ }, '*');
+</script>
+'''
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py
new file mode 100644
index 000000000..4b40762d8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py
@@ -0,0 +1,15 @@
+def main(request, response):
+ if "url" in request.GET:
+ headers = [("Location", request.GET["url"])]
+ return 302, headers, ''
+
+ return [], '''
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage(
+ {
+ id: 'last_url',
+ result: location.href
+ }, '*');
+</script>
+'''
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-worker.js
new file mode 100644
index 000000000..cb15b3ff1
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-worker.js
@@ -0,0 +1,75 @@
+// We store an empty response for each fetch event request we see
+// in this Cache object so we can get the list of urls in the
+// message event.
+var cacheName = 'urls-' + self.registration.scope;
+
+var waitUntilPromiseList = [];
+
+self.addEventListener('message', function(event) {
+ var urls;
+ event.waitUntil(Promise.all(waitUntilPromiseList).then(function() {
+ waitUntilPromiseList = [];
+ return caches.open(cacheName);
+ }).then(function(cache) {
+ return cache.keys();
+ }).then(function(requestList) {
+ urls = requestList.map(function(request) { return request.url; });
+ return caches.delete(cacheName);
+ }).then(function() {
+ event.data.port.postMessage({urls: urls});
+ }));
+ });
+
+function get_query_params(url) {
+ var search = (new URL(url)).search;
+ if (!search) {
+ return {};
+ }
+ var ret = {};
+ var params = search.substring(1).split('&');
+ params.forEach(function(param) {
+ var element = param.split('=');
+ ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]);
+ });
+ return ret;
+}
+
+self.addEventListener('fetch', function(event) {
+ var waitUntilPromise = caches.open(cacheName).then(function(cache) {
+ return cache.put(event.request, new Response());
+ });
+ event.waitUntil(waitUntilPromise);
+
+ var params = get_query_params(event.request.url);
+ if (!params['sw']) {
+ // To avoid races, add the waitUntil() promise to our global list.
+ // If we get a message event before we finish here, it will wait
+ // these promises to complete before proceeding to read from the
+ // cache.
+ waitUntilPromiseList.push(waitUntilPromise);
+ return;
+ }
+
+ event.respondWith(waitUntilPromise.then(function() {
+ if (params['sw'] == 'gen') {
+ return Response.redirect(params['url']);
+ } else if (params['sw'] == 'fetch') {
+ return fetch(event.request);
+ } else if (params['sw'] == 'opaque') {
+ return fetch(new Request(event.request.url, {redirect: 'manual'}));
+ } else if (params['sw'] == 'opaqueThroughCache') {
+ var url = event.request.url;
+ var cache;
+ return caches.delete(url)
+ .then(function() { return self.caches.open(url); })
+ .then(function(c) {
+ cache = c;
+ return fetch(new Request(url, {redirect: 'manual'}));
+ })
+ .then(function(res) { return cache.put(event.request, res); })
+ .then(function() { return cache.match(url); });
+ }
+
+ // unexpected... trigger an interception failure
+ }));
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-from-nested-event-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-from-nested-event-worker.js
new file mode 100644
index 000000000..7c97014fd
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-from-nested-event-worker.js
@@ -0,0 +1,13 @@
+var max_nesting_level = 8;
+
+self.addEventListener('message', function(event) {
+ var level = event.data;
+ if (level < max_nesting_level)
+ dispatchEvent(new MessageEvent('message', { data: level + 1 }));
+ throw Error('error at level ' + level);
+ });
+
+self.addEventListener('activate', function(event) {
+ dispatchEvent(new MessageEvent('message', { data: 1 }));
+ });
+
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-cancel-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-cancel-worker.js
new file mode 100644
index 000000000..0bd9d318b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-cancel-worker.js
@@ -0,0 +1,3 @@
+self.onerror = function(event) { return true; };
+
+self.addEventListener('activate', function(event) { throw new Error(); });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-prevent-default-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-prevent-default-worker.js
new file mode 100644
index 000000000..d56c95113
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-prevent-default-worker.js
@@ -0,0 +1,7 @@
+// Ensure we can handle multiple error handlers. One error handler
+// calling preventDefault should cause the event to be treated as
+// handled.
+self.addEventListener('error', function(event) {});
+self.addEventListener('error', function(event) { event.preventDefault(); });
+self.addEventListener('error', function(event) {});
+self.addEventListener('activate', function(event) { throw new Error(); });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-with-empty-onerror-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-with-empty-onerror-worker.js
new file mode 100644
index 000000000..eb12ae862
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-with-empty-onerror-worker.js
@@ -0,0 +1,2 @@
+self.addEventListener('error', function(event) {});
+self.addEventListener('activate', function(event) { throw new Error(); });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-worker.js
new file mode 100644
index 000000000..1e88ac5c4
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-worker.js
@@ -0,0 +1,7 @@
+// Ensure we can handle multiple activate handlers. One handler throwing an
+// error should cause the event dispatch to be treated as having unhandled
+// errors.
+self.addEventListener('activate', function(event) {});
+self.addEventListener('activate', function(event) {});
+self.addEventListener('activate', function(event) { throw new Error(); });
+self.addEventListener('activate', function(event) {});
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-from-nested-event-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-from-nested-event-worker.js
new file mode 100644
index 000000000..6729ab61a
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-from-nested-event-worker.js
@@ -0,0 +1,12 @@
+var max_nesting_level = 8;
+
+self.addEventListener('message', function(event) {
+ var level = event.data;
+ if (level < max_nesting_level)
+ dispatchEvent(new MessageEvent('message', { data: level + 1 }));
+ throw Error('error at level ' + level);
+ });
+
+self.addEventListener('install', function(event) {
+ dispatchEvent(new MessageEvent('message', { data: 1 }));
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-cancel-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-cancel-worker.js
new file mode 100644
index 000000000..c2c499ab1
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-cancel-worker.js
@@ -0,0 +1,3 @@
+self.onerror = function(event) { return true; };
+
+self.addEventListener('install', function(event) { throw new Error(); });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-prevent-default-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-prevent-default-worker.js
new file mode 100644
index 000000000..7667c2781
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-prevent-default-worker.js
@@ -0,0 +1,7 @@
+// Ensure we can handle multiple error handlers. One error handler
+// calling preventDefault should cause the event to be treated as
+// handled.
+self.addEventListener('error', function(event) {});
+self.addEventListener('error', function(event) { event.preventDefault(); });
+self.addEventListener('error', function(event) {});
+self.addEventListener('install', function(event) { throw new Error(); });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-with-empty-onerror-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-with-empty-onerror-worker.js
new file mode 100644
index 000000000..8f56d1bf1
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-with-empty-onerror-worker.js
@@ -0,0 +1,2 @@
+self.addEventListener('error', function(event) {});
+self.addEventListener('install', function(event) { throw new Error(); });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-worker.js
new file mode 100644
index 000000000..cc2f6d7e5
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-worker.js
@@ -0,0 +1,7 @@
+// Ensure we can handle multiple install handlers. One handler throwing an
+// error should cause the event dispatch to be treated as having unhandled
+// errors.
+self.addEventListener('install', function(event) {});
+self.addEventListener('install', function(event) {});
+self.addEventListener('install', function(event) { throw new Error(); });
+self.addEventListener('install', function(event) {});
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/other.html b/testing/web-platform/tests/service-workers/service-worker/resources/other.html
new file mode 100644
index 000000000..b9f350438
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/other.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<title>Other</title>
+Here's an other html file.
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/override_assert_object_equals.js b/testing/web-platform/tests/service-workers/service-worker/resources/override_assert_object_equals.js
new file mode 100644
index 000000000..835046d47
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/override_assert_object_equals.js
@@ -0,0 +1,58 @@
+// .body attribute of Request and Response object are experimental feture. It is
+// enabled when --enable-experimental-web-platform-features flag is set.
+// Touching this attribute can change the behavior of the objects. To avoid
+// touching it while comparing the objects in LayoutTest, we overwrite
+// assert_object_equals method.
+
+(function() {
+ var original_assert_object_equals = self.assert_object_equals;
+ function _brand(object) {
+ return Object.prototype.toString.call(object).match(/^\[object (.*)\]$/)[1];
+ }
+ var assert_request_equals = function(actual, expected, prefix) {
+ if (typeof actual !== 'object') {
+ assert_equals(actual, expected, prefix);
+ return;
+ }
+ assert_true(actual instanceof Request, prefix);
+ assert_true(expected instanceof Request, prefix);
+ assert_equals(actual.bodyUsed, expected.bodyUsed, prefix + '.bodyUsed');
+ assert_equals(actual.method, expected.method, prefix + '.method');
+ assert_equals(actual.url, expected.url, prefix + '.url');
+ original_assert_object_equals(actual.headers, expected.headers,
+ prefix + '.headers');
+ assert_equals(actual.context, expected.context, prefix + '.context');
+ assert_equals(actual.referrer, expected.referrer, prefix + '.referrer');
+ assert_equals(actual.mode, expected.mode, prefix + '.mode');
+ assert_equals(actual.credentials, expected.credentials,
+ prefix + '.credentials');
+ assert_equals(actual.cache, expected.cache, prefix + '.cache');
+ };
+ var assert_response_equals = function(actual, expected, prefix) {
+ if (typeof actual !== 'object') {
+ assert_equals(actual, expected, prefix);
+ return;
+ }
+ assert_true(actual instanceof Response, prefix);
+ assert_true(expected instanceof Response, prefix);
+ assert_equals(actual.bodyUsed, expected.bodyUsed, prefix + '.bodyUsed');
+ assert_equals(actual.type, expected.type, prefix + '.type');
+ assert_equals(actual.url, expected.url, prefix + '.url');
+ assert_equals(actual.status, expected.status, prefix + '.status');
+ assert_equals(actual.statusText, expected.statusText,
+ prefix + '.statusText');
+ original_assert_object_equals(actual.headers, expected.headers,
+ prefix + '.headers');
+ };
+ var assert_object_equals = function(actual, expected, description) {
+ var prefix = (description ? description + ': ' : '') + _brand(expected);
+ if (expected instanceof Request) {
+ assert_request_equals(actual, expected, prefix);
+ } else if (expected instanceof Response) {
+ assert_response_equals(actual, expected, prefix);
+ } else {
+ original_assert_object_equals(actual, expected, description);
+ }
+ };
+ self.assert_object_equals = assert_object_equals;
+})();
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/performance-timeline-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/performance-timeline-worker.js
new file mode 100644
index 000000000..6f7df75e9
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/performance-timeline-worker.js
@@ -0,0 +1,58 @@
+importScripts('/resources/testharness.js');
+
+promise_test(function(test) {
+ var durationMsec = 100;
+ // There are limits to our accuracy here. Timers may fire up to a
+ // millisecond early due to platform-dependent rounding. In addition
+ // the performance API introduces some rounding as well to prevent
+ // timing attacks.
+ var accuracy = 1.5;
+ return new Promise(function(resolve) {
+ performance.mark('startMark');
+ setTimeout(resolve, durationMsec);
+ }).then(function() {
+ performance.mark('endMark');
+ performance.measure('measure', 'startMark', 'endMark');
+ var startMark = performance.getEntriesByName('startMark')[0];
+ var endMark = performance.getEntriesByName('endMark')[0];
+ var measure = performance.getEntriesByType('measure')[0];
+ assert_equals(measure.startTime, startMark.startTime);
+ assert_approx_equals(endMark.startTime - startMark.startTime,
+ measure.duration, 0.001);
+ assert_greater_than(measure.duration, durationMsec - accuracy);
+ assert_equals(performance.getEntriesByType('mark').length, 2);
+ assert_equals(performance.getEntriesByType('measure').length, 1);
+ performance.clearMarks('startMark');
+ performance.clearMeasures('measure');
+ assert_equals(performance.getEntriesByType('mark').length, 1);
+ assert_equals(performance.getEntriesByType('measure').length, 0);
+ });
+ }, 'User Timing');
+
+promise_test(function(test) {
+ return fetch('dummy.txt')
+ .then(function(resp) {
+ return resp.text();
+ })
+ .then(function(text) {
+ var expectedResources = ['testharness.js', 'dummy.txt'];
+ assert_equals(performance.getEntriesByType('resource').length, expectedResources.length);
+ for (var i = 0; i < expectedResources.length; i++) {
+ var entry = performance.getEntriesByType('resource')[i];
+ assert_true(entry.name.endsWith(expectedResources[i]));
+ assert_equals(entry.workerStart, 0);
+ assert_greater_than(entry.startTime, 0);
+ assert_greater_than(entry.responseEnd, entry.startTime);
+ }
+ return new Promise(function(resolve) {
+ performance.onresourcetimingbufferfull = resolve;
+ performance.setResourceTimingBufferSize(expectedResources.length);
+ });
+ })
+ .then(function() {
+ performance.clearResourceTimings();
+ assert_equals(performance.getEntriesByType('resource').length, 0);
+ })
+ }, 'Resource Timing');
+
+done();
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-msgport-to-client-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-msgport-to-client-worker.js
new file mode 100644
index 000000000..3475321f4
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-msgport-to-client-worker.js
@@ -0,0 +1,20 @@
+self.onmessage = function(e) {
+ self.clients.matchAll().then(function(clients) {
+ clients.forEach(function(client) {
+ var messageChannel = new MessageChannel();
+ messageChannel.port1.onmessage =
+ onMessageViaMessagePort.bind(null, client);
+ client.postMessage({port: messageChannel.port2},
+ [messageChannel.port2]);
+ });
+ });
+};
+
+function onMessageViaMessagePort(client, e) {
+ var message = e.data;
+ if ('value' in message) {
+ client.postMessage({ack: 'Acking value: ' + message.value});
+ } else if ('done' in message) {
+ client.postMessage({done: true});
+ }
+}
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-to-client-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-to-client-worker.js
new file mode 100644
index 000000000..290a4a9b3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-to-client-worker.js
@@ -0,0 +1,10 @@
+self.onmessage = function(e) {
+ self.clients.matchAll().then(function(clients) {
+ clients.forEach(function(client) {
+ client.postMessage('Sending message via clients');
+ if (!Array.isArray(clients))
+ client.postMessage('clients is not an array');
+ client.postMessage('quit');
+ });
+ });
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-worker.js
new file mode 100644
index 000000000..858cf0426
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-worker.js
@@ -0,0 +1,19 @@
+var port;
+
+// Exercise the 'onmessage' handler:
+self.onmessage = function(e) {
+ var message = e.data;
+ if ('port' in message) {
+ port = message.port;
+ }
+};
+
+// And an event listener:
+self.addEventListener('message', function(e) {
+ var message = e.data;
+ if ('value' in message) {
+ port.postMessage('Acking value: ' + message.value);
+ } else if ('done' in message) {
+ port.postMessage('quit');
+ }
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/redirect.py b/testing/web-platform/tests/service-workers/service-worker/resources/redirect.py
new file mode 100644
index 000000000..20521b00c
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/redirect.py
@@ -0,0 +1,25 @@
+def main(request, response):
+ if 'Status' in request.GET:
+ status = int(request.GET["Status"])
+ else:
+ status = 302
+
+ headers = []
+
+ url = request.GET['Redirect']
+ headers.append(("Location", url))
+
+ if "ACAOrigin" in request.GET:
+ for item in request.GET["ACAOrigin"].split(","):
+ headers.append(("Access-Control-Allow-Origin", item))
+
+ for suffix in ["Headers", "Methods", "Credentials"]:
+ query = "ACA%s" % suffix
+ header = "Access-Control-Allow-%s" % suffix
+ if query in request.GET:
+ headers.append((header, request.GET[query]))
+
+ if "ACEHeaders" in request.GET:
+ headers.append(("Access-Control-Expose-Headers", request.GET["ACEHeaders"]))
+
+ return status, headers, ""
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/referer-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/referer-iframe.html
new file mode 100644
index 000000000..491262e9d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/referer-iframe.html
@@ -0,0 +1,39 @@
+<script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js"></script>
+<script>
+function check_referer(url, expected_referer) {
+ return fetch(url)
+ .then(function(res) { return res.json(); })
+ .then(function(headers) {
+ if (headers['referer'] === expected_referer) {
+ return Promise.resolve();
+ } else {
+ return Promise.reject('Referer for ' + url + ' must be ' +
+ expected_referer + ' but got ' +
+ headers['referer']);
+ }
+ });
+}
+
+window.addEventListener('message', function(evt) {
+ var host_info = get_host_info();
+ var port = evt.ports[0];
+ check_referer('request-headers.py?ignore=true',
+ host_info['HTTPS_ORIGIN'] +
+ base_path() + 'referer-iframe.html')
+ .then(function() {
+ return check_referer(
+ 'request-headers.py',
+ host_info['HTTPS_ORIGIN'] +
+ base_path() + 'referer-iframe.html');
+ })
+ .then(function() {
+ return check_referer(
+ 'request-headers.py?url=request-headers.py',
+ host_info['HTTPS_ORIGIN'] +
+ base_path() + 'fetch-rewrite-worker.js');
+ })
+ .then(function() { port.postMessage({results: 'finish'}); })
+ .catch(function(e) { port.postMessage({results: 'failure:' + e}); });
+ });
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/register-closed-window-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/register-closed-window-iframe.html
new file mode 100644
index 000000000..ed743ea05
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/register-closed-window-iframe.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<script>
+window.addEventListener('message', function(evt) {
+ if (evt.data === 'START') {
+ var w = window.open('./');
+ var sw = w.navigator.serviceWorker;
+ w.close();
+ w = null;
+ sw.register('doesntmatter.js');
+ parent.postMessage('OK', '*');
+ }
+});
+</script>
+</head>
+</html>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/registration-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/registration-worker.js
new file mode 100644
index 000000000..44d1d2774
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/registration-worker.js
@@ -0,0 +1 @@
+// empty for now
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/reject-install-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/reject-install-worker.js
new file mode 100644
index 000000000..41f07fd5d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/reject-install-worker.js
@@ -0,0 +1,3 @@
+self.oninstall = function(event) {
+ event.waitUntil(Promise.reject());
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/request-end-to-end-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/request-end-to-end-worker.js
new file mode 100644
index 000000000..323c7f243
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/request-end-to-end-worker.js
@@ -0,0 +1,32 @@
+var port = undefined;
+
+onmessage = function(e) {
+ var message = e.data;
+ if (typeof message === 'object' && 'port' in message) {
+ port = message.port;
+ }
+};
+
+onfetch = function(e) {
+ var headers = {};
+ var errorNameWhileAppendingHeader;
+ for (var header of e.request.headers) {
+ var key = header[0], value = header[1];
+ headers[key] = value;
+ }
+ var errorNameWhileAddingHeader = '';
+ try {
+ e.request.headers.append('Test-Header', 'TestValue');
+ } catch (e) {
+ errorNameWhileAppendingHeader = e.name;
+ }
+ port.postMessage({
+ url: e.request.url,
+ mode: e.request.mode,
+ method: e.request.method,
+ referrer: e.request.referrer,
+ headers: headers,
+ headerSize: e.request.headers.size,
+ errorNameWhileAppendingHeader: errorNameWhileAppendingHeader
+ });
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/request-headers.py b/testing/web-platform/tests/service-workers/service-worker/resources/request-headers.py
new file mode 100644
index 000000000..29897f4ec
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/request-headers.py
@@ -0,0 +1,6 @@
+import json
+
+def main(request, response):
+ data = {key:request.headers[key] for key,value in request.headers.iteritems()}
+
+ return [("Content-Type", "application/json")], json.dumps(data)
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-iframe.html
new file mode 100644
index 000000000..2af679369
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-iframe.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script src="empty.js"></script>
+<script src="dummy.js"></script>
+<script src="redirect.py?Redirect=empty.js"></script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js
new file mode 100644
index 000000000..481a6536a
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js
@@ -0,0 +1,5 @@
+self.addEventListener('fetch', function(event) {
+ if (event.request.url.indexOf('dummy.js') != -1) {
+ event.respondWith(new Response());
+ }
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/service-worker-csp-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/service-worker-csp-worker.py
new file mode 100644
index 000000000..4e5c6f3b6
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/service-worker-csp-worker.py
@@ -0,0 +1,153 @@
+bodyDefault = '''
+importScripts('worker-testharness.js');
+importScripts('test-helpers.sub.js');
+importScripts('../resources/get-host-info.sub.js');
+
+var host_info = get_host_info();
+
+test(function() {
+ var import_script_failed = false;
+ try {
+ importScripts(host_info.HTTPS_REMOTE_ORIGIN +
+ base_path() + 'empty.js');
+ } catch(e) {
+ import_script_failed = true;
+ }
+ assert_true(import_script_failed,
+ 'Importing the other origins script should fail.');
+ }, 'importScripts test for default-src');
+
+async_test(function(t) {
+ fetch(host_info.HTTPS_REMOTE_ORIGIN +
+ base_path() + 'fetch-access-control.py?ACAOrigin=*',
+ {mode: 'cors'})
+ .then(function(response){
+ assert_unreached('fetch should fail.');
+ }, function(){
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Fetch test for default-src');
+
+async_test(function(t) {
+ var REDIRECT_URL = host_info.HTTPS_ORIGIN +
+ base_path() + 'redirect.py?Redirect=';
+ var OTHER_BASE_URL = host_info.HTTPS_REMOTE_ORIGIN +
+ base_path() + 'fetch-access-control.py?'
+ fetch(REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL + 'ACAOrigin=*'),
+ {mode: 'cors'})
+ .then(function(response){
+ assert_unreached('Redirected fetch should fail.');
+ }, function(){
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Redirected fetch test for default-src');'''
+
+bodyScript = '''
+importScripts('worker-testharness.js');
+importScripts('test-helpers.sub.js');
+importScripts('../resources/get-host-info.sub.js');
+
+var host_info = get_host_info();
+
+test(function() {
+ var import_script_failed = false;
+ try {
+ importScripts(host_info.HTTPS_REMOTE_ORIGIN +
+ base_path() + 'empty.js');
+ } catch(e) {
+ import_script_failed = true;
+ }
+ assert_true(import_script_failed,
+ 'Importing the other origins script should fail.');
+ }, 'importScripts test for script-src');
+
+async_test(function(t) {
+ fetch(host_info.HTTPS_REMOTE_ORIGIN +
+ base_path() + 'fetch-access-control.py?ACAOrigin=*',
+ {mode: 'cors'})
+ .then(function(response){
+ t.done();
+ }, function(){
+ assert_unreached('fetch should not fail.');
+ })
+ .catch(unreached_rejection(t));
+ }, 'Fetch test for script-src');
+
+async_test(function(t) {
+ var REDIRECT_URL = host_info.HTTPS_ORIGIN +
+ base_path() + 'redirect.py?Redirect=';
+ var OTHER_BASE_URL = host_info.HTTPS_REMOTE_ORIGIN +
+ base_path() + 'fetch-access-control.py?'
+ fetch(REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL + 'ACAOrigin=*'),
+ {mode: 'cors'})
+ .then(function(response){
+ t.done();
+ }, function(){
+ assert_unreached('Redirected fetch should not fail.');
+ })
+ .catch(unreached_rejection(t));
+ }, 'Redirected fetch test for script-src');'''
+
+bodyConnect = '''
+importScripts('worker-testharness.js');
+importScripts('test-helpers.sub.js');
+importScripts('../resources/get-host-info.sub.js');
+
+var host_info = get_host_info();
+
+test(function() {
+ var import_script_failed = false;
+ try {
+ importScripts(host_info.HTTPS_REMOTE_ORIGIN +
+ base_path() + 'empty.js');
+ } catch(e) {
+ import_script_failed = true;
+ }
+ assert_false(import_script_failed,
+ 'Importing the other origins script should not fail.');
+ }, 'importScripts test for connect-src');
+
+async_test(function(t) {
+ fetch(host_info.HTTPS_REMOTE_ORIGIN +
+ base_path() + 'fetch-access-control.py?ACAOrigin=*',
+ {mode: 'cors'})
+ .then(function(response){
+ assert_unreached('fetch should fail.');
+ }, function(){
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Fetch test for connect-src');
+
+async_test(function(t) {
+ var REDIRECT_URL = host_info.HTTPS_ORIGIN +
+ base_path() + 'redirect.py?Redirect=';
+ var OTHER_BASE_URL = host_info.HTTPS_REMOTE_ORIGIN +
+ base_path() + 'fetch-access-control.py?'
+ fetch(REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL + 'ACAOrigin=*'),
+ {mode: 'cors'})
+ .then(function(response){
+ assert_unreached('Redirected fetch should fail.');
+ }, function(){
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Redirected fetch test for connect-src');'''
+
+def main(request, response):
+ headers = []
+ headers.append(('Content-Type', 'application/javascript'))
+ directive = request.GET['directive']
+ body = 'ERROR: Unknown directive'
+ if directive == 'default':
+ headers.append(('Content-Security-Policy', "default-src 'self'"))
+ body = bodyDefault
+ elif directive == 'script':
+ headers.append(('Content-Security-Policy', "script-src 'self'"))
+ body = bodyScript
+ elif directive == 'connect':
+ headers.append(('Content-Security-Policy', "connect-src 'self'"))
+ body = bodyConnect
+ return headers, body
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-controlled.js b/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-controlled.js
new file mode 100644
index 000000000..1ccc2fe3b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-controlled.js
@@ -0,0 +1,8 @@
+onconnect = function(e) {
+ var port = e.ports[0];
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function() { port.postMessage(this.responseText); };
+ xhr.onerror = function(e) { port.postMessage(e); };
+ xhr.open('GET', 'dummy.txt?simple', true);
+ xhr.send();
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-import.js b/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-import.js
new file mode 100644
index 000000000..7c554bd74
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-import.js
@@ -0,0 +1 @@
+importScripts('import-dummy-shared-worker.js');
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/silence.oga b/testing/web-platform/tests/service-workers/service-worker/resources/silence.oga
new file mode 100644
index 000000000..af5918804
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/silence.oga
Binary files differ
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/simple-intercept-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/simple-intercept-worker.js
new file mode 100644
index 000000000..f8b5f8c5c
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/simple-intercept-worker.js
@@ -0,0 +1,5 @@
+self.onfetch = function(event) {
+ if (event.request.url.indexOf('simple') != -1)
+ event.respondWith(
+ new Response(new Blob(['intercepted by service worker'])));
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/simple.html b/testing/web-platform/tests/service-workers/service-worker/resources/simple.html
new file mode 100644
index 000000000..0c3e3e787
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/simple.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<title>Simple</title>
+Here's a simple html file.
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/simple.txt b/testing/web-platform/tests/service-workers/service-worker/resources/simple.txt
new file mode 100644
index 000000000..9e3cb91fb
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/simple.txt
@@ -0,0 +1 @@
+a simple text file
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js
new file mode 100644
index 000000000..bf582c770
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js
@@ -0,0 +1,24 @@
+self.state = 'starting';
+
+self.addEventListener('install', function() {
+ self.state = 'installing';
+ });
+
+self.addEventListener('message', function(event) {
+ var port = event.data.port;
+ if (self.state !== 'installing') {
+ port.postMessage('FAIL: Worker should be waiting in installed state');
+ return;
+ }
+ self.skipWaiting()
+ .then(function(result) {
+ if (result !== undefined) {
+ port.postMessage('FAIL: Promise should be resolved with undefined');
+ return;
+ }
+ port.postMessage('PASS');
+ })
+ .catch(function(e) {
+ port.postMessage('FAIL: unexpected exception: ' + e);
+ });
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-worker.js
new file mode 100644
index 000000000..3fc1d1e23
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-worker.js
@@ -0,0 +1,21 @@
+importScripts('worker-testharness.js');
+
+promise_test(function() {
+ return skipWaiting()
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Promise should be resolved with undefined');
+ })
+ .then(function() {
+ var promises = [];
+ for (var i = 0; i < 8; ++i)
+ promises.push(self.skipWaiting());
+ return Promise.all(promises);
+ })
+ .then(function(results) {
+ results.forEach(function(r) {
+ assert_equals(r, undefined,
+ 'Promises should be resolved with undefined');
+ });
+ });
+ }, 'skipWaiting');
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/square.png b/testing/web-platform/tests/service-workers/service-worker/resources/square.png
new file mode 100644
index 000000000..01c9666a8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/square.png
Binary files differ
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/success.py b/testing/web-platform/tests/service-workers/service-worker/resources/success.py
new file mode 100644
index 000000000..bcbb487d2
--- /dev/null
+++ b/testing/web-platform/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\" }"
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/test-helpers.sub.js b/testing/web-platform/tests/service-workers/service-worker/resources/test-helpers.sub.js
new file mode 100644
index 000000000..b0ffbd406
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/test-helpers.sub.js
@@ -0,0 +1,227 @@
+// Adapter for testharness.js-style tests with Service Workers
+
+function service_worker_unregister_and_register(test, url, scope) {
+ if (!scope || scope.length == 0)
+ return Promise.reject(new Error('tests must define a scope'));
+
+ var options = { scope: scope };
+ return service_worker_unregister(test, scope)
+ .then(function() {
+ return navigator.serviceWorker.register(url, options);
+ })
+ .catch(unreached_rejection(test,
+ 'unregister and register should not fail'));
+}
+
+// This unregisters the registration that precisely matches scope. Use this
+// when unregistering by scope. If no registration is found, it just resolves.
+function service_worker_unregister(test, scope) {
+ var absoluteScope = (new URL(scope, window.location).href);
+ return navigator.serviceWorker.getRegistration(scope)
+ .then(function(registration) {
+ if (registration && registration.scope === absoluteScope)
+ return registration.unregister();
+ })
+ .catch(unreached_rejection(test, 'unregister should not fail'));
+}
+
+function service_worker_unregister_and_done(test, scope) {
+ return service_worker_unregister(test, scope)
+ .then(test.done.bind(test));
+}
+
+function unreached_fulfillment(test, prefix) {
+ return test.step_func(function(result) {
+ var error_prefix = prefix || 'unexpected fulfillment';
+ assert_unreached(error_prefix + ': ' + result);
+ });
+}
+
+// Rejection-specific helper that provides more details
+function unreached_rejection(test, prefix) {
+ return test.step_func(function(error) {
+ var reason = error.message || error.name || error;
+ var error_prefix = prefix || 'unexpected rejection';
+ assert_unreached(error_prefix + ': ' + reason);
+ });
+}
+
+// Adds an iframe to the document and returns a promise that resolves to the
+// iframe when it finishes loading. The caller is responsible for removing the
+// iframe later if needed.
+function with_iframe(url) {
+ return new Promise(function(resolve) {
+ var frame = document.createElement('iframe');
+ frame.src = url;
+ frame.onload = function() { resolve(frame); };
+ document.body.appendChild(frame);
+ });
+}
+
+function normalizeURL(url) {
+ return new URL(url, self.location).toString().replace(/#.*$/, '');
+}
+
+function wait_for_update(test, registration) {
+ if (!registration || registration.unregister == undefined) {
+ return Promise.reject(new Error(
+ 'wait_for_update must be passed a ServiceWorkerRegistration'));
+ }
+
+ return new Promise(test.step_func(function(resolve) {
+ registration.addEventListener('updatefound', test.step_func(function() {
+ resolve(registration.installing);
+ }));
+ }));
+}
+
+function wait_for_state(test, worker, state) {
+ if (!worker || worker.state == undefined) {
+ return Promise.reject(new Error(
+ 'wait_for_state must be passed a ServiceWorker'));
+ }
+ if (worker.state === state)
+ return Promise.resolve(state);
+
+ if (state === 'installing') {
+ switch (worker.state) {
+ case 'installed':
+ case 'activating':
+ case 'activated':
+ case 'redundant':
+ return Promise.reject(new Error(
+ 'worker is ' + worker.state + ' but waiting for ' + state));
+ }
+ }
+
+ if (state === 'installed') {
+ switch (worker.state) {
+ case 'activating':
+ case 'activated':
+ case 'redundant':
+ return Promise.reject(new Error(
+ 'worker is ' + worker.state + ' but waiting for ' + state));
+ }
+ }
+
+ if (state === 'activating') {
+ switch (worker.state) {
+ case 'activated':
+ case 'redundant':
+ return Promise.reject(new Error(
+ 'worker is ' + worker.state + ' but waiting for ' + state));
+ }
+ }
+
+ if (state === 'activated') {
+ switch (worker.state) {
+ case 'redundant':
+ return Promise.reject(new Error(
+ 'worker is ' + worker.state + ' but waiting for ' + state));
+ }
+ }
+
+ return new Promise(test.step_func(function(resolve) {
+ worker.addEventListener('statechange', test.step_func(function() {
+ if (worker.state === state)
+ resolve(state);
+ }));
+ }));
+}
+
+// Declare a test that runs entirely in the ServiceWorkerGlobalScope. The |url|
+// is the service worker script URL. This function:
+// - Instantiates a new test with the description specified in |description|.
+// The test will succeed if the specified service worker can be successfully
+// registered and installed.
+// - Creates a new ServiceWorker registration with a scope unique to the current
+// document URL. Note that this doesn't allow more than one
+// service_worker_test() to be run from the same document.
+// - Waits for the new worker to begin installing.
+// - Imports tests results from tests running inside the ServiceWorker.
+function service_worker_test(url, description) {
+ // If the document URL is https://example.com/document and the script URL is
+ // https://example.com/script/worker.js, then the scope would be
+ // https://example.com/script/scope/document.
+ var scope = new URL('scope' + window.location.pathname,
+ new URL(url, window.location)).toString();
+ promise_test(function(test) {
+ return service_worker_unregister_and_register(test, url, scope)
+ .then(function(registration) {
+ add_completion_callback(function() {
+ registration.unregister();
+ });
+ return wait_for_update(test, registration)
+ .then(function(worker) {
+ return fetch_tests_from_worker(worker);
+ });
+ });
+ }, description);
+}
+
+function base_path() {
+ return location.pathname.replace(/\/[^\/]*$/, '/');
+}
+
+function test_login(test, origin, username, password, cookie) {
+ return new Promise(function(resolve, reject) {
+ with_iframe(
+ origin + base_path() +
+ 'resources/fetch-access-control-login.html')
+ .then(test.step_func(function(frame) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = test.step_func(function() {
+ frame.remove();
+ resolve();
+ });
+ frame.contentWindow.postMessage(
+ {username: username, password: password, cookie: cookie},
+ origin, [channel.port2]);
+ }));
+ });
+}
+
+function test_websocket(test, frame, url) {
+ return new Promise(function(resolve, reject) {
+ var ws = new frame.contentWindow.WebSocket(url, ['echo', 'chat']);
+ var openCalled = false;
+ ws.addEventListener('open', test.step_func(function(e) {
+ assert_equals(ws.readyState, 1, "The WebSocket should be open");
+ openCalled = true;
+ ws.close();
+ }), true);
+
+ ws.addEventListener('close', test.step_func(function(e) {
+ assert_true(openCalled, "The WebSocket should be closed after being opened");
+ resolve();
+ }), true);
+
+ ws.addEventListener('error', reject);
+ });
+}
+
+function login(test) {
+ return test_login(test, 'http://{{domains[www1]}}:{{ports[http][0]}}',
+ 'username1', 'password1', 'cookie1')
+ .then(function() {
+ return test_login(test, 'http://{{host}}:{{ports[http][0]}}',
+ 'username2', 'password2', 'cookie2');
+ });
+}
+
+function login_https(test) {
+ return test_login(test, 'https://{{domains[www1]}}:{{ports[https][0]}}',
+ 'username1s', 'password1s', 'cookie1')
+ .then(function() {
+ return test_login(test, 'https://{{host}}:{{ports[https][0]}}',
+ 'username2s', 'password2s', 'cookie2');
+ });
+}
+
+function websocket(test, frame) {
+ return test_websocket(test, frame, get_websocket_url());
+}
+
+function get_websocket_url() {
+ return 'wss://{{host}}:{{ports[wss][0]}}/echo';
+}
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/testharness-helpers.js b/testing/web-platform/tests/service-workers/service-worker/resources/testharness-helpers.js
new file mode 100644
index 000000000..4d7af1ff9
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/testharness-helpers.js
@@ -0,0 +1,163 @@
+/*
+ * testharness-helpers contains various useful extensions to testharness.js to
+ * allow them to be used across multiple tests before they have been
+ * upstreamed. This file is intended to be usable from both document and worker
+ * environments, so code should for example not rely on the DOM.
+ */
+
+// Returns a promise that fulfills after the provided |promise| is fulfilled.
+// The |test| succeeds only if |promise| rejects with an exception matching
+// |code|. Accepted values for |code| follow those accepted for assert_throws().
+// The optional |description| describes the test being performed.
+//
+// E.g.:
+// assert_promise_rejects(
+// new Promise(...), // something that should throw an exception.
+// 'NotFoundError',
+// 'Should throw NotFoundError.');
+//
+// assert_promise_rejects(
+// new Promise(...),
+// new TypeError(),
+// 'Should throw TypeError');
+function assert_promise_rejects(promise, code, description) {
+ return promise.then(
+ function() {
+ throw 'assert_promise_rejects: ' + description + ' Promise did not reject.';
+ },
+ function(e) {
+ if (code !== undefined) {
+ assert_throws(code, function() { throw e; }, description);
+ }
+ });
+}
+
+// Asserts that two objects |actual| and |expected| are weakly equal under the
+// following definition:
+//
+// |a| and |b| are weakly equal if any of the following are true:
+// 1. If |a| is not an 'object', and |a| === |b|.
+// 2. If |a| is an 'object', and all of the following are true:
+// 2.1 |a.p| is weakly equal to |b.p| for all own properties |p| of |a|.
+// 2.2 Every own property of |b| is an own property of |a|.
+//
+// This is a replacement for the the version of assert_object_equals() in
+// testharness.js. The latter doesn't handle own properties correctly. I.e. if
+// |a.p| is not an own property, it still requires that |b.p| be an own
+// property.
+//
+// Note that |actual| must not contain cyclic references.
+self.assert_object_equals = function(actual, expected, description) {
+ var object_stack = [];
+
+ function _is_equal(actual, expected, prefix) {
+ if (typeof actual !== 'object') {
+ assert_equals(actual, expected, prefix);
+ return;
+ }
+ assert_true(typeof expected === 'object', prefix);
+ assert_equals(object_stack.indexOf(actual), -1,
+ prefix + ' must not contain cyclic references.');
+
+ object_stack.push(actual);
+
+ Object.getOwnPropertyNames(expected).forEach(function(property) {
+ assert_own_property(actual, property, prefix);
+ _is_equal(actual[property], expected[property],
+ prefix + '.' + property);
+ });
+ Object.getOwnPropertyNames(actual).forEach(function(property) {
+ assert_own_property(expected, property, prefix);
+ });
+
+ object_stack.pop();
+ }
+
+ function _brand(object) {
+ return Object.prototype.toString.call(object).match(/^\[object (.*)\]$/)[1];
+ }
+
+ _is_equal(actual, expected,
+ (description ? description + ': ' : '') + _brand(expected));
+};
+
+// Equivalent to assert_in_array, but uses a weaker equivalence relation
+// (assert_object_equals) than '==='.
+function assert_object_in_array(actual, expected_array, description) {
+ assert_true(expected_array.some(function(element) {
+ try {
+ assert_object_equals(actual, element);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }), description);
+}
+
+// Assert that the two arrays |actual| and |expected| contain the same set of
+// elements as determined by assert_object_equals. The order is not significant.
+//
+// |expected| is assumed to not contain any duplicates as determined by
+// assert_object_equals().
+function assert_array_equivalent(actual, expected, description) {
+ assert_true(Array.isArray(actual), description);
+ assert_equals(actual.length, expected.length, description);
+ expected.forEach(function(expected_element) {
+ // assert_in_array treats the first argument as being 'actual', and the
+ // second as being 'expected array'. We are switching them around because
+ // we want to be resilient against the |actual| array containing
+ // duplicates.
+ assert_object_in_array(expected_element, actual, description);
+ });
+}
+
+// Asserts that two arrays |actual| and |expected| contain the same set of
+// elements as determined by assert_object_equals(). The corresponding elements
+// must occupy corresponding indices in their respective arrays.
+function assert_array_objects_equals(actual, expected, description) {
+ assert_true(Array.isArray(actual), description);
+ assert_equals(actual.length, expected.length, description);
+ actual.forEach(function(value, index) {
+ assert_object_equals(value, expected[index],
+ description + ' : object[' + index + ']');
+ });
+}
+
+// Asserts that |object| that is an instance of some interface has the attribute
+// |attribute_name| following the conditions specified by WebIDL, but it's
+// acceptable that the attribute |attribute_name| is an own property of the
+// object because we're in the middle of moving the attribute to a prototype
+// chain. Once we complete the transition to prototype chains,
+// assert_will_be_idl_attribute must be replaced with assert_idl_attribute
+// defined in testharness.js.
+//
+// FIXME: Remove assert_will_be_idl_attribute once we complete the transition
+// of moving the DOM attributes to prototype chains. (http://crbug.com/43394)
+function assert_will_be_idl_attribute(object, attribute_name, description) {
+ assert_true(typeof object === "object", description);
+
+ assert_true("hasOwnProperty" in object, description);
+
+ // Do not test if |attribute_name| is not an own property because
+ // |attribute_name| is in the middle of the transition to a prototype
+ // chain. (http://crbug.com/43394)
+
+ assert_true(attribute_name in object, description);
+}
+
+// Stringifies a DOM object. This function stringifies not only own properties
+// but also DOM attributes which are on a prototype chain. Note that
+// JSON.stringify only stringifies own properties.
+function stringifyDOMObject(object)
+{
+ function deepCopy(src) {
+ if (typeof src != "object")
+ return src;
+ var dst = Array.isArray(src) ? [] : {};
+ for (var property in src) {
+ dst[property] = deepCopy(src[property]);
+ }
+ return dst;
+ }
+ return JSON.stringify(deepCopy(object));
+}
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/unregister-controller-page.html b/testing/web-platform/tests/service-workers/service-worker/resources/unregister-controller-page.html
new file mode 100644
index 000000000..18a95ee89
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/unregister-controller-page.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script>
+function fetch_url(url) {
+ return new Promise(function(resolve, reject) {
+ var request = new XMLHttpRequest();
+ request.addEventListener('load', function(event) {
+ if (request.status == 200)
+ resolve(request.response);
+ else
+ reject(Error(request.statusText));
+ });
+ request.open('GET', url);
+ request.send();
+ });
+}
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/update-nocookie-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/update-nocookie-worker.py
new file mode 100644
index 000000000..0f09b7e32
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/update-nocookie-worker.py
@@ -0,0 +1,15 @@
+import time
+
+def main(request, response):
+ # 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')]
+
+ # Set a normal mimetype.
+ content_type = 'application/javascript'
+
+ 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' % (time.time(), time.clock())
+
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/update-recovery-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/update-recovery-worker.py
new file mode 100644
index 000000000..8aaa5ca93
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/update-recovery-worker.py
@@ -0,0 +1,25 @@
+def main(request, response):
+ # Set mode to 'init' for initial fetch.
+ mode = 'init'
+ 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')]
+
+ extra_body = ''
+
+ if mode == 'init':
+ # 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', 'application/javascript'))
+ return headers, '%s' % (extra_body)
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/update-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/update-worker.py
new file mode 100644
index 000000000..bc9b32ad3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/update-worker.py
@@ -0,0 +1,46 @@
+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
+
+ # 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'); });"
+
+ 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)
+
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/update/update-after-oneday.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/update/update-after-oneday.https.html
new file mode 100644
index 000000000..9d4c98272
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/update/update-after-oneday.https.html
@@ -0,0 +1,8 @@
+<body>
+<script>
+function load_image(url) {
+ var img = document.createElement('img');
+ img.src = url;
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/wait-forever-in-install-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/wait-forever-in-install-worker.js
new file mode 100644
index 000000000..af85a73ad
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/wait-forever-in-install-worker.js
@@ -0,0 +1,12 @@
+var waitUntilResolve;
+self.addEventListener('install', function(event) {
+ event.waitUntil(new Promise(function(resolve) {
+ waitUntilResolve = resolve;
+ }));
+ });
+
+self.addEventListener('message', function(event) {
+ if (event.data === 'STOP_WAITING') {
+ waitUntilResolve();
+ }
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/websocket.js b/testing/web-platform/tests/service-workers/service-worker/resources/websocket.js
new file mode 100644
index 000000000..fc6abd283
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/websocket.js
@@ -0,0 +1,7 @@
+self.urls = [];
+self.addEventListener('fetch', function(event) {
+ self.urls.push(event.request.url);
+ });
+self.addEventListener('message', function(event) {
+ event.data.port.postMessage({urls: self.urls});
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/worker-interception-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/worker-interception-iframe.https.html
new file mode 100644
index 000000000..12a461ea5
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/worker-interception-iframe.https.html
@@ -0,0 +1,39 @@
+<script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js?pipe=sub"></script>
+<script>
+var host_info = get_host_info();
+
+function boilerplate_test(msg) {
+ return new Promise(function(resolve, reject) {
+ var worker = new Worker("load_worker.js");
+ worker.onmessage = function(e) { resolve(e.data) };
+ worker.onerror = function(e) { reject(e) };
+ worker.postMessage(msg);
+ })
+ .then(function(data) {
+ window.parent.postMessage({results: data}, host_info['HTTPS_ORIGIN']);
+ });
+}
+
+function xhr_test() {
+ return boilerplate_test("xhr");
+}
+
+function fetch_test() {
+ return boilerplate_test("fetch");
+}
+
+function importScripts_test() {
+ return boilerplate_test("importScripts");
+}
+
+window.addEventListener('message', function(evt) {
+ var port = evt.ports[0];
+ xhr_test()
+ .then(fetch_test)
+ .then(importScripts_test)
+ .then(function() { port.postMessage({results: 'finish'}); })
+ .catch(function(e) { port.postMessage({results: 'failure:' + e}); });
+ });
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/worker-load-interceptor.js b/testing/web-platform/tests/service-workers/service-worker/resources/worker-load-interceptor.js
new file mode 100644
index 000000000..960c6328c
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/worker-load-interceptor.js
@@ -0,0 +1,13 @@
+importScripts('get-host-info.sub.js');
+
+var response_text = "This load was successfully intercepted.";
+var response_script = "postMessage(\"This load was successfully intercepted.\");";
+
+self.onfetch = function(event) {
+ var url = event.request.url;
+ if (url.indexOf("synthesized-response.txt") != -1) {
+ event.respondWith(new Response(response_text));
+ } else if (url.indexOf("synthesized-response.js") != -1) {
+ event.respondWith(new Response(response_script));
+ }
+};
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/worker-testharness.js b/testing/web-platform/tests/service-workers/service-worker/resources/worker-testharness.js
new file mode 100644
index 000000000..fdf5868e3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/worker-testharness.js
@@ -0,0 +1,49 @@
+/*
+ * worker-test-harness should be considered a temporary polyfill around
+ * testharness.js for supporting Service Worker based tests. It should not be
+ * necessary once the test harness is able to drive worker based tests natively.
+ * See https://github.com/w3c/testharness.js/pull/82 for status of effort to
+ * update upstream testharness.js. Once the upstreaming is complete, tests that
+ * reference worker-test-harness should be updated to directly import
+ * testharness.js.
+ */
+
+importScripts('/resources/testharness.js');
+
+(function() {
+ var next_cache_index = 1;
+
+ // Returns a promise that resolves to a newly created Cache object. The
+ // returned Cache will be destroyed when |test| completes.
+ function create_temporary_cache(test) {
+ var uniquifier = String(++next_cache_index);
+ var cache_name = self.location.pathname + '/' + uniquifier;
+
+ test.add_cleanup(function() {
+ self.caches.delete(cache_name);
+ });
+
+ return self.caches.delete(cache_name)
+ .then(function() {
+ return self.caches.open(cache_name);
+ });
+ }
+
+ self.create_temporary_cache = create_temporary_cache;
+})();
+
+// Runs |test_function| with a temporary unique Cache passed in as the only
+// argument. The function is run as a part of Promise chain owned by
+// promise_test(). As such, it is expected to behave in a manner identical (with
+// the exception of the argument) to a function passed into promise_test().
+//
+// E.g.:
+// cache_test(function(cache) {
+// // Do something with |cache|, which is a Cache object.
+// }, "Some Cache test");
+function cache_test(test_function, description) {
+ promise_test(function(test) {
+ return create_temporary_cache(test)
+ .then(test_function);
+ }, description);
+}
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/xhr.js b/testing/web-platform/tests/service-workers/service-worker/resources/xhr.js
new file mode 100644
index 000000000..387c4a48e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/xhr.js
@@ -0,0 +1,6 @@
+self.addEventListener('activate', function(event) {
+ event.waitUntil(clients.claim());
+ });
+self.addEventListener('message', function(event) {
+ event.data.port.postMessage({xhr: !!("XMLHttpRequest" in self)});
+ });
diff --git a/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-connect.https.html b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-connect.https.html
new file mode 100644
index 000000000..226f4a40e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-connect.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Service Worker: CSP connect directive for ServiceWorker script</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+service_worker_test(
+ 'resources/service-worker-csp-worker.py?directive=connect',
+ 'CSP test for connect-src in ServiceWorkerGlobalScope');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-default.https.html b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-default.https.html
new file mode 100644
index 000000000..1d4e7624d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-default.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Service Worker: CSP default directive for ServiceWorker script</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+service_worker_test(
+ 'resources/service-worker-csp-worker.py?directive=default',
+ 'CSP test for default-src in ServiceWorkerGlobalScope');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-script.https.html b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-script.https.html
new file mode 100644
index 000000000..14c2eb72b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-script.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Service Worker: CSP script directive for ServiceWorker script</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+service_worker_test(
+ 'resources/service-worker-csp-worker.py?directive=script',
+ 'CSP test for script-src in ServiceWorkerGlobalScope');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/serviceworkerobject-scripturl.https.html b/testing/web-platform/tests/service-workers/service-worker/serviceworkerobject-scripturl.https.html
new file mode 100644
index 000000000..95587a5a4
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/serviceworkerobject-scripturl.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>ServiceWorker object: scriptURL property</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+function url_test(name, url) {
+ var scope = 'resources/scope/' + name;
+ async_test(function(t) {
+ var expectedURL = (new URL(url, window.location)).toString();
+ service_worker_unregister_and_register(t, url, scope)
+ .then(function(registration) {
+ var worker = registration.installing;
+ assert_equals(worker.scriptURL, expectedURL,
+ 'Returned ServiceWorker object should have scriptURL');
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify the scriptURL property: ' + name);
+}
+
+url_test('relative', 'resources/empty-worker.js');
+url_test('absolute', (new URL('./resources/empty-worker.js', window.location)).href);
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/shared-worker-controlled.https.html b/testing/web-platform/tests/service-workers/service-worker/shared-worker-controlled.https.html
new file mode 100644
index 000000000..33d52e011
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/shared-worker-controlled.https.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<title>Service Worker: controlling a SharedWorker</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(function(t) {
+ var shared_worker = 'resources/shared-worker-controlled.js';
+ var service_worker = 'resources/simple-intercept-worker.js';
+ var scope = shared_worker;
+
+ return service_worker_unregister_and_register(t, service_worker, scope)
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return new Promise(function(resolve, reject) {
+ var w = new SharedWorker(shared_worker);
+ w.port.onmessage = function(e) {
+ resolve(e.data);
+ }
+ });
+ })
+ .then(function(data) {
+ assert_equals(data, 'intercepted by service worker');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Verify subresource loads in SharedWorker are controlled by a Service Worker');
+
+promise_test(function(t) {
+ var shared_worker = 'resources/dummy-shared-worker.js';
+ var service_worker = 'resources/dummy-shared-worker-interceptor.js';
+ var scope = shared_worker;
+
+ return service_worker_unregister_and_register(t, service_worker, scope)
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return new Promise(function(resolve, reject) {
+ var w = new SharedWorker(shared_worker);
+ w.port.onmessage = function(e) {
+ resolve(e.data);
+ }
+ });
+ })
+ .then(function(data) {
+ assert_equals(data, 'worker loading intercepted by service worker');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Verify SharedWorker construction is controlled by a Service Worker');
+
+promise_test(function(t) {
+ var shared_worker = 'resources/shared-worker-import.js';
+ var service_worker = 'resources/dummy-shared-worker-interceptor.js';
+ var scope = shared_worker;
+
+ return service_worker_unregister_and_register(t, service_worker, scope)
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return new Promise(function(resolve, reject) {
+ var w = new SharedWorker(shared_worker);
+ w.port.onmessage = function(e) {
+ resolve(e.data);
+ }
+ });
+ })
+ .then(function(data) {
+ assert_equals(data, 'worker loading intercepted by service worker');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Verify importScripts from SharedWorker is controlled by a Service Worker');
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/skip-waiting-installed.https.html b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-installed.https.html
new file mode 100644
index 000000000..42e4000b1
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-installed.https.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>Service Worker: Skip waiting installed worker</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(function(t) {
+ var scope = 'resources/blank.html';
+ var url1 = 'resources/empty.js';
+ var url2 = 'resources/skip-waiting-installed-worker.js';
+ var frame, frame_sw, service_worker, registration, onmessage, oncontrollerchanged;
+ var saw_message = new Promise(function(resolve) {
+ onmessage = function(e) {
+ var message = e.data;
+ assert_equals(
+ message, 'PASS',
+ 'skipWaiting promise should be resolved with undefined');
+
+ assert_equals(registration.active.scriptURL, normalizeURL(url2),
+ "skipWaiting should make worker become active");
+ resolve();
+ };
+ });
+ var saw_controllerchanged = new Promise(function(resolve) {
+ oncontrollerchanged = function() {
+ assert_equals(
+ frame_sw.controller.scriptURL, normalizeURL(url2),
+ 'Controller scriptURL should change to the second one');
+ resolve();
+ };
+ });
+ return service_worker_unregister_and_register(t, url1, scope)
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ frame_sw = f.contentWindow.navigator.serviceWorker;
+ assert_equals(
+ frame_sw.controller.scriptURL, normalizeURL(url1),
+ 'Document controller scriptURL should equal to the first one');
+ frame_sw.oncontrollerchange = t.step_func(oncontrollerchanged);
+ return navigator.serviceWorker.register(url2, {scope: scope});
+ })
+ .then(function(r) {
+ registration = r;
+ service_worker = r.installing;
+ return wait_for_state(t, service_worker, 'installed');
+ })
+ .then(function() {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(onmessage);
+ service_worker.postMessage({port: channel.port2}, [channel.port2]);
+ return Promise.all([saw_message, saw_controllerchanged]);
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Test skipWaiting when a installed worker is waiting');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/skip-waiting-using-registration.https.html b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-using-registration.https.html
new file mode 100644
index 000000000..5f84f0b8e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-using-registration.https.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Service Worker: Skip waiting using registration</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(function(t) {
+ var scope = 'resources/blank.html';
+ var url1 = 'resources/empty.js';
+ var url2 = 'resources/skip-waiting-worker.js';
+ var frame, frame_sw, sw_registration, oncontrollerchanged;
+ var saw_controllerchanged = new Promise(function(resolve) {
+ oncontrollerchanged = function(e) {
+ assert_equals(e.type, 'controllerchange',
+ 'Event name should be "controllerchange"');
+ assert_true(
+ e.target instanceof frame.contentWindow.ServiceWorkerContainer,
+ 'Event target should be a ServiceWorkerContainer');
+ assert_equals(e.target.controller.state, 'activating',
+ 'Controller state should be activating');
+ assert_equals(
+ frame_sw.controller.scriptURL, normalizeURL(url2),
+ 'Controller scriptURL should change to the second one');
+ resolve();
+ };
+ });
+ return service_worker_unregister_and_register(t, url1, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ frame_sw = f.contentWindow.navigator.serviceWorker;
+ assert_equals(
+ frame_sw.controller.scriptURL, normalizeURL(url1),
+ 'Document controller scriptURL should equal to the first one');
+ frame_sw.oncontrollerchange = t.step_func(oncontrollerchanged);
+ return navigator.serviceWorker.register(url2, {scope: scope});
+ })
+ .then(function(registration) {
+ sw_registration = registration;
+ add_completion_callback(function() {
+ frame.remove();
+ registration.unregister();
+ });
+ return saw_controllerchanged;
+ })
+ .then(function() {
+ assert_not_equals(sw_registration.active, null,
+ 'Registration active worker should not be null');
+ fetch_tests_from_worker(sw_registration.active);
+ });
+ }, 'Test skipWaiting while a client is using the registration');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-client.https.html b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-client.https.html
new file mode 100644
index 000000000..38fca1726
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-client.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Service Worker: Skip waiting without client</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+service_worker_test(
+ 'resources/skip-waiting-worker.js',
+ 'Test single skipWaiting() when no client attached');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-using-registration.https.html b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-using-registration.https.html
new file mode 100644
index 000000000..2535ffe09
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-using-registration.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Service Worker: Skip waiting without using registration</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(function(t) {
+ var scope = 'resources/blank.html';
+ var url = 'resources/skip-waiting-worker.js';
+ var frame, frame_sw, sw_registration;
+
+ return service_worker_unregister(t, scope)
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ frame_sw = f.contentWindow.navigator.serviceWorker;
+ assert_equals(frame_sw.controller, null,
+ 'Document controller should be null');
+ return navigator.serviceWorker.register(url, {scope: scope});
+ })
+ .then(function(registration) {
+ sw_registration = registration;
+ add_completion_callback(function() {
+ frame.remove();
+ registration.unregister();
+ });
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ assert_equals(frame_sw.controller, null,
+ 'Document controller should still be null');
+ assert_not_equals(sw_registration.active, null,
+ 'Registration active worker should not be null');
+ fetch_tests_from_worker(sw_registration.active);
+ });
+ }, 'Test skipWaiting while a client is not being controlled');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/skip-waiting.https.html b/testing/web-platform/tests/service-workers/service-worker/skip-waiting.https.html
new file mode 100644
index 000000000..7c1c41f3f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/skip-waiting.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Service Worker: Skip waiting</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(function(t) {
+ var scope = 'resources/blank.html';
+ var url1 = 'resources/empty.js';
+ var url2 = 'resources/empty-worker.js';
+ var url3 = 'resources/skip-waiting-worker.js';
+ var frame, sw_registration, activated_worker, waiting_worker;
+ return service_worker_unregister_and_register(t, url1, scope)
+ .then(function(registration) {
+ sw_registration = registration;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ return navigator.serviceWorker.register(url2, {scope: scope});
+ })
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'installed');
+ })
+ .then(function() {
+ activated_worker = sw_registration.active;
+ waiting_worker = sw_registration.waiting;
+ assert_equals(activated_worker.scriptURL, normalizeURL(url1),
+ 'Worker with url1 should be activated');
+ assert_equals(waiting_worker.scriptURL, normalizeURL(url2),
+ 'Worker with url2 should be waiting');
+ return navigator.serviceWorker.register(url3, {scope: scope});
+ })
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ assert_equals(activated_worker.state, 'redundant',
+ 'Worker with url1 should be redundant');
+ assert_equals(waiting_worker.state, 'redundant',
+ 'Worker with url2 should be redundant');
+ assert_equals(sw_registration.active.scriptURL, normalizeURL(url3),
+ 'Worker with url3 should be activated');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Test skipWaiting with both active and waiting workers');
+
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/state.https.html b/testing/web-platform/tests/service-workers/service-worker/state.https.html
new file mode 100644
index 000000000..3810362e0
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/state.https.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+(function () {
+ var t = async_test('Service Worker state property and "statechange" event');
+ var currentState = 'test-is-starting';
+ var scope = 'resources/state/';
+
+ service_worker_unregister_and_register(
+ t, 'resources/empty-worker.js', scope)
+ .then(t.step_func(function(registration) {
+ var sw = registration.installing;
+ sw.addEventListener('statechange', t.step_func(onStateChange(sw)));
+ assert_equals(sw.state, 'installing',
+ 'the service worker should be in "installing" state.');
+ checkStateTransition(sw.state);
+ }))
+ .catch(unreached_rejection(t));
+
+ function checkStateTransition(newState) {
+ switch (currentState) {
+ case 'test-is-starting':
+ break; // anything goes
+ case 'installing':
+ assert_in_array(newState, ['installed', 'redundant']);
+ break;
+ case 'installed':
+ assert_in_array(newState, ['activating', 'redundant']);
+ break;
+ case 'activating':
+ assert_in_array(newState, ['activated', 'redundant']);
+ break;
+ case 'activated':
+ assert_equals(newState, 'redundant');
+ break;
+ case 'redundant':
+ assert_unreached('a ServiceWorker should not transition out of ' +
+ 'the "redundant" state');
+ break;
+ default:
+ assert_unreached('should not transition into unknown state "' +
+ newState + '"');
+ break;
+ }
+ currentState = newState;
+ }
+
+ function onStateChange(expectedTarget) {
+ return function(event) {
+ assert_true(event.target instanceof ServiceWorker,
+ 'the target of the statechange event should be a ' +
+ 'ServiceWorker.');
+ assert_equals(event.target, expectedTarget,
+ 'the target of the statechange event should be ' +
+ 'the installing ServiceWorker');
+ assert_equals(event.type, 'statechange',
+ 'the type of the event should be "statechange".');
+
+ checkStateTransition(event.target.state);
+
+ if (event.target.state == 'activated')
+ service_worker_unregister_and_done(t, scope);
+ };
+ }
+}());
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/synced-state.https.html b/testing/web-platform/tests/service-workers/service-worker/synced-state.https.html
new file mode 100644
index 000000000..d842378be
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/synced-state.https.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<title>ServiceWorker: worker objects have synced state</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+// Tests that ServiceWorker objects representing the same Service Worker
+// entity have the same state. JS object equality is not tested, since the spec
+// does not require it.
+promise_test(function(t) {
+ var scope = 'resources/synced-state';
+ var script = 'resources/empty-worker.js';
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ return new Promise(function(resolve) {
+ var step = 0;
+ registration.installing.addEventListener('statechange',
+ function(e) {
+ step++;
+ if (step == 1) {
+ assert_equals(e.currentTarget.state, 'installed',
+ 'original SW should be installed');
+ assert_equals(registration.installing, null,
+ 'in installed, .installing should be null');
+ // The Activate algorithm may have cleared the waiting worker
+ // by now.
+ if (registration.waiting) {
+ assert_equals(registration.waiting.state, 'installed',
+ 'in installed, .waiting should be installed');
+ assert_equals(registration.active, null,
+ 'in installed, .active should be null');
+ } else {
+ assert_equals(registration.active.state, 'activating',
+ 'in installed, .active should be activating');
+ }
+ } else if (step == 2) {
+ assert_equals(e.currentTarget.state, 'activating',
+ 'original SW should be activating');
+ assert_equals(registration.installing, null,
+ 'in activating, .installing should be null');
+ assert_equals(registration.waiting, null,
+ 'in activating, .waiting should be null');
+ assert_equals(
+ registration.active.state, 'activating',
+ 'in activating, .active should be activating');
+ } else if (step == 3) {
+ assert_equals(e.currentTarget.state, 'activated',
+ 'original SW should be activated');
+ assert_equals(registration.installing, null,
+ 'in activated, .installing should be null');
+ assert_equals(registration.waiting, null,
+ 'in activated, .waiting should be null');
+ assert_equals(registration.active.state, 'activated',
+ 'in activated .active should be activated');
+ resolve();
+ }
+ })
+ })
+ })
+ .then(function() {
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'worker objects for the same entity have the same state');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/uncontrolled-page.https.html b/testing/web-platform/tests/service-workers/service-worker/uncontrolled-page.https.html
new file mode 100644
index 000000000..a9523a54c
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/uncontrolled-page.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<title>Service Worker: Registration</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+function fetch_url(url) {
+ return new Promise(function(resolve, reject) {
+ var request = new XMLHttpRequest();
+ request.addEventListener('load', function(event) {
+ if (request.status == 200)
+ resolve(request.response);
+ else
+ reject(Error(request.statusText));
+ });
+ request.open('GET', url);
+ request.send();
+ });
+}
+var worker = 'resources/fail-on-fetch-worker.js';
+
+async_test(function(t) {
+ var scope = 'resources/scope/uncontrolled-page/';
+ service_worker_unregister_and_register(t, worker, scope)
+ .then(function(reg) {
+ return wait_for_state(t, reg.installing, 'activated');
+ })
+ .then(function() {
+ return fetch_url('resources/simple.txt');
+ })
+ .then(function(text) {
+ assert_equals(text, 'a simple text file\n');
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(t.step_func(function(reason) {
+ assert_unreached(reason.message);
+ }));
+ }, 'Fetch events should not go through uncontrolled page.');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/unregister-controller.https.html b/testing/web-platform/tests/service-workers/service-worker/unregister-controller.https.html
new file mode 100644
index 000000000..3bf4cff72
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/unregister-controller.https.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var worker_url = 'resources/simple-intercept-worker.js';
+
+async_test(function(t) {
+ var scope =
+ 'resources/unregister-controller-page.html?load-before-unregister';
+ var frame_window;
+ var controller;
+ var registration;
+ var frame;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ frame_window = frame.contentWindow;
+ controller = frame_window.navigator.serviceWorker.controller;
+ assert_true(controller instanceof frame_window.ServiceWorker,
+ 'document should load with a controller');
+ return registration.unregister();
+ })
+ .then(function() {
+ assert_equals(frame_window.navigator.serviceWorker.controller,
+ controller,
+ 'unregistration should not modify controller');
+ return frame_window.fetch_url('simple.txt');
+ })
+ .then(function(response) {
+ assert_equals(response, 'intercepted by service worker',
+ 'controller should intercept requests');
+ frame.remove();
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Unregister does not affect existing controller');
+
+async_test(function(t) {
+ var scope =
+ 'resources/unregister-controller-page.html?load-after-unregister';
+ var registration;
+ var frame;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return registration.unregister();
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ var frame_window = frame.contentWindow;
+ assert_equals(frame_window.navigator.serviceWorker.controller, null,
+ 'document should not have a controller');
+ return frame_window.fetch_url('simple.txt');
+ })
+ .then(function(response) {
+ assert_equals(response, 'a simple text file\n',
+ 'requests should not be intercepted');
+ frame.remove();
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Unregister prevents control of subsequent navigations');
+
+async_test(function(t) {
+ var scope =
+ 'resources/scope/no-new-controllee-even-if-registration-is-still-used';
+ var registration;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ return registration.unregister();
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
+ null,
+ 'document should not have a controller');
+ frame.remove();
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Unregister prevents new controllee even if registration is still in use');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/unregister-then-register-new-script.https.html b/testing/web-platform/tests/service-workers/service-worker/unregister-then-register-new-script.https.html
new file mode 100644
index 000000000..385430c2d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/unregister-then-register-new-script.https.html
@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var worker_url = 'resources/empty-worker.js';
+
+async_test(function(t) {
+ var scope = 'resources/scope/unregister-then-register-new-script-that-exists';
+ var new_worker_url = worker_url + '?new';
+ var iframe;
+ var registration;
+ var new_registration;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ iframe = frame;
+ return registration.unregister();
+ })
+ .then(function() {
+ return navigator.serviceWorker.register(new_worker_url,
+ { scope: scope });
+ })
+ .then(function(r) {
+ new_registration = r;
+ assert_equals(registration.installing.scriptURL,
+ normalizeURL(new_worker_url),
+ 'before activated registration.installing');
+ assert_equals(registration.waiting, null,
+ 'before activated registration.waiting');
+ assert_equals(registration.active.scriptURL, normalizeURL(worker_url),
+ 'before activated registration.active');
+ assert_equals(new_registration.installing.scriptURL,
+ normalizeURL(new_worker_url),
+ 'before activated new_registration.installing');
+ assert_equals(new_registration.waiting, null,
+ 'before activated new_registration.waiting');
+ assert_equals(new_registration.active.scriptURL,
+ normalizeURL(worker_url),
+ 'before activated new_registration.active');
+ iframe.remove();
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ assert_equals(new_registration.installing, null,
+ 'after activated new_registration.installing');
+ assert_equals(new_registration.waiting, null,
+ 'after activated new_registration.waiting');
+ assert_equals(new_registration.active.scriptURL,
+ normalizeURL(new_worker_url),
+ 'after activated new_registration.active');
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ assert_equals(
+ frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
+ normalizeURL(new_worker_url),
+ 'the new worker should control a new document');
+ frame.remove();
+ return registration.unregister();
+ })
+ .then(function() {
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+}, 'Registering a new script URL while an unregistered registration is in use');
+
+async_test(function(t) {
+ var scope = 'resources/scope/unregister-then-register-new-script-that-404s';
+ var iframe;
+ var registration;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ iframe = frame;
+ return registration.unregister();
+ })
+ .then(function() {
+ var promise = navigator.serviceWorker.register('this-will-404',
+ { scope: scope });
+ iframe.remove();
+ return promise;
+ })
+ .then(
+ function() {
+ assert_unreached('register should reject the promise');
+ },
+ function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
+ null,
+ 'document should not load with a controller');
+ frame.remove();
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+}, 'Registering a new script URL that 404s does not resurrect an ' +
+ 'unregistered registration');
+
+async_test(function(t) {
+ var scope = 'resources/scope/unregister-then-register-reject-install-worker';
+ var iframe;
+ var registration;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ iframe = frame;
+ return registration.unregister();
+ })
+ .then(function() {
+ var promise = navigator.serviceWorker.register(
+ 'resources/reject-install-worker.js', { scope: scope });
+ iframe.remove();
+ return promise;
+ })
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'redundant');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
+ null,
+ 'document should not load with a controller');
+ frame.remove();
+ return registration.unregister();
+ })
+ .then(function() {
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Registering a new script URL that fails to install does not resurrect ' +
+ 'an unregistered registration');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/unregister-then-register.https.html b/testing/web-platform/tests/service-workers/service-worker/unregister-then-register.https.html
new file mode 100644
index 000000000..d75904d15
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/unregister-then-register.https.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var worker_url = 'resources/empty-worker.js';
+
+async_test(function(t) {
+ var scope = 'resources/scope/re-register-resolves-to-new-value';
+ var registration;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return registration.unregister();
+ })
+ .then(function() {
+ return navigator.serviceWorker.register(worker_url, { scope: scope });
+ })
+ .then(function(new_registration) {
+ assert_not_equals(registration, new_registration,
+ 'register should resolve to a new value');
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Unregister then register resolves to a new value');
+
+async_test(function(t) {
+ var scope = 'resources/scope/re-register-while-old-registration-in-use';
+ var registration;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ return registration.unregister();
+ })
+ .then(function() {
+ return navigator.serviceWorker.register(worker_url, { scope: scope });
+ })
+ .then(function(new_registration) {
+ assert_equals(registration, new_registration,
+ 'new registration should resolve to the same registration');
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Unregister then register resolves to the original value if the ' +
+ 'registration is in use.');
+
+async_test(function(t) {
+ var scope = 'resources/scope/re-register-does-not-affect-existing-controllee';
+ var iframe;
+ var registration;
+ var controller;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ iframe = frame;
+ controller = iframe.contentWindow.navigator.serviceWorker.controller;
+ return registration.unregister();
+ })
+ .then(function() {
+ return navigator.serviceWorker.register(worker_url, { scope: scope });
+ })
+ .then(function(registration) {
+ assert_equals(registration.installing, null,
+ 'installing version is null');
+ assert_equals(registration.waiting, null, 'waiting version is null');
+ assert_equals(
+ iframe.contentWindow.navigator.serviceWorker.controller,
+ controller,
+ 'the worker from the first registration is the controller');
+ iframe.remove();
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Unregister then register does not affect existing controllee');
+
+async_test(function(t) {
+ var scope = 'resources/scope/resurrection';
+ var iframe;
+ var registration;
+
+ service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ iframe = frame;
+ return registration.unregister();
+ })
+ .then(function() {
+ return navigator.serviceWorker.register(worker_url, { scope: scope });
+ })
+ .then(function() {
+ iframe.remove();
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ // FIXME: When crbug.com/400602 is fixed, assert that controller
+ // equals the original worker.
+ assert_not_equals(
+ frame.contentWindow.navigator.serviceWorker.controller, null,
+ 'document should have a controller');
+ frame.remove();
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Unregister then register resurrects the registration');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/unregister.https.html b/testing/web-platform/tests/service-workers/service-worker/unregister.https.html
new file mode 100644
index 000000000..492aecb21
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/unregister.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+async_test(function(t) {
+ var scope = 'resources/scope/unregister-twice';
+ var registration;
+ navigator.serviceWorker.register('resources/empty-worker.js',
+ {scope: scope})
+ .then(function(r) {
+ registration = r;
+ return registration.unregister();
+ })
+ .then(function() {
+ return registration.unregister();
+ })
+ .then(function(value) {
+ assert_equals(value, false,
+ 'unregistering twice should resolve with false');
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Unregister twice');
+
+async_test(function(t) {
+ var scope = 'resources/scope/successful-unregister/';
+ navigator.serviceWorker.register('resources/empty-worker.js',
+ {scope: scope})
+ .then(function(registration) {
+ return registration.unregister();
+ })
+ .then(function(value) {
+ assert_equals(value, true,
+ 'unregistration should resolve with true');
+ t.done();
+ })
+ .catch(unreached_rejection(t));
+ }, 'Register then unregister');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/update-after-navigation-fetch-event.https.html b/testing/web-platform/tests/service-workers/service-worker/update-after-navigation-fetch-event.https.html
new file mode 100644
index 000000000..04cd9960f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/update-after-navigation-fetch-event.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Service Worker: Update should be triggered after a navigation fetch event.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(function(t) {
+ var script = 'resources/update-nocookie-worker.py';
+ var scope = 'resources/scope/update';
+ var parsed_url = normalizeURL(script);
+ var registration;
+ var frame;
+
+ return service_worker_unregister_and_register(t, parsed_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ // Navigation to the iframe.
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ // Navigation fetch event should trigger update.
+ return wait_for_update(t, registration);
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Update should be triggered after a navigation fetch event.');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/update-after-oneday.https.html b/testing/web-platform/tests/service-workers/service-worker/update-after-oneday.https.html
new file mode 100644
index 000000000..151a59ebc
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/update-after-oneday.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<!-- This test requires browser to treat all registrations are older than 24 hours.
+ Preference 'dom.serviceWorkers.testUpdateOverOneDay' should be enabled during
+ the execution of the test -->
+<title>Service Worker: Functional events should trigger update if last update time is over 24 hours</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(function(t) {
+ var script = 'resources/update-nocookie-worker.py';
+ var scope = 'resources/update/update-after-oneday.https.html';
+ var expected_url = normalizeURL(script);
+ var registration;
+ var frame;
+
+ return service_worker_unregister_and_register(t, expected_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame = f;
+ return 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() {
+ // Trigger a non-navigation fetch event
+ frame.contentWindow.load_image(normalizeURL('resources/update/dummy'));
+ return wait_for_update(t, registration);
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ }, 'Update should be triggered after a functional event when last update time is over 24 hours');
+
+</script>
+
+
diff --git a/testing/web-platform/tests/service-workers/service-worker/update-recovery.https.html b/testing/web-platform/tests/service-workers/service-worker/update-recovery.https.html
new file mode 100644
index 000000000..3b3d955b1
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/update-recovery.https.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<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>
+async_test(function(t) {
+ var scope = 'resources/simple.txt';
+ var worker_url = 'resources/update-recovery-worker.py';
+ var expected_url = normalizeURL(worker_url);
+ var registration;
+
+ 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() {
+ return Promise.all([
+ with_update(t),
+ with_bad_iframe(scope)
+ ]);
+ })
+ .then(function() {
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ assert_equals(frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
+ expected_url);
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Recover from a bad service worker by updating after a failed navigation.');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/update.https.html b/testing/web-platform/tests/service-workers/service-worker/update.https.html
new file mode 100644
index 000000000..213b72ac8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/update.https.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<title>Service Worker: Registration 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) {
+ var scope = 'resources/simple.txt';
+ var worker_url = 'resources/update-worker.py';
+ var expected_url = normalizeURL(worker_url);
+ var registration;
+ var iframe;
+ return 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.');
+ })
+ .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 a TypeError.');
+ })
+ .then(function() {
+ iframe.remove();
+ return t.done();
+ });
+ }, 'Update a registration.');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/waiting.https.html b/testing/web-platform/tests/service-workers/service-worker/waiting.https.html
new file mode 100644
index 000000000..eff9c80a4
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/waiting.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>ServiceWorker: navigator.serviceWorker.waiting</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+// "waiting" is set
+async_test(function(t) {
+ var step = t.step_func.bind(t);
+ var url = 'resources/empty-worker.js';
+ var scope = 'resources/blank.html';
+ var frame;
+ var registration;
+
+ service_worker_unregister(t, scope)
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame = f;
+ return navigator.serviceWorker.register(url, {scope: scope});
+ })
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, 'installed');
+ }, unreached_rejection(t, 'Registration should not fail'))
+ .then(function() {
+ var controller = frame.contentWindow.navigator.serviceWorker.controller;
+ assert_equals(controller, null);
+ // Nothing in the spec prohibits a worker from going to active
+ // immediately.
+ // Step 26 of the [[Install]] algorithm
+ // "If registration's waiting worker waitingWorker is not null and
+ // waitingWorker's skip waiting flag is not set, invoke Activate
+ // algorithm, or its equivalent, with registration as its argument."
+ var w = registration.waiting || registration.active;
+ assert_equals(w.scriptURL, normalizeURL(url));
+ assert_equals(registration.installing, null);
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ });
+}, 'waiting or active is set');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/websocket.https.html b/testing/web-platform/tests/service-workers/service-worker/websocket.https.html
new file mode 100644
index 000000000..3dfa6e514
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/websocket.https.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>Service Worker: WebSocket handshake channel is not intercepted</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+
+async_test(function(t) {
+ var path = new URL(".", window.location).pathname
+ var url = 'resources/websocket.js';
+ var scope = 'resources/blank.html?websocket';
+ var host_info = get_host_info();
+ var frameURL = host_info['HTTPS_ORIGIN'] + path + scope;
+ var frame;
+
+ service_worker_unregister_and_register(t, url, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(frameURL); })
+ .then(function(f) {
+ frame = f;
+ return websocket(t, frame);
+ })
+ .then(function() {
+ return new Promise(function(resolve, reject) {
+ function onMessage(e) {
+ for (var url in e.data.urls) {
+ assert_equals(url.indexOf(get_websocket_url()), -1,
+ "Observed an unexpected FetchEvent for the WebSocket handshake");
+ }
+ t.done();
+ }
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(onMessage);
+ frame.contentWindow.navigator.serviceWorker.controller.postMessage({port: channel.port2}, [channel.port2]);
+ });
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify WebSocket handshake channel does not get intercepted');
+</script>
diff --git a/testing/web-platform/tests/service-workers/service-worker/worker-interception.https.html b/testing/web-platform/tests/service-workers/service-worker/worker-interception.https.html
new file mode 100644
index 000000000..3ec66a54b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/worker-interception.https.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<title>Service Worker: intercepting Worker script loads</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(function(t) {
+ var worker_url = 'resources/dummy-synthesized-worker.js';
+ var service_worker = 'resources/dummy-worker-interceptor.js';
+ var scope = worker_url;
+
+ return service_worker_unregister_and_register(t, service_worker, scope)
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return new Promise(function(resolve, reject) {
+ var w = new Worker(worker_url);
+ w.onmessage = function(e) {
+ resolve(e.data);
+ }
+
+ w.onerror = function(e) {
+ reject(e.message);
+ }
+ });
+ })
+ .then(function(data) {
+ assert_equals(data, 'worker loading intercepted by service worker');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Verify worker script from uncontrolled document is intercepted by Service Worker');
+
+promise_test(function(t) {
+ var worker_url = 'resources/dummy-same-origin-worker.js';
+ var service_worker = 'resources/dummy-worker-interceptor.js';
+ var scope = worker_url;
+
+ return service_worker_unregister_and_register(t, service_worker, scope)
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return new Promise(function(resolve, reject) {
+ var w = new Worker(worker_url);
+ w.onmessage = function(e) {
+ resolve(e.data);
+ }
+
+ w.onerror = function(e) {
+ reject(e.message);
+ }
+ });
+ })
+ .then(function(data) {
+ assert_equals(data, 'dummy-worker-script loaded');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Verify worker script intercepted by same-origin response succeeds');
+
+promise_test(function(t) {
+ var worker_url = 'resources/dummy-cors-worker.js';
+ var service_worker = 'resources/dummy-worker-interceptor.js';
+ var scope = worker_url;
+
+ return service_worker_unregister_and_register(t, service_worker, scope)
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return new Promise(function(resolve, reject) {
+ var w = new Worker(worker_url);
+ w.onmessage = function(e) {
+ resolve(e.data);
+ }
+
+ w.onerror = function(e) {
+ reject(e.message);
+ }
+ });
+ })
+ .then(function(data) {
+ assert_equals(data, 'dummy-worker-script loaded');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Verify worker script intercepted by cors response succeeds');
+
+promise_test(function(t) {
+ var worker_url = 'resources/dummy-no-cors-worker.js';
+ var service_worker = 'resources/dummy-worker-interceptor.js';
+ var scope = worker_url;
+
+ return service_worker_unregister_and_register(t, service_worker, scope)
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() {
+ return new Promise(function(resolve, reject) {
+ var w = new Worker(worker_url);
+ w.onmessage = function(e) {
+ resolve(e.data);
+ }
+
+ w.onerror = function(e) {
+ reject(e);
+ return true;
+ }
+ });
+ })
+ .then(function(data) {
+ assert_unreached('intercepted no-cors worker load should fail');
+ service_worker_unregister_and_done(t, scope);
+ })
+ .catch(function(e) {
+ assert_true(true, 'intercepted no-cors worker load should fail');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Verify worker script intercepted by no-cors cross-origin response fails');
+
+promise_test(function(t) {
+ var subdoc_url = 'resources/worker-interception-iframe.https.html?bypass';
+ var service_worker = 'resources/worker-load-interceptor.js';
+ var scope = 'resources/';
+
+ window.addEventListener('message', t.step_func(on_message), false);
+ function on_message(e) {
+ assert_equals(e.data.results, "This load was successfully intercepted.");
+ t.done();
+ }
+
+ return service_worker_unregister_and_register(t, service_worker, scope)
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'activated');
+ })
+ .then(function() { return with_iframe(subdoc_url); })
+ .then(function(frame) {
+ return new Promise(function(resolve, reject) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function(e) {
+ frame.remove();
+ resolve(e.data);
+ }
+
+ frame.contentWindow.postMessage("GO", "*", [channel.port2]);
+ });
+ })
+ .then(function(data) {
+ assert_equals(data.results, 'finish');
+ service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Verify worker loads from controlled document are intercepted by Service Worker');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/service-workers/service-worker/xhr.https.html b/testing/web-platform/tests/service-workers/service-worker/xhr.https.html
new file mode 100644
index 000000000..776e61db1
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/xhr.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Service Worker: XHR doesn't exist</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+
+async_test(function(t) {
+ var path = new URL(".", window.location).pathname
+ var url = 'resources/xhr.js';
+ var scope = 'resources/blank.html?xhr';
+ var host_info = get_host_info();
+ var frameURL = host_info['HTTPS_ORIGIN'] + path + scope;
+
+ service_worker_unregister_and_register(t, url, scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(frameURL); })
+ .then(function(frame) {
+ return new Promise(function(resolve, reject) {
+ function onMessage(e) {
+ assert_false(e.data.xhr);
+ frame.remove();
+ service_worker_unregister_and_done(t, scope);
+ }
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(onMessage);
+ frame.contentWindow.navigator.serviceWorker.controller.postMessage({port: channel.port2}, [channel.port2]);
+ })
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify XHR does not exist');
+</script>
diff --git a/testing/web-platform/tests/service-workers/specgen.json b/testing/web-platform/tests/service-workers/specgen.json
new file mode 100644
index 000000000..88c36ed04
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/specgen.json
@@ -0,0 +1,658 @@
+{
+ "sections": [
+ {
+ "href": "#introduction",
+ "id": "introduction",
+ "hash": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ "secno": "1",
+ "testable": false
+ },
+ {
+ "href": "#about",
+ "id": "about",
+ "hash": "8d3cf149aa73cff52328509ebbaffd933e8fb6af",
+ "secno": "1.1",
+ "testable": false
+ },
+ {
+ "href": "#dependencies",
+ "id": "dependencies",
+ "hash": "1355f2d7ec9bf4e617ee632c0db44f834c96435b",
+ "secno": "1.2",
+ "testable": false
+ },
+ {
+ "href": "#motivations",
+ "id": "motivations",
+ "hash": "92d899bc1e63a170d2324638d16f580b97b4f4d6",
+ "secno": "1.3",
+ "testable": false
+ },
+ {
+ "href": "#concepts",
+ "id": "concepts",
+ "hash": "589023372dc033b0a77be1cd01f54f5f8c3ebfa8",
+ "secno": "2",
+ "testable": false
+ },
+ {
+ "href": "#document-context",
+ "id": "document-context",
+ "hash": "34feeb18dea978349a2f76e6b17c127123b3db74",
+ "secno": "3",
+ "testable": false
+ },
+ {
+ "href": "#service-worker-obj",
+ "id": "service-worker-obj",
+ "hash": "6cbd0107199072ab86b36e72d08d5465b42e6da8",
+ "secno": "3.1",
+ "testPageHash": "8dbbc9aa4300f0203524f3e405dbf7ca462e7164",
+ "testPagePath": "stub-3.1-service-worker-obj.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-scope",
+ "id": "service-worker-scope",
+ "hash": "136f25ef227515a7be9b32c44967f68b34ad8924",
+ "secno": "3.1.1",
+ "testPageHash": "965a00b32d56192330aa9f6337072bb3633ad382",
+ "testPagePath": "stub-3.1.1-service-worker-scope.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-url",
+ "id": "service-worker-url",
+ "hash": "df66a1b4b3bfa3e7ab96fd491a6829fab1d18a88",
+ "secno": "3.1.2",
+ "testPageHash": "92f6aed1437bb39c5941b495ac6c5f342c025b38",
+ "testPagePath": "stub-3.1.2-service-worker-url.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-state",
+ "id": "service-worker-state",
+ "hash": "8f80f2b4cbb1c228867c9dd90c05cbecfc92dd77",
+ "secno": "3.1.3",
+ "testPageHash": "4aad1dc47572879fdc2c79a814ad21e1ef9a64ec",
+ "testPagePath": "stub-3.1.3-service-worker-state.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-on-state-change",
+ "id": "service-worker-on-state-change",
+ "hash": "0f8fd9d1431deacea72fe739f42992ab5a396bf2",
+ "secno": "3.1.4",
+ "testPageHash": "6bb309bccc1e7c74ade7fc4c6e400bafb60daceb",
+ "testPagePath": "stub-3.1.4-service-worker-on-state-change.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker",
+ "id": "navigator-service-worker",
+ "hash": "22f1ebbafca6976d0f4814b0fbb8f173bf919f06",
+ "secno": "3.2",
+ "testPageHash": "6d597735816a09ec774150029ed5136198f52ab7",
+ "testPagePath": "stub-3.2-navigator-service-worker.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-installing",
+ "id": "navigator-service-worker-installing",
+ "hash": "9675c3cdf5ba4b4155284e06a19e4de631645509",
+ "secno": "3.2.1",
+ "testPageHash": "2c8e56e74c130104e395de46bad20fb5d3021d95",
+ "testPagePath": "stub-3.2.1-navigator-service-worker-installing.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-waiting",
+ "id": "navigator-service-worker-waiting",
+ "hash": "88b4db92cc49109e6a15ddebdd219690d9648e76",
+ "secno": "3.2.2",
+ "testPageHash": "1cf6ed58bf5ecf963fed8c3d9211b853dab564e2",
+ "testPagePath": "stub-3.2.2-navigator-service-worker-waiting.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-active",
+ "id": "navigator-service-worker-active",
+ "hash": "0da48e885c77da60d1837197780049904789e3cb",
+ "secno": "3.2.3",
+ "testPageHash": "f5dca8c6eb5f29a0f9a5f06e25861e7f3106cc67",
+ "testPagePath": "stub-3.2.3-navigator-service-worker-active.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-controller",
+ "id": "navigator-service-worker-controller",
+ "hash": "293433ccb7bb2a22d8d5a81e788892e071b25e65",
+ "secno": "3.2.4",
+ "testPageHash": "6452f431d0765d7aa3135d18fee43e6664dcbb12",
+ "testPagePath": "stub-3.2.4-navigator-service-worker-controller.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-ready",
+ "id": "navigator-service-worker-ready",
+ "hash": "6240fde8d7168beeb95f4f36aa9e143319b2061b",
+ "secno": "3.2.5",
+ "testPageHash": "ae4fd694c88bab72f338d97bf96b7d23e2e83e87",
+ "testPagePath": "stub-3.2.5-navigator-service-worker-ready.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-getAll",
+ "id": "navigator-service-worker-getAll",
+ "hash": "292ee3af2cc8fadc24302446809d04bf2e9811a5",
+ "secno": "3.2.6",
+ "testPageHash": "4096ae712cc3e753456fbe05bb4d0cfc4399d2c9",
+ "testPagePath": "stub-3.2.6-navigator-service-worker-getAll.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-register",
+ "id": "navigator-service-worker-register",
+ "hash": "c999dc5f67126c9f0f02b25fd943a34b48cff618",
+ "secno": "3.2.7",
+ "testPageHash": "bde900b97dbb08b053ff8115775ea3b79a124b6e",
+ "testPagePath": "stub-3.2.7-navigator-service-worker-register.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-unregister",
+ "id": "navigator-service-worker-unregister",
+ "hash": "fd196f926f181563855e4683cc995405c1e611d0",
+ "secno": "3.2.8",
+ "testPageHash": "dbd99a1dcbcb629431617790a305e840495049eb",
+ "testPagePath": "stub-3.2.8-navigator-service-worker-unregister.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-onupdatefound",
+ "id": "navigator-service-worker-onupdatefound",
+ "hash": "2bb5aabaca24a68f9e6b4c4443968178eb1ccfe8",
+ "secno": "3.2.9",
+ "testPageHash": "eef0c1c39577abefb3654a6e9917ff2da657871b",
+ "testPagePath": "stub-3.2.9-navigator-service-worker-onupdatefound.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-oncontrollerchange",
+ "id": "navigator-service-worker-oncontrollerchange",
+ "hash": "c89a4ffba10d9285e07c38c28718719d87053994",
+ "secno": "3.2.10",
+ "testPageHash": "35e0ce2b8f4527ebbd75d4dfa3436fd7f8c79792",
+ "testPagePath": "stub-3.2.10-navigator-service-worker-oncontrollerchange.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-onreloadpage",
+ "id": "navigator-service-worker-onreloadpage",
+ "hash": "424441910abf2e1bdc3db658fe46827f7abe60a4",
+ "secno": "3.2.11",
+ "testPageHash": "ae614de17e5f339b65f77cafa6e0f5625491abfb",
+ "testPagePath": "stub-3.2.11-navigator-service-worker-onreloadpage.html",
+ "testable": true
+ },
+ {
+ "href": "#navigator-service-worker-onerror",
+ "id": "navigator-service-worker-onerror",
+ "hash": "710f7fcd2f5340147b9e030bc5932b8242cef828",
+ "secno": "3.2.12",
+ "testPageHash": "cd62779e27151d55f14ac6ab7aa41dcf723e0ac7",
+ "testPagePath": "stub-3.2.12-navigator-service-worker-onerror.html",
+ "testable": true
+ },
+ {
+ "href": "#execution-context",
+ "id": "execution-context",
+ "hash": "ddf24f0adf58237e264c3c43cb7ab07af3013c9d",
+ "secno": "4",
+ "testable": false
+ },
+ {
+ "href": "#service-worker-global-scope",
+ "id": "service-worker-global-scope",
+ "hash": "e6b8bb7f99c125f4226fc5b6c51cf03a7437f2ef",
+ "secno": "4.1",
+ "testPageHash": "2f596b6b07bcfb71c01d75f725eb52c84e9c69dd",
+ "testPagePath": "stub-4.1-service-worker-global-scope.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-global-scope-caches",
+ "id": "service-worker-global-scope-caches",
+ "hash": "43d3c9f441b3a7abd0d3a7f55d93faaceeb7d97d",
+ "secno": "4.1.1",
+ "testPageHash": "f19b91c887f6312688b66b1988147a599cd9470f",
+ "testPagePath": "stub-4.1.1-service-worker-global-scope-caches.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-global-scope-clients",
+ "id": "service-worker-global-scope-clients",
+ "hash": "cb83230107645229da9776ed0fc9f7bc6fcce747",
+ "secno": "4.1.2",
+ "testPageHash": "45b3aae572f7161748fa98e97b4f2b738c3dcfef",
+ "testPagePath": "stub-4.1.2-service-worker-global-scope-clients.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-global-scope-scope",
+ "id": "service-worker-global-scope-scope",
+ "hash": "08c808048b647aa9d4cc0b0a0f70b06ca89af4a3",
+ "secno": "4.1.3",
+ "testPageHash": "bfe7eaf8deb8de7d2ccfbba97640478b1c81d6c7",
+ "testPagePath": "stub-4.1.3-service-worker-global-scope-scope.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-global-scope-fetch",
+ "id": "service-worker-global-scope-fetch",
+ "hash": "b66133d8a2c67f9b10c274d5b05383ff76d2cd42",
+ "secno": "4.1.4",
+ "testPageHash": "2b1ffa915afddeb099dfff23f4ecf555b0710ed4",
+ "testPagePath": "stub-4.1.4-service-worker-global-scope-fetch.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-global-scope-update",
+ "id": "service-worker-global-scope-update",
+ "hash": "3ddf48cecb4d4a67a329248787dd220ce17f4eff",
+ "secno": "4.1.5",
+ "testPageHash": "15879bf45f460c0ab0c02793655096c1bca418a7",
+ "testPagePath": "stub-4.1.5-service-worker-global-scope-update.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-global-scope-unregister",
+ "id": "service-worker-global-scope-unregister",
+ "hash": "fff9ef2daa5689b38a17eeb9a6bd7071098ca778",
+ "secno": "4.1.6",
+ "testPageHash": "c4bf327228628b794db9c6f2eb17519e37cea6b9",
+ "testPagePath": "stub-4.1.6-service-worker-global-scope-unregister.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-global-scope-onmessage",
+ "id": "service-worker-global-scope-onmessage",
+ "hash": "bc8f6aed2d515dc7f6b0757afa02f37899082668",
+ "secno": "4.1.7",
+ "testPageHash": "9e6f2732d21871ec06e9541ea881baf962f7cdf4",
+ "testPagePath": "stub-4.1.7-service-worker-global-scope-onmessage.html",
+ "testable": true
+ },
+ {
+ "href": "#client",
+ "id": "client",
+ "hash": "47a1c10cd9e4db9a5c86d9bcf80477f771ea954c",
+ "secno": "4.2",
+ "testPageHash": "21d74c1af0b3176b029c9b62b37fe73436e0f197",
+ "testPagePath": "stub-4.2-client.html",
+ "testable": true
+ },
+ {
+ "href": "#service-worker-clients",
+ "id": "service-worker-clients",
+ "hash": "c2c6f4873f07b53705a46b2bd44ba10f84dd2b56",
+ "secno": "4.3",
+ "testPageHash": "9c0366e6cfd28caaeaf940bad2b3c7ace93037f6",
+ "testPagePath": "stub-4.3-service-worker-clients.html",
+ "testable": true
+ },
+ {
+ "href": "#get-serviced-method",
+ "id": "get-serviced-method",
+ "hash": "299abaa21cf096e423edfa19755987986f742a1f",
+ "secno": "4.3.1",
+ "testPageHash": "efeb1c2dc8144c30e6628cb56b3e532531ee1e88",
+ "testPagePath": "stub-4.3.1-get-serviced-method.html",
+ "testable": true
+ },
+ {
+ "href": "#reloadall-method",
+ "id": "reloadall-method",
+ "hash": "bb4d775d261e69cbeaf65c123e949c24cf542ae7",
+ "secno": "4.3.2",
+ "testPageHash": "d1a4dde873b77201b4de745d2083bf63549b0b8b",
+ "testPagePath": "stub-4.3.2-reloadall-method.html",
+ "testable": true
+ },
+ {
+ "href": "#request-objects",
+ "id": "request-objects",
+ "hash": "65ae6c08f720a2eedb7b140f5635a5ac46ddadfc",
+ "secno": "4.4",
+ "testPageHash": "ec493c70e8a0d8d3eeb0ecaef59610aed97d298e",
+ "testPagePath": "stub-4.4-request-objects.html",
+ "testable": true
+ },
+ {
+ "href": "#response-objects",
+ "id": "response-objects",
+ "hash": "2efbff63c70ab92f93e4acd021409b9df4776882",
+ "secno": "4.5",
+ "testPageHash": "8340b69d62f111f56095c5fe9047d9215fa7aefc",
+ "testPagePath": "stub-4.5-response-objects.html",
+ "testable": true
+ },
+ {
+ "href": "#abstract-response",
+ "id": "abstract-response",
+ "hash": "bddc306a9892c0bca43e8b361c1ee22b87759e23",
+ "secno": "4.5.1",
+ "testable": false
+ },
+ {
+ "href": "#response",
+ "id": "response",
+ "hash": "6471d25755bdab0d4f72413f9367b7bb36c53a6f",
+ "secno": "4.5.2",
+ "testPageHash": "346d63cc7eb8ee412f5f704ba241205c8d437540",
+ "testPagePath": "stub-4.5.2-response.html",
+ "testable": true
+ },
+ {
+ "href": "#header",
+ "id": "header",
+ "hash": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ "secno": "4.5.3",
+ "testable": false
+ },
+ {
+ "href": "#opaque-response",
+ "id": "opaque-response",
+ "hash": "df5431f4fbd26d81f2d4f567309c6a7a26dbfd4a",
+ "secno": "4.5.4",
+ "testPageHash": "85373f290cf594f0f09eb0a76bc6ef6299be595f",
+ "testPagePath": "stub-4.5.4-opaque-response.html",
+ "testable": true
+ },
+ {
+ "href": "#cors-response",
+ "id": "cors-response",
+ "hash": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ "secno": "4.5.5",
+ "testable": false
+ },
+ {
+ "href": "#cache-objects",
+ "id": "cache-objects",
+ "hash": "001d0dfb8fbcbcb6443d1be2b722c9a84d6fd95b",
+ "secno": "4.6",
+ "testPageHash": "c1ef341d15a8c76d015eef57842ed10e62c02927",
+ "testPagePath": "stub-4.6-cache-objects.html",
+ "testable": true
+ },
+ {
+ "href": "#cache-lifetimes",
+ "id": "cache-lifetimes",
+ "hash": "7c73698ca9b686a0314ddf368bf8ad4ca6af392f",
+ "secno": "4.6.1",
+ "testPageHash": "f3524320a98f2fbdc5d711de82770957a7f5ec4b",
+ "testPagePath": "stub-4.6.1-cache-lifetimes.html",
+ "testable": true
+ },
+ {
+ "href": "#cache",
+ "id": "cache",
+ "hash": "bf1fe844577ab57a60eb550be24335a3321ca2ee",
+ "secno": "4.6.2",
+ "testPageHash": "c55b7b05c8e2f4b65722e16cdbcd78ffdfe1e4bf",
+ "testPagePath": "stub-4.6.2-cache.html",
+ "testable": true
+ },
+ {
+ "href": "#cache-storage",
+ "id": "cache-storage",
+ "hash": "9cdaac070f56e55d66a89cd4b6e669a04aa73b82",
+ "secno": "4.6.3",
+ "testPageHash": "ee6902f170d94cc1e3a4a00f4c90e7e19c4dff95",
+ "testPagePath": "stub-4.6.3-cache-storage.html",
+ "testable": true
+ },
+ {
+ "href": "#events",
+ "id": "events",
+ "hash": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ "secno": "4.7",
+ "testable": false
+ },
+ {
+ "href": "#install-phase-event",
+ "id": "install-phase-event",
+ "hash": "8495382b418adbbed436b2002ab0155a3a295ef2",
+ "secno": "4.7.1",
+ "testPageHash": "e48e98d51936bd57d21903615203f2b78d3f4b12",
+ "testPagePath": "stub-4.7.1-install-phase-event.html",
+ "testable": true
+ },
+ {
+ "href": "#wait-until-method",
+ "id": "wait-until-method",
+ "hash": "295fb5d4932396fd13365ed2fe57aa672f1f2a56",
+ "secno": "4.7.1.1",
+ "testPageHash": "c3769e51852b8438a97c39c50fa62351a73c4ee6",
+ "testPagePath": "stub-4.7.1.1-wait-until-method.html",
+ "testable": true
+ },
+ {
+ "href": "#install-event",
+ "id": "install-event",
+ "hash": "3a0f6da1771c22ab21ddc00729433a4d95ac6782",
+ "secno": "4.7.2",
+ "testPageHash": "9a103cc461eaca3da75db583ce08f13ecd2b1a98",
+ "testPagePath": "stub-4.7.2-install-event.html",
+ "testable": true
+ },
+ {
+ "href": "#install-event-section",
+ "id": "install-event-section",
+ "hash": "4631577df2efc1a4350000461629bc1ca93dbd14",
+ "secno": "4.7.2.1",
+ "testPageHash": "32f54e74bef784d2f0ac772b44abeee06573062d",
+ "testPagePath": "stub-4.7.2.1-install-event-section.html",
+ "testable": true
+ },
+ {
+ "href": "#replace-method",
+ "id": "replace-method",
+ "hash": "b9093b05204d09748311023b4c737ede02ff8115",
+ "secno": "4.7.2.2",
+ "testPageHash": "372bed923f8c35c4923634ae27fa121919ac0fec",
+ "testPagePath": "stub-4.7.2.2-replace-method.html",
+ "testable": true
+ },
+ {
+ "href": "#activate-event",
+ "id": "activate-event",
+ "hash": "ac3d03aa0ed961fb1122850aeab92c302c55ecd0",
+ "secno": "4.7.3",
+ "testPageHash": "6241762ab1d6f430fa9b7cc8f02a00e6591c6bc6",
+ "testPagePath": "stub-4.7.3-activate-event.html",
+ "testable": true
+ },
+ {
+ "href": "#fetch-event",
+ "id": "fetch-event",
+ "hash": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ "secno": "4.7.4",
+ "testable": false
+ },
+ {
+ "href": "#fetch-event-section",
+ "id": "fetch-event-section",
+ "hash": "ae24fda9664a3bd7b7fe2a8712ac469c3ee7128e",
+ "secno": "4.7.4.1",
+ "testPageHash": "393fc7b65e9f5afd18da666b6b206ccd639397cd",
+ "testPagePath": "stub-4.7.4.1-fetch-event-section.html",
+ "testable": true
+ },
+ {
+ "href": "#respond-with-method",
+ "id": "respond-with-method",
+ "hash": "7e4f010e2ec1ea0500b435cf599ba58942164457",
+ "secno": "4.7.4.2",
+ "testPageHash": "31e0acd058b9a5b722ae9f405b50bc94d31596b8",
+ "testPagePath": "stub-4.7.4.2-respond-with-method.html",
+ "testable": true
+ },
+ {
+ "href": "#default-method",
+ "id": "default-method",
+ "hash": "4d6f8f93b2e10ab0e486dbf464ff107ec1a6aa4c",
+ "secno": "4.7.4.3",
+ "testPageHash": "34e015c973887e2b3bf8b6db62f75d5d417a43cc",
+ "testPagePath": "stub-4.7.4.3-default-method.html",
+ "testable": true
+ },
+ {
+ "href": "#is-reload-attribute",
+ "id": "is-reload-attribute",
+ "hash": "6e1afd9e8940e9cd38aa7de1ed57e8c5b1a60e3d",
+ "secno": "4.7.4.4",
+ "testPageHash": "703a6469782d37be3c25e2214f897d1064acca47",
+ "testPagePath": "stub-4.7.4.4-is-reload-attribute.html",
+ "testable": true
+ },
+ {
+ "href": "#security-considerations",
+ "id": "security-considerations",
+ "hash": "5b02b143172647dd7f74f0464dffa7ec7d0e8f94",
+ "secno": "5",
+ "testable": false
+ },
+ {
+ "href": "#origin-relativity",
+ "id": "origin-relativity",
+ "hash": "72bbbd7d3d43a859af6ff9f19353210ddfcc26de",
+ "secno": "5.1",
+ "testPageHash": "1c92607dfac57b0f59654d059a4a67e0f984b84d",
+ "testPagePath": "stub-5.1-origin-relativity.html",
+ "testable": true
+ },
+ {
+ "href": "#cross-origin-resources",
+ "id": "cross-origin-resources",
+ "hash": "6176879ecfb5ae769679ceef4ee1e8889be8df92",
+ "secno": "5.2",
+ "testPageHash": "bcf85ba278c70c086645c416cee729ce753bc528",
+ "testPagePath": "stub-5.2-cross-origin-resources.html",
+ "testable": true
+ },
+ {
+ "href": "#storage-considerations",
+ "id": "storage-considerations",
+ "hash": "e101cee2062749b1a73086492377458251a5e875",
+ "secno": "6",
+ "testable": false
+ },
+ {
+ "href": "#extensibility",
+ "id": "extensibility",
+ "hash": "ef1b382bb89c52e01edad421b02b237765a21ce7",
+ "secno": "7",
+ "testable": false
+ },
+ {
+ "href": "#algorithms",
+ "id": "algorithms",
+ "hash": "d130247eab1d368efea646ff369e65f6c0c19481",
+ "secno": "8",
+ "testable": false
+ },
+ {
+ "href": "#registration-algorithm",
+ "id": "registration-algorithm",
+ "hash": "b688d090671c08ca17ea7cadc561e6d471ee099e",
+ "secno": "8.1",
+ "testable": false
+ },
+ {
+ "href": "#update-algorithm",
+ "id": "update-algorithm",
+ "hash": "679a19fef428affc83103c1eec0dbd3be40c4e2a",
+ "secno": "8.2",
+ "testable": false
+ },
+ {
+ "href": "#soft-update-algorithm",
+ "id": "soft-update-algorithm",
+ "hash": "8eb103f5cd0e595ee5e25f075e8c6239211e482a",
+ "secno": "8.3",
+ "testable": false
+ },
+ {
+ "href": "#installation-algorithm",
+ "id": "installation-algorithm",
+ "hash": "5874d9247d979009b67aedf964ae097837cfb3d9",
+ "secno": "8.4",
+ "testable": false
+ },
+ {
+ "href": "#activation-algorithm",
+ "id": "activation-algorithm",
+ "hash": "648b34baf6e7c2096a842e6d367949117843108e",
+ "secno": "8.5",
+ "testable": false
+ },
+ {
+ "href": "#on-fetch-request-algorithm",
+ "id": "on-fetch-request-algorithm",
+ "hash": "e1da43671071ec307f99cd781fc9b46353f3adfd",
+ "secno": "8.6",
+ "testable": false
+ },
+ {
+ "href": "#on-document-unload-algorithm",
+ "id": "on-document-unload-algorithm",
+ "hash": "8a7196b5dd04ad4fb9b96e16a52f4f7ac1906763",
+ "secno": "8.7",
+ "testable": false
+ },
+ {
+ "href": "#unregistration-algorithm",
+ "id": "unregistration-algorithm",
+ "hash": "0114db166d42211d0d7ab4b8e77de64a9fc97517",
+ "secno": "8.8",
+ "testable": false
+ },
+ {
+ "href": "#update-state-algorithm",
+ "id": "update-state-algorithm",
+ "hash": "2ed8a1e7479f1a8ad038aa44ccdd5e4f6b65cf05",
+ "secno": "8.9",
+ "testable": false
+ },
+ {
+ "href": "#scope-match-algorithm",
+ "id": "scope-match-algorithm",
+ "hash": "a2117fb34a8fa4ca3e832d9276477cfc1318dd1a",
+ "secno": "8.10",
+ "testable": false
+ },
+ {
+ "href": "#get-registration-algorithm",
+ "id": "get-registration-algorithm",
+ "hash": "b20332db952ba8f4b7e5f65b740a18da4a199c2e",
+ "secno": "8.11",
+ "testable": false
+ },
+ {
+ "href": "#get-newest-worker-algorithm",
+ "id": "get-newest-worker-algorithm",
+ "hash": "72dc1cbee8c98501931c411018fd1cad4376142b",
+ "secno": "8.12",
+ "testable": false
+ },
+ {
+ "href": "#acknowledgements",
+ "id": "acknowledgements",
+ "hash": "6347067ca5a574f8cc80c76d95dee568042d059b",
+ "secno": "9",
+ "testable": false
+ }
+ ],
+ "specUrl": "https://w3c.github.io/ServiceWorker/"
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/service-workers/stub-3.1-service-worker-obj.html b/testing/web-platform/tests/service-workers/stub-3.1-service-worker-obj.html
new file mode 100644
index 000000000..b1c7f1ad0
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.1-service-worker-obj.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: ServiceWorker</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-obj">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<script type=text/plain id="idl_0">
+[Constructor()] // no-op constructor
+interface ServiceWorker : Worker {
+ readonly attribute DOMString scope;
+ readonly attribute DOMString url;
+ readonly attribute ServiceWorkerState state;
+
+ // event
+ attribute EventHandler onstatechange;
+};
+
+enum ServiceWorkerState {
+ "installing",
+ "installed",
+ "activating",
+ "activated",
+ "redundant"
+};
+</pre>
+
+<!--
+The `ServiceWorker` interface represents the document-side view of a Service
+Worker. This object provides a no-op constructor. Callers should note that only
+`ServiceWorker` objects created by the user agent (see
+`navigator.serviceWorker.installing`, `navigator.serviceWorker.waiting`,
+`navigator.serviceWorker.active` and `navigator.serviceWorker.controller`) will
+provide meaningful functionality.
+-->
+
+
+ <script type=text/plain id="untested_idls">
+ interface EventHandler {};
+ interface Worker {};
+ </pre>
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ ServiceWorker: ["throw new Error ('No object defined for the ServiceWorker interface')"],
+ ServiceWorkerState: ["throw new Error ('No object defined for the ServiceWorkerState enum')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.1.1-service-worker-scope.html b/testing/web-platform/tests/service-workers/stub-3.1.1-service-worker-scope.html
new file mode 100644
index 000000000..8c75c6082
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.1.1-service-worker-scope.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: scope</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-scope">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+The `scope` of a `ServiceWorker` object reflects the [URL scope][1] of the
+associated Service Worker [registration][2]. The `scope` attribute must return
+the [serialization][3] of the URL representing the [URL scope][1] of the
+associated Service Worker [registration][2].
+
+For example, consider a document created by a navigation to
+`https://example.com/app.html` which [matches][4] via the following
+registration call which has been previously executed:
+// Script on the page https://example.com/app.html
+navigator.serviceWorker.register("/service_worker.js", { scope: "/*" });
+The value of `navigator.serviceWorker.controller.scope` will be
+`"https://example.com/*"`.
+
+
+
+[1]: #url-scope
+[2]: #registration
+[3]: http://url.spec.whatwg.org/#concept-url-serializer
+[4]: #on-fetch-request-algorithm
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section scope so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.1.2-service-worker-url.html b/testing/web-platform/tests/service-workers/stub-3.1.2-service-worker-url.html
new file mode 100644
index 000000000..5674df7fc
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.1.2-service-worker-url.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: url</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-url">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+The `url` attribute must return the [serialization][1] of the URL of the script
+of the Service Worker, identified by its [URL scope][2], that is associated
+with the [ServiceWorkerGlobalScope][3] object. The `url` attribute is always an
+[absolute URL][4] corresponding to the script file which the Service Worker
+evaluates.
+
+In the example in section 3.1.1, the value of
+`navigator.serviceWorker.controller.url` will be
+`"https://example.com/service_worker.js"`.
+
+
+
+[1]: http://url.spec.whatwg.org/#concept-url-serializer
+[2]: #url-scope
+[3]: #service-worker-global-scope-interface
+[4]: http://url.spec.whatwg.org/#concept-absolute-url
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section url so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.1.3-service-worker-state.html b/testing/web-platform/tests/service-workers/stub-3.1.3-service-worker-state.html
new file mode 100644
index 000000000..8e729baf8
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.1.3-service-worker-state.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: state</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-state">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+The [ServiceWorker][1] object can be in several states. The `state` attribute
+must return the current state, which must be one of the following values
+defined in the [ServiceWorkerState][2] enumeration:
+
+`"installing"`:
+ The Service Worker represented by the [ServiceWorker][1] object has entered
+ and is running the steps in the [installation process][3]. During this
+ state, `e.waitUntil(p)` can be called inside the `oninstall` event handler
+ of the associcated [ServiceWorkerGloberScope][4] object to extend the life
+ of the [installing worker][5] until the passed [Promise][6] resolves
+ successfully. This is primarily used to ensure that the Service Worker is
+ not active until all of the core caches are populated.
+`"installed"`:
+ The Service Worker represented by the [ServiceWorker][1] object has
+ completed the steps in the [installation process][3]. The Service Worker in
+ this state is considered the [worker in waiting][7].
+`"activating"`:
+ The Service Worker represented by the [ServiceWorker][1] object has entered
+ and is running the steps in the [activation process][8]. During this state,
+ `e.waitUntil(p)` can be called inside the `onactivate` event handler of the
+ associated [ServiceWorkerGloberScope][9] object to extend the life of the
+ activating [active worker][10] until the passed [Promise][6] resolves
+ successfully. Note that no [functional events][11] are dispatched until the
+ state becomes `"activated"`.
+`"activated"`:
+ The Service Worker represented by the [ServiceWorker][1] object has
+ completed the steps in the [activation process][8]. The Service Worker in
+ this state is considered the [active worker][10] ready to [control][12] the
+ documents in matching scope upon subsequence [navigation][13].
+`"redundant"`:
+ A newly created Service Worker [registration][14] is replacing the current
+ [registration][14] of the Service Worker.
+
+
+
+[1]: #service-worker-interface
+[2]: #service-worker-state-enum
+[3]: #installation-process
+[4]: #service-worker-glober-scope-interface
+[5]: #installing-worker
+[6]: http://goo.gl/3TobQS
+[7]: #worker-in-waiting
+[8]: #activation-process
+[9]: #service-worker-global-scope-interface
+[10]: #active-worker
+[11]: #functional-events
+[12]: #document-control
+[13]: http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html#navigate
+[14]: #registration
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section state so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.1.4-service-worker-on-state-change.html b/testing/web-platform/tests/service-workers/stub-3.1.4-service-worker-on-state-change.html
new file mode 100644
index 000000000..c87dce601
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.1.4-service-worker-on-state-change.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: onstatechange</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-on-state-change">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`onstatechange` is the [event handler][1] that must be supported as attribute
+by the `[ServiceWorker][2]` object. A `statechange` event using the
+`[Event][3]` interface is dispatched on `[ServiceWorker][2]` object when the
+`state` attribute of the `ServiceWorker` object is changed.
+
+[1]: http://goo.gl/rBfiz0
+[2]: #service-worker-interface
+[3]: http://goo.gl/Mzv7Dv
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section onstatechange so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2-navigator-service-worker.html b/testing/web-platform/tests/service-workers/stub-3.2-navigator-service-worker.html
new file mode 100644
index 000000000..2654c60f0
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2-navigator-service-worker.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: navigator.serviceWorker</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<!--
+The `serviceWorker` attribute of the [Navigator][1] interface must return an
+instance of the `ServiceWorkerContainer` interface, which provides access to
+registration, removal, upgrade, and communication with Service Workers that are
+(or will become) active for the current document. Communication with these
+workers is provided via standard [HTML5 messaging APIs][2], and [messaging
+occurs as per usual with Web Workers][3].
+-->
+<script type=text/plain id="idl_0">
+partial interface Navigator {
+ readonly attribute ServiceWorkerContainer serviceWorker;
+};
+
+interface ServiceWorkerContainer : EventTarget {
+ [Unforgeable] readonly attribute ServiceWorker? installing;
+ [Unforgeable] readonly attribute ServiceWorker? waiting;
+ [Unforgeable] readonly attribute ServiceWorker? active;
+ [Unforgeable] readonly attribute ServiceWorker? controller;
+ readonly attribute Promise<ServiceWorker> ready;
+
+ Promise<sequence<ServiceWorker>?> getAll();
+ Promise<ServiceWorker> register(DOMString url, optional RegistrationOptionList options);
+ Promise<any> unregister(DOMString? scope);
+
+ // events
+ attribute EventHandler onupdatefound;
+ attribute EventHandler oncontrollerchange;
+ attribute EventHandler onreloadpage;
+ attribute EventHandler onerror;
+};
+
+dictionary RegistrationOptionList {
+ DOMString scope = "/*";
+};
+
+interface ReloadPageEvent : Event {
+ void waitUntil(Promise<any> f);
+};
+</script>
+
+<!--
+[1]: http://goo.gl/I7WAhg
+[2]: http://www.whatwg.org/specs/web-apps/current-work/multipage/web-messaging.html
+[3]: http://www.w3.org/TR/workers/#dom-worker-postmessage
+-->
+
+
+ <script type=text/plain id="untested_idls">
+ interface ServiceWorker {};
+ interface EventHandler {};
+ interface EventTarget {};
+ interface Event {};
+ </pre>
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ Navigator: ["throw new Error ('No object defined for the Navigator interface')"],
+ ServiceWorkerContainer: ["throw new Error ('No object defined for the ServiceWorkerContainer interface')"],
+ RegistrationOptionList: ["throw new Error ('No object defined for the RegistrationOptionList dictionary')"],
+ ReloadPageEvent: ["throw new Error ('No object defined for the ReloadPageEvent interface')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.1-navigator-service-worker-installing.html b/testing/web-platform/tests/service-workers/stub-3.2.1-navigator-service-worker-installing.html
new file mode 100644
index 000000000..59e4f3d6a
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.1-navigator-service-worker-installing.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: installing</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-installing">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.installing` must return a [ServiceWorker][1] object
+representing the [installing worker][2] that is currently undergoing the
+installation process (from step 1 to step 7 of the [_Installation
+algorithm][3]) for the given [URL scope][4] in which the document may be
+[controlled][5] when the Service Worker becomes the [active worker][6].
+`navigator.serviceWorker.installing` returns `null` if no Service Worker
+[registration][7] is in the [installation process][8].
+
+[1]: #service-worker-interface
+[2]: #installing-worker
+[3]: #installation-algorithm
+[4]: #url-scope
+[5]: #document-control
+[6]: #active-worker
+[7]: #service-worker-registration-internal-interface
+[8]: #installation-process
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section installing so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.10-navigator-service-worker-oncontrollerchange.html b/testing/web-platform/tests/service-workers/stub-3.2.10-navigator-service-worker-oncontrollerchange.html
new file mode 100644
index 000000000..478860146
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.10-navigator-service-worker-oncontrollerchange.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: oncontrollerchange</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-oncontrollerchange">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.oncontrollerchange` is the [event handler][1] that
+must be supported as attribute by the `[ServiceWorkerContainer][2]` object. A
+`controllerchange` event using the `[Event][3]` interface is dispatched on
+`[ServiceWorkerContainer][2]` object (See step 7 of the [_Activation
+algorithm][4]) when the associated Service Worker [registration][5] for the
+document enters the [activation process][6]. When the [activation process][6]
+is triggered by `replace()` method call within the event handler of the
+`install` event, `navigator.serviceWorker.controller` immediately reflects the
+[active worker][7] as the Service Worker that [controls][8] the document.
+
+[1]: http://goo.gl/rBfiz0
+[2]: #service-worker-container-interface
+[3]: http://goo.gl/Mzv7Dv
+[4]: #activation-algorithm
+[5]: #registration
+[6]: #activation-process
+[7]: #active-worker
+[8]: #document-control
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section oncontrollerchange so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.11-navigator-service-worker-onreloadpage.html b/testing/web-platform/tests/service-workers/stub-3.2.11-navigator-service-worker-onreloadpage.html
new file mode 100644
index 000000000..e2207db10
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.11-navigator-service-worker-onreloadpage.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: onreloadpage</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-onreloadpage">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.onreloadpage` is the [event handler][1] that must be
+supported as attribute by the `[ServiceWorkerContainer][2]` object. An event
+named `reloadpage` using the `[ReloadPageEvent][3]` interface is dispatched on
+`[ServiceWorkerContainer][2]` object when the page reload is triggered by the
+`[self.clients.reloadAll()][4]` method call from the [active worker][5],
+represented by its associated [ServiceWorkerGlobalScope][6] object, for the
+document.
+
+[1]: http://goo.gl/rBfiz0
+[2]: #service-worker-container-interface
+[3]: #reload-page-event-interface
+[4]: #reloadall-method
+[5]: #active-worker
+[6]: #service-worker-global-scope-interface
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section onreloadpage so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.12-navigator-service-worker-onerror.html b/testing/web-platform/tests/service-workers/stub-3.2.12-navigator-service-worker-onerror.html
new file mode 100644
index 000000000..313f0bdfc
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.12-navigator-service-worker-onerror.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: onerror</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-onerror">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.onerror` is the [event handler][1] that must be
+supported as attribute by the `[ServiceWorkerContainer][2]` object. An event
+named `error` using the `[ErrorEvent][3]` interface is dispatched on
+`[ServiceWorkerContainer][2]` object for any error from the associated
+`[ServiceWorker][4]` objects.
+
+[1]: http://goo.gl/rBfiz0
+[2]: #service-worker-container-interface
+[3]: http://goo.gl/FKuWgu
+[4]: #service-worker-interface
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section onerror so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.2-navigator-service-worker-waiting.html b/testing/web-platform/tests/service-workers/stub-3.2.2-navigator-service-worker-waiting.html
new file mode 100644
index 000000000..663ce82f9
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.2-navigator-service-worker-waiting.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: waiting</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-waiting">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.waiting` must return a [ServiceWorker][1] object
+representing the waiting Service Worker that is considered the [worker in
+waiting][2] for the document. `navigator.serviceWorker.waiting` returns `null`
+if there is no [worker in waiting][2] for the document.
+
+[1]: #service-worker-interface
+[2]: #worker-in-waiting
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section waiting so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.3-navigator-service-worker-active.html b/testing/web-platform/tests/service-workers/stub-3.2.3-navigator-service-worker-active.html
new file mode 100644
index 000000000..f7406f590
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.3-navigator-service-worker-active.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: active</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-active">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.active` must return a [ServiceWorker][1] object
+representing the [active worker][2] that is currently undergoing or completed
+the activation process (from step 4 to step 9 of the [_Activation
+algorithm][3]) for the given [URL scope][4] in which the document is controlled
+(or to be controlled). `navigator.serviceWorker.active` returns `null` if no
+Service Worker [registration][5] is in the [activation process][6].
+
+[1]: #service-worker-interface
+[2]: #active-worker
+[3]: #activation-algorithm
+[4]: #url-scope
+[5]: #service-worker-registration-internal-interface
+[6]: #activation-process
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section active so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.4-navigator-service-worker-controller.html b/testing/web-platform/tests/service-workers/stub-3.2.4-navigator-service-worker-controller.html
new file mode 100644
index 000000000..1a26cce6d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.4-navigator-service-worker-controller.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: controller</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-controller">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.controller` must return a [ServiceWorker][1] object
+representing the [active worker][2] that currently handles resource requests
+for the document. `navigator.serviceWorker.controller` returns `null` if the
+current document was not [created under a Service Worker][3] (See step 6-1 of
+[_OnFetchRequest][3] algorithm) or the request is a force refresh
+(shift+refresh).
+
+[1]: #service-worker-interface
+[2]: #active-worker
+[3]: #on-fetch-request-algorithm
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section controller so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.5-navigator-service-worker-ready.html b/testing/web-platform/tests/service-workers/stub-3.2.5-navigator-service-worker-ready.html
new file mode 100644
index 000000000..67a690ddc
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.5-navigator-service-worker-ready.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: ready</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-ready">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.ready` attribute must return the result of running
+these steps:
+
+1. Let `promise` be a newly-created [promise][1].
+2. Return `promise`.
+3. Run the following steps asynchronously:
+ 1. Let `registration` be the result of running [_ScopeMatch
+ algorithm][2] with document's url as its argument.
+ 2. If `registration` is null, then:
+ 1. Wait for the document to have a matching [registration][3].
+ 3. If the [registration][3], represented by `registration`, for the
+ document has an [active worker][4], then:
+ 1. Resolve `promise` with the [ServiceWorker][5] object associated
+ with the [active worker][4].
+ 2. Abort these steps.
+ 4. If the [registration][3], represented by `registration`, for the
+ document has a [worker in waiting][6], then:
+ 1. Resolve `promise` with the [ServiceWorker][5] object associated
+ with the [worker in waiting][6].
+ 2. Abort these steps.
+ 5. Wait until the [registration][3], represented by `registration`,
+ for the document acquires a [worker in waiting][6] through a new
+ [installation process][7].
+ 6. Resolve `promise` with the [ServiceWorker][5] object associated
+ with the [worker in waiting][6].
+Note that `ready` attribute is desinged in a way that the returned [promise][1]
+will never reject. Instead, it waits until the [promise][1] resolves with a
+newly installed [worker in waiting][6]. Hence, the `state` of the acquired
+[`ServiceWorker`][8] object is either `installed`, `activating` or `activated`.
+
+
+
+[1]: http://goo.gl/3TobQS
+[2]: #scope-match-algorithm
+[3]: #registration
+[4]: #active-worker
+[5]: #service-worker-interface
+[6]: #worker-in-waiting
+[7]: #installation-process
+[8]: #service-worker
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section ready so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.6-navigator-service-worker-getAll.html b/testing/web-platform/tests/service-workers/stub-3.2.6-navigator-service-worker-getAll.html
new file mode 100644
index 000000000..3c2afe99c
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.6-navigator-service-worker-getAll.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: getAll()</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-getAll">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.getAll()` method must return a promise that resolves
+with the array of the ServiceWorker objects in `installing`, `installed`,
+`activating` and `activated` states.
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section getAll() so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.7-navigator-service-worker-register.html b/testing/web-platform/tests/service-workers/stub-3.2.7-navigator-service-worker-register.html
new file mode 100644
index 000000000..df469de42
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.7-navigator-service-worker-register.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: register()</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-register">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.register(url, options)` method must run the
+[Registration algorithm][1] passing `url` and `options`.`scope` as the
+arguments.
+
+[1]: #registration-algorithm
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section register() so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.8-navigator-service-worker-unregister.html b/testing/web-platform/tests/service-workers/stub-3.2.8-navigator-service-worker-unregister.html
new file mode 100644
index 000000000..6f1b43b6b
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.8-navigator-service-worker-unregister.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: unregister()</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-unregister">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.unregister(scope)` method must run the [Unregistration
+algorithm][1] passing `scope` as the argument.
+
+[1]: #unregistration-algorithm
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section unregister() so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-3.2.9-navigator-service-worker-onupdatefound.html b/testing/web-platform/tests/service-workers/stub-3.2.9-navigator-service-worker-onupdatefound.html
new file mode 100644
index 000000000..7babe7c24
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-3.2.9-navigator-service-worker-onupdatefound.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: onupdatefound</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-onupdatefound">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`navigator.serviceWorker.onupdatefound` is the [event handler][1] that must be
+supported as attribute by the `[ServiceWorkerContainer][2]` object. An
+`updatefound` event using the `[Event][3]` interface is dispatched on
+`[ServiceWorkerContainer][2]` object (See step 4 of the [_Installation
+algorithm][4]) when the associated Service Worker [registration][5] for the
+document enters the [installation process][6] such that
+`navigator.serviceWorker.installing` becomes the new [installing worker][7].
+
+[1]: http://goo.gl/rBfiz0
+[2]: #service-worker-container-interface
+[3]: http://goo.gl/Mzv7Dv
+[4]: #installation-algorithm
+[5]: #registration
+[6]: #installation-process
+[7]: #installing-worker
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section onupdatefound so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.1-service-worker-global-scope.html b/testing/web-platform/tests/service-workers/stub-4.1-service-worker-global-scope.html
new file mode 100644
index 000000000..48697f01e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.1-service-worker-global-scope.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: ServiceWorkerGlobalScope</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<script type=text/plain id="idl_0">
+[Global]
+interface ServiceWorkerGlobalScope : WorkerGlobalScope {
+ readonly attribute CacheStorage caches;
+ // A container for a list of window objects, identifiable by ID, that
+ // correspond to windows (or workers) that are "controlled" by this SW
+ readonly attribute ServiceWorkerClients clients;
+ [Unforgeable] readonly attribute DOMString scope;
+
+ Promise<any> fetch((Request or ScalarValueString) request);
+
+ void update();
+ void unregister();
+
+ attribute EventHandler oninstall;
+ attribute EventHandler onactivate;
+ attribute EventHandler onfetch;
+ attribute EventHandler onbeforeevicted;
+ attribute EventHandler onevicted;
+
+ // The event.source of these MessageEvents are instances of Client
+ attribute EventHandler onmessage;
+
+ // close() method inherited from WorkerGlobalScope is not exposed.
+};
+</pre>
+
+<!--
+The `ServiceWorkerGlobalScope` interface represents the global execution
+context of a Service Worker. `ServiceWorkerGlobalScope` object provides
+generic, event-driven, time-limited script execution contexts that run at an
+origin. Once successfully [registered][1], a Service Worker is started, kept
+alive and killed by their relationship to events, not documents. Any type of
+synchronous requests MUST NOT be initiated inside of a Service Worker.
+
+[1]: #navigator-service-worker-register
+-->
+
+
+ <script type=text/plain id="untested_idls">
+ interface CacheStorage {};
+ interface ServiceWorkerClients {};
+ interface Request {};
+ interface ScalarValueString {};
+ interface EventHandler {};
+ interface WorkerGlobalScope {};
+ </pre>
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ ServiceWorkerGlobalScope: ["throw new Error ('No object defined for the ServiceWorkerGlobalScope interface')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.1.1-service-worker-global-scope-caches.html b/testing/web-platform/tests/service-workers/stub-4.1.1-service-worker-global-scope-caches.html
new file mode 100644
index 000000000..f1fce5036
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.1.1-service-worker-global-scope-caches.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: caches</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-caches">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`self.caches` must return the `[CacheStorage][1]` object that is the global
+asynchronous map object for the `[ServiceWorkerGlobalScope][2]` execution
+context containing the cache objects keyed by the name of the caches. Caches
+are always enumerable via `self.caches` in insertion order (per [ECMAScript 6
+Map objects][3].)
+
+[1]: #cache-storage-interface
+[2]: #service-worker-global-scope-interface
+[3]: http://goo.gl/gNnDPO
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section caches so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.1.2-service-worker-global-scope-clients.html b/testing/web-platform/tests/service-workers/stub-4.1.2-service-worker-global-scope-clients.html
new file mode 100644
index 000000000..cd5d28353
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.1.2-service-worker-global-scope-clients.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: clients</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-clients">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`self.clients` must return the `[ServiceWorkerClients][1]` object containing a
+list of client objects, identifiable by ID, that correspond to windows or
+workers that are [controlled][2] by this Service Worker.
+
+[1]: #service-worker-clients-interface
+[2]: #document-control
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section clients so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.1.3-service-worker-global-scope-scope.html b/testing/web-platform/tests/service-workers/stub-4.1.3-service-worker-global-scope-scope.html
new file mode 100644
index 000000000..7b6ce78fa
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.1.3-service-worker-global-scope-scope.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: scope</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-scope">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+The `scope` attribute of a [ServiceWorkerGlobalScope][1] object reflects the
+[URL scope][2] of the associated Service Worker [registration][3]. The `scope`
+attribute must return the [serialization][4] of the URL representing the [URL
+scope][2] of the associated Service Worker [registration][3].
+
+[1]: #service-worker-global-scope-interface
+[2]: #url-scope
+[3]: #registration
+[4]: http://url.spec.whatwg.org/#concept-url-serializer
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section scope so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.1.4-service-worker-global-scope-fetch.html b/testing/web-platform/tests/service-workers/stub-4.1.4-service-worker-global-scope-fetch.html
new file mode 100644
index 000000000..98345471f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.1.4-service-worker-global-scope-fetch.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: fetch(request)</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-fetch">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`self.fetch(request)` method must run these steps:
+
+1. Let `request` be a [request][1] represented by `request`.
+2. Set [`client`][2] of `request` to the [JavaScript global
+ environment][3] represented by `self` object.
+3. Let `promise` be a newly-created [promise][4].
+4. Return `promise.`
+5. Run the following steps asynchronously:
+ 1. Let `response` be the result of running [fetch algorithm][5] with
+ `request` as its argument.
+ 2. If `response` is a [network error][6], then:
+ 1. Reject `promise` with a new [DOMException][7] whose name is
+ "[NetworkError][8]".
+ 3. Else,
+ 1. Resolve `promise` with a new [Response][9] object associated
+ with `response`.
+
+
+
+[1]: http://goo.gl/ucOuXl
+[2]: http://goo.gl/Oxj4xQ
+[3]: http://goo.gl/ifwwCC
+[4]: http://goo.gl/3TobQS
+[5]: http://goo.gl/fGMifs
+[6]: http://goo.gl/jprjjc
+[7]: http://goo.gl/A0U8qC
+[8]: http://goo.gl/lud5HB
+[9]: http://goo.gl/Deazjv
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section fetch(request) so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.1.5-service-worker-global-scope-update.html b/testing/web-platform/tests/service-workers/stub-4.1.5-service-worker-global-scope-update.html
new file mode 100644
index 000000000..26e255dd4
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.1.5-service-worker-global-scope-update.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: update()</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-update">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`update()` pings the server for an updated version of this script without
+consulting caches. `self.update()` method must run the [_SoftUpdate
+algorithm][1] passing its serviceWorkerRegistration object as the argument
+which is the result of running the [_GetRegistration algorithm][2] with
+`self.scope` as the argument. (This is conceptually the same operation that UA
+does maximum once per every 24 hours.)
+
+[1]: #soft-update-algorithm
+[2]: #get-registration-algorithm
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section update() so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.1.6-service-worker-global-scope-unregister.html b/testing/web-platform/tests/service-workers/stub-4.1.6-service-worker-global-scope-unregister.html
new file mode 100644
index 000000000..de1d64a6e
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.1.6-service-worker-global-scope-unregister.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: unregister()</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-unregister">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`self.unregister()` method must run the [Unregistration algorithm][1]
+implicitly passing `self.scope` as the argument.
+
+[1]: #unregistration-algorithm
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section unregister() so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.1.7-service-worker-global-scope-onmessage.html b/testing/web-platform/tests/service-workers/stub-4.1.7-service-worker-global-scope-onmessage.html
new file mode 100644
index 000000000..b93043948
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.1.7-service-worker-global-scope-onmessage.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: onmessage</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-onmessage">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`self.onmessage` is the [event handler][1] that must be supported as attribute
+by the `ServiceWorkerGlobalScope` object. `ServiceWorkerGlobalScope` objects
+act as if they had an implicit `[MessagePort][2]` associated with them. This
+port is part of a channel that is set up when the worker is created, but it is
+not exposed. This object must never be garbage collected before the
+`ServiceWorkerGlobalScope` object.
+
+All messages received by that port must immediately be retargeted at the
+`ServiceWorkerGlobalScope` object. That is, an event named `message` using the
+`[MessageEvent][3]` interface is dispatched on ServiceWorkerGlobalScope object.
+The `event.source` of these `[MessageEvent][3]`s are instances of `[Client][4]`.
+
+
+
+[1]: http://goo.gl/rBfiz0
+[2]: http://goo.gl/tHBrI6
+[3]: http://goo.gl/S5e0b6
+[4]: #client-interface
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section onmessage so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.2-client.html b/testing/web-platform/tests/service-workers/stub-4.2-client.html
new file mode 100644
index 000000000..4e8c3639d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.2-client.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: Client</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#client">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<script type=text/plain id="idl_0">
+[Constructor()] // no-op constructor
+interface Client {
+ readonly attribute unsigned long id;
+ void postMessage(any message, DOMString targetOrigin,
+ optional sequence<Transferable> transfer);
+};
+</pre>
+
+<!--
+The `Client` interface represents the window or the worker (defined as client)
+that is [controlled][1] by the Service Worker. This object provides a no-op
+constructor. Callers should note that only `Client` objects created by the user
+agent (see [`this.clients.getServiced()`][2]) will provide meaningful
+functionality.
+
+The `id` of a `Client` identifies the specific client object from the list of
+client objects serviced by the Service Worker. The `postMessage(message,
+targetOrigin, transfer)` method of a `[Client][3]`, when called, causes a
+`[MessageEvent][4]` to be dispatched at the client object.
+
+
+
+[1]: #document-control
+[2]: #get-serviced-method
+[3]: #client-interface
+[4]: http://goo.gl/4SLWiH
+-->
+
+
+ <script type=text/plain id="untested_idls">
+ interface Transferable {};
+ </pre>
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ Client: ["throw new Error ('No object defined for the Client interface')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.3-service-worker-clients.html b/testing/web-platform/tests/service-workers/stub-4.3-service-worker-clients.html
new file mode 100644
index 000000000..c8a967a73
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.3-service-worker-clients.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: ServiceWorkerClients</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-clients">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<script type=text/plain id="idl_0">
+interface ServiceWorkerClients {
+ // A list of client objects, identifiable by ID, that correspond to windows
+ // (or workers) that are "controlled" by this SW
+ Promise<sequence<Client>?> getServiced();
+ Promise<any> reloadAll();
+};
+</pre>
+
+<!--
+The `ServiceWorkerClients` interface represents a container for a list of
+`[Client][1]` objects.
+
+[1]: #client-interface
+-->
+
+
+ <script type=text/plain id="untested_idls">
+ interface Client {};
+ </pre>
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ ServiceWorkerClients: ["throw new Error ('No object defined for the ServiceWorkerClients interface')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.3.1-get-serviced-method.html b/testing/web-platform/tests/service-workers/stub-4.3.1-get-serviced-method.html
new file mode 100644
index 000000000..b3cd06fdf
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.3.1-get-serviced-method.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: getServiced()</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#get-serviced-method">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+The `getServiced()` method of a `ServiceWorkerClients`, when called, returns a
+[Promise][1] that will resolve with a list of `[Client][2]` objects that are
+[controlled][3] by this Service Worker.
+
+[1]: http://goo.gl/3TobQS
+[2]: #client-interface
+[3]: #document-control
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section getServiced() so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.3.2-reloadall-method.html b/testing/web-platform/tests/service-workers/stub-4.3.2-reloadall-method.html
new file mode 100644
index 000000000..c5a9dd454
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.3.2-reloadall-method.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: reloadAll()</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#reloadall-method">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`reloadAll()` provides a mechanism for the worker to request synchronized
+re-fetch of all documents whose URLs match the registration's [URL scope][1].
+An event named `reloadpage` is dispatched on the `navigator.serviceWorker`
+object of each document. The in-document handlers may allow the event to
+continue, request an extension (via [`e.waitUntil()`][2]), or cancel the
+collective reload by calling [`e.preventDefault()`][3].
+
+[1]: #url-scope
+[2]: #wait-until-method
+[3]: http://goo.gl/2zH6ie
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section reloadAll() so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.4-request-objects.html b/testing/web-platform/tests/service-workers/stub-4.4-request-objects.html
new file mode 100644
index 000000000..bad4ea159
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.4-request-objects.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: Request Objects</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#request-objects">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<script type=text/plain id="idl_0">
+[Constructor(optional RequestInit init)]
+interface Request {
+ attribute unsigned long timeout;
+ attribute DOMString url;
+ attribute ByteString method;
+ readonly attribute DOMString origin;
+ readonly attribute Mode mode;
+ attribute boolean synchronous;
+ attribute boolean forcePreflight;
+ attribute boolean omitCredentials;
+ readonly attribute DOMString referrer;
+ readonly attribute HeaderMap headers; // alternative: sequence<Header> headers;
+ attribute any body;
+};
+
+dictionary RequestInit {
+ unsigned long timeout = 0;
+ DOMString url;
+ boolean synchronous = false;
+ boolean forcePreflight = false;
+ boolean omitCredentials = false;
+ ByteString method = "GET";
+ HeaderMap headers;
+ any body;
+};
+
+enum Mode {
+ "same origin",
+ "tainted cross-origin",
+ "CORS",
+ "CORS-with-forced-preflight"
+};
+
+[MapClass(DOMString, DOMString)]
+interface HeaderMap {
+};
+</pre>
+
+
+
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ Request: ["throw new Error ('No object defined for the Request interface')"],
+ RequestInit: ["throw new Error ('No object defined for the RequestInit dictionary')"],
+ Mode: ["throw new Error ('No object defined for the Mode enum')"],
+ HeaderMap: ["throw new Error ('No object defined for the HeaderMap interface')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.5-response-objects.html b/testing/web-platform/tests/service-workers/stub-4.5-response-objects.html
new file mode 100644
index 000000000..5ebf34314
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.5-response-objects.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: Response Objects</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#response-objects">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<!--
+`Response` objects model HTTP responses.
+-->
+<script type=text/plain id="idl_0">
+[Constructor]
+interface AbstractResponse {
+};
+
+interface OpaqueResponse : AbstractResponse {
+ readonly attribute unsigned short status;
+ readonly attribute ByteString statusText;
+ // Returns a filtered list of headers. See prose for details.
+ readonly attribute HeaderMap headers;
+ // No setter for headers
+ readonly attribute DOMString url;
+};
+
+interface CORSResponse : Response {
+ readonly attribute HeaderMap headers;
+};
+
+[Constructor(optional ResponseInit responseInitDict)]
+interface Response : AbstractResponse {
+ attribute unsigned short status;
+ attribute ByteString statusText;
+ readonly attribute HeaderMap headers;
+ attribute DOMString url;
+ Promise<Blob> toBlob();
+};
+
+dictionary ResponseInit {
+ unsigned short status = 200;
+ ByteString statusText = "OK";
+ HeaderMap headers;
+};
+</pre>
+
+
+
+ <script type=text/plain id="untested_idls">
+ interface HeaderMap {};
+ interface Blob {};
+ </pre>
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ AbstractResponse: ["throw new Error ('No object defined for the AbstractResponse interface')"],
+ OpaqueResponse: ["throw new Error ('No object defined for the OpaqueResponse interface')"],
+ CORSResponse: ["throw new Error ('No object defined for the CORSResponse interface')"],
+ Response: ["throw new Error ('No object defined for the Response interface')"],
+ ResponseInit: ["throw new Error ('No object defined for the ResponseInit dictionary')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.5.2-response.html b/testing/web-platform/tests/service-workers/stub-4.5.2-response.html
new file mode 100644
index 000000000..fbc72f30a
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.5.2-response.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: Response</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#response">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`Response` objects are mutable and constructable. They model HTTP responses.
+The `fetch()` API returns this type for same-origin responses.
+
+It may be possible to set the `Location` header of a `Response` object to
+someplace not in the current origin but this is not a security issue.
+Cross-origin response bodies are opaque to script, and since only same-origin
+documents will encounter these responses, the only systems the Service Worker
+can "lie to" are same-origin (and therefore safe from the perspective of other
+origins).
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section Response so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.5.4-opaque-response.html b/testing/web-platform/tests/service-workers/stub-4.5.4-opaque-response.html
new file mode 100644
index 000000000..a91306f4c
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.5.4-opaque-response.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: OpaqueResponse</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#opaque-response">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`OpaqueResponse` objects are immutable but constructable. The `fetch()` API
+returns this type for cross-origin responses.
+
+Their role is to encapsulate the security properties of the web platform. As
+such, their `body` attribute will always be `undefined` and the list of
+readable `headers` is heavily filtered.
+
+`OpaqueResponse` objects may be forwarded on to rendering documents in exactly
+the same way as mutable `Response` objects.
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section OpaqueResponse so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.6-cache-objects.html b/testing/web-platform/tests/service-workers/stub-4.6-cache-objects.html
new file mode 100644
index 000000000..befd67cb3
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.6-cache-objects.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: Caches</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-objects">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+To allow authors to fully manage their content caches for offline use, the
+`[ServiceWorkerGlobalScope][1]` execution context provides the caching methods
+largely conforming to [ECMAScript 6 Map objects][2] with additional convenience
+methods. A domain can have multiple, named `[Cache][3]` objects, whose contents
+are entirely under the control of scripts. Caches are not shared across
+domains, and they are completely isolated from the browser's HTTP cache.
+
+[1]: #service-worker-global-scope-interface
+[2]: http://goo.gl/gNnDPO
+[3]: #cache-interface
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section Caches so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.6.1-cache-lifetimes.html b/testing/web-platform/tests/service-workers/stub-4.6.1-cache-lifetimes.html
new file mode 100644
index 000000000..f6c9ecbd1
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.6.1-cache-lifetimes.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: Understanding Cache Lifetimes</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-lifetimes">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+The `[Cache][1]` instances are not part of the browser's HTTP cache. The
+`[Cache][1]` objects are exactly what authors have to manage themselves. The
+`[Cache][1]` objects do not get updated unless authors explicitly request them
+to be. The `[Cache][1]` objects do not expire unless authors delete the
+entries. The `[Cache][1]` objects do not disappear just because the Service
+Worker script is updated. That is, caches are not updated automatically.
+Updates must be manually managed. This implies that authors should version
+their caches by name and make sure to use the caches only from the version of
+the ServiceWorker that can safely operate on.
+
+[1]: #cache-interface
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section Understanding Cache Lifetimes so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.6.2-cache.html b/testing/web-platform/tests/service-workers/stub-4.6.2-cache.html
new file mode 100644
index 000000000..8999a4dd5
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.6.2-cache.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: Cache</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#cache">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<script type=text/plain id="idl_0">
+[Exposed=(Window,Worker)]
+interface Cache {
+ Promise<Response> match(RequestInfo request, optional CacheQueryOptions options);
+ Promise<sequence<Response>> matchAll(optional RequestInfo request, optional CacheQueryOptions options);
+ Promise<void> add(RequestInfo request);
+ Promise<void> addAll(sequence<RequestInfo> requests);
+ Promise<void> put(RequestInfo request, Response response);
+ Promise<boolean> delete(RequestInfo request, optional CacheQueryOptions options);
+ Promise<sequence<Request>> keys(optional RequestInfo request, optional CacheQueryOptions options);
+};
+
+dictionary CacheQueryOptions {
+ boolean ignoreSearch = false;
+ boolean ignoreMethod = false;
+ boolean ignoreVary = false;
+ DOMString cacheName;
+};
+
+dictionary CacheBatchOperation {
+ DOMString type;
+ Request request;
+ Response response;
+ CacheQueryOptions options;
+};
+</pre>
+
+
+
+ <script type=text/plain id="untested_idls">
+ interface AbstractResponse {};
+ interface Request {};
+ interface ScalarValueString {};
+ </pre>
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ Cache: ["throw new Error ('No object defined for the Cache interface')"],
+ QueryParams: ["throw new Error ('No object defined for the QueryParams dictionary')"],
+ CacheIterationCallback: ["throw new Error ('No object defined for the CacheIterationCallback callback')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.6.3-cache-storage.html b/testing/web-platform/tests/service-workers/stub-4.6.3-cache-storage.html
new file mode 100644
index 000000000..3f9e9b438
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.6.3-cache-storage.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: CacheStorage</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<script type=text/plain id="idl_0">
+[Constructor(sequence<any> iterable)]
+interface CacheStorage {
+ Promise<any> match(ScalarValueString url, optional DOMString cacheName);
+ Promise<Cache> get(DOMString key);
+ Promise<boolean> has(DOMString key);
+ Promise<any> set(DOMString key, Cache val);
+ Promise<any> clear();
+ Promise<any> delete(DOMString key);
+ void forEach(CacheStorageIterationCallback callback, optional object thisArg);
+ Promise<sequence<any>> entries();
+ Promise<sequence<DOMString>> keys();
+ Promise<sequence<Cache>> values();
+ Promise<unsigned long> size();
+};
+
+callback CacheStorageIterationCallback = void (Cache value, DOMString key, CacheStorage map);
+</pre>
+
+<!--
+**Note**:[CacheStorage][1]interface is designed to largely conform
+to[ECMAScript 6 Map objects][2]but entirely async, and with additional
+convenience methods.
+
+[1]: #cache-storage-interface
+[2]: http://goo.gl/gNnDPO
+-->
+
+
+ <script type=text/plain id="untested_idls">
+ interface ScalarValueString {};
+ interface Cache {};
+ </pre>
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ CacheStorage: ["throw new Error ('No object defined for the CacheStorage interface')"],
+ CacheStorageIterationCallback: ["throw new Error ('No object defined for the CacheStorageIterationCallback callback')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.7.1-install-phase-event.html b/testing/web-platform/tests/service-workers/stub-4.7.1-install-phase-event.html
new file mode 100644
index 000000000..994da0200
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.7.1-install-phase-event.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: InstallPhaseEvent</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#install-phase-event">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<script type=text/plain id="idl_0">
+interface InstallPhaseEvent : Event {
+ Promise<any> waitUntil(Promise<any> f);
+};
+</pre>
+
+<!--
+Service Workers have two [Lifecycle events][1], `[install][2]` and
+`[activate][3]`. Service Workers use the `[InstallPhaseEvent][4]` interface for
+`[activate][3]` event and the `[InstallEvent][5]` interface, which inherits
+from the `[InstallPhaseEvent][4]` interface, for `[install][2]` event.
+
+[1]: #lifecycle-events
+[2]: #install-event
+[3]: #activate-event
+[4]: #install-phase-event-interface
+[5]: #install-event-interface
+-->
+
+
+ <script type=text/plain id="untested_idls">
+ interface Event {};
+ </pre>
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ InstallPhaseEvent: ["throw new Error ('No object defined for the InstallPhaseEvent interface')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.7.1.1-wait-until-method.html b/testing/web-platform/tests/service-workers/stub-4.7.1.1-wait-until-method.html
new file mode 100644
index 000000000..318318b13
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.7.1.1-wait-until-method.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: event.waitUntil(f)</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#wait-until-method">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`event.waitUntil(f)` method, when called in `oninstall` or `onactivate`,
+extends the lifetime of the event. When called in `oninstall`, it delays
+treating the installing worker until the passed [Promise][1] resolves
+successfully. This is primarily used to ensure that a `ServiceWorker` is not
+active until all of the core caches it depends on are populated. When called in
+`onactivate`, it delays treating the activating worker until the passed
+[Promise][1] resolves successfully. This is primarily used to ensure that any
+[Functional events][2] are not dispatched to the `ServiceWorker` until it
+upgrades database schemas and deletes the outdated cache entries.
+
+[1]: http://goo.gl/3TobQS
+[2]: #functional-events
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section event.waitUntil(f) so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.7.2-install-event.html b/testing/web-platform/tests/service-workers/stub-4.7.2-install-event.html
new file mode 100644
index 000000000..77702ed02
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.7.2-install-event.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: install Event</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#install-event">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+An event named `[install][1]` using the `[InstallEvent][2]` interface is
+dispatched on `ServiceWorkerGlobalScope` object when the state of the
+associated `ServiceWorker` changes its value to `installing`. (See step 3 of
+[_Installation algorithm][3])
+
+[1]: #install-event
+[2]: #install-event-interface
+[3]: #installation-algorithm
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section install Event so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.7.2.1-install-event-section.html b/testing/web-platform/tests/service-workers/stub-4.7.2.1-install-event-section.html
new file mode 100644
index 000000000..17d52c6b0
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.7.2.1-install-event-section.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: InstallEvent</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#install-event-section">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<script type=text/plain id="idl_0">
+interface InstallEvent : InstallPhaseEvent {
+ readonly attribute ServiceWorker? activeWorker;
+ void replace();
+};
+</pre>
+
+<!--
+Service Workers use the `[InstallEvent][1]` interface for `[install][2]` event.
+
+[1]: #install-event-interface
+[2]: #install-event
+-->
+
+
+ <script type=text/plain id="untested_idls">
+ interface ServiceWorker {};
+ interface InstallPhaseEvent {};
+ </pre>
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ InstallEvent: ["throw new Error ('No object defined for the InstallEvent interface')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.7.2.2-replace-method.html b/testing/web-platform/tests/service-workers/stub-4.7.2.2-replace-method.html
new file mode 100644
index 000000000..6981d3079
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.7.2.2-replace-method.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: event.replace()</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#replace-method">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`replace()` interacts with `waitUntil` method in the following way:
+
+- Successful installation can be delayed by `waitUntil`, perhaps by
+ subsequent event handlers.
+- Replacement only happens upon successful installation
+- Therefore, replacement of the [active worker][1] (if any) is not
+ immediate, however it may occur as soon as the end of the current turn.
+
+
+
+[1]: #navigator-service-worker-active
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section event.replace() so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.7.3-activate-event.html b/testing/web-platform/tests/service-workers/stub-4.7.3-activate-event.html
new file mode 100644
index 000000000..2a0162e5f
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.7.3-activate-event.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: activate Event</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#activate-event">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+An event named `[activate][1]` using the `[InstallPhaseEvent][2]` interface is
+dispatched on `ServiceWorkerGlobalScope` object when the state of the
+associated `ServiceWorker` changes its value to `activating`. (See step 6 of
+[_Activation algorithm][3])
+
+Service Workers use the `[InstallPhaseEvent][4]` interface for `[activate][1]`
+event.
+
+
+
+[1]: #activate-event
+[2]: #install-phase-event
+[3]: #activation-algorithm
+[4]: #install-phase-event-interface
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section activate Event so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.7.4.1-fetch-event-section.html b/testing/web-platform/tests/service-workers/stub-4.7.4.1-fetch-event-section.html
new file mode 100644
index 000000000..84d8c3880
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.7.4.1-fetch-event-section.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: FetchEvent</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#fetch-event-section">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src=/resources/WebIDLParser.js></script>
+ <script src=/resources/idlharness.js></script>
+
+ </head>
+ <body>
+
+<script type=text/plain id="idl_0">
+[Constructor]
+interface FetchEvent : Event {
+ readonly attribute Request request;
+ readonly attribute Client client; // The window issuing the request.
+ readonly attribute Context context;
+ readonly attribute boolean isReload;
+
+ void respondWith(Promise<AbstractResponse> r);
+ Promise<any> forwardTo(ScalarValueString url);
+ Promise<any> default();
+};
+
+enum Context {
+ "connect",
+ "font",
+ "img",
+ "object",
+ "script",
+ "style",
+ "worker",
+ "popup",
+ "child",
+ "navigate"
+};
+</pre>
+
+<!--
+Service Workers use the `[FetchEvent][1]` interface for `[fetch][2]` event.
+
+[1]: #fetch-event-interface
+[2]: #fetch-event
+-->
+
+
+ <script type=text/plain id="untested_idls">
+ interface Request {};
+ interface Client {};
+ interface AbstractResponse {};
+ interface ScalarValueString {};
+ interface Event {};
+ </pre>
+
+ <script>
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById("untested_idls").textContent);
+ idl_array.add_idls(document.getElementById("idl_0").textContent);
+ idl_array.add_objects({
+ FetchEvent: ["throw new Error ('No object defined for the FetchEvent interface')"],
+ Context: ["throw new Error ('No object defined for the Context enum')"]
+ });
+ idl_array.test();
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.7.4.2-respond-with-method.html b/testing/web-platform/tests/service-workers/stub-4.7.4.2-respond-with-method.html
new file mode 100644
index 000000000..416b8ef51
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.7.4.2-respond-with-method.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: event.respondWith(r)</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#respond-with-method">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`event.respondWith(r)` method must run the steps, from step 10 to step 15,
+defined in the [_OnFetchRequest algorithm][1].
+
+The `r` argument must resolve with a [AbstractResponse][2], else a
+[NetworkError][3] is thrown. If the request is a top-level navigation and the
+return value is a [OpaqueResponse][4] (an opaque response body), a
+[NetworkError][3] is thrown. The final URL of all successful (non
+network-error) responses is the [requested][5] URL. Renderer-side security
+checks about tainting for cross-origin content are tied to the transparency (or
+opacity) of the [Response][6] body, not URLs.
+
+
+
+[1]: #on-fetch-request-algorithm
+[2]: #abstract-response-interface
+[3]: http://w3c.github.io/dom/#networkerror
+[4]: #opaque-response-interface
+[5]: #request-objects
+[6]: #response-interface
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section event.respondWith(r) so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.7.4.3-default-method.html b/testing/web-platform/tests/service-workers/stub-4.7.4.3-default-method.html
new file mode 100644
index 000000000..deff7ac42
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.7.4.3-default-method.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: event.default()</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#default-method">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+`event.default()` method must run these steps:
+
+1. Let `promise` be a newly-created [promise][1].
+2. Return `promise.`
+3. Run the following steps asynchronously:
+ 1. Let `request` be `event`'s `request`.
+ 2. Set `request`'s [skip service worker flag][2].
+ 3. Let `response` be the result of running [fetch algorithm][3] with
+ `request` as its argument.
+ 4. If `response` is a [network error][4], then:
+ 1. Reject `promise` with a new [DOMException][5] whose name is
+ "[NetworkError][6]".
+ 5. Else,
+ 1. Resolve `promise` with a new [Response][7] object associated
+ with `response`.
+
+
+
+[1]: http://goo.gl/3TobQS
+[2]: http://goo.gl/gP7IWW
+[3]: http://goo.gl/fGMifs
+[4]: http://goo.gl/jprjjc
+[5]: http://goo.gl/A0U8qC
+[6]: http://goo.gl/lud5HB
+[7]: http://goo.gl/Deazjv
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section event.default() so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-4.7.4.4-is-reload-attribute.html b/testing/web-platform/tests/service-workers/stub-4.7.4.4-is-reload-attribute.html
new file mode 100644
index 000000000..fffe5d5b2
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-4.7.4.4-is-reload-attribute.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: event.isReload</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#is-reload-attribute">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+Returns true if `event` was dispatched with the user's intention for the page
+reload, and false otherwise. Pressing the refresh button should be considered a
+reload while clicking a link and pressing the back button should not. The
+behavior of the `Ctrl+l enter` is left to the implementations of the user
+agents.
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section event.isReload so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-5.1-origin-relativity.html b/testing/web-platform/tests/service-workers/stub-5.1-origin-relativity.html
new file mode 100644
index 000000000..469ce2975
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-5.1-origin-relativity.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: Origin Relativity</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#origin-relativity">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+One of the advanced concerns that major applications would encounter is whether
+they can be hosted from a CDN. By definition, these are servers in other
+places, often on other domains. Therefore, Service Workers cannot be hosted on
+CDNs. But they can include resources via [importScripts()][1]. The reason for
+this restriction is that Service Workers create the opportunity for a bad actor
+to turn a bad day into a bad eternity.
+
+[1]: http://goo.gl/Owcfs2
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section Origin Relativity so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/stub-5.2-cross-origin-resources.html b/testing/web-platform/tests/service-workers/stub-5.2-cross-origin-resources.html
new file mode 100644
index 000000000..42c685b1d
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/stub-5.2-cross-origin-resources.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<title>Service Workers: Cross-Origin Resources &amp; CORS</title>
+ <head>
+ <link rel="help" href="https://w3c.github.io/ServiceWorker/#cross-origin-resources">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ </head>
+ <body>
+
+<!--
+
+Applications tend to cache items that come from a CDN or other domain. It is
+possible to request many of them directly using <script>, <img>, <video> and
+<link> elements. It would be hugely limiting if this sort of runtime
+collaboration broke when offline. Similarly, it is possible to XHR many sorts
+of off-domain resources when appropriate CORS headers are set.
+
+ServiceWorkers enable this by allowing `Cache`s to fetch and cache off-origin
+items. Some restrictions apply, however. First, unlike same-origin resources
+which are managed in the `Cache` as `[Promise][1]`s for `Response` instances,
+the objects stored are `[Promise][1]`s for `OpaqueResponse` instances.
+`OpaqueResponse` provides a much less expressive API than `Response`; the
+bodies and headers cannot be read or set, nor many of the other aspects of
+their content inspected. They can be passed to `respondWith()` and
+`forwardTo()` in the same manner as `Response`s, but cannot be meaningfully
+created programmatically. These limitations are necessary to preserve the
+security invariants of the platform. Allowing `Cache`s to store them allows
+applications to avoid re-architecting in most cases.
+
+
+
+[1]: http://goo.gl/3TobQS
+
+-->
+
+
+
+ <script>
+ test(function() {
+ // not_implemented();
+ }, "There are no tests for section Cross-Origin Resources &amp; CORS so far.");
+ </script>
+
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/service-workers/tools/blink-import.py b/testing/web-platform/tests/service-workers/tools/blink-import.py
new file mode 100644
index 000000000..355df0796
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/tools/blink-import.py
@@ -0,0 +1,204 @@
+import os
+import re
+import shutil
+import glob
+import tempfile
+import sys
+from collections import defaultdict
+
+here = os.path.abspath(os.path.split(__file__)[0])
+
+def get_extra_files(chromium_root):
+ return [(os.path.join(chromium_root, "LayoutTests", "http", "tests", "resources", "testharness-helpers.js"),
+ os.path.join("resources", "testharness-helpers.js"))]
+
+resources_re = re.compile("/?(?:\.\./)*resources/(testharness(?:report)?)\.js")
+
+def resources_path(line, depth):
+ return False, resources_re.sub(r"/resources/\1.js", line)
+
+php_re = re.compile("\.php")
+
+def python_to_php(line, depth):
+ return False, php_re.sub(".py", line)
+
+abs_testharness_helpers_re = re.compile("([\"'])/resources/testharness-helpers.js")
+testharness_helpers_re = re.compile("\.\./((?:\.\./)*)resources/testharness-helpers.js")
+
+def testharness_helpers(line, depth):
+ if abs_testharness_helpers_re.findall(line):
+ return False, abs_testharness_helpers_re.sub(r"\1%sresources/testharness-helpers.js" % ("../" * (depth - 1)), line)
+ return False, testharness_helpers_re.sub(r"\1resources/testharness-helpers.js", line)
+
+serviceworker_path_re = re.compile("/serviceworker/")
+def service_worker_path(line, depth):
+ return False, serviceworker_path_re.sub("/service-workers/", line)
+
+localhost_re = re.compile("localhost")
+alt_host_re = re.compile("127\.0\.0\.1")
+port_http_re = re.compile("8000")
+port_https_re = re.compile("8000")
+
+
+def server_names(line, depth):
+ line, count_0 = localhost_re.subn("{{host}}", line)
+ line, count_1 = alt_host_re.subn("{{domains[www]}}", line)
+ line, count_2 = port_http_re.subn("{{ports[http][0]}}", line)
+ line, count_3 = port_https_re.subn("{{ports[https][0]}}", line)
+
+ count = count_0 + count_1 + count_2 + count_3
+
+ return bool(count), line
+
+
+def source_paths(chromium_root):
+ for dirpath, dirnames, filenames in os.walk(chromium_root):
+ if "chromium" in dirnames:
+ dirnames.remove("chromium")
+ for filename in filenames:
+ if filename.endswith("-expected.txt") or filename.endswith(".php"):
+ continue
+ yield os.path.relpath(os.path.join(dirpath, filename), chromium_root)
+
+
+def do_subs(path, line):
+ depth = len(os.path.split(os.path.sep))
+ subs = [resources_path, python_to_php, testharness_helpers, service_worker_path, server_names]
+ file_is_template = False
+ for sub in subs:
+ added_template, line = sub(line, depth)
+ if added_template:
+ file_is_template = True
+ return file_is_template, line
+
+def get_head(git):
+ return git("rev-parse", "HEAD")
+
+def get_changes(git, path, old, new):
+ data = git("diff", "--name-status", "-z", "--no-renames", "%s..%s" % (old, new), "--", path)
+ items = data.split("\0")
+ rv = defaultdict(list)
+ for status, path in items:
+ rv[status].append(path)
+
+ return rv
+
+def copy(src_path, out_dir, rel_path):
+ dest = os.path.normpath(os.path.join(out_dir, rel_path))
+ dest_dir = os.path.split(dest)[0]
+ if not os.path.exists(dest_dir):
+ os.makedirs(dest_dir)
+ shutil.copy2(src_path, dest)
+
+def copy_local_files(local_files, out_root, tmp_dir):
+ for path in local_files:
+ rel_path = os.path.relpath(path, out_root)
+ copy(path, tmp_dir, rel_path)
+
+def copy_extra_files(chromium_root, tmp_dir):
+ for in_path, rel_path in get_extra_files(chromium_root):
+ copy(in_path, tmp_dir, rel_path)
+
+def sub_changed_filenames(filename_changes, f):
+ rv = []
+ for line in f:
+ for in_name, out_name in filename_changes.iteritems():
+ line = line.replace(in_name, out_name)
+ rv.append(line)
+ return "".join(rv)
+
+testharness_re = re.compile("<script[^>]*src=[\"']?/resources/testharness.js[\"' ][^>]*>")
+
+def is_top_level_test(path, data):
+ if os.path.splitext(path)[1] != ".html":
+ return False
+ for line in data:
+ if testharness_re.findall(line):
+ return True
+ return False
+
+def add_suffix(path, suffix):
+ root, ext = os.path.splitext(path)
+ return root + ".%s" % suffix + ext
+
+def main():
+ if "--cache-tests" in sys.argv:
+ sw_path = os.path.join("LayoutTests", "http", "tests", "cachestorage")
+ out_root = os.path.abspath(os.path.join(here, "..", "cache-storage"))
+ elif "--sw-tests" in sys.argv:
+ sw_path = os.path.join("LayoutTests", "http", "tests", "serviceworkers")
+ out_root = os.path.abspath(os.path.join(here, "..", "service-worker"))
+ else:
+ raise ValueError("Must supply either --cache-tests or --sw-tests")
+
+ chromium_root = os.path.abspath(sys.argv[1])
+
+ work_path = tempfile.mkdtemp()
+
+ test_path = os.path.join(chromium_root, sw_path)
+
+ local_files = glob.glob(os.path.normpath(os.path.join(here, "..", "resources", "*.py")))
+
+ if not os.path.exists(out_root):
+ os.mkdir(out_root)
+
+ copy_local_files(local_files, out_root, work_path)
+ copy_extra_files(chromium_root, work_path)
+
+ path_changes = {}
+
+ for path in source_paths(test_path):
+ out_path = os.path.join(work_path, path)
+ out_dir = os.path.dirname(out_path)
+ if not os.path.exists(out_dir):
+ os.makedirs(out_dir)
+ with open(os.path.join(test_path, path), "r") as in_f:
+ data = []
+ sub = False
+ for line in in_f:
+ sub_flag, output_line = do_subs(path, line)
+ data.append(output_line)
+ if sub_flag:
+ sub = True
+ is_test = is_top_level_test(out_path, data)
+
+ initial_path = out_path
+
+ if is_test:
+ path_1 = add_suffix(out_path, "https")
+ else:
+ path_1 = out_path
+
+ if sub:
+ path_2 = add_suffix(out_path, "sub")
+ else:
+ path_2 = path_1
+
+ if path_2 != initial_path:
+ path_changes[initial_path] = path_2
+
+ with open(path_2, "w") as out_f:
+ out_f.write("".join(data))
+
+ filename_changes = {}
+
+ for k, v in path_changes.iteritems():
+ if os.path.basename(k) in filename_changes:
+ print "Got duplicate name:" + os.path.basename(k)
+ filename_changes[os.path.basename(k)] = os.path.basename(v)
+
+ for path in source_paths(work_path):
+ full_path = os.path.join(work_path, path)
+ with open(full_path, "r") as f:
+ data = sub_changed_filenames(filename_changes, f)
+ with open(full_path, "w") as f:
+ f.write(data)
+
+ for dirpath, dirnames, filenames in os.walk(work_path):
+ for filename in filenames:
+ in_path = os.path.join(dirpath, filename)
+ rel_path = os.path.relpath(in_path, work_path)
+ copy(in_path, out_root, rel_path)
+
+if __name__ == "__main__":
+ main()