| <!DOCTYPE html> |
| |
| <meta charset="utf-8"> |
| <title>Creating a receiving browsing context</title> |
| <link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> |
| <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="stash.js"></script> |
| <style> |
| a { visibility: hidden; } |
| iframe { width: 100%; } |
| </style> |
| |
| <iframe id="child" src="PresentationReceiver_create_receiving-ua_child.html"></iframe> |
| <p id="notice">Checking <code id="modal"></code>: if you see this message, please wait for test to time out.</p> |
| <a href="PresentationReceiver_unreached_receiving-ua.html">do not navigate</a> |
| <script> |
| let finished = false; |
| |
| const sendResult = (test, status) => { |
| if(!finished) { |
| finished = true; |
| const stash = new Stash(stashIds.toReceiver, stashIds.toController); |
| stash.send(JSON.stringify({ test: test, status: status })); |
| } |
| }; |
| |
| add_completion_callback((tests, status) => { |
| // note: a single test result is supposed to appear here. |
| sendResult(tests[0], status); |
| }); |
| |
| const child = document.getElementById('child'); |
| child.addEventListener('load', () => { |
| const notice = document.getElementById('notice'); |
| const modal = document.getElementById('modal'); |
| |
| const dbName = { |
| controller: 'db-presentation-api-controlling-ua', |
| receiver: 'db-presentation-api-receiving-ua' |
| }; |
| |
| promise_test(t => { |
| t.add_cleanup(() => { |
| document.cookie = cookieName + '=False;Expires=' + new Date().toUTCString(); |
| document.cookie = cookieNameChild + '=False;Expires=' + new Date().toUTCString(); |
| sessionStorage.removeItem(storageName); |
| localStorage.removeItem(storageName); |
| sessionStorage.removeItem(storageNameChild); |
| localStorage.removeItem(storageNameChild); |
| |
| Object.values(dbName).forEach(name => { |
| indexedDB.deleteDatabase(name); |
| }); |
| |
| 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))); |
| }); |
| } |
| }); |
| |
| // Session history |
| assert_equals(window.history.length, 1, 'Session history consists of the current page only.'); |
| |
| // The Sandboxed auxiliary navigation browsing context flag |
| assert_equals(window.open('PresentationReceiver_unreached_receiving-ua.html'), null, 'Sandboxed auxiliary navigation browsing context flag is set.'); |
| |
| // The sandboxed top-level navigation browsing context flag (codes below are expected to be ignored) |
| window.close(); |
| document.querySelector('a').click(); |
| location.href = 'PresentationReceiver_unreached_receiving-ua.html'; |
| |
| // The sandboxed modals flag (codes below are expected to be ignored): |
| // If user agent prompts user, a timeout will occur and the test will eventually fail |
| let message = 'If you see this prompt, do not dismiss it and wait for test to time out.'; |
| notice.style.display = 'block'; |
| modal.textContent = 'alert()'; |
| alert(message); |
| modal.textContent = 'confirm()'; |
| confirm(message); |
| modal.textContent = 'print()'; |
| print(); |
| modal.textContent = 'prompt()'; |
| prompt(message); |
| notice.style.display = 'none'; |
| |
| // Permissions |
| const checkPermission = query => { |
| return navigator.permissions ? navigator.permissions.query(query).then(status => { |
| assert_equals(status.state, 'denied', 'The state of the "' + query.name + '" permission is set to "denied".'); |
| }, () => { /* skip this assertion if the specified permission is not implemented */ }) : Promise.resolve(); |
| } |
| |
| // Cookie |
| assert_equals(document.cookie, '', 'A cookie store is set to an empty store.') |
| |
| // Indexed Database |
| const checkIndexedDB = () => { |
| if ('indexedDB' in window) { |
| // The test would fail when the database is already created by the controlling UA |
| const req = indexedDB.open(dbName.controller); |
| 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(); |
| const req = indexedDB.open(dbName.receiver, 2); |
| const eventWatcher = new EventWatcher(t, req, 'upgradeneeded'); |
| return eventWatcher.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 controlling UA is visible to the receiving UA |
| assert_unreached('Indexed Database is set to an empty storage.'); |
| }) |
| ]); |
| } |
| else |
| return Promise.resolve(); |
| }; |
| |
| // Web Storage |
| assert_equals(sessionStorage.length, 0, 'Session storage is set to an empty storage.'); |
| assert_equals(localStorage.length, 0, 'Local storage is set to an empty storage.'); |
| |
| // Service Workers |
| const checkServiceWorkers = () => { |
| return 'serviceWorker' in navigator ? navigator.serviceWorker.getRegistrations().then(registrations => { |
| assert_equals(registrations.length, 0, 'List of registered service worker registrations is empty.'); |
| }) : Promise.resolve(); |
| }; |
| const checkCaches = () => { |
| return 'caches' in window ? caches.keys().then(keys => { |
| assert_equals(keys.length, 0, 'Cache storage is empty.') |
| }) : Promise.resolve(); |
| }; |
| |
| // Navigation |
| const checkNavigation = () => { |
| return navigator.presentation.receiver.connectionList.then(connectionList => { |
| assert_equals(connectionList.connections.length, 1, 'The initial number of presentation connections is one'); |
| assert_equals(location.href, connectionList.connections[0].url, 'A receiving browsing context is navigated to the presentation URL.'); |
| }); |
| }; |
| |
| // Update storages and service workers shared with the top-level brosing context |
| const cookieName = 'PresentationApiTest'; |
| const cookieValue = 'Receiving-UA'; |
| const storageName = 'presentation_api_test'; |
| const storageValue = 'receiving-ua'; |
| document.cookie = cookieName + '=' + cookieValue; |
| sessionStorage.setItem(storageName, storageValue); |
| localStorage.setItem(storageName, storageValue); |
| |
| // Service Workers and Caches |
| const cacheName = 'receiving-ua'; |
| const getClientUrls = () => { |
| return new Promise(resolve => { |
| navigator.serviceWorker.getRegistration().then(reg => { |
| const channel = new MessageChannel(); |
| channel.port1.onmessage = event => { |
| resolve(event.data); |
| }; |
| reg.active.postMessage('test', [channel.port2]); |
| }); |
| }); |
| }; |
| |
| 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 => { |
| assert_true(urls.every(url => { return url !== new Request('../PresentationReceiver_create-manual.https.html').url }), |
| 'A window client in a controlling user agent is not accessible to a service worker on a receiving user agent.'); |
| }); |
| }; |
| |
| const openCaches = () => { |
| return 'caches' in window ? caches.open(cacheName).then(cache => cache.add('../cache.txt')) : Promise.resolve(); |
| }; |
| |
| const getChildFrameResult = () => { |
| return new Promise(resolve => { |
| window.addEventListener('message', t.step_func(event => { |
| const result = event.data; |
| if (result.type === 'presentation-api') { |
| // if the test in iframe failed, report the result and abort the test |
| if (result.test.status === 0) |
| resolve(); |
| else { |
| sendResult(result.test, result.status); |
| assert_unreached('A test for a nested browsing context failed.'); |
| } |
| } |
| })); |
| child.contentWindow.postMessage('start', location.origin); |
| }); |
| }; |
| |
| // check the results from updates by iframe |
| const cookieNameChild = 'NestedBrowsingContext'; |
| const cookieValueChild = 'True'; |
| const storageNameChild = 'nested_browsing_context'; |
| const storageValueChild = 'yes'; |
| |
| const checkUpdatedResult = () => { |
| // cookie |
| const cookies = document.cookie.split(/;\s*/).reduce((result, item) => { |
| const t = item.split('='); |
| result[t[0]] = t[1]; |
| return result; |
| }, {}); |
| message = 'A cookie store is shared by top-level and nested browsing contexts.' |
| assert_equals(Object.keys(cookies).length, 2, message); |
| assert_equals(cookies[cookieName], cookieValue, message); |
| assert_equals(cookies[cookieNameChild], cookieValueChild, message); |
| |
| // Web Storage |
| message = 'Session storage is shared by top-level and nested browsing contexts.'; |
| assert_equals(sessionStorage.length, 2, message); |
| assert_equals(sessionStorage.getItem(storageName), storageValue, message); |
| assert_equals(sessionStorage.getItem(storageNameChild), storageValueChild, message); |
| message = 'Local storage is shared by top-level and nested browsing contexts.'; |
| assert_equals(localStorage.length, 2, message); |
| assert_equals(localStorage.getItem(storageName), storageValue, message); |
| assert_equals(localStorage.getItem(storageNameChild), storageValueChild, message); |
| }; |
| |
| // Indexed Database |
| const checkUpdatedIndexedDB = () => { |
| if ('indexedDB' in window) { |
| message = 'Indexed Database is shared by top-level and nested browsing contexts.'; |
| 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(); |
| // Check if the version of the database is upgraded to 3 by the nested browsing context |
| assert_unreached(message); |
| }), |
| successWatcher.wait_for('success').then(evt => { |
| const db = evt.target.result; |
| const version = db.version; |
| db.close(); |
| // Check if the version of the database is upgraded to 3 by the nested browsing context |
| assert_equals(version, 3, message); |
| }) |
| ]); |
| } |
| else |
| return Promise.resolve(); |
| }; |
| |
| // Service Workers |
| const checkUpdatedServiceWorkers = () => { |
| return 'serviceWorker' in window ? navigator.serviceWorker.getRegistrations().then(registrations => { |
| message = 'List of registered service worker registrations is shared by top-level and nested browsing contexts.'; |
| assert_equals(registrations.length, 2, message); |
| const scriptURLs = registrations.map(reg => { return reg.active.scriptURL; }).sort(); |
| assert_equals(scriptURLs[0], new Request('../serviceworker.js').url, message); |
| assert_equals(scriptURLs[1], new Request('serviceworker.js').url, message); |
| }) : Promise.resolve(); |
| }; |
| const cacheNameChild = 'nested-browsing-context'; |
| const checkUpdatedCaches = () => { |
| message = 'Cache storage is shared by top-level and nested browsing contexts.'; |
| return 'caches' in window ? caches.keys().then(keys => { |
| assert_equals(keys.length, 2, message); |
| const cacheKeys = keys.sort(); |
| assert_equals(cacheKeys[0], cacheNameChild, message); |
| assert_equals(cacheKeys[1], cacheName, message); |
| return Promise.all(keys.map( |
| key => caches.open(key) |
| .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(); |
| }; |
| |
| // Asynchronous tests |
| const permissionList = [ |
| { name: 'geolocation' }, |
| { name: 'notifications' }, |
| { name: 'push', userVisibleOnly: true }, |
| { name: 'midi' }, |
| { name: 'camera' }, |
| { name: 'microphone' }, |
| { name: 'speaker' }, |
| { name: 'background-sync' } |
| ]; |
| return Promise.all(permissionList.map(perm => { return checkPermission(perm); })) |
| .then(checkIndexedDB) |
| .then(checkServiceWorkers) |
| .then(checkCaches) |
| .then(checkNavigation) |
| .then(registerServiceWorker) |
| .then(openCaches) |
| .then(getChildFrameResult) |
| .then(checkUpdatedResult) |
| .then(checkUpdatedIndexedDB) |
| .then(checkUpdatedServiceWorkers) |
| .then(checkUpdatedCaches); |
| }); |
| }); |
| </script> |