summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/cross-origin-opener-policy/javascript-url.https.html
blob: 8ebb3ccd4a08daca0a7c25b6e4c451659ce9deb3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<!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>