diff --git a/service-workers/service-worker/link-element-register-basic.https.html b/service-workers/service-worker/link-element-register-basic.https.html new file mode 100644 index 000000000000000..d235160a829435b --- /dev/null +++ b/service-workers/service-worker/link-element-register-basic.https.html @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/service-workers/service-worker/link-element-register-mime-types.https.html b/service-workers/service-worker/link-element-register-mime-types.https.html new file mode 100644 index 000000000000000..36462e54aa2a088 --- /dev/null +++ b/service-workers/service-worker/link-element-register-mime-types.https.html @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/service-workers/service-worker/link-element-register-scope.https.html b/service-workers/service-worker/link-element-register-scope.https.html new file mode 100644 index 000000000000000..9c93db87ad39512 --- /dev/null +++ b/service-workers/service-worker/link-element-register-scope.https.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/service-workers/service-worker/link-element-register-script-url.https.html b/service-workers/service-worker/link-element-register-script-url.https.html new file mode 100644 index 000000000000000..3044b90baf048c6 --- /dev/null +++ b/service-workers/service-worker/link-element-register-script-url.https.html @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/service-workers/service-worker/link-element-register-script.https.html b/service-workers/service-worker/link-element-register-script.https.html new file mode 100644 index 000000000000000..19cdafbc61bb6a1 --- /dev/null +++ b/service-workers/service-worker/link-element-register-script.https.html @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/service-workers/service-worker/link-element-register-security-error.https.html b/service-workers/service-worker/link-element-register-security-error.https.html new file mode 100644 index 000000000000000..bb2604e0916986a --- /dev/null +++ b/service-workers/service-worker/link-element-register-security-error.https.html @@ -0,0 +1,11 @@ +Page Title + + + + + + + + diff --git a/service-workers/service-worker/register-link-element.https.html b/service-workers/service-worker/register-link-element.https.html deleted file mode 100644 index a4a4a19eb630e32..000000000000000 --- a/service-workers/service-worker/register-link-element.https.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - diff --git a/service-workers/service-worker/registration-basic.https.html b/service-workers/service-worker/registration-basic.https.html new file mode 100644 index 000000000000000..1411a5d48cd69b0 --- /dev/null +++ b/service-workers/service-worker/registration-basic.https.html @@ -0,0 +1,9 @@ + +Service Worker: Registration (basic) + + + + + diff --git a/service-workers/service-worker/registration-mime-types.https.html b/service-workers/service-worker/registration-mime-types.https.html new file mode 100644 index 000000000000000..f5675bdcbf6a7b6 --- /dev/null +++ b/service-workers/service-worker/registration-mime-types.https.html @@ -0,0 +1,9 @@ + +Service Worker: Registration (basic) + + + + + diff --git a/service-workers/service-worker/registration-scope.https.html b/service-workers/service-worker/registration-scope.https.html new file mode 100644 index 000000000000000..f4a77d72b912009 --- /dev/null +++ b/service-workers/service-worker/registration-scope.https.html @@ -0,0 +1,9 @@ + +Service Worker: Registration (scope) + + + + + diff --git a/service-workers/service-worker/registration-script-url.https.html b/service-workers/service-worker/registration-script-url.https.html new file mode 100644 index 000000000000000..ea9ae7fe917ec33 --- /dev/null +++ b/service-workers/service-worker/registration-script-url.https.html @@ -0,0 +1,9 @@ + +Service Worker: Registration (scriptURL) + + + + + diff --git a/service-workers/service-worker/registration-script.https.html b/service-workers/service-worker/registration-script.https.html new file mode 100644 index 000000000000000..d1b3f0e700f3c4c --- /dev/null +++ b/service-workers/service-worker/registration-script.https.html @@ -0,0 +1,9 @@ + +Service Worker: Registration (script) + + + + + diff --git a/service-workers/service-worker/registration-security-error.https.html b/service-workers/service-worker/registration-security-error.https.html new file mode 100644 index 000000000000000..8f0609ba5dfd265 --- /dev/null +++ b/service-workers/service-worker/registration-security-error.https.html @@ -0,0 +1,9 @@ + +Service Worker: Registration (SecurityError) + + + + + diff --git a/service-workers/service-worker/registration.https.html b/service-workers/service-worker/registration.https.html deleted file mode 100644 index 587cac2fe4ae983..000000000000000 --- a/service-workers/service-worker/registration.https.html +++ /dev/null @@ -1,9 +0,0 @@ - -Service Worker: Registration - - - - - diff --git a/service-workers/service-worker/resources/registration-tests-basic.js b/service-workers/service-worker/resources/registration-tests-basic.js new file mode 100644 index 000000000000000..eedb980a0fc4d1d --- /dev/null +++ b/service-workers/service-worker/resources/registration-tests-basic.js @@ -0,0 +1,44 @@ +// Basic registration tests that succeed. We don't want too many successful +// registration tests in the same file since starting a service worker can be +// slow. +function registration_tests_basic(register_method, check_error_types) { + promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'resources/registration/normal'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + return registration.unregister(); + }); + }, 'Registering normal scope'); + + promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'resources/registration/scope-with-fragment#ref'; + return register_method(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') + return registration.unregister(); + }); + }, 'Registering scope with fragment'); + + promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'resources/'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + return registration.unregister(); + }); + }, 'Registering same scope as the script directory'); +} diff --git a/service-workers/service-worker/resources/registration-tests-mime-types.js b/service-workers/service-worker/resources/registration-tests-mime-types.js new file mode 100644 index 000000000000000..1b8ea9be008ce3c --- /dev/null +++ b/service-workers/service-worker/resources/registration-tests-mime-types.js @@ -0,0 +1,86 @@ +// Registration tests that mostly verify the MIME type. +// +// This file tests every MIME type so it necessarily starts many service +// workers, so it may be slow. +function registration_tests_mime_types(register_method, check_error_types) { + promise_test(function(t) { + var script = 'resources/mime-type-worker.py'; + var scope = 'resources/scope/no-mime-type-worker/'; + return promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + '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 promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + 'Registration of plain text script should fail.'); + }, 'Registering script with bad MIME type'); + + promise_test(function(t) { + var script = 'resources/import-mime-type-worker.py'; + var scope = 'resources/scope/no-mime-type-worker/'; + return promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + 'Registration of no MIME type imported script should fail.'); + }, 'Registering script that imports script with no MIME type'); + + promise_test(function(t) { + var script = 'resources/import-mime-type-worker.py?mime=text/plain'; + var scope = 'resources/scope/bad-mime-type-worker/'; + return promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + 'Registration of plain text imported script should fail.'); + }, 'Registering script that imports script with bad MIME type'); + + const validMimeTypes = [ + 'application/ecmascript', + 'application/javascript', + 'application/x-ecmascript', + 'application/x-javascript', + 'text/ecmascript', + 'text/javascript', + 'text/javascript1.0', + 'text/javascript1.1', + 'text/javascript1.2', + 'text/javascript1.3', + 'text/javascript1.4', + 'text/javascript1.5', + 'text/jscript', + 'text/livescript', + 'text/x-ecmascript', + 'text/x-javascript' + ]; + + for (const validMimeType of validMimeTypes) { + promise_test(() => { + var script = `resources/mime-type-worker.py?mime=${validMimeType}`; + var scope = 'resources/scope/good-mime-type-worker/'; + + return register_method(script, {scope}).then(registration => { + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + return registration.unregister(); + }); + }, `Registering script with good MIME type ${validMimeType}`); + + promise_test(() => { + var script = `resources/import-mime-type-worker.py?mime=${validMimeType}`; + var scope = 'resources/scope/good-mime-type-worker/'; + + return register_method(script, { scope }).then(registration => { + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + return registration.unregister(); + }); + }, `Registering script that imports script with good MIME type ${validMimeType}`); + } +} diff --git a/service-workers/service-worker/resources/registration-tests-scope.js b/service-workers/service-worker/resources/registration-tests-scope.js new file mode 100644 index 000000000000000..51b0ab4ad51a3c0 --- /dev/null +++ b/service-workers/service-worker/resources/registration-tests-scope.js @@ -0,0 +1,102 @@ +// Registration tests that mostly exercise the scope option. +function registration_tests_scope(register_method, check_error_types) { + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/scope%2fencoded-slash-in-scope'; + return promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + 'URL-encoded slash in the scope should be rejected.'); + }, 'Scope including URL-encoded slash'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/scope%5cencoded-slash-in-scope'; + return promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + 'URL-encoded backslash in the scope should be rejected.'); + }, 'Scope including URL-encoded backslash'); + + 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 register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL(scope), + 'URL-encoded multibyte characters should be available.'); + return registration.unregister(); + }); + }, '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 register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL(scope), + 'Non-URL-encoded multibyte characters should be available.'); + return registration.unregister(); + }); + }, 'Scope including non-escaped multibyte characters'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/././scope/self-reference-in-scope'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL('resources/scope/self-reference-in-scope'), + 'Scope including self-reference should be normalized.'); + return registration.unregister(); + }); + }, 'Scope including self-reference'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/../resources/scope/parent-reference-in-scope'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL('resources/scope/parent-reference-in-scope'), + 'Scope including parent-reference should be normalized.'); + return registration.unregister(); + }); + }, 'Scope including parent-reference'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/scope////consecutive-slashes-in-scope'; + return register_method(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.'); + return registration.unregister(); + }) + }, 'Scope including consecutive slashes'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'filesystem:' + normalizeURL('resources/scope/filesystem-scope-url'); + return promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + 'Registering with the scope that has same-origin filesystem: URL ' + + 'should fail with SecurityError.'); + }, 'Scope URL is same-origin filesystem: URL'); +} diff --git a/service-workers/service-worker/resources/registration-tests-script-url.js b/service-workers/service-worker/resources/registration-tests-script-url.js new file mode 100644 index 000000000000000..8d777a83099d54b --- /dev/null +++ b/service-workers/service-worker/resources/registration-tests-script-url.js @@ -0,0 +1,64 @@ +// Registration tests that mostly exercise the scriptURL parameter. +function registration_tests_script_url(register_method, check_error_types) { + promise_test(function(t) { + var script = 'resources%2fempty-worker.js'; + var scope = 'resources/scope/encoded-slash-in-script-url'; + return promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + 'URL-encoded slash in the script URL should be rejected.'); + }, 'Script URL including URL-encoded slash'); + + promise_test(function(t) { + var script = 'resources%2Fempty-worker.js'; + var scope = 'resources/scope/encoded-slash-in-script-url'; + return promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + 'URL-encoded slash in the script URL should be rejected.'); + }, 'Script URL including uppercase URL-encoded slash'); + + promise_test(function(t) { + var script = 'resources%5cempty-worker.js'; + var scope = 'resources/scope/encoded-slash-in-script-url'; + return promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + 'URL-encoded backslash in the script URL should be rejected.'); + }, 'Script URL including URL-encoded backslash'); + + promise_test(function(t) { + var script = 'resources%5Cempty-worker.js'; + var scope = 'resources/scope/encoded-slash-in-script-url'; + return promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + 'URL-encoded backslash in the script URL should be rejected.'); + }, 'Script URL including uppercase URL-encoded backslash'); + + promise_test(function(t) { + var script = 'resources/././empty-worker.js'; + var scope = 'resources/scope/parent-reference-in-script-url'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + get_newest_worker(registration).scriptURL, + normalizeURL('resources/empty-worker.js'), + 'Script URL including self-reference should be normalized.'); + return registration.unregister(); + }); + }, 'Script URL including self-reference'); + + promise_test(function(t) { + var script = 'resources/../resources/empty-worker.js'; + var scope = 'resources/scope/parent-reference-in-script-url'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + get_newest_worker(registration).scriptURL, + normalizeURL('resources/empty-worker.js'), + 'Script URL including parent-reference should be normalized.'); + return registration.unregister(); + }); + }, 'Script URL including parent-reference'); +} diff --git a/service-workers/service-worker/resources/registration-tests-script.js b/service-workers/service-worker/resources/registration-tests-script.js new file mode 100644 index 000000000000000..0e6859bdcea2f75 --- /dev/null +++ b/service-workers/service-worker/resources/registration-tests-script.js @@ -0,0 +1,88 @@ +// Registration tests that mostly exercise the service worker script contents or +// response. +function registration_tests_script(register_method, check_error_types) { + promise_test(function(t) { + var script = 'resources/invalid-chunked-encoding.py'; + var scope = 'resources/scope/invalid-chunked-encoding/'; + return promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + '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 promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + 'Registration of invalid chunked encoding script should fail.'); + }, 'Registering invalid chunked encoding script with flush'); + + promise_test(function(t) { + var script = 'resources/malformed-worker.py?parse-error'; + var scope = 'resources/scope/parse-error'; + return promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + '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 promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + '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 promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + 'Registration of script including uncaught exception should fail.'); + }, 'Registering script including uncaught exception'); + + promise_test(function(t) { + var script = 'resources/malformed-worker.py?import-malformed-script'; + var scope = 'resources/scope/import-malformed-script'; + return promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + 'Registration of script importing malformed script should fail.'); + }, 'Registering script importing malformed script'); + + promise_test(function(t) { + var script = 'resources/no-such-worker.js'; + var scope = 'resources/scope/no-such-worker'; + return promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + 'Registration of non-existent script should fail.'); + }, 'Registering non-existent script'); + + promise_test(function(t) { + var script = 'resources/malformed-worker.py?import-no-such-script'; + var scope = 'resources/scope/import-no-such-script'; + return promise_rejects(t, + check_error_types ? new TypeError : null, + register_method(script, {scope: scope}), + 'Registration of script importing non-existent script should fail.'); + }, 'Registering script importing non-existent script'); + + promise_test(function(t) { + var script = 'resources/malformed-worker.py?caught-exception'; + var scope = 'resources/scope/caught-exception'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + return registration.unregister(); + }); + }, 'Registering script including caught exception'); + +} diff --git a/service-workers/service-worker/resources/registration-tests-security-error.js b/service-workers/service-worker/resources/registration-tests-security-error.js new file mode 100644 index 000000000000000..c84bb66e042e135 --- /dev/null +++ b/service-workers/service-worker/resources/registration-tests-security-error.js @@ -0,0 +1,78 @@ +// Registration tests that mostly exercise SecurityError cases. +function registration_tests_security_error(register_method, check_error_types) { + promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'resources'; + return promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + '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 promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + '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 promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + '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 promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + 'Registration script outside domain should fail with SecurityError.'); + }, 'Registering script outside domain'); + + promise_test(function(t) { + var script = 'resources/redirect.py?Redirect=' + + encodeURIComponent('/resources/registration-worker.js'); + var scope = 'resources/scope/redirect/'; + return promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + 'Registration of redirected script should fail.'); + }, 'Registering redirected script'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/../scope/parent-reference-in-scope'; + return promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + '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 promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + 'Consecutive slashes in the script url should not be unified.'); + }, 'Script URL including consecutive slashes'); + + promise_test(function(t) { + var script = 'filesystem:' + normalizeURL('resources/empty-worker.js'); + var scope = 'resources/scope/filesystem-script-url'; + return promise_rejects(t, + check_error_types ? 'SecurityError' : null, + register_method(script, {scope: scope}), + 'Registering a script which has same-origin filesystem: URL should ' + + 'fail with SecurityError.'); + }, 'Script URL is same-origin filesystem: URL'); +} diff --git a/service-workers/service-worker/resources/test-helpers.sub.js b/service-workers/service-worker/resources/test-helpers.sub.js index 1d7643a71e9142d..1df4363ce2a4c8d 100644 --- a/service-workers/service-worker/resources/test-helpers.sub.js +++ b/service-workers/service-worker/resources/test-helpers.sub.js @@ -226,3 +226,32 @@ function websocket(test, frame) { function get_websocket_url() { return 'wss://{{host}}:{{ports[wss][0]}}/echo'; } + +// The navigator.serviceWorker.register() method guarantees that the newly +// installing worker is available as registration.installing when its promise +// resolves. However some tests test installation using a element where +// it is possible for the installing worker to have already become the waiting +// or active worker. So this method is used to get the newest worker when these +// tests need access to the ServiceWorker itself. +function get_newest_worker(registration) { + if (registration.installing) + return registration.installing; + if (registration.waiting) + return registration.waiting; + if (registration.active) + return registration.active; +} + +function register_using_link(script, options) { + var scope = options.scope; + var link = document.createElement('link'); + link.setAttribute('rel', 'serviceworker'); + link.setAttribute('href', script); + link.setAttribute('scope', scope); + document.getElementsByTagName('head')[0].appendChild(link); + return new Promise(function(resolve, reject) { + link.onload = resolve; + link.onerror = reject; + }) + .then(() => navigator.serviceWorker.getRegistration(scope)); +}