| // Copyright 2022 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| h5vcc.storage.clearServiceWorkerCache(); |
| |
| const eventCounts = {}; |
| const countEvent = (fnName, expectedEventCount) => { |
| if (fnName in eventCounts) { |
| eventCounts[fnName]++; |
| } else { |
| eventCounts[fnName] = 1; |
| } |
| if (expectedEventCount) { |
| assertEqual(expectedEventCount, eventCounts[fnName]) |
| } |
| }; |
| |
| const unregisterAll = () => navigator.serviceWorker.getRegistrations().then(registrations => |
| Promise.all(registrations.map(r => r.unregister()))); |
| setTearDown(unregisterAll); |
| const err = msg => new Error(msg); |
| const fail = msg => { |
| const userDefinedError = new Error(msg); |
| return error => { |
| printError(userDefinedError); |
| return unregisterAll().then(() => notReached(error)); |
| }; |
| }; |
| const timeoutId = window.setTimeout(fail('timeout'), 10000); |
| const done = () => { |
| window.clearTimeout(timeoutId); |
| console.log('Done!'); |
| onEndTest(); |
| }; |
| |
| const expectEquals = (expected, actual, msg) => { |
| window.assertEqual(expected, actual, new Error(msg || '')); |
| }; |
| |
| setupFinished(); |
| |
| assertFalse(!!self.clients); |
| assertTrue(!!navigator.serviceWorker); |
| |
| navigator.serviceWorker.ready.then(registration => { |
| assertTrue(!!registration); |
| assertTrue(!!registration.active); |
| countEvent('testMain', /*expectedEventCount=*/1); |
| }); |
| |
| navigator.serviceWorker.oncontrollerchange = event => { |
| countEvent('testMain', /*expectedEventCount=*/2); |
| }; |
| |
| const promiseForEach = (xs, fn) => new Promise((resolve, reject) => { |
| let index = -1; |
| const doNext = () => { |
| if (failed()) { |
| reject(new Error(`Tests failed. Current sequence index is ${index}.`)); |
| } |
| index++; |
| if (index === xs.length) { |
| resolve(); |
| return; |
| } |
| fn(xs[index]).then(doNext).catch(reject); |
| }; |
| doNext(); |
| }); |
| |
| const promiseSequence = fns => promiseForEach(fns, fn => fn()); |
| |
| const promiseMap = (xs, fn) => Promise.all(xs.map(fn)); |
| |
| const checkInvalidUrls = () => promiseForEach( |
| [ |
| 'http://..:..', |
| 'arg:service_worker_test_worker.js', |
| 'http:%2fservice_worker_test_worker.js', |
| ], |
| url => navigator.serviceWorker.register(url) |
| .then(fail(`URL ${url} did not raise error.`)) |
| .catch(error => { |
| assertEqual('TypeError', error.name, `url: ${url}`); |
| }) |
| ); |
| |
| const checkInvalidScopes = () => promiseForEach( |
| [ |
| ['service_worker_test_worker.js', {scope: 'arg:/'}, 'TypeError'], |
| ['service_worker_test_worker.js', {scope: '/foo'}, 'SecurityError'], |
| ['service_worker_test_worker.js', {scope: '/%5c'}, 'TypeError'], |
| ], |
| args => navigator.serviceWorker.register(args[0], args[1]) |
| .then(fail(`Scope ${args[1].scope} did not raise error.`)) |
| .catch(error => { |
| assertEqual(args[2], error.name); |
| }) |
| ); |
| |
| const checkNotTrustedAndEquivalentJob = () => promiseMap( |
| Array.from({length: 15}), |
| () => navigator.serviceWorker.register('http://www.example.com/', {scope: '/'}) |
| .then(fail('Untrusted URL did not raise error')) |
| .catch(error => { |
| assertEqual('SecurityError', error.name); |
| }) |
| ); |
| |
| const checkSuccessfulRegistration = () => Promise.all([ |
| navigator.serviceWorker.register('service_worker_test_worker.js', { |
| scope: '/bar/registration/scope', |
| }) |
| .then(registration => { |
| assertTrue(!!registration); |
| assertEqual('imports', registration.updateViaCache); |
| assertTrue(registration.scope.endsWith('bar/registration/scope')); |
| |
| let worker = registration.installing; |
| if (worker) { |
| worker.postMessage( |
| 'Registration with scope successful, ' + |
| 'current worker state is installing.'); |
| } |
| worker = registration.waiting; |
| if (worker) { |
| worker.postMessage( |
| 'Registration with scope successful, ' + |
| 'current worker state is waiting.'); |
| } |
| worker = registration.active; |
| if (worker) { |
| worker.postMessage( |
| 'Registration with scope successful, ' + |
| 'current worker state is active.'); |
| } |
| |
| registration.onupdatefound = event => { |
| assertTrue(event.target.scope.endsWith('bar/registration/scope')); |
| }; |
| |
| return navigator.serviceWorker.ready.then(registration => { |
| assertTrue(!!registration.active); |
| registration.active.postMessage('Registration is active after waiting.'); |
| assertEqual('activating', registration.active.state); |
| |
| return promiseSequence([ |
| () => promiseMap( |
| Array.from({length: 5}), |
| () => registration.update() |
| .then(registration => { |
| assertTrue(!!registration); |
| }) |
| ).catch(fail('getRegistration() failed.')), |
| () => navigator.serviceWorker.getRegistration('/bo/gus') |
| .then(registration => { |
| assertFalse(!!registration); |
| }) |
| .catch(fail('getRegistration() failed.')), |
| () => navigator.serviceWorker.getRegistration( |
| '/bar/registration/scope/deeper') |
| .then(registration => { |
| assertTrue(!!registration); |
| assertEqual('imports', registration.updateViaCache); |
| assertTrue(registration.scope.endsWith( |
| 'bar/registration/scope')); |
| }) |
| .catch(fail('getRegistration() failed.')), |
| () => navigator.serviceWorker.getRegistration('registration') |
| .then(registration => { |
| assertFalse(!!registration); |
| }) |
| .catch(fail('getRegistration() failed.')), |
| () => navigator.serviceWorker.getRegistration() |
| .then(registration => { |
| assertFalse(!!registration); |
| }) |
| .catch(fail), |
| () => navigator.serviceWorker.getRegistration( |
| '/bar/registration/scope') |
| .then(registration => { |
| assertTrue(!!registration); |
| assertEqual('imports', registration.updateViaCache); |
| assertTrue(registration.scope.endsWith( |
| 'bar/registration/scope')); |
| }) |
| .catch(fail('getRegistration() failed.')), |
| () => registration.unregister() |
| .then(success => { |
| assertTrue(success); |
| }) |
| .then(() => |
| navigator.serviceWorker.getRegistration('bar/registration/scope')) |
| .then(registration => { |
| assertFalse(!!registration) |
| }) |
| .catch(fail('getRegistration() failed after unregister.')), |
| ]); |
| }); |
| }), |
| ]); |
| |
| const checkClaimableWorker = () => Promise.all([ |
| new Promise((resolve, reject) => { |
| let messageCount = 0; |
| navigator.serviceWorker.onmessage = event => { |
| console.log('onmessage: ' + messageCount) |
| messageCount++; |
| // Expecting two messages: |
| // - when sent a message |
| // - when claimed |
| if (messageCount === 2) { |
| setTimeout(resolve); |
| return; |
| } |
| }; |
| }), |
| navigator.serviceWorker.ready |
| .then(registration => { |
| assertTrue(!!registration); |
| assertTrue(!!registration.active); |
| registration. |
| registration.active.postMessage( |
| 'Registration ready received for claimable worker.'); |
| return registration.unregister(); |
| }) |
| .then(success => { |
| assertTrue(success); |
| }) |
| .catch(fail), |
| navigator.serviceWorker.register( |
| 'service_worker_test_claimable.js', {scope: './'}) |
| .then(registration => { |
| let worker = registration.installing; |
| if (worker) { |
| worker.postMessage( |
| 'Registration successful, current worker state is ' + |
| 'installing.'); |
| } |
| worker = registration.waiting; |
| if (worker) { |
| worker.postMessage( |
| 'Registration successful, current worker state is ' + |
| 'waiting.'); |
| } |
| worker = registration.active; |
| if (worker) { |
| worker.postMessage( |
| 'Registration successful, current worker state is ' + |
| 'active.'); |
| } |
| // TODO (b/259734597) : This registration is persisting since it is |
| // not unregistered and h5vcc storage clearCache does not correctly clear |
| // registrations. |
| // TODO (b/259731731) : Adding registration.unregister() causes memory |
| // leaks. |
| }) |
| .catch(fail), |
| ]); |
| |
| promiseSequence([ |
| unregisterAll, |
| checkInvalidUrls, |
| checkInvalidScopes, |
| checkNotTrustedAndEquivalentJob, |
| () => navigator.serviceWorker.register('http://127.0.0.100:2345/') |
| .then(fail(`Invalid URL did not raise error.`)) |
| .catch(error => { |
| assertEqual('SecurityError', error.name); |
| }), |
| () => navigator.serviceWorker.register('service_worker_test_worker.js', { |
| scope: 'http://127.0.0.100:2345/', |
| }) |
| .then(fail(`Invalid scope did not raise error.`)) |
| .catch(error => { |
| assertEqual('SecurityError', error.name); |
| }), |
| () => navigator.serviceWorker.register('service_worker_test.html') |
| .then(fail(`HTML did not raise error.`)) |
| .catch(error => { |
| assertEqual('SecurityError', error.name); |
| }), |
| () => navigator.serviceWorker.register('service_worker_test_nonexist.js') |
| .then(fail(`Unknown script did not raise error.`)) |
| .catch(error => { |
| assertEqual('SecurityError', error.name); |
| }), |
| checkSuccessfulRegistration, |
| unregisterAll, |
| checkClaimableWorker, |
| ]).then(done).catch(fail('Error not caught.')); |