summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-navigation.https.html
blob: dea89478183870fa3db4ff6fef1d97a2a4ced516 (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
<!doctype html>
<html>
<meta name="timeout" content="long">
<body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="./credentialless/resources/common.js"></script>
<script>
const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
const COEP = '|header(cross-origin-embedder-policy,require-corp)';
const COEP_RO =
  '|header(cross-origin-embedder-policy-report-only,require-corp)';
const CORP_CROSS_ORIGIN =
  '|header(cross-origin-resource-policy,cross-origin)';
const CSP_FRAME_ANCESTORS_NONE =
  '|header(content-security-policy,frame-ancestors \'none\')';
const XFRAMEOPTIONS_DENY =
  '|header(x-frame-options,deny)';
const FRAME_URL = `${ORIGIN}/common/blank.html?pipe=`;
const REMOTE_FRAME_URL = `${REMOTE_ORIGIN}/common/blank.html?pipe=`;

function checkCorpReport(report, contextUrl, blockedUrl, disposition) {
  assert_equals(report.type, 'coep');
  assert_equals(report.url, contextUrl);
  assert_equals(report.body.type, 'corp');
  assert_equals(report.body.blockedURL, blockedUrl);
  assert_equals(report.body.disposition, disposition);
  assert_equals(report.body.destination, 'iframe');
}

function checkCoepMismatchReport(report, contextUrl, blockedUrl, disposition) {
  assert_equals(report.type, 'coep');
  assert_equals(report.url, contextUrl);
  assert_equals(report.body.type, 'navigation');
  assert_equals(report.body.blockedURL, blockedUrl);
  assert_equals(report.body.disposition, disposition);
}

function loadFrame(document, url) {
  return new Promise((resolve, reject) => {
    const frame = document.createElement('iframe');
    frame.src = url;
    frame.onload = () => resolve(frame);
    frame.onerror = reject;
    document.body.appendChild(frame);
  });
}

// |parentSuffix| is a suffix for the parent frame URL.
// When |withEmptyFrame| is true, this function creates an empty frame
// between the parent and target frames.
// |targetUrl| is a URL for the target frame.
async function loadFrames(test, parentSuffix, withEmptyFrame, targetUrl) {
  const frame = await loadFrame(document, FRAME_URL + parentSuffix);
  test.add_cleanup(() => frame.remove());
  let parent;
  if (withEmptyFrame) {
    parent = frame.contentDocument.createElement('iframe');
    frame.contentDocument.body.appendChild(parent);
  } else {
    parent = frame;
  }
  // Here we don't need "await". This loading may or may not succeed, and
  // we're not interested in the result.
  loadFrame(parent.contentDocument, targetUrl);

  return parent;
}

async function observeReports(global, expected_count) {
  const reports = [];
  const receivedEveryReports = new Promise(resolve => {
    if (expected_count == 0)
      resolve();

    const observer = new global.ReportingObserver((rs) => {
      for (const r of rs) {
        reports.push(r.toJSON());
      }
      if (expected_count <= reports.length)
        resolve();
    });
    observer.observe();

  });

  // Wait 5000 ms more to catch additionnal unexpected reports.
  await receivedEveryReports;
  await new Promise(r => step_timeout(r, 5000));
  return reports;
}

// CASES is a list of test case. Each test case consists of:
//   parent: the suffix of the URL of the parent frame.
//   target: the suffix of the URL of the target frame.
//   reports: the expectation of reports to be made. Each report is one of:
//     'CORP': CORP violation
//     'CORP-RO' CORP violation (report only)
//     'NAV': COEP mismatch between the frames.
//     'NAV-RO': COEP mismatch between the frames (report only).
const CASES = [
  { parent: '', target: '', reports: [] },
  { parent: '', target: COEP, reports: [] },
  { parent: COEP, target: COEP, reports: ['CORP'] },
  { parent: COEP, target: '', reports: ['CORP'] },

  { parent: '', target: CORP_CROSS_ORIGIN, reports: [] },
  { parent: COEP, target: CORP_CROSS_ORIGIN, reports: ['NAV'] },

  { parent: '', target: COEP + CORP_CROSS_ORIGIN, reports: [] },
  { parent: COEP, target: COEP + CORP_CROSS_ORIGIN, reports: [] },

  { parent: COEP_RO, target: COEP, reports: ['CORP-RO'] },
  { parent: COEP_RO, target: '', reports: ['CORP-RO', 'NAV-RO'] },
  { parent: COEP_RO, target: CORP_CROSS_ORIGIN, reports: ['NAV-RO'] },
  { parent: COEP_RO, target: COEP + CORP_CROSS_ORIGIN, reports: [] },

  { parent: COEP, target: COEP_RO + CORP_CROSS_ORIGIN, reports: ['NAV'] },

  // Test ordering of CSP frame-ancestors, COEP, and X-Frame-Options
  { parent: COEP, target: CORP_CROSS_ORIGIN + CSP_FRAME_ANCESTORS_NONE, reports: [] },
  { parent: COEP, target: CORP_CROSS_ORIGIN + XFRAMEOPTIONS_DENY, reports: ['NAV'] },
];

for (const testcase of CASES) {
  for (const withEmptyFrame of [false, true]) {
    function desc(s) {
      return s === '' ? '(none)' : s;
    }
    // These tests are very slow, so they must be run in parallel using
    // async_test.
    async_test(t => {
      const targetUrl = REMOTE_FRAME_URL + testcase.target;
      loadFrames(t, testcase.parent, withEmptyFrame, targetUrl)
          .then(t.step_func(parent => {
        const contextUrl = parent.src ? parent.src : 'about:blank';
        observeReports(parent.contentWindow, testcase.reports.length)
          .then(t.step_func(reports => {
            assert_equals(reports.length, testcase.reports.length);
            for (let i = 0; i < reports.length; i += 1) {
              const report = reports[i];
              switch (testcase.reports[i]) {
                case 'CORP':
                  checkCorpReport(report, contextUrl, targetUrl, 'enforce');
                  break;
                case 'CORP-RO':
                  checkCorpReport(report, contextUrl, targetUrl, 'reporting');
                  break;
                case 'NAV':
                  checkCoepMismatchReport(report, contextUrl, targetUrl, 'enforce');
                  break;
                case 'NAV-RO':
                  checkCoepMismatchReport(report, contextUrl, targetUrl, 'reporting');
                  break;
                default:
                  assert_unreached(
                    'Unexpected report expeaction: ' + testcase.reports[i]);
              }
            }
            t.done();
          })).catch(t.step_func(e => { throw e; }));
      })).catch(t.step_func(e => { throw e; }));
    }, `parent: ${desc(testcase.parent)}, target: ${desc(testcase.target)}, ` +
       `with empty frame: ${withEmptyFrame}`);
  }
}

</script>
</body></html>