<!DOCTYPE html>
<title>Service Worker: WindowClient.navigate() tests</title>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<body>
<script>
'use strict';
const SCOPE = 'resources/blank.html';
const SCRIPT_URL = 'resources/windowclient-navigate-worker.js';
const CROSS_ORIGIN_URL =
get_host_info()['HTTPS_REMOTE_ORIGIN'] + base_path() + 'resources/blank.html';
navigateTest({
description: 'normal',
destUrl: 'blank.html?navigate',
expected: normalizeURL(SCOPE) + '?navigate',
});
navigateTest({
description: 'blank url',
destUrl: '',
expected: normalizeURL(SCRIPT_URL)
});
navigateTest({
description: 'in scope but not controlled test on installing worker',
destUrl: 'blank.html?navigate',
expected: 'TypeError',
waitState: 'installing',
});
navigateTest({
description: 'in scope but not controlled test on active worker',
destUrl: 'blank.html?navigate',
expected: 'TypeError',
controlled: false,
});
navigateTest({
description: 'out of scope',
srcUrl: '/common/blank.html',
destUrl: 'blank.html?navigate',
expected: 'TypeError',
});
navigateTest({
description: 'cross orgin url',
destUrl: CROSS_ORIGIN_URL,
expected: null
});
navigateTest({
description: 'invalid url (http://[example.com])',
destUrl: 'http://[example].com',
expected: 'TypeError'
});
navigateTest({
description: 'invalid url (view-source://example.com)',
destUrl: 'view-source://example.com',
expected: 'TypeError'
});
navigateTest({
description: 'invalid url (file:///)',
destUrl: 'file:///',
expected: 'TypeError'
});
navigateTest({
description: 'invalid url (about:blank)',
destUrl: 'about:blank',
expected: 'TypeError'
});
navigateTest({
description: 'navigate on a top-level window client',
destUrl: 'blank.html?navigate',
srcUrl: 'resources/loaded.html',
scope: 'resources/loaded.html',
expected: normalizeURL(SCOPE) + '?navigate',
frameType: 'top-level'
});
async function createFrame(t, parameters) {
if (parameters.frameType === 'top-level') {
// Wait for window.open is completed.
await new Promise(resolve => {
const win = window.open(parameters.srcUrl);
t.add_cleanup(() => win.close());
window.addEventListener('message', (e) => {
if (e.data.type === 'LOADED') {
resolve();
}
});
});
}
if (parameters.frameType === 'nested') {
const frame = await with_iframe(parameters.srcUrl);
t.add_cleanup(() => frame.remove());
}
}
function navigateTest(overrideParameters) {
// default parameters
const parameters = {
description: null,
srcUrl: SCOPE,
destUrl: null,
expected: null,
waitState: 'activated',
scope: SCOPE,
controlled: true,
// `frameType` can be 'nested' for an iframe WindowClient or 'top-level' for
// a main frame WindowClient.
frameType: 'nested'
};
for (const key in overrideParameters)
parameters[key] = overrideParameters[key];
promise_test(async function(t) {
let pausedLifecyclePort;
let scriptUrl = SCRIPT_URL;
// For in-scope-but-not-controlled test on installing worker,
// if the waitState is "installing", then append the query to scriptUrl.
if (parameters.waitState === 'installing') {
scriptUrl += '?' + parameters.waitState;
navigator.serviceWorker.addEventListener('message', (event) => {
if (event.data.port) {
pausedLifecyclePort = event.data.port;
}
});
}
t.add_cleanup(() => {
// Some tests require that the worker remain in a given lifecycle phase.
// "Clean up" logic for these tests requires signaling the worker to
// release the hold; this allows the worker to be properly discarded
// prior to the execution of additional tests.
if (pausedLifecyclePort) {
// The value of the posted message is inconsequential. A string is
// specified here solely to aid in test debugging.
pausedLifecyclePort.postMessage('continue lifecycle');
}
});
// Create a frame that is not controlled by a service worker.
if (!parameters.controlled) {
await createFrame(t, parameters);
}
const registration = await service_worker_unregister_and_register(
t, scriptUrl, parameters.scope);
const serviceWorker = registration.installing;
await wait_for_state(t, serviceWorker, parameters.waitState);
t.add_cleanup(() => registration.unregister());
// Create a frame after a service worker is registered so that the frmae is
// controlled by an active service worker.
if (parameters.controlled) {
await createFrame(t, parameters);
}
const response = await new Promise(resolve => {
const channel = new MessageChannel();
channel.port1.onmessage = t.step_func(resolve);
serviceWorker.postMessage({
port: channel.port2,
url: parameters.destUrl,
clientUrl: new URL(parameters.srcUrl, location).toString(),
frameType: parameters.frameType,
expected: parameters.expected,
description: parameters.description,
}, [channel.port2]);
});
assert_equals(response.data, null);
await fetch_tests_from_worker(serviceWorker);
}, parameters.description);
}
</script>
</body>