Source code
Revision control
Copy as Markdown
Other Tools
<!DOCTYPE html>
<meta charset="utf-8">
<title>Creating a receiving browsing context</title>
<link rel="help" href="https://w3c.github.io/presentation-api/#creating-a-receiving-browsing-context">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="common.js"></script>
<script src="support/stash.js"></script>
<p id="notice">
Click the button below and select the available presentation display, to start the manual test. The test passes if a "PASS" result appears.<br>
This test asks you to click the button twice, unless the test fails.<br>
<button id="presentBtn">Start Presentation Test</button>
</p>
<script>
setup({explicit_timeout: true});
let receiverStack;
add_completion_callback(() => {
// overwrite a stack written in the test result
if (receiverStack) {
document.querySelector('#log pre').textContent = receiverStack;
}
});
let connection;
const presentBtn = document.getElementById('presentBtn');
const dbName = {
controller: 'db-presentation-api-controlling-ua',
receiver: 'db-presentation-api-receiving-ua'
};
const main = () => {
promise_test(t => {
presentBtn.disabled = true;
const stash = new Stash(stashIds.toController, stashIds.toReceiver);
const request = new PresentationRequest('support/PresentationReceiver_create_receiving-ua.html');
t.add_cleanup(() => {
const notice = document.getElementById('notice');
notice.parentNode.removeChild(notice);
stash.stop();
// history.back();
document.cookie = 'PresentationApiTest=true; Expires=' + new Date().toUTCString();
sessionStorage.removeItem('presentation_api_test');
localStorage.removeItem('presentation_api_test');
Object.values(dbName).forEach(name => {
indexedDB.deleteDatabase(name);
});
if (connection) {
connection.onconnect = () => { connection.terminate(); };
if (connection.state === 'closed')
request.reconnect(connection.id);
else
connection.terminate();
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(registrations => {
return Promise.all(registrations.map(reg => reg.unregister()));
});
}
if ('caches' in window) {
caches.keys().then(keys => {
return Promise.all(keys.map(key => caches.delete(key)));
});
}
});
history.pushState(null, 'test', 'PresentationReceiver_create-manual.https.html');
document.cookie = 'PresentationApiTest=Controlling-UA';
const storageName = 'presentation_api_test';
const storageValue = 'receiving-ua';
sessionStorage.setItem(storageName, storageValue);
localStorage.setItem(storageName, storageValue);
const openIndexedDB = () => {
if ('indexedDB' in window) {
const req = indexedDB.open(dbName.controller, 1);
const eventWatcher = new EventWatcher(t, req, 'upgradeneeded');
return eventWatcher.wait_for('upgradeneeded').then(evt => {
evt.target.result.close();
});
}
else
return Promise.resolve();
};
const cacheName = 'controlling-ua';
let clientUrls;
const getClientUrls = () => {
return new Promise(resolve => {
navigator.serviceWorker.getRegistration().then(reg => {
if (reg) {
const channel = new MessageChannel();
channel.port1.onmessage = event => {
resolve(event.data);
};
reg.active.postMessage('', [channel.port2]);
}
else
resolve([]);
});
});
};
const registerServiceWorker = () => {
return ('serviceWorker' in navigator ?
navigator.serviceWorker.register('serviceworker.js').then(registration => {
return new Promise((resolve, reject) => {
if (registration.installing) {
registration.installing.addEventListener('statechange', event => {
if(event.target.state === 'installed')
resolve();
});
}
else
resolve();
});
}) : Promise.resolve()).then(getClientUrls).then(urls => {
clientUrls = urls;
});
};
const openCaches = () => {
return 'caches' in window ? caches.open(cacheName).then(cache => cache.add('cache.txt')) : Promise.resolve();
};
const checkUpdates = () => {
// Cookie
assert_equals(document.cookie, 'PresentationApiTest=Controlling-UA', 'A cookie store is not shared with a receiving user agent.');
// Web Storage
assert_equals(sessionStorage.length, 1, 'Session storage is not shared with a receiving user agent.');
assert_equals(sessionStorage.getItem(storageName), storageValue, 'Session storage is not shared with a receiving user agent.');
assert_equals(localStorage.length, 1, 'Local storage is not shared with a receiving user agent.');
assert_equals(localStorage.getItem(storageName), storageValue, 'Local storage is not shared with a receiving user agent.');
};
// Indexed Database
const checkIndexedDB = t => {
if ('indexedDB' in window) {
const req = indexedDB.open(dbName.receiver);
const upgradeneededWatcher = new EventWatcher(t, req, 'upgradeneeded');
const successWatcher = new EventWatcher(t, req, 'success');
return Promise.race([
upgradeneededWatcher.wait_for('upgradeneeded').then(evt => {
evt.target.result.close();
}),
successWatcher.wait_for('success').then(evt => {
evt.target.result.close();
// This would fail if the database created by the receiving UA is visible to the controlling UA
assert_unreached('Indexed Database is not shared with a receiving user agent.');
})
]);
}
else
return Promise.resolve();
};
// Service Workers
const checkServiceWorkers = () => {
return 'serviceWorker' in navigator ? navigator.serviceWorker.getRegistrations().then(registrations => {
const message = 'List of registered service worker registrations is not shared with a receiving user agent.';
assert_equals(registrations.length, 1, message);
assert_equals(registrations[0].active.scriptURL, new Request('serviceworker.js').url, message);
}) : Promise.resolve();
};
const checkCaches = () => {
const message = 'Cache storage is not shared with a receiving user agent.';
return 'caches' in window ? caches.keys().then(keys => {
assert_equals(keys.length, 1, message);
assert_equals(keys[0], cacheName, message);
return caches.open(keys[0]);
}).then(cache => cache.matchAll())
.then(responses => {
assert_equals(responses.length, 1, message);
assert_equals(responses[0].url, new Request('cache.txt').url, message);
}) : Promise.resolve();
};
let timeout;
const enableTimeout = () => {
timeout = t.step_timeout(() => {
t.force_timeout();
t.done();
}, 5000);
};
const disableTimeout = () => {
clearTimeout(timeout);
};
const cancelWait = () => {
connection.removeEventListener('close', onTerminate);
connection.removeEventListener('terminate', onTerminate);
};
const onTerminate = (reject, event) => {
cancelWait();
reject('A receiving user agent unexpectedly ' + event.type + 'd a presentation. ');
};
const waitForTerminate = () => {
return new Promise((resolve, reject) => {
connection.addEventListener('close', onTerminate.bind(this, reject));
connection.addEventListener('terminate', onTerminate.bind(this, reject));
});
};
// Start a presentation for receiving user agent tests
return request.start().then(c => {
connection = c;
enableTimeout();
// This Promise.race will be rejected if a receiving side terminates/closes the connection when window.close() is invoked
return Promise.race([
openIndexedDB()
.then(registerServiceWorker)
.then(openCaches)
.then(() => { return stash.init(); })
.then(() => { return stash.receive(); }),
waitForTerminate()]);
}).then(result => {
// terminate and connect again if the result is PASS
cancelWait();
const json = JSON.parse(result);
if (json.test.status !== 0)
return json;
// Check accessibility to window clients before terminating a presentation
return getClientUrls().then(urls => {
assert_true(urls.length === clientUrls.length && urls.every((value, index) => { return clientUrls[index] === value}),
'A window client in a receiving user agent is not accessible to a service worker on a controlling user agent.');
const eventWatcher = new EventWatcher(t, connection, 'terminate');
connection.terminate();
return eventWatcher.wait_for('terminate');
}).then(() => {
connection = null;
disableTimeout();
presentBtn.removeEventListener('click', main);
presentBtn.textContent = 'Continue Presentation Test';
presentBtn.disabled = false;
const eventWatcher = new EventWatcher(t, presentBtn, 'click');
return eventWatcher.wait_for('click');
}).then(() => {
presentBtn.disabled = true;
return request.start();
}).then(c => {
connection = c;
enableTimeout();
return stash.receive();
}).then(result => {
return JSON.parse(result);
});
}).then(json => {
if (json.test.status === 0) {
checkUpdates();
return checkIndexedDB(t)
.then(checkServiceWorkers)
.then(checkCaches);
}
else {
receiverStack = json.test.stack;
parseResult(json.test.message);
}
});
});
};
presentBtn.addEventListener('click', main);
</script>