| <!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="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, db; |
| const presentBtn = document.getElementById('presentBtn'); |
| 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'); |
| |
| if (db) |
| db.close(); |
| indexedDB.deleteDatabase(dbName); |
| |
| 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 dbName = 'db-presentation-api'; |
| const storeName = 'store-controlling-ua'; |
| const storeData = { |
| id: 'controller', |
| data: 'controlling user agent' |
| }; |
| const openIndexedDB = () => { |
| if ('indexedDB' in window) { |
| const dbReq = indexedDB.open(dbName, 1); |
| return new Promise((resolve, reject) => { |
| dbReq.onupgradeneeded = () => { |
| db = dbReq.result; |
| const store = db.createObjectStore(storeName, { keyPath: 'id' }); |
| store.add(storeData); |
| }; |
| dbReq.onsuccess = () => { |
| db = dbReq.result; |
| db.close(); |
| db = null; |
| resolve(); |
| }; |
| dbReq.onerror = reject; |
| }); |
| } |
| 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]); |
| } |
| }); |
| }); |
| }; |
| 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 message = 'Indexed Database is not shared with a receiving user agent.'; |
| let req = indexedDB.open(dbName, 1), store; |
| let eventWatcher = new EventWatcher(t, req, 'success'); |
| return eventWatcher.wait_for('success').then(() => { |
| db = req.result; |
| const transaction = db.transaction(storeName, 'readwrite'); |
| store = transaction.objectStore(storeName); |
| req = store.openCursor(); |
| eventWatcher = new EventWatcher(t, req, 'success'); |
| return eventWatcher.wait_for('success'); |
| }).then(() => { |
| assert_true(req.result instanceof IDBCursorWithValue, message); |
| const cursor = req.result; |
| const item = cursor.value; |
| assert_equals(item.id, storeData.id, message); |
| assert_equals(item.data, storeData.data, message); |
| cursor.continue(); |
| return eventWatcher.wait_for('success'); |
| }).then(() => { |
| assert_equals(req.result, null, message); |
| db.close(); |
| db = null; |
| }); |
| } |
| 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> |