| <!DOCTYPE html> |
| <title>Cross-Origin-Opener-Policy and a "javascript:" URL popup</title> |
| <meta charset="utf-8"> |
| <meta name="timeout" content="long"> |
| <meta name="variant" content="?1-2"> |
| <meta name="variant" content="?3-4"> |
| <meta name="variant" content="?5-6"> |
| <meta name="variant" content="?7-8"> |
| <meta name="variant" content="?9-10"> |
| <meta name="variant" content="?11-12"> |
| <meta name="variant" content="?13-14"> |
| <meta name="variant" content="?15-16"> |
| <meta name="variant" content="?17-last"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/common/dispatcher/dispatcher.js"></script> |
| <script src="/common/get-host-info.sub.js"></script> |
| <script src="/common/subset-tests.js"></script> |
| <script src="/common/utils.js"></script> |
| <script src="resources/common.js"></script> |
| |
| <p>According to HTML's navigate algorithm, requests to <code>javascript:</code> |
| URLs should inherit the cross-origin opener policy of the active document. To |
| observe this, each subtest uses the following procedure.</p> |
| |
| <ol> |
| <li>create popup with a given COOP (the <code>parentCOOP</code>)</li> |
| <li>navigate the popup to a <code>javascript:</code> URL (the new document is |
| expected to inherit the <code>parentCOOP</code>)</li> |
| <li>from the popup, create a second popup window with a given COOP (the |
| <code>childCOOP</code>)</li> |
| </ol> |
| |
| <p>Both popup windows inspect state and report back to the test.</p> |
| |
| <pre> |
| .---- test ----. |
| | open(https:) | |
| | parentCOOP | .----- subject -------. |
| | '---------> | --------. | |
| | | | v | |
| | | | assign(javascript:) | |
| | | | (COOP under test) | |
| | | | | | |
| | | | open(https:) | |
| | | | childCOOP | .- child -. |
| | | | '--------------> | | |
| | | '---------------------' '---------' |
| | | | | |
| | validate | <--status---+---------------------' |
| '--------------' |
| </pre> |
| |
| <script> |
| 'use strict'; |
| |
| function getExecutorPath(uuid, origin, coopHeader) { |
| const executorPath = '/common/dispatcher/executor.html?'; |
| const coopHeaderPipe = |
| `|header(Cross-Origin-Opener-Policy,${encodeURIComponent(coopHeader)})`; |
| return origin + executorPath + `uuid=${uuid}` + '&pipe=' + coopHeaderPipe; |
| } |
| |
| function assert_isolated(results) { |
| assert_equals(results.childName, '', 'child name'); |
| assert_false(results.childOpener, 'child opener'); |
| // The test subject's reference to the "child" window must report "closed" |
| // when COOP enforces isolation because the document initially created during |
| // the window open steps must be discarded when a new document object is |
| // created at the end of the navigation. |
| assert_true(results.childClosed, 'child closed'); |
| } |
| |
| function assert_not_isolated(results, expectedName) { |
| assert_equals(results.childName, expectedName, 'child name'); |
| assert_true(results.childOpener, 'child opener'); |
| assert_false(results.childClosed, 'child closed'); |
| } |
| |
| async function javascript_url_test(parentCOOP, childCOOP, origin, resultsVerification) { |
| promise_test(async t => { |
| const parentToken = token(); |
| const childToken = token(); |
| const responseToken = token(); |
| |
| const parentURL = getExecutorPath( |
| parentToken, |
| SAME_ORIGIN.origin, |
| parentCOOP); |
| const childURL = getExecutorPath( |
| childToken, |
| origin.origin, |
| childCOOP); |
| |
| // Open a first popup, referred to as the parent popup, and wait for it to |
| // load. |
| window.open(parentURL); |
| send(parentToken, `send('${responseToken}', 'Done loading');`); |
| assert_equals(await receive(responseToken), 'Done loading'); |
| |
| // Make sure the parent popup is removed once the test has run, keeping a |
| // clean state. |
| add_completion_callback(() => { |
| send(parentToken, 'close'); |
| }); |
| |
| // Navigate the popup to the javascript URL. It should inherit the current |
| // document's COOP. Because we're navigating to a page that is not an |
| // executor, we lose access to easy messaging, making things a bit more |
| // complicated. We use a predetermined scenario of communication that |
| // enables us to retrieve whether the child popup appears closed from the |
| // parent popup. |
| // |
| // Notes: |
| // - Splitting the script tag prevents HTML parsing to kick in. |
| // - The innermost double quotes need a triple backslash, because it goes |
| // through two rounds of consuming escape characters (\\\" -> \" -> "). |
| // - The javascript URL does not accept \n characters so we need to use |
| // a new template literal for each line. |
| send(parentToken, |
| `location.assign("javascript:'` + |
| // Include dispatcher.js to have access to send() and receive(). |
| `<script src=\\\"/common/dispatcher/dispatcher.js\\\"></scr` + `ipt>` + |
| `<script> (async () => {` + |
| |
| // Open the child popup and keep a handle to it. |
| `const w = open(\\\"${childURL}\\\", \\\"${childToken}\\\");` + |
| |
| // We wait for the main frame to query the w.closed property. |
| `await receive(\\\"${parentToken}\\\");` + |
| `send(\\\"${responseToken}\\\", w.closed);` + |
| |
| // Finally we wait for the cleanup indicating that this popup can be |
| // closed. |
| `await receive(\\\"${parentToken}\\\");` + |
| `close();` + |
| `})()</scr` + `ipt>'");` |
| ); |
| |
| // Make sure the javascript navigation ran, and the child popup was created. |
| send(childToken, `send('${responseToken}', 'Done loading');`); |
| assert_equals(await receive(responseToken), 'Done loading'); |
| |
| // Make sure the child popup is removed once the test has run, keeping a |
| // clean state. |
| add_completion_callback(() => { |
| send(childToken, `close()`); |
| }); |
| |
| // Give some time for things to settle across processes etc. before |
| // proceeding with verifications. |
| await new Promise(resolve => { t.step_timeout(resolve, 500); }); |
| |
| // Gather information about the child popup and verify that they match what |
| // we expect. |
| const results = {}; |
| send(parentToken, 'query'); |
| results.childClosed = await receive(responseToken) === 'true'; |
| |
| send(childToken, `send('${responseToken}', opener != null);`); |
| results.childOpener = await receive(responseToken) === 'true'; |
| |
| send(childToken, `send('${responseToken}', name);`); |
| results.childName = await receive(responseToken); |
| |
| resultsVerification(results, childToken); |
| }, `navigation: ${origin.name}; ` + `parentCOOP: ${parentCOOP}; ` + |
| `childCOOP: ${childCOOP}`); |
| } |
| |
| const tests = [ |
| ['unsafe-none', 'unsafe-none', SAME_ORIGIN, assert_not_isolated], |
| ['unsafe-none', 'unsafe-none', SAME_SITE, assert_not_isolated], |
| ['unsafe-none', 'same-origin-allow-popups', SAME_ORIGIN, assert_isolated], |
| ['unsafe-none', 'same-origin-allow-popups', SAME_SITE, assert_isolated], |
| ['unsafe-none', 'same-origin', SAME_ORIGIN, assert_isolated], |
| ['unsafe-none', 'same-origin', SAME_SITE, assert_isolated], |
| ['same-origin-allow-popups', 'unsafe-none', SAME_ORIGIN, assert_not_isolated], |
| ['same-origin-allow-popups', 'unsafe-none', SAME_SITE, assert_not_isolated], |
| ['same-origin-allow-popups', 'same-origin-allow-popups', SAME_ORIGIN, assert_not_isolated], |
| ['same-origin-allow-popups', 'same-origin-allow-popups', SAME_SITE, assert_isolated], |
| ['same-origin-allow-popups', 'same-origin', SAME_ORIGIN, assert_isolated], |
| ['same-origin-allow-popups', 'same-origin', SAME_SITE, assert_isolated], |
| ['same-origin', 'unsafe-none', SAME_ORIGIN, assert_isolated], |
| ['same-origin', 'unsafe-none', SAME_SITE, assert_isolated], |
| ['same-origin', 'same-origin-allow-popups', SAME_ORIGIN, assert_isolated], |
| ['same-origin', 'same-origin-allow-popups', SAME_SITE, assert_isolated], |
| ['same-origin', 'same-origin', SAME_ORIGIN, assert_not_isolated], |
| ['same-origin', 'same-origin', SAME_SITE, assert_isolated], |
| ].forEach(([parentCOOP, childCOOP, origin, expectation]) => { |
| subsetTest( |
| javascript_url_test, |
| parentCOOP, |
| childCOOP, |
| origin, |
| expectation); |
| }); |
| |
| </script> |