| <!DOCTYPE html> |
| <title>Service Worker: UseCounter</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="resources/test-helpers.js"></script> |
| <script> |
| |
| const kFeature = 675; // From UseCounter.h |
| const kDeprecatedFeature = 538; // From Deprecation.h |
| |
| function isUseCounted(win, feature) { |
| return win.internals.isUseCounted(win.document, feature); |
| } |
| |
| function observeUseCounter(win, feature) { |
| return win.internals.observeUseCounter(win.document, feature); |
| } |
| |
| // Use a window instead of an iframe because UseCounter is shared among frames |
| // in a document and these tests cannot be conducted in such an environment. |
| // A window has its own UseCounter. |
| function openWindow(url) { |
| return new Promise(resolve => { |
| let win = window.open(url, '_blank'); |
| add_completion_callback(() => win.close()); |
| window.onmessage = e => { |
| assert_equals(e.data, 'LOADED'); |
| resolve(win); |
| }; |
| }); |
| } |
| |
| promise_test(t => { |
| const kUrl = 'resources/usecounter-worker.js'; |
| const kScope = 'resources/usecounter-window.html?basic'; |
| let worker; |
| let win1; |
| let win2; |
| |
| return service_worker_unregister_and_register(t, kUrl, kScope) |
| .then(registration => { |
| add_completion_callback(function() { registration.unregister(); }); |
| worker = registration.installing; |
| return wait_for_state(t, registration.installing, 'activated'); |
| }) |
| .then(() => { return openWindow(kScope); }) |
| .then(win => { |
| win1 = win; |
| return openWindow(kScope); |
| }) |
| .then(win => { |
| win2 = win; |
| |
| assert_false(isUseCounted(win1, kFeature)); |
| assert_false(isUseCounted(win2, kFeature)); |
| |
| // Request to count a feature. |
| worker.postMessage({type: 'COUNT_FEATURE', feature: kFeature}); |
| return Promise.all([ |
| observeUseCounter(win1, kFeature), |
| observeUseCounter(win2, kFeature) |
| ]); |
| }) |
| .then(() => { |
| // API use on ServiceWorkerGlobalScope should be recorded in all |
| // controlled windows. |
| assert_true(isUseCounted(win1, kFeature)); |
| assert_true(isUseCounted(win2, kFeature)); |
| |
| assert_false(isUseCounted(win1, kDeprecatedFeature)); |
| assert_false(isUseCounted(win2, kDeprecatedFeature)); |
| |
| // Request to count a deprecated feature. |
| worker.postMessage( |
| {type: 'COUNT_DEPRECATION', feature: kDeprecatedFeature}); |
| return Promise.all([ |
| observeUseCounter(win1, kDeprecatedFeature), |
| observeUseCounter(win2, kDeprecatedFeature) |
| ]); |
| }) |
| .then(() => { |
| // Deprecated API use on ServiceWorkerGlobalScope should be recorded |
| // in all controlled windows. |
| assert_true(isUseCounted(win1, kDeprecatedFeature)); |
| assert_true(isUseCounted(win2, kDeprecatedFeature)); |
| |
| return openWindow(kScope); |
| }) |
| .then(win => { |
| // Check UseCounters have been sent to the new window. Since this is can |
| // happen after the new window's document load, an observer is used to |
| // wait until it happens. |
| return Promise.all([ |
| observeUseCounter(win, kFeature), |
| observeUseCounter(win, kDeprecatedFeature) |
| ]); |
| }) |
| }, 'UseCounter on ServiceWorkerGlobalScope'); |
| |
| promise_test(t => { |
| const kUrl = 'resources/usecounter-worker.js'; |
| const kScope = 'resources/usecounter-window.html?claim'; |
| let worker; |
| let win1; |
| let win2; |
| |
| return openWindow(kScope) |
| .then(win => { |
| win1 = win; |
| return openWindow(kScope); |
| }) |
| .then(win => { |
| win2 = win; |
| return service_worker_unregister_and_register(t, kUrl, kScope) |
| }) |
| .then(registration => { |
| add_completion_callback(function() { registration.unregister(); }); |
| worker = registration.installing; |
| return wait_for_state(t, registration.installing, 'activated'); |
| }) |
| .then(() => { |
| // Request to count a feature. |
| worker.postMessage({type: 'COUNT_FEATURE', feature: kFeature}); |
| return new Promise(resolve => { |
| navigator.serviceWorker.onmessage = resolve; |
| // There is no way to verify that API use is never counted. As a |
| // workaround, wait for only one round-trip. |
| worker.postMessage({type: 'PING'}); |
| }); |
| }) |
| .then(e => { |
| assert_equals(e.data.type, 'PONG'); |
| |
| // API use on ServiceWorkerGlobalScope should not be recorded in |
| // windows because they are not controlled yet. |
| assert_false(isUseCounted(win1, kFeature)); |
| assert_false(isUseCounted(win2, kFeature)); |
| |
| // Request to count a deprecated feature. |
| worker.postMessage( |
| {type: 'COUNT_DEPRECATION', feature: kDeprecatedFeature}); |
| return new Promise(resolve => { |
| navigator.serviceWorker.onmessage = resolve; |
| // There is no way to verify that API use is never counted. As a |
| // workaround, wait for only one round-trip. |
| worker.postMessage({type: 'PING'}); |
| }); |
| }) |
| .then(e => { |
| assert_equals(e.data.type, 'PONG'); |
| |
| // Deprecated API use on ServiceWorkerGlobalScope should not be |
| // recorded in windows because they are not controlled yet. |
| assert_false(isUseCounted(win1, kDeprecatedFeature)); |
| assert_false(isUseCounted(win2, kDeprecatedFeature)); |
| |
| assert_equals(win1.navigator.serviceWorker.controller, null); |
| assert_equals(win2.navigator.serviceWorker.controller, null); |
| |
| // Request to claim. |
| return new Promise(resolve => { |
| navigator.serviceWorker.onmessage = resolve; |
| worker.postMessage({type: 'CLAIM'}); |
| }); |
| }) |
| .then(e => { |
| assert_equals(e.data.type, 'CLAIMED'); |
| assert_false(e.data.restarted); |
| assert_not_equals(win1.navigator.serviceWorker.controller, null); |
| assert_not_equals(win2.navigator.serviceWorker.controller, null); |
| |
| // The windows are now controlled by the service worker. Their |
| // UseCounter should be synchronized with worker's counter. |
| assert_true(isUseCounted(win1, kFeature)); |
| assert_true(isUseCounted(win2, kFeature)); |
| assert_true(isUseCounted(win1, kDeprecatedFeature)); |
| assert_true(isUseCounted(win2, kDeprecatedFeature)); |
| }); |
| }, 'UseCounter on ServiceWorkerGlobalScope - A use counter owned by newly ' + |
| 'controlled window should be synchronized with worker\'s counter'); |
| |
| // Test that features used during service worker installation are persisted. |
| // This test could be non-deterministic because there is no handy way to |
| // sweep out on-memory representation of ServiceWorker in the browser process |
| // and make sure to restore it from the storage. |
| promise_test(t => { |
| const kUrl = 'resources/usecounter-worker.js'; |
| const kScope = 'resources/usecounter-window.html' + |
| '?type=features-during-install' + |
| '&feature=' + kFeature + |
| '&deprecated=' + kDeprecatedFeature; |
| let worker; |
| let win1; |
| let win2; |
| |
| return openWindow(kScope) |
| .then(win => { |
| win1 = win; |
| return openWindow(kScope); |
| }) |
| .then(win => { |
| win2 = win; |
| // A service worker will call some APIs during the install event. |
| return service_worker_unregister_and_register(t, kUrl, kScope) |
| }) |
| .then(registration => { |
| add_completion_callback(function() { registration.unregister(); }); |
| worker = registration.installing; |
| return wait_for_state(t, registration.installing, 'activated'); |
| }) |
| .then(e => { |
| assert_equals(win1.navigator.serviceWorker.controller, null); |
| assert_equals(win2.navigator.serviceWorker.controller, null); |
| |
| // API use on ServiceWorkerGlobalScope should not be recorded in |
| // windows because they are not controlled yet. |
| assert_false(isUseCounted(win1, kFeature)); |
| assert_false(isUseCounted(win2, kFeature)); |
| assert_false(isUseCounted(win1, kDeprecatedFeature)); |
| assert_false(isUseCounted(win2, kDeprecatedFeature)); |
| |
| // Terminate the service worker. |
| return internals.terminateServiceWorker(worker); |
| }) |
| .then(() => { |
| // Request to claim. This will restart the service worker. |
| return new Promise(resolve => { |
| navigator.serviceWorker.onmessage = resolve; |
| worker.postMessage({type: 'CLAIM'}); |
| }); |
| }) |
| .then(e => { |
| assert_equals(e.data.type, 'CLAIMED'); |
| assert_true(e.data.restarted); |
| assert_not_equals(win1.navigator.serviceWorker.controller, null); |
| assert_not_equals(win2.navigator.serviceWorker.controller, null); |
| |
| // The windows are now controlled by the service worker. Their |
| // UseCounter should be synchronized with worker's counter retrieved |
| // from the storage. |
| assert_true(isUseCounted(win1, kFeature)); |
| assert_true(isUseCounted(win2, kFeature)); |
| assert_true(isUseCounted(win1, kDeprecatedFeature)); |
| assert_true(isUseCounted(win2, kDeprecatedFeature)); |
| }); |
| }, 'UseCounter on ServiceWorkerGlobalScope - counts during the install ' + |
| 'event should be persisted'); |
| |
| // TODO(nhiroki): Test that features used after service worker installation are |
| // not persisted. This could be impossible because there is no handy way to |
| // sweep out on-memory representation of ServiceWorker in the browser process |
| // and make sure to restore it from the storage. |
| |
| promise_test(t => { |
| const kUrl = 'resources/usecounter-worker.js'; |
| const kScope = 'resources/usecounter-window.html?type=skip-waiting'; |
| let worker1; |
| let worker2; |
| let win1; |
| let win2; |
| |
| return service_worker_unregister_and_register(t, kUrl, kScope) |
| .then(registration => { |
| add_completion_callback(function() { registration.unregister(); }); |
| worker1 = registration.installing; |
| return wait_for_state(t, registration.installing, 'activated'); |
| }) |
| .then(() => { return openWindow(kScope); }) |
| .then(win => { |
| win1 = win; |
| assert_false(isUseCounted(win1, kFeature)); |
| |
| // Request to count a feature. |
| worker1.postMessage({type: 'COUNT_FEATURE', feature: kFeature}); |
| return observeUseCounter(win1, kFeature); |
| }) |
| .then(e => { |
| // API use on ServiceWorkerGlobalScope should be recorded in a |
| // controlled window. |
| assert_true(isUseCounted(win1, kFeature)); |
| |
| // Update a controller using skipWaiting(). |
| return navigator.serviceWorker.register( |
| kUrl + '?skip-waiting', {scope: kScope}); |
| }) |
| .then(registration => { |
| add_completion_callback(function() { registration.unregister(); }); |
| worker2 = registration.installing; |
| // Wait until the new worker gets activated. |
| return wait_for_state(t, worker2, 'activated'); |
| }) |
| .then(() => { return openWindow(kScope); }) |
| .then(win => { |
| // This window wasn't controlled by the previous worker. |
| win2 = win; |
| assert_not_equals(win2.navigator.serviceWorker.controller, undefined); |
| |
| // An updated worker does not take over the previous counter, so API |
| // use on the previous worker should not be recorded in the newly |
| // controlled window. |
| assert_true(isUseCounted(win1, kFeature)); |
| assert_false(isUseCounted(win2, kFeature)); |
| |
| assert_false(isUseCounted(win1, kDeprecatedFeature)); |
| assert_false(isUseCounted(win2, kDeprecatedFeature)); |
| |
| // Request to count a deprecated feature. |
| worker2.postMessage( |
| {type: 'COUNT_DEPRECATION', feature: kDeprecatedFeature}); |
| return Promise.all([ |
| observeUseCounter(win1, kDeprecatedFeature), |
| observeUseCounter(win2, kDeprecatedFeature) |
| ]); |
| }) |
| .then(e => { |
| // Deprecated API use on the updated worker should be recorded in |
| // all controlled windows. |
| assert_true(isUseCounted(win1, kFeature)); |
| assert_false(isUseCounted(win2, kFeature)); |
| assert_true(isUseCounted(win1, kDeprecatedFeature)); |
| assert_true(isUseCounted(win2, kDeprecatedFeature)); |
| }); |
| }, 'UseCounter on ServiceWorkerGlobalScope - an updated worker should not ' + |
| 'take over a previous counter'); |
| |
| promise_test(t => { |
| const kFetchEventIsReload = 2032; // from web_feature.mojom |
| const kUrl = 'resources/use-isReload-worker.js'; |
| const kScope = 'resources/usecounter-window.html?isReload'; |
| let win; |
| return service_worker_unregister_and_register(t, kUrl, kScope) |
| .then(registration => { |
| add_result_callback(() => { |
| if (win) { |
| win.close(); |
| } |
| registration.unregister(); |
| }); |
| return wait_for_state(t, registration.installing, 'activated'); |
| }) |
| .then(() => { return openWindow(kScope); }) |
| .then(new_window => { |
| win = new_window; |
| return observeUseCounter(win, kFetchEventIsReload); |
| }) |
| .then(() => { |
| assert_true(isUseCounted(win, kFetchEventIsReload)); |
| }); |
| }, 'FetchEvent.isReload is counted'); |
| |
| promise_test(t => { |
| const kServiceWorkerFrameType = 2033; // from web_feature.mojom |
| const kUrl = 'resources/feature-worker.js'; |
| const kScope = 'resources/usecounter-window.html?frameType'; |
| let worker; |
| let win; |
| return service_worker_unregister_and_register(t, kUrl, kScope) |
| .then(registration => { |
| add_result_callback(() => { |
| if (win) { |
| win.close(); |
| } |
| registration.unregister(); |
| }); |
| worker = registration.installing; |
| return wait_for_state(t, worker, 'activated'); |
| }) |
| .then(() => { return openWindow(kScope); }) |
| .then(new_window => { |
| win = new_window; |
| worker.postMessage('use-frameType'); |
| return observeUseCounter(win, kServiceWorkerFrameType); |
| }) |
| .then(() => { |
| assert_true(isUseCounted(win, kServiceWorkerFrameType)); |
| }); |
| }, 'Client.frameType is counted'); |
| |
| // TODO(nhiroki): Test a case where ServiceWorker controls SharedWorker that is |
| // connected from multiple windows. In such a case, API use on ServiceWorker |
| // should be propagated to all connecting windows via SharedWorker. |
| |
| </script> |
| </html> |