tor-browser

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

noopener-helper.js (6167B)


      1 const executor_path = '/common/dispatcher/executor.html?pipe=';
      2 const coop_header = policy => {
      3  return `|header(Cross-Origin-Opener-Policy,${policy})`;
      4 };
      5 
      6 function getExecutorPath(uuid, origin, coop_header) {
      7  return origin.origin + executor_path + coop_header + `&uuid=${uuid}`;
      8 }
      9 
     10 // Open a same-origin popup with `opener_coop` header, then open a popup with
     11 // `openee_coop` header. Check whether the opener can script the openee.
     12 const test_noopener_opening_popup = (
     13  opener_coop,
     14  openee_coop,
     15  origin,
     16  opener_expectation
     17 ) => {
     18  promise_test(async t => {
     19    // Set up dispatcher communications.
     20    const popup_token = token();
     21    const reply_token = token();
     22    const popup_reply_token = token();
     23    const popup_openee_token = token();
     24 
     25    const popup_url = getExecutorPath(
     26      popup_token, SAME_ORIGIN, coop_header(opener_coop));
     27 
     28    // We open a popup and then ping it, it will respond after loading.
     29    const popup = window.open(popup_url);
     30    t.add_cleanup(() => send(popup_token, 'window.close()'));
     31    send(popup_token, `send('${reply_token}', 'Popup loaded');`);
     32    assert_equals(await receive(reply_token), 'Popup loaded');
     33 
     34    if (opener_coop == 'noopener-allow-popups') {
     35      // Assert that we can't script the popup.
     36      await t.step_wait(() => popup.closed, 'Opener popup.closed')
     37    }
     38 
     39    // Ensure that the popup has no access to its opener.
     40    send(popup_token, `
     41      let openerDOMAccessAllowed = false;
     42      try {
     43        openerDOMAccessAllowed = !!self.opener.document.URL;
     44      } catch(ex) {}
     45      const payload = {
     46        opener: !!self.opener,
     47        openerDOMAccess: openerDOMAccessAllowed
     48      };
     49      send('${reply_token}', JSON.stringify(payload));
     50    `);
     51    let payload = JSON.parse(await receive(reply_token));
     52    if (opener_coop == 'noopener-allow-popups') {
     53      assert_false(payload.opener, 'popup opener');
     54      assert_false(payload.openerDOMAccess, 'popup DOM access');
     55    }
     56 
     57    // Open another popup from inside the popup, and wait for it to load.
     58    const popup_openee_url = getExecutorPath(popup_openee_token, origin,
     59      coop_header(openee_coop));
     60    send(popup_token, `
     61      window.openee = open("${popup_openee_url}");
     62      await receive('${popup_reply_token}');
     63      const payload = {
     64        openee: !!window.openee,
     65        closed: window.openee.closed
     66      };
     67      send('${reply_token}', JSON.stringify(payload));
     68    `);
     69    t.add_cleanup(() => send(popup_token, 'window.openee.close()'));
     70    // Notify the popup that its openee was loaded.
     71    send(popup_openee_token, `
     72      send('${popup_reply_token}', 'popup openee opened');
     73    `);
     74 
     75    // Assert that the popup has access to its openee.
     76    payload = JSON.parse(await receive(reply_token));
     77    assert_true(payload.openee, 'popup openee');
     78 
     79    // Assert that the openee has access to its popup opener.
     80    send(popup_openee_token, `
     81      let openerDOMAccessAllowed = false;
     82      try {
     83        openerDOMAccessAllowed = !!self.opener.document.URL;
     84      } catch(ex) {}
     85      const payload = {
     86        opener: !!self.opener,
     87        openerDOMAccess: openerDOMAccessAllowed
     88      };
     89      send('${reply_token}', JSON.stringify(payload));
     90    `);
     91    payload = JSON.parse(await receive(reply_token));
     92    if (opener_expectation) {
     93      assert_true(payload.opener, 'Opener is not null');
     94      assert_true(payload.openerDOMAccess, 'No DOM access');
     95    } else {
     96      assert_false(payload.opener, 'Opener is null');
     97      assert_false(payload.openerDOMAccess, 'No DOM access');
     98    }
     99  },
    100  'noopener-allow-popups ensures that the opener cannot script the openee,' +
    101  ' but further popups with no COOP can access their opener: ' +
    102  opener_coop + '/' + openee_coop + ':' + origin == SAME_ORIGIN);
    103 };
    104 
    105 // Open a same-origin popup with `popup_coop` header, then navigate away toward
    106 // one with `noopener-allow-popups`. Check the opener can't script the openee.
    107 const test_noopener_navigating_away = (popup_coop) => {
    108  promise_test(async t => {
    109    // Set up dispatcher communications.
    110    const popup_token = token();
    111    const reply_token = token();
    112    const popup_reply_token = token();
    113    const popup_second_token = token();
    114 
    115    const popup_url =
    116      getExecutorPath(popup_token, SAME_ORIGIN, coop_header(popup_coop));
    117 
    118    // We open a popup and then ping it, it will respond after loading.
    119    const popup = window.open(popup_url);
    120    send(popup_token, `send('${reply_token}', 'Popup loaded');`);
    121    assert_equals(await receive(reply_token), 'Popup loaded');
    122    t.add_cleanup(() => send(popup_token, 'window.close()'));
    123 
    124    // There's an open question if we should check that popup.window is null here.
    125    // See https://github.com/whatwg/html/issues/10457
    126    // Assert that we cannot script the popup.
    127    assert_false(popup.closed, 'popup closed');
    128 
    129    // Ensure that the popup has no access to its opener.
    130    send(popup_token, `
    131      let openerDOMAccessAllowed = false;
    132      try {
    133        openerDOMAccessAllowed = !!self.opener.document.URL;
    134      } catch(ex) {}
    135      const payload = {
    136        opener: !!self.opener,
    137        openerDOMAccess: openerDOMAccessAllowed
    138      };
    139      send('${reply_token}', JSON.stringify(payload));
    140    `);
    141    let payload = JSON.parse(await receive(reply_token));
    142    assert_true(payload.opener, 'popup opener');
    143    assert_true(payload.openerDOMAccess, 'popup DOM access');
    144 
    145    // Navigate the popup.
    146    const popup_second_url = getExecutorPath(
    147      popup_second_token, SAME_ORIGIN,
    148      coop_header('noopener-allow-popups'));
    149 
    150    send(popup_token, `
    151      window.location = '${popup_second_url}';
    152    `);
    153 
    154    // Notify the popup that its openee was loaded.
    155    send(popup_second_token, `
    156      send('${reply_token}', 'popup navigated away');
    157    `);
    158    assert_equals(await receive(reply_token), 'popup navigated away');
    159 
    160    // There's an open question if we should check that popup.window is null here.
    161    // See https://github.com/whatwg/html/issues/10457
    162    assert_true(popup.closed, 'popup.closed');
    163  },
    164  'noopener-allow-popups ensures that the opener cannot script the openee,' +
    165  ' even after a navigation: ' + popup_coop);
    166 };