Skip to content

Commit de87af0

Browse files
committed
Bug 1897564 - Add wpt test scenarios for BucketFS folder moves with open WritableFileStream. r=jesup,dom-storage-reviewers
Based on the discussions at whatwg/fs#59 and whatwg/fs#10 moves should overwrite unconditionally and the developers should use WebLocks if this is not acceptable. However, currently we block moves and removes whenever there are any exclusive or shared locks and this behavior is required to comply with the current wpt tests. The problem with this is that another tab holding the lock can the make the site unusable. The current spec state is WICG/file-system-access#413 , therefore these wpt tests are under the mozilla directory. The tests are meant to provide answers to how the current implementation handles cases where parent directory is moved, renamed or removed while writable file streams are present. Differential Revision: https://phabricator.services.mozilla.com/D228287
1 parent ea29530 commit de87af0

10 files changed

+960
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[fs-writable_lifecycle_after_parent_dir_change.https.window.html]
2+
type: testharness
3+
prefs: [dom.fs.enabled:true]
4+
expected: [ERROR, OK]
5+
6+
[closing writable yields error on move of the parent directory]
7+
expected: FAIL
8+
9+
[closing writable yields error on rename of the parent directory]
10+
expected: FAIL
11+
12+
[after overwriting directory with move check that no files are left behind]
13+
expected: FAIL
14+
15+
[after overwriting directory with rename check that no files are left behind]
16+
expected: FAIL
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
// META: title=Origin private file system used from multiple tabs
2+
// META: script=support/testHelpers.js
3+
// META: timeout=long
4+
5+
const okResponse = "200 OK";
6+
const firstTabContent = "Hello from the first tab!";
7+
8+
const expectCloseIsOk = async (t, ev, aSubDir, aName) => {
9+
if (ev.data != okResponse) {
10+
throw new Error("Expect close is ok callback failed");
11+
}
12+
13+
const fileHandle = await aSubDir.getFileHandle(aName, { create: false });
14+
const file = await fileHandle.getFile();
15+
const text = await file.text();
16+
17+
t.step(() => {
18+
assert_equals(firstTabContent, text, "Does the content look good?");
19+
});
20+
};
21+
22+
const expectErrorCallback = errorMsg => {
23+
return async (t, ev) => {
24+
if (ev.data != errorMsg) {
25+
t.step(() => {
26+
assert_equals(ev.data, errorMsg, "Expect error callback failed");
27+
});
28+
}
29+
};
30+
};
31+
32+
const expectInternalError = expectErrorCallback(
33+
"Internal error closing file stream"
34+
);
35+
36+
const expectModificationError = expectErrorCallback("No modification allowed");
37+
38+
const expectNotFoundError = expectErrorCallback("Entry not found");
39+
40+
function addWritableClosingTest(testName, secondTabURL, expectationChecks) {
41+
promise_test(async t => {
42+
const firstTabURL =
43+
"support/fs-create_writables_and_close_on_trigger.sub.html";
44+
const channelName = crypto.randomUUID(); // Avoid cross-talk between tests
45+
const dirHandleName = "dusty-dir-handle-" + channelName;
46+
const fileHandleName = "funky-file-handle-" + channelName;
47+
48+
const params = new URLSearchParams(
49+
secondTabURL.slice(secondTabURL.lastIndexOf("?"))
50+
);
51+
t.step(() => {
52+
assert_true(params.size > 0, "Missing search parameters");
53+
});
54+
55+
const channelParams = "channel=" + channelName;
56+
const bc = new BroadcastChannel(channelName);
57+
t.add_cleanup(() => {
58+
bc.close();
59+
});
60+
61+
const rootDir = await navigator.storage.getDirectory();
62+
const subDir = await rootDir.getDirectoryHandle(dirHandleName, {
63+
create: true,
64+
});
65+
await subDir.getFileHandle(fileHandleName, { create: true });
66+
67+
async function firstReady(win) {
68+
return new Promise((resolve, reject) => {
69+
bc.onmessage = e => {
70+
if (e.data === "First window ready!") {
71+
resolve(win);
72+
} else {
73+
t.step(() => {
74+
reject(e.data);
75+
assert_equals(
76+
"First window ready!",
77+
e.data,
78+
"Is first window ready?"
79+
);
80+
});
81+
}
82+
};
83+
});
84+
}
85+
86+
const firstTabLocation = firstTabURL + "?" + channelParams;
87+
const firstTab = await firstReady(window.open(firstTabLocation));
88+
t.step(() => {
89+
assert_true(!!firstTab, "Is the first tab fine?");
90+
assert_false(firstTab.closed, "Is the first tab open?");
91+
});
92+
93+
let secondTab = null;
94+
async function secondReady(secondTabLocation) {
95+
let win = null;
96+
return new Promise((resolve, reject) => {
97+
bc.onmessage = async ev => {
98+
if (expectationChecks.length > 1) {
99+
try {
100+
await expectationChecks.shift()(t, ev, subDir, fileHandleName);
101+
} catch (err) {
102+
reject(err);
103+
return;
104+
}
105+
}
106+
107+
resolve(win);
108+
};
109+
110+
try {
111+
win = window.open(secondTabLocation);
112+
} catch (err) {
113+
reject(err);
114+
}
115+
});
116+
}
117+
const secondTabLocation = secondTabURL + "&" + channelParams;
118+
try {
119+
secondTab = await secondReady(secondTabLocation);
120+
} catch (err) {
121+
if (expectationChecks.length > 1) {
122+
await expectationChecks.shift()(t, { data: err.message });
123+
} else {
124+
t.step_func(() => {
125+
throw err;
126+
});
127+
}
128+
} finally {
129+
try {
130+
const closeHandles = async () => {
131+
return new Promise((resolve, reject) => {
132+
bc.onmessage = async ev => {
133+
try {
134+
if (expectationChecks) {
135+
await expectationChecks.shift()(
136+
t,
137+
ev,
138+
subDir,
139+
fileHandleName
140+
);
141+
} else {
142+
t.step_func(() => {
143+
throw err;
144+
});
145+
}
146+
resolve();
147+
} catch (err) {
148+
reject(err);
149+
}
150+
};
151+
152+
bc.postMessage("trigger");
153+
});
154+
};
155+
156+
await closeHandles();
157+
} catch (err) {
158+
if (expectationChecks) {
159+
await expectationChecks.shift()(t, { data: err.message });
160+
}
161+
} finally {
162+
const waitForCleanup = async () => {
163+
return new Promise((resolve, reject) => {
164+
let firstDone = false;
165+
let secondDone = !secondTab;
166+
167+
bc.onmessage = ev => {
168+
if (ev.data == "first done") {
169+
firstDone = true;
170+
} else if (ev.data == "done") {
171+
secondDone = true;
172+
} else {
173+
t.step(() => {
174+
assert_false(
175+
true,
176+
"We got a cleanup message " + JSON.stringify(ev.data)
177+
);
178+
});
179+
reject(new Error(ev.data));
180+
}
181+
182+
if (firstDone && secondDone) {
183+
resolve();
184+
}
185+
};
186+
187+
firstTab.postMessage("cleanup");
188+
if (secondTab) {
189+
bc.postMessage("cleanup");
190+
}
191+
});
192+
};
193+
194+
try {
195+
await waitForCleanup();
196+
197+
for await (let entry of rootDir.values()) {
198+
console.log(entry.name);
199+
await rootDir.removeEntry(entry.name, { recursive: true });
200+
}
201+
} catch (err) {
202+
t.step_func(() => {
203+
assert_unreached(err.message);
204+
});
205+
} finally {
206+
t.done();
207+
}
208+
}
209+
}
210+
}, testName);
211+
}
212+
213+
addWritableClosingTest(
214+
`closing writable in single tab is success`,
215+
"support/fs-noop.sub.html?op=move",
216+
[expectCloseIsOk]
217+
);
218+
219+
addWritableClosingTest(
220+
`closing writable fails silently on move of the parent directory`,
221+
"support/fs-relocate_dir_to_trash.sub.html?op=move",
222+
[expectCloseIsOk, expectNotFoundError, expectCloseIsOk, expectNotFoundError]
223+
);
224+
225+
addWritableClosingTest(
226+
`closing writable fails silently on rename of the parent directory`,
227+
"support/fs-relocate_dir_to_trash.sub.html?op=rename",
228+
[expectCloseIsOk, expectNotFoundError, expectCloseIsOk, expectNotFoundError]
229+
);
230+
231+
addWritableClosingTest(
232+
`closing writable succeeds after move of the parent directory is rolled back`,
233+
"support/fs-relocate_dir_to_trash_and_back.sub.html?op=move",
234+
[expectCloseIsOk]
235+
);
236+
237+
addWritableClosingTest(
238+
`closing writable succeeds after rename of the parent directory is rolled back`,
239+
"support/fs-relocate_dir_to_trash_and_back.sub.html?op=rename",
240+
[expectCloseIsOk]
241+
);
242+
243+
/**
244+
* Test case
245+
* `removeEntry() of a directory while a containing file has an open writable fails`
246+
* in web platform test /fs/FileSystemDirectoryHandle-removeEntry.https.any.html
247+
* currently requires that directory cannot be moved while it contains open
248+
* writable file streams, even if they are not owner by the current context.
249+
*
250+
* Without this limitation, the test should yield
251+
* "Internal error closing file stream" because the requested close cannot be completed
252+
* because the required file path no longer exists, and the request is aborted.
253+
*/
254+
addWritableClosingTest(
255+
`closing writable yields error on removal of the parent directory`,
256+
"support/fs-remove_dir.sub.html?op=remove",
257+
[expectModificationError, expectCloseIsOk]
258+
);
259+
260+
addWritableClosingTest(
261+
`closing old writable succeeds after directory tree is moved and created again`,
262+
"support/fs-relocate_dir_to_trash_and_recreate.sub.html?op=move",
263+
[expectCloseIsOk]
264+
);
265+
266+
addWritableClosingTest(
267+
`closing old writable succeeds after directory tree is renamed and created again`,
268+
"support/fs-relocate_dir_to_trash_and_recreate.sub.html?op=rename",
269+
[expectCloseIsOk]
270+
);
271+
272+
addWritableClosingTest(
273+
`closing old writable succeeds after directory tree is moved, created again and the new file has shared lock open`,
274+
"support/fs-relocate_dir_to_trash_and_recreate.sub.html?op=move&keep_open=true",
275+
[expectCloseIsOk]
276+
);
277+
278+
addWritableClosingTest(
279+
`closing old writable fails after directory tree is renamed and created again and the new file has shared lock open`,
280+
"support/fs-relocate_dir_to_trash_and_recreate.sub.html?op=rename&keep_open=true",
281+
[expectCloseIsOk]
282+
);
283+
284+
addWritableClosingTest(
285+
`closing old writable succeeds after directory tree is overwritten by move`,
286+
"support/fs-overwrite_existing_dir.sub.html?op=move",
287+
[expectCloseIsOk]
288+
);
289+
290+
addWritableClosingTest(
291+
`closing old writable succeeds after directory tree is overwritten by rename`,
292+
"support/fs-overwrite_existing_dir.sub.html?op=rename",
293+
[expectCloseIsOk]
294+
);
295+
296+
addWritableClosingTest(
297+
`closing old writable succeeds after directory tree is overwritten by move with new writable open`,
298+
"support/fs-overwrite_existing_dir.sub.html?op=move&keep_open=true",
299+
[expectCloseIsOk]
300+
);
301+
302+
addWritableClosingTest(
303+
`closing old writable succeeds after directory tree is overwritten by rename with new writable open`,
304+
"support/fs-overwrite_existing_dir.sub.html?op=rename&keep_open=true",
305+
[expectCloseIsOk]
306+
);
307+
308+
addWritableClosingTest(
309+
`after overwriting directory with move check that no files are left behind`,
310+
"support/fs-overwrite_leaves_no_files_behind.sub.html?op=move",
311+
[expectNotFoundError, expectCloseIsOk]
312+
);
313+
314+
addWritableClosingTest(
315+
`after overwriting directory with rename check that no files are left behind`,
316+
"support/fs-overwrite_leaves_no_files_behind.sub.html?op=rename",
317+
[expectNotFoundError, expectCloseIsOk]
318+
);

0 commit comments

Comments
 (0)