summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/service-workers/service-worker
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /testing/web-platform/tests/service-workers/service-worker
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'testing/web-platform/tests/service-workers/service-worker')
-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
239 files changed, 13898 insertions, 0 deletions
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>