tor-browser

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

javascript-url.https.html (8260B)


      1 <!DOCTYPE html>
      2 <title>Cross-Origin-Opener-Policy and a "javascript:" URL popup</title>
      3 <meta charset="utf-8">
      4 <meta name="timeout" content="long">
      5 <meta name="variant" content="?1-2">
      6 <meta name="variant" content="?3-4">
      7 <meta name="variant" content="?5-6">
      8 <meta name="variant" content="?7-8">
      9 <meta name="variant" content="?9-10">
     10 <meta name="variant" content="?11-12">
     11 <meta name="variant" content="?13-14">
     12 <meta name="variant" content="?15-16">
     13 <meta name="variant" content="?17-last">
     14 <script src="/resources/testharness.js"></script>
     15 <script src="/resources/testharnessreport.js"></script>
     16 <script src="/common/dispatcher/dispatcher.js"></script>
     17 <script src="/common/get-host-info.sub.js"></script>
     18 <script src="/common/subset-tests.js"></script>
     19 <script src="/common/utils.js"></script>
     20 <script src="resources/common.js"></script>
     21 
     22 <p>According to HTML's navigate algorithm, requests to <code>javascript:</code>
     23 URLs should inherit the cross-origin opener policy of the active document. To
     24 observe this, each subtest uses the following procedure.</p>
     25 
     26 <ol>
     27  <li>create popup with a given COOP (the <code>parentCOOP</code>)</li>
     28  <li>navigate the popup to a <code>javascript:</code> URL (the new document is
     29  expected to inherit the <code>parentCOOP</code>)</li>
     30  <li>from the popup, create a second popup window with a given COOP (the
     31  <code>childCOOP</code>)</li>
     32 </ol>
     33 
     34 <p>Both popup windows inspect state and report back to the test.</p>
     35 
     36 <pre>
     37    .---- test ----.
     38    | open(https:) |
     39    |  parentCOOP  |   .----- subject -------.
     40    |      '---------> | --------.           |
     41    |              |   |         v           |
     42    |              |   | assign(javascript:) |
     43    |              |   |  (COOP under test)  |
     44    |              |   |         |           |
     45    |              |   |    open(https:)     |
     46    |              |   |     childCOOP       |    .- child -.
     47    |              |   |         '--------------> |         |
     48    |              |   '---------------------'    '---------'
     49    |              |             |                     |
     50    |  validate    | <--status---+---------------------'
     51    '--------------'
     52 </pre>
     53 
     54 <script>
     55 'use strict';
     56 
     57 function getExecutorPath(uuid, origin, coopHeader) {
     58  const executorPath = '/common/dispatcher/executor.html?';
     59  const coopHeaderPipe =
     60    `|header(Cross-Origin-Opener-Policy,${encodeURIComponent(coopHeader)})`;
     61  return origin + executorPath + `uuid=${uuid}` + '&pipe=' + coopHeaderPipe;
     62 }
     63 
     64 function assert_isolated(results) {
     65  assert_equals(results.childName, '', 'child name');
     66  assert_false(results.childOpener, 'child opener');
     67  // The test subject's reference to the  "child" window must report "closed"
     68  // when COOP enforces isolation because the document initially created during
     69  // the window open steps must be discarded when a new document object is
     70  // created at the end of the navigation.
     71  assert_true(results.childClosed, 'child closed');
     72 }
     73 
     74 function assert_not_isolated(results, expectedName) {
     75  assert_equals(results.childName, expectedName, 'child name');
     76  assert_true(results.childOpener, 'child opener');
     77  assert_false(results.childClosed, 'child closed');
     78 }
     79 
     80 async function javascript_url_test(parentCOOP, childCOOP, origin, resultsVerification) {
     81  promise_test(async t => {
     82    const parentToken = token();
     83    const childToken = token();
     84    const responseToken = token();
     85 
     86    const parentURL = getExecutorPath(
     87      parentToken,
     88      SAME_ORIGIN.origin,
     89      parentCOOP);
     90    const childURL = getExecutorPath(
     91      childToken,
     92      origin.origin,
     93      childCOOP);
     94 
     95    // Open a first popup, referred to as the parent popup, and wait for it to
     96    // load.
     97    window.open(parentURL);
     98    send(parentToken, `send('${responseToken}', 'Done loading');`);
     99    assert_equals(await receive(responseToken), 'Done loading');
    100 
    101    // Make sure the parent popup is removed once the test has run, keeping a
    102    // clean state.
    103    add_completion_callback(() => {
    104      send(parentToken, 'close');
    105    });
    106 
    107    // Navigate the popup to the javascript URL. It should inherit the current
    108    // document's COOP. Because we're navigating to a page that is not an
    109    // executor, we lose access to easy messaging, making things a bit more
    110    // complicated. We use a predetermined scenario of communication that
    111    // enables us to retrieve whether the child popup appears closed from the
    112    // parent popup.
    113    //
    114    // Notes:
    115    // - Splitting the script tag prevents HTML parsing to kick in.
    116    // - The innermost double quotes need a triple backslash, because it goes
    117    //   through two rounds of consuming escape characters (\\\" -> \" -> ").
    118    // - The javascript URL does not accept \n characters so we need to use
    119    //   a new template literal for each line.
    120    send(parentToken,
    121      `location.assign("javascript:'` +
    122      // Include dispatcher.js to have access to send() and receive().
    123      `<script src=\\\"/common/dispatcher/dispatcher.js\\\"></scr` + `ipt>` +
    124      `<script> (async () => {` +
    125 
    126        // Open the child popup and keep a handle to it.
    127        `const w = open(\\\"${childURL}\\\", \\\"${childToken}\\\");` +
    128 
    129        // We wait for the main frame to query the w.closed property.
    130        `await receive(\\\"${parentToken}\\\");` +
    131        `send(\\\"${responseToken}\\\", w.closed);` +
    132 
    133        // Finally we wait for the cleanup indicating that this popup can be
    134        // closed.
    135        `await receive(\\\"${parentToken}\\\");` +
    136        `close();` +
    137        `})()</scr` + `ipt>'");`
    138    );
    139 
    140    // Make sure the javascript navigation ran, and the child popup was created.
    141    send(childToken, `send('${responseToken}', 'Done loading');`);
    142    assert_equals(await receive(responseToken), 'Done loading');
    143 
    144    // Make sure the child popup is removed once the test has run, keeping a
    145    // clean state.
    146    add_completion_callback(() => {
    147      send(childToken, `close()`);
    148    });
    149 
    150    // Give some time for things to settle across processes etc. before
    151    // proceeding with verifications.
    152    await new Promise(resolve => { t.step_timeout(resolve, 500); });
    153 
    154    // Gather information about the child popup and verify that they match what
    155    // we expect.
    156    const results = {};
    157    send(parentToken, 'query');
    158    results.childClosed = await receive(responseToken) === 'true';
    159 
    160    send(childToken, `send('${responseToken}', opener != null);`);
    161    results.childOpener = await receive(responseToken) === 'true';
    162 
    163    send(childToken, `send('${responseToken}', name);`);
    164    results.childName = await receive(responseToken);
    165 
    166    resultsVerification(results, childToken);
    167  }, `navigation: ${origin.name}; ` + `parentCOOP: ${parentCOOP}; ` +
    168     `childCOOP: ${childCOOP}`);
    169 }
    170 
    171 const tests = [
    172  ['unsafe-none', 'unsafe-none', SAME_ORIGIN, assert_not_isolated],
    173  ['unsafe-none', 'unsafe-none', SAME_SITE, assert_not_isolated],
    174  ['unsafe-none', 'same-origin-allow-popups', SAME_ORIGIN, assert_isolated],
    175  ['unsafe-none', 'same-origin-allow-popups', SAME_SITE, assert_isolated],
    176  ['unsafe-none', 'same-origin', SAME_ORIGIN, assert_isolated],
    177  ['unsafe-none', 'same-origin', SAME_SITE, assert_isolated],
    178  ['same-origin-allow-popups', 'unsafe-none', SAME_ORIGIN, assert_not_isolated],
    179  ['same-origin-allow-popups', 'unsafe-none', SAME_SITE, assert_not_isolated],
    180  ['same-origin-allow-popups', 'same-origin-allow-popups', SAME_ORIGIN, assert_not_isolated],
    181  ['same-origin-allow-popups', 'same-origin-allow-popups', SAME_SITE, assert_isolated],
    182  ['same-origin-allow-popups', 'same-origin', SAME_ORIGIN, assert_isolated],
    183  ['same-origin-allow-popups', 'same-origin', SAME_SITE, assert_isolated],
    184  ['same-origin', 'unsafe-none', SAME_ORIGIN, assert_isolated],
    185  ['same-origin', 'unsafe-none', SAME_SITE, assert_isolated],
    186  ['same-origin', 'same-origin-allow-popups', SAME_ORIGIN, assert_isolated],
    187  ['same-origin', 'same-origin-allow-popups', SAME_SITE, assert_isolated],
    188  ['same-origin', 'same-origin', SAME_ORIGIN, assert_not_isolated],
    189  ['same-origin', 'same-origin', SAME_SITE, assert_isolated],
    190 ].forEach(([parentCOOP, childCOOP, origin, expectation]) => {
    191  subsetTest(
    192    javascript_url_test,
    193    parentCOOP,
    194    childCOOP,
    195    origin,
    196    expectation);
    197 });
    198 
    199 </script>