| <!DOCTYPE html> | |
| <meta name="timeout" content="long"> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="/resources/testdriver.js"></script> | |
| <script src="/resources/testdriver-vendor.js"></script> | |
| <script src="/common/utils.js"></script> | |
| <script src="/common/dispatcher/dispatcher.js"></script> | |
| <script src="../resources/utils.js"></script> | |
| <script src="resources/utils.js"></script> | |
| <script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> | |
| <script> | |
| function getNextWindowMessageFromFrame(frame) { | |
| return new Promise(resolve => { | |
| window.addEventListener('message', event => { | |
| if (event.source === frame.contentWindow) { | |
| resolve(event.data); | |
| } | |
| }); | |
| }); | |
| } | |
| function getNextMessageFromServiceWorker(serviceWorker) { | |
| return new Promise(resolve => { | |
| serviceWorker.addEventListener('message', event => { | |
| resolve(event.data); | |
| }, {once: true}); | |
| }); | |
| } | |
| promise_setup(async () => { | |
| assertSpeculationRulesIsSupported() | |
| await test_driver.set_rph_registration_mode('autoAccept'); | |
| await test_driver.bless('handler registration'); | |
| }); | |
| // The overall idea for this test is: | |
| // 1. Register a protocol handler for a custom URI scheme. | |
| // 2. Create a prerendered page that unregisters the protocol handler. | |
| // 3. Navigate an iframe to the custom URI scheme. It should navigate | |
| // successfully. | |
| // 4. Activate the prerendered page. This should run the deferred unregistration | |
| // work. | |
| // 5. Navigate the iframe to the custom URI scheme again. It should fail to | |
| // navigate. | |
| // To detect the navigation failure, we use a service worker to perform the | |
| // navigation via client.navigate and report the result back to | |
| // the test. This is because the service worker's client.navigate method | |
| // actually reports if the navigation failed, unlike other mechanisms which | |
| // tell us nothing in this case. | |
| promise_test(async t => { | |
| const customUrlScheme = 'web+wptrphtest'; | |
| function getProtocolHandlerUrlTemplate(id) { | |
| return new URL( | |
| `resources/protocol-handler.html?id=${id}&s=%s`, location.href).href; | |
| } | |
| const urlTemplate = getProtocolHandlerUrlTemplate('unregister'); | |
| t.add_cleanup(() => { | |
| navigator.unregisterProtocolHandler( | |
| customUrlScheme, urlTemplate); | |
| }); | |
| navigator.registerProtocolHandler(customUrlScheme, urlTemplate); | |
| const {exec, activate} = await create_prerendered_page(t); | |
| const result = await exec( | |
| (customUrlScheme, urlTemplate) => { | |
| try { | |
| navigator.unregisterProtocolHandler( | |
| customUrlScheme, urlTemplate); | |
| } catch (registerProtocolHandlerException) { | |
| return 'registerProtocolHandler failed with \'' + | |
| registerProtocolHandlerException.name + '\''; | |
| } | |
| return 'success'; | |
| }, [customUrlScheme, urlTemplate]); | |
| assert_equals(result, 'success', 'unregisterProtocolHandler should succeed.'); | |
| const frame1 = await with_iframe('about:blank'); | |
| const frame1MessagePromise = getNextWindowMessageFromFrame(frame1); | |
| frame1.src = `${customUrlScheme}:1`; | |
| assert_equals((await frame1MessagePromise).id, 'unregister', | |
| 'Until activation, the initial handler should be registered.'); | |
| frame1.remove(); | |
| // Activate the prerendered page. | |
| await activate(); | |
| // At this point the deferred unregistration work has run during activation | |
| // and the protocol handler is no longer registered. | |
| // We use Service Worker client.navigate to detect the failed navigation since | |
| // it is well supported and reliably reports error for unknown URL schemes. | |
| const serviceWorkerScope = | |
| 'resources/protocol-handler.html?service_worker_client'; | |
| const frame2Url = serviceWorkerScope + '&id=communication'; | |
| const frame3Url = serviceWorkerScope + '&id=nav_target'; | |
| const serviceWorkerUrl = 'resources/protocol-handler-service-worker.js'; | |
| const serviceWorkerRegistration = | |
| await service_worker_unregister_and_register( | |
| t, serviceWorkerUrl, serviceWorkerScope); | |
| t.add_cleanup(async () => { | |
| await serviceWorkerRegistration.unregister(); | |
| }); | |
| await wait_for_state(t, serviceWorkerRegistration.installing, 'activated'); | |
| // We use frame2 to communicate with the service worker. | |
| const frame2 = await with_iframe(frame2Url); | |
| // Frame3 is used by the service worker to attempt navigation. | |
| const frame3 = await with_iframe(frame3Url); | |
| const serviceWorkerMessagePromise = getNextMessageFromServiceWorker( | |
| frame2.contentWindow.navigator.serviceWorker); | |
| // Post message via frame2 to the service worker to tell it to navigate frame3 | |
| // to the custom URI. | |
| frame2.contentWindow.navigator.serviceWorker.controller.postMessage( | |
| {clientUrlMatch: new URL(frame3Url, location.href).href, | |
| navigationUrl: `${customUrlScheme}:3`}); | |
| // The service worker will post message back the result of the navigation. | |
| const navigationResult = await serviceWorkerMessagePromise; | |
| frame2.remove(); | |
| frame3.remove(); | |
| assert_false(navigationResult.success, 'Navigation to unregistered URI should fail'); | |
| assert_equals(navigationResult.message, 'navigate failure: TypeError', | |
| 'unregisterProtocolHandler should have completed.'); | |
| }, 'prerendering page unregisterProtocolHandler call defers registration until activation.'); | |
| </script> |