tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PresentationReceiver_create-manual.https.html (12645B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8">
      3 <title>Creating a receiving browsing context</title>
      4 <link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs">
      5 <link rel="help" href="https://w3c.github.io/presentation-api/#creating-a-receiving-browsing-context">
      6 <script src="/resources/testharness.js"></script>
      7 <script src="/resources/testharnessreport.js"></script>
      8 <script src="common.js"></script>
      9 <script src="support/stash.js"></script>
     10 
     11 <p id="notice">
     12  Click the button below and select the available presentation display, to start the manual test. The test passes if a "PASS" result appears.<br>
     13  This test asks you to click the button twice, unless the test fails.<br>
     14  <button id="presentBtn">Start Presentation Test</button>
     15 </p>
     16 
     17 <script>
     18    setup({explicit_timeout: true});
     19 
     20    let receiverStack;
     21    add_completion_callback(() => {
     22        // overwrite a stack written in the test result
     23        if (receiverStack) {
     24            document.querySelector('#log pre').textContent = receiverStack;
     25        }
     26    });
     27 
     28    let connection;
     29    const presentBtn = document.getElementById('presentBtn');
     30 
     31    const dbName = {
     32        controller: 'db-presentation-api-controlling-ua',
     33        receiver: 'db-presentation-api-receiving-ua'
     34    };
     35 
     36    const main = () => {
     37        promise_test(t => {
     38            presentBtn.disabled = true;
     39            const stash = new Stash(stashIds.toController, stashIds.toReceiver);
     40            const request = new PresentationRequest('support/PresentationReceiver_create_receiving-ua.html');
     41 
     42            t.add_cleanup(() => {
     43                const notice = document.getElementById('notice');
     44                notice.parentNode.removeChild(notice);
     45                stash.stop();
     46 
     47                // history.back();
     48                document.cookie = 'PresentationApiTest=true; Expires=' + new Date().toUTCString();
     49                sessionStorage.removeItem('presentation_api_test');
     50                localStorage.removeItem('presentation_api_test');
     51 
     52                Object.values(dbName).forEach(name => {
     53                    indexedDB.deleteDatabase(name);
     54                });
     55 
     56                if (connection) {
     57                    connection.onconnect = () => { connection.terminate(); };
     58                    if (connection.state === 'closed')
     59                        request.reconnect(connection.id);
     60                    else
     61                        connection.terminate();
     62                }
     63 
     64                if ('serviceWorker' in navigator) {
     65                    navigator.serviceWorker.getRegistrations().then(registrations => {
     66                        return Promise.all(registrations.map(reg => reg.unregister()));
     67                    });
     68                }
     69                if ('caches' in window) {
     70                    caches.keys().then(keys => {
     71                        return Promise.all(keys.map(key => caches.delete(key)));
     72                    });
     73                }
     74            });
     75 
     76            history.pushState(null, 'test', 'PresentationReceiver_create-manual.https.html');
     77            document.cookie = 'PresentationApiTest=Controlling-UA';
     78 
     79            const storageName = 'presentation_api_test';
     80            const storageValue = 'receiving-ua';
     81            sessionStorage.setItem(storageName, storageValue);
     82            localStorage.setItem(storageName, storageValue);
     83 
     84            const openIndexedDB = () => {
     85                if ('indexedDB' in window) {
     86                    const req = indexedDB.open(dbName.controller, 1);
     87                    const eventWatcher = new EventWatcher(t, req, 'upgradeneeded');
     88                    return eventWatcher.wait_for('upgradeneeded').then(evt => {
     89                        evt.target.result.close();
     90                    });
     91                }
     92                else
     93                    return Promise.resolve();
     94            };
     95 
     96            const cacheName = 'controlling-ua';
     97            let clientUrls;
     98            const getClientUrls = () => {
     99                return new Promise(resolve => {
    100                    navigator.serviceWorker.getRegistration().then(reg => {
    101                        if (reg) {
    102                            const channel = new MessageChannel();
    103                            channel.port1.onmessage = event => {
    104                                resolve(event.data);
    105                            };
    106                            reg.active.postMessage('', [channel.port2]);
    107                        }
    108                        else
    109                            resolve([]);
    110                    });
    111                });
    112            };
    113            const registerServiceWorker = () => {
    114                return ('serviceWorker' in navigator ?
    115                    navigator.serviceWorker.register('serviceworker.js').then(registration => {
    116                        return new Promise((resolve, reject) => {
    117                            if (registration.installing) {
    118                                registration.installing.addEventListener('statechange', event => {
    119                                    if(event.target.state === 'installed')
    120                                        resolve();
    121                                });
    122                            }
    123                            else
    124                                resolve();
    125                        });
    126                    }) : Promise.resolve()).then(getClientUrls).then(urls => {
    127                        clientUrls = urls;
    128                    });
    129            };
    130            const openCaches = () => {
    131                return 'caches' in window ? caches.open(cacheName).then(cache => cache.add('cache.txt')) : Promise.resolve();
    132            };
    133 
    134            const checkUpdates = () => {
    135                // Cookie
    136                assert_equals(document.cookie, 'PresentationApiTest=Controlling-UA', 'A cookie store is not shared with a receiving user agent.');
    137 
    138                // Web Storage
    139                assert_equals(sessionStorage.length, 1, 'Session storage is not shared with a receiving user agent.');
    140                assert_equals(sessionStorage.getItem(storageName), storageValue, 'Session storage is not shared with a receiving user agent.');
    141                assert_equals(localStorage.length, 1, 'Local storage is not shared with a receiving user agent.');
    142                assert_equals(localStorage.getItem(storageName), storageValue, 'Local storage is not shared with a receiving user agent.');
    143            };
    144 
    145            // Indexed Database
    146            const checkIndexedDB = t => {
    147                if ('indexedDB' in window) {
    148                    const req = indexedDB.open(dbName.receiver);
    149                    const upgradeneededWatcher = new EventWatcher(t, req, 'upgradeneeded');
    150                    const successWatcher = new EventWatcher(t, req, 'success');
    151                    return Promise.race([
    152                        upgradeneededWatcher.wait_for('upgradeneeded').then(evt => {
    153                            evt.target.result.close();
    154                        }),
    155                        successWatcher.wait_for('success').then(evt => {
    156                            evt.target.result.close();
    157                            // This would fail if the database created by the receiving UA is visible to the controlling UA
    158                            assert_unreached('Indexed Database is not shared with a receiving user agent.');
    159                        })
    160                    ]);
    161                }
    162                else
    163                    return Promise.resolve();
    164            };
    165 
    166            // Service Workers
    167            const checkServiceWorkers = () => {
    168                return 'serviceWorker' in navigator ? navigator.serviceWorker.getRegistrations().then(registrations => {
    169                    const message = 'List of registered service worker registrations is not shared with a receiving user agent.';
    170                    assert_equals(registrations.length, 1, message);
    171                    assert_equals(registrations[0].active.scriptURL, new Request('serviceworker.js').url, message);
    172                }) : Promise.resolve();
    173            };
    174            const checkCaches = () => {
    175                const message = 'Cache storage is not shared with a receiving user agent.';
    176                return 'caches' in window ? caches.keys().then(keys => {
    177                    assert_equals(keys.length, 1, message);
    178                    assert_equals(keys[0], cacheName, message);
    179                    return caches.open(keys[0]);
    180                }).then(cache => cache.matchAll())
    181                .then(responses => {
    182                    assert_equals(responses.length, 1, message);
    183                    assert_equals(responses[0].url, new Request('cache.txt').url, message);
    184                }) : Promise.resolve();
    185            };
    186 
    187            let timeout;
    188            const enableTimeout = () => {
    189                timeout = t.step_timeout(() => {
    190                    t.force_timeout();
    191                    t.done();
    192                }, 5000);
    193            };
    194            const disableTimeout = () => {
    195                clearTimeout(timeout);
    196            };
    197            const cancelWait = () => {
    198                connection.removeEventListener('close', onTerminate);
    199                connection.removeEventListener('terminate', onTerminate);
    200            };
    201            const onTerminate = (reject, event) => {
    202                cancelWait();
    203                reject('A receiving user agent unexpectedly ' + event.type + 'd a presentation. ');
    204            };
    205            const waitForTerminate = () => {
    206                return new Promise((resolve, reject) => {
    207                    connection.addEventListener('close', onTerminate.bind(this, reject));
    208                    connection.addEventListener('terminate', onTerminate.bind(this, reject));
    209                });
    210            };
    211 
    212            // Start a presentation for receiving user agent tests
    213            return request.start().then(c => {
    214                connection = c;
    215                enableTimeout();
    216 
    217                // This Promise.race will be rejected if a receiving side terminates/closes the connection when window.close() is invoked
    218                return Promise.race([
    219                    openIndexedDB()
    220                        .then(registerServiceWorker)
    221                        .then(openCaches)
    222                        .then(() => { return stash.init(); })
    223                        .then(() => { return stash.receive(); }),
    224                    waitForTerminate()]);
    225            }).then(result => {
    226                // terminate and connect again if the result is PASS
    227                cancelWait();
    228                const json = JSON.parse(result);
    229                if (json.test.status !== 0)
    230                    return json;
    231 
    232                // Check accessibility to window clients before terminating a presentation
    233                return getClientUrls().then(urls => {
    234                    assert_true(urls.length === clientUrls.length && urls.every((value, index) => { return clientUrls[index] === value}),
    235                        'A window client in a receiving user agent is not accessible to a service worker on a controlling user agent.');
    236                    const eventWatcher = new EventWatcher(t, connection, 'terminate');
    237                    connection.terminate();
    238                    return eventWatcher.wait_for('terminate');
    239                }).then(() => {
    240                    connection = null;
    241                    disableTimeout();
    242                    presentBtn.removeEventListener('click', main);
    243                    presentBtn.textContent = 'Continue Presentation Test';
    244                    presentBtn.disabled = false;
    245                    const eventWatcher = new EventWatcher(t, presentBtn, 'click');
    246                    return eventWatcher.wait_for('click');
    247                }).then(() => {
    248                    presentBtn.disabled = true;
    249                    return request.start();
    250                }).then(c => {
    251                    connection = c;
    252                    enableTimeout();
    253                    return stash.receive();
    254                }).then(result => {
    255                    return JSON.parse(result);
    256                });
    257            }).then(json => {
    258                if (json.test.status === 0) {
    259                    checkUpdates();
    260                    return checkIndexedDB(t)
    261                            .then(checkServiceWorkers)
    262                            .then(checkCaches);
    263                }
    264                else {
    265                    receiverStack = json.test.stack;
    266                    parseResult(json.test.message);
    267                }
    268            });
    269        });
    270    };
    271    presentBtn.addEventListener('click', main);
    272 </script>