summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/file-system-access/resources/local-fs-test-helpers.js
blob: 4bb9793085ad8395e603a1b301b72648e841ded5 (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
// This file defines a directory_test() function that can be used to define
// tests that require a FileSystemDirectoryHandle. The implementation of that
// function in this file will ask the user to select an empty directory and uses
// that directory.
//
// Another implementation of this function exists in
// fs/resources/sandboxed-fs-test-helpers.js, where that version uses the
// sandboxed file system instead.

function getFileSystemType() {
  return 'local';
}

const directory_promise = (async () => {
  await new Promise(resolve => {
    window.addEventListener('DOMContentLoaded', resolve);
  });

  // Small delay to give chrome's test automation a chance to actually install
  // itself.
  await new Promise(resolve => step_timeout(resolve, 100));

  await window.test_driver.bless(
      'show a file picker.<br />Please select an empty directory');
  const entries = await self.showDirectoryPicker();
  assert_true(entries instanceof FileSystemHandle);
  assert_true(entries instanceof FileSystemDirectoryHandle);
  for await (const entry of entries) {
    assert_unreached('Selected directory is not empty');
  }
  return entries;
})();

function directory_test(func, description) {
  promise_test(async t => {
    const directory = await directory_promise;
    // To be resilient against tests not cleaning up properly, cleanup before
    // every test.
    for await (let entry of directory.values()) {
      await directory.removeEntry(
          entry.name, {recursive: entry.kind === 'directory'});
    }
    await func(t, directory);
  }, description);
}

directory_test(async (t, dir) => {
  assert_equals(await dir.queryPermission({mode: 'read'}), 'granted');
}, 'User succesfully selected an empty directory.');

directory_test(async (t, dir) => {
  const status = await dir.queryPermission({mode: 'readwrite'});
  if (status == 'granted')
    return;

  await window.test_driver.bless('ask for write permission');
  assert_equals(await dir.requestPermission({mode: 'readwrite'}), 'granted');
}, 'User granted write access.');

const child_frame_js = (origin, frameFn, done) => `
  const importScript = ${importScript};
  await importScript("/html/cross-origin-embedder-policy/credentialless" +
                  "/resources/common.js");
  await importScript("/html/anonymous-iframe/resources/common.js");
  await importScript("/common/utils.js");
  await send("${done}", ${frameFn}("${origin}"));
`;

/**
 * Context identifiers for executor subframes of framed tests. Individual
 * contexts (or convenience context lists below) can be used to send JavaScript
 * for evaluation in each frame (see framed_test below).
 *
 * Note that within framed tests:
 *  - firstParty represents the top-level document.
 *  - thirdParty represents an embedded context (iframe).
 *  - ancestorBit contexts include a cross-site ancestor iframe.
 *  - anonymousFrame contexts are third-party anonymous iframe contexts.
 */
const FRAME_CONTEXT = {
  firstParty: 0,
  thirdPartySameSite: 1,
  thirdPartySameSite_AncestorBit: 2,
  thirdPartyCrossSite: 3,
  anonymousFrameSameSite: 4,
  anonymousFrameSameSite_AncestorBit: 5,
  anonymousFrameCrossSite: 6,
};

// TODO(crbug.com/1322897): Add AncestorBit contexts.
const sameSiteContexts = [
  FRAME_CONTEXT.firstParty,
  FRAME_CONTEXT.thirdPartySameSite,
  FRAME_CONTEXT.anonymousFrameSameSite,
];

// TODO(crbug.com/1322897): Add AncestorBit contexts.
const crossSiteContexts = [
  FRAME_CONTEXT.thirdPartyCrossSite,
  FRAME_CONTEXT.anonymousFrameCrossSite,
];

// TODO(crbug.com/1322897): Add AncestorBit contexts.
const childContexts = [
  FRAME_CONTEXT.thirdPartySameSite,
  FRAME_CONTEXT.thirdPartyCrossSite,
  FRAME_CONTEXT.anonymousFrameSameSite,
  FRAME_CONTEXT.anonymousFrameCrossSite,
];

/**
 * Creates a promise test with same- & cross-site executor subframes.
 *
 * In addition to the standard testing object, the provided func will be called
 * with a sendTo function. sendTo expects:
 *   - contexts: an Iterable of FRAME_CONTEXT constants representing the
 *               frame(s) in which the provided script will be concurrently run.
 *   - js_gen: a function which should generate a script string when called
 *             with a string token. sendTo will wait until a "done" message
 *             is sent to this queue.
 */
function framed_test(func, description) {
  const same_site_origin = get_host_info().HTTPS_ORIGIN;
  const cross_site_origin = get_host_info().HTTPS_NOTSAMESITE_ORIGIN;
  const frames = Object.values(FRAME_CONTEXT);

  promise_test(async (t) => {
    return new Promise(async (resolve, reject) => {
      try {
        // Set up handles to all third party frames.
        const handles = [
          null,  // firstParty
          newIframe(same_site_origin),  // thirdPartySameSite
          null,  // thirdPartySameSite_AncestorBit
          newIframe(cross_site_origin),  // thirdPartyCrossSite
          newAnonymousIframe(same_site_origin),  // anonymousFrameSameSite
          null,  // anonymousFrameSameSite_AncestorBit
          newAnonymousIframe(cross_site_origin),  // anonymousFrameCrossSite
        ];
        // Set up nested SameSite frames for ancestor bit contexts.
        const setUpQueue = token();
        send(newIframe(cross_site_origin),
          child_frame_js(same_site_origin, "newIframe", setUpQueue));
        handles[FRAME_CONTEXT.thirdPartySameSite_AncestorBit] =
          await receive(setUpQueue);
        send(newAnonymousIframe(cross_site_origin),
          child_frame_js(same_site_origin, "newAnonymousIframe", setUpQueue));
        handles[FRAME_CONTEXT.anonymousFrameSameSite_AncestorBit] =
          await receive(setUpQueue);

        const sendTo = (contexts, js_generator) => {
          // Send to all contexts in parallel to minimize timeout concerns.
          return Promise.all(contexts.map(async (context) => {
            const queue = token();
            const js_string = js_generator(queue, context);
            switch (context) {
              case FRAME_CONTEXT.firstParty:
                // Code is executed directly in this frame via eval() rather
                // than in a new context to avoid differences in API access.
                eval(`(async () => {${js_string}})()`);
                break;
              case FRAME_CONTEXT.thirdPartySameSite:
              case FRAME_CONTEXT.thirdPartyCrossSite:
              case FRAME_CONTEXT.anonymousFrameSameSite:
              case FRAME_CONTEXT.anonymousFrameCrossSite:
              case FRAME_CONTEXT.thirdPartySameSite_AncestorBit:
              case FRAME_CONTEXT.anonymousFrameSameSite_AncestorBit:
                send(handles[context], js_string);
                break;
              default:
                reject(`Cannot execute in context: ${context}`);
            }
            if (await receive(queue) != "done") {
              reject(`Script failed in frame ${context}: ${js_string}`);
            }
          }));
        };

        await func(t, sendTo);
      } catch (e) {
        reject(e);
      }
      resolve();
    });
  }, description);
}