Skip to content

Commit 1e0a022

Browse files
committedMar 4, 2025
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 2987848 commit 1e0a022

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+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<!doctype html>
2+
<html>
3+
<title>Create a file and keep it locked until a trigger is received</title>
4+
<head>
5+
<script src="/resources/testharness.js"></script>
6+
</head>
7+
<body>
8+
<div id="log"></div>
9+
<script>
10+
const params = new URLSearchParams(window.location.search);
11+
const channelName = params.get("channel");
12+
if (!channelName) {
13+
// On irrecoverable errors, window is closed: parent should check this.
14+
window.close();
15+
16+
throw new Error("Unknown channel name");
17+
}
18+
19+
const channel = new BroadcastChannel(channelName);
20+
const dirHandleName = "dusty-dir-handle-" + channelName;
21+
const fileHandleName = "funky-file-handle-" + channelName;
22+
23+
var writable = null;
24+
25+
window.addEventListener("message", async ev => {
26+
if (ev.data === "cleanup") {
27+
if (writable && !writable.getWriter().closed) {
28+
await writable.abort();
29+
writable = null;
30+
}
31+
}
32+
33+
channel.postMessage("first done");
34+
});
35+
36+
window.addEventListener("load", async () => {
37+
try {
38+
const rootDir = await navigator.storage.getDirectory();
39+
const opts = { create: false };
40+
const subDir = await rootDir.getDirectoryHandle(dirHandleName, opts);
41+
const file = await subDir.getFileHandle(fileHandleName, opts);
42+
43+
writable = await file.createWritable({});
44+
45+
const encoder = new TextEncoder();
46+
const writeBuffer = encoder.encode("Hello from the first tab!");
47+
await writable.write(writeBuffer);
48+
49+
channel.onmessage = async ev => {
50+
let message = "200 OK";
51+
if (ev.data === "trigger") {
52+
try {
53+
await writable.close();
54+
writable = null;
55+
} catch (err) {
56+
message = err.message;
57+
}
58+
channel.postMessage(message);
59+
}
60+
};
61+
62+
channel.postMessage("First window ready!");
63+
} catch (err) {
64+
channel.postMessage(err.message);
65+
}
66+
});
67+
</script>
68+
</body>
69+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!doctype html>
2+
<html>
3+
<title>No-op window</title>
4+
<head>
5+
<script src="/resources/testharness.js"></script>
6+
</head>
7+
<body>
8+
<div id="log"></div>
9+
<script>
10+
window.addEventListener("load", () => {
11+
const params = new URLSearchParams(window.location.search);
12+
const channelName = params.get("channel");
13+
if (!channelName) {
14+
// On irrecoverable errors, window is closed: parent should check this.
15+
window.close();
16+
17+
throw new Error("Unknown channel name");
18+
}
19+
20+
const channel = new BroadcastChannel(channelName);
21+
22+
channel.onmessage = async ev => {
23+
if (ev.data == "cleanup") {
24+
channel.postMessage("done");
25+
}
26+
};
27+
28+
channel.postMessage("200 OK");
29+
});
30+
</script>
31+
</body>
32+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<!doctype html>
2+
<html>
3+
<title>Overwrite dusty-dir-handle</title>
4+
<head>
5+
<script src="/resources/testharness.js"></script>
6+
</head>
7+
<body>
8+
<div id="log"></div>
9+
<script>
10+
var writable = null;
11+
12+
window.addEventListener("load", async () => {
13+
const params = new URLSearchParams(window.location.search);
14+
15+
const channelName = params.get("channel");
16+
if (!channelName) {
17+
// On irrecoverable errors, window is closed: parent should check this.
18+
window.close();
19+
20+
throw new Error("Unknown channel name");
21+
}
22+
23+
const opName = params.get("op");
24+
if (!opName || !["move", "rename"].includes(opName)) {
25+
// On irrecoverable errors, window is closed: parent should check this.
26+
window.close();
27+
28+
throw new Error("Unknown operation name");
29+
}
30+
31+
const channel = new BroadcastChannel(channelName);
32+
const dirHandleName = "dusty-dir-handle-" + channelName;
33+
const fileHandleName = "funky-file-handle-" + channelName;
34+
const trashBinName = "trash-bin-" + channelName;
35+
36+
channel.onmessage = async ev => {
37+
if (ev.data == "cleanup") {
38+
if (writable && !writable.getWriter().closed) {
39+
try {
40+
await writable.abort();
41+
} finally {
42+
writable = null;
43+
}
44+
}
45+
46+
channel.postMessage("done");
47+
}
48+
};
49+
50+
try {
51+
const rootDir = await navigator.storage.getDirectory();
52+
const trashBin = await rootDir.getDirectoryHandle(trashBinName, {
53+
create: true,
54+
});
55+
const trashId = crypto.randomUUID();
56+
57+
// Let's start from scratch
58+
let subDir = null;
59+
if (opName == "move") {
60+
subDir = await trashBin.getDirectoryHandle(dirHandleName, {
61+
create: true,
62+
});
63+
} else {
64+
subDir = await rootDir.getDirectoryHandle(trashId, {
65+
create: true,
66+
});
67+
}
68+
const file = await subDir.getFileHandle(fileHandleName, {
69+
create: true,
70+
});
71+
writable = await file.createWritable({});
72+
73+
const encoder = new TextEncoder();
74+
const writeBuffer = encoder.encode("Hello from the second tab!");
75+
await writable.write(writeBuffer);
76+
77+
const keep_open = params.has("keep_open") && params.get("keep_open");
78+
if (!keep_open) {
79+
await writable.close();
80+
writable = null;
81+
}
82+
83+
// Let's overwrite the originals
84+
if (opName == "move") {
85+
await subDir.move(rootDir);
86+
} else {
87+
await subDir.move(dirHandleName);
88+
}
89+
90+
channel.postMessage("200 OK");
91+
} catch (err) {
92+
channel.postMessage(err.message);
93+
}
94+
});
95+
</script>
96+
</body>
97+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<!doctype html>
2+
<html>
3+
<title>Overwrite dusty-dir-handle with a better one from the trash-bin</title>
4+
<head>
5+
<script src="/resources/testharness.js"></script>
6+
</head>
7+
<body>
8+
<div id="log"></div>
9+
<script>
10+
var writable = null;
11+
var otherWritable = null;
12+
13+
window.addEventListener("load", async () => {
14+
const params = new URLSearchParams(window.location.search);
15+
16+
const channelName = params.get("channel");
17+
if (!channelName) {
18+
// On irrecoverable errors, window is closed: parent should check this.
19+
window.close();
20+
21+
throw new Error("Unknown channel name");
22+
}
23+
24+
const opName = params.get("op");
25+
if (!opName || !["move", "rename"].includes(opName)) {
26+
// On irrecoverable errors, window is closed: parent should check this.
27+
window.close();
28+
29+
throw new Error("Unknown operation name");
30+
}
31+
32+
const channel = new BroadcastChannel(channelName);
33+
const dirHandleName = "dusty-dir-handle-" + channelName;
34+
const fileHandleName = "funky-file-handle-" + channelName;
35+
const otherFileHandleName = "other-file-handle-" + channelName;
36+
const trashBinName = "trash-bin-" + channelName;
37+
38+
channel.onmessage = async ev => {
39+
if (ev.data == "cleanup") {
40+
if (writable && !writable.getWriter().closed) {
41+
try {
42+
await writable.abort();
43+
} finally {
44+
writable = null;
45+
}
46+
}
47+
48+
if (otherWritable && !otherWritable.getWriter().closed) {
49+
try {
50+
await otherWritable.abort();
51+
} finally {
52+
otherWritable = null;
53+
}
54+
}
55+
56+
channel.postMessage("done");
57+
}
58+
};
59+
60+
try {
61+
const encoder = new TextEncoder();
62+
const rootDir = await navigator.storage.getDirectory();
63+
64+
{
65+
const originalDir = await rootDir.getDirectoryHandle(
66+
dirHandleName,
67+
{ create: true }
68+
);
69+
const otherFile = await originalDir.getFileHandle(
70+
otherFileHandleName,
71+
{ create: true }
72+
);
73+
otherWritable = await otherFile.createWritable({});
74+
75+
const otherBuffer = encoder.encode("Hello from the second tab!");
76+
await otherWritable.write(otherBuffer);
77+
await otherWritable.close();
78+
otherWritable = null;
79+
}
80+
81+
const trashBin = await rootDir.getDirectoryHandle(trashBinName, {
82+
create: true,
83+
});
84+
const trashId = crypto.randomUUID();
85+
86+
// Let's create a new but identical directory tree in the trash bin
87+
let subDir = null;
88+
if (opName == "move") {
89+
subDir = await trashBin.getDirectoryHandle(dirHandleName, {
90+
create: true,
91+
});
92+
} else {
93+
subDir = await rootDir.getDirectoryHandle(trashId, {
94+
create: true,
95+
});
96+
}
97+
98+
const file = await subDir.getFileHandle(fileHandleName, {
99+
create: true,
100+
});
101+
writable = await file.createWritable({});
102+
103+
const writeBuffer = encoder.encode("Hello from the second tab!");
104+
await writable.write(writeBuffer);
105+
106+
const keep_open = params.has("keep_open") && params.get("keep_open");
107+
if (!keep_open) {
108+
await writable.close();
109+
writable = null;
110+
}
111+
112+
// Let's overwrite the originals!
113+
if (opName == "move") {
114+
await subDir.move(rootDir);
115+
} else {
116+
await subDir.move(dirHandleName);
117+
}
118+
119+
{
120+
const originalDir = await rootDir.getDirectoryHandle(
121+
dirHandleName,
122+
{ create: false }
123+
);
124+
125+
// The next line should throw, the file should be gone!
126+
await originalDir.getFileHandle(otherFileHandleName, {
127+
create: false,
128+
});
129+
}
130+
131+
channel.postMessage("200 OK");
132+
} catch (err) {
133+
channel.postMessage(err.message);
134+
}
135+
});
136+
</script>
137+
</body>
138+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!doctype html>
2+
<html>
3+
<title>Move dusty-dir-handle to trash-bin!</title>
4+
<head>
5+
<script src="/resources/testharness.js"></script>
6+
</head>
7+
<body>
8+
<div id="log"></div>
9+
<script>
10+
window.addEventListener("load", async () => {
11+
const params = new URLSearchParams(window.location.search);
12+
13+
const channelName = params.get("channel");
14+
if (!channelName) {
15+
// On irrecoverable errors, window is closed: parent should check this.
16+
window.close();
17+
18+
throw new Error("Unknown channel name");
19+
}
20+
21+
const opName = params.get("op");
22+
if (!opName || !["move", "rename"].includes(opName)) {
23+
// On irrecoverable errors, window is closed: parent should check this.
24+
window.close();
25+
26+
throw new Error("Unknown operation name");
27+
}
28+
29+
const channel = new BroadcastChannel(channelName);
30+
const dirHandleName = "dusty-dir-handle-" + channelName;
31+
const trashBinName = "trash-bin-" + channelName;
32+
33+
channel.onmessage = async ev => {
34+
if (ev.data == "cleanup") {
35+
channel.postMessage("done");
36+
}
37+
};
38+
39+
try {
40+
const rootDir = await navigator.storage.getDirectory();
41+
const trashBin = await rootDir.getDirectoryHandle(trashBinName, {
42+
create: true,
43+
});
44+
const subDir = await rootDir.getDirectoryHandle(dirHandleName, {
45+
create: false,
46+
});
47+
const trashId = crypto.randomUUID();
48+
49+
// Let's do some clean up!
50+
if (opName == "move") {
51+
await subDir.move(trashBin);
52+
} else {
53+
await subDir.move(trashId);
54+
}
55+
56+
channel.postMessage("200 OK");
57+
} catch (err) {
58+
channel.postMessage(err.message);
59+
}
60+
});
61+
</script>
62+
</body>
63+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<!doctype html>
2+
<html>
3+
<title>Move dusty-dir-handle to trash-bin and back!</title>
4+
<head>
5+
<script src="/resources/testharness.js"></script>
6+
</head>
7+
<body>
8+
<div id="log"></div>
9+
<script>
10+
window.addEventListener("load", async () => {
11+
const params = new URLSearchParams(window.location.search);
12+
const channelName = params.get("channel");
13+
if (!channelName) {
14+
// On irrecoverable errors, window is closed: parent should check this.
15+
window.close();
16+
17+
throw new Error("Unknown channel name");
18+
}
19+
20+
const opName = params.get("op");
21+
if (!opName || !["move", "rename"].includes(opName)) {
22+
// On irrecoverable errors, window is closed: parent should check this.
23+
window.close();
24+
25+
throw new Error("Unknown operation name");
26+
}
27+
28+
const channel = new BroadcastChannel(channelName);
29+
const dirHandleName = "dusty-dir-handle-" + channelName;
30+
const trashBinName = "trash-bin-" + channelName;
31+
32+
channel.onmessage = async ev => {
33+
if (ev.data == "cleanup") {
34+
channel.postMessage("done");
35+
}
36+
};
37+
38+
try {
39+
const rootDir = await navigator.storage.getDirectory();
40+
const trashBin = await rootDir.getDirectoryHandle(trashBinName, {
41+
create: true,
42+
});
43+
const trashId = crypto.randomUUID();
44+
45+
{
46+
const subDir = await rootDir.getDirectoryHandle(dirHandleName, {
47+
create: false,
48+
});
49+
50+
// Let's do some clean up!
51+
if (opName == "move") {
52+
await subDir.move(trashBin);
53+
} else {
54+
await subDir.move(trashId);
55+
}
56+
}
57+
58+
// Oops! Didn't really mean to throw that to the trash bin!
59+
if (opName == "move") {
60+
const subDir = await trashBin.getDirectoryHandle(dirHandleName, {
61+
create: false,
62+
});
63+
await subDir.move(rootDir);
64+
} else {
65+
const subDir = await rootDir.getDirectoryHandle(trashId, {
66+
create: false,
67+
});
68+
await subDir.move(dirHandleName);
69+
}
70+
71+
channel.postMessage("200 OK");
72+
} catch (err) {
73+
channel.postMessage(err.message);
74+
}
75+
});
76+
</script>
77+
</body>
78+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<!doctype html>
2+
<html>
3+
<title>Move dusty-dir-handle to trash-bin and start from scratch!</title>
4+
<head>
5+
<script src="/resources/testharness.js"></script>
6+
</head>
7+
<body>
8+
<div id="log"></div>
9+
<script>
10+
var writable = null;
11+
12+
window.addEventListener("load", async () => {
13+
const params = new URLSearchParams(window.location.search);
14+
15+
const channelName = params.get("channel");
16+
if (!channelName) {
17+
// On irrecoverable errors, window is closed: parent should check this.
18+
window.close();
19+
20+
throw new Error("Unknown channel name");
21+
}
22+
23+
const opName = params.get("op");
24+
if (!opName || !["move", "rename"].includes(opName)) {
25+
// On irrecoverable errors, window is closed: parent should check this.
26+
window.close();
27+
28+
throw new Error("Unknown operation name");
29+
}
30+
31+
const channel = new BroadcastChannel(channelName);
32+
const dirHandleName = "dusty-dir-handle-" + channelName;
33+
const fileHandleName = "funky-file-handle-" + channelName;
34+
const trashBinName = "trash-bin-" + channelName;
35+
36+
channel.onmessage = async ev => {
37+
if (ev.data == "cleanup") {
38+
if (writable && !writable.getWriter().closed) {
39+
try {
40+
await writable.abort();
41+
} finally {
42+
writable = null;
43+
}
44+
}
45+
46+
channel.postMessage("done");
47+
}
48+
};
49+
50+
try {
51+
const rootDir = await navigator.storage.getDirectory();
52+
const trashBin = await rootDir.getDirectoryHandle(trashBinName, {
53+
create: true,
54+
});
55+
const trashId = crypto.randomUUID();
56+
{
57+
const subDir = await rootDir.getDirectoryHandle(dirHandleName, {
58+
create: false,
59+
});
60+
61+
// Let's do some clean up!
62+
if (opName == "move") {
63+
await subDir.move(trashBin);
64+
} else {
65+
await subDir.move(trashId);
66+
}
67+
}
68+
69+
{
70+
// Let's start from scratch!
71+
const subDir = await rootDir.getDirectoryHandle(dirHandleName, {
72+
create: true,
73+
});
74+
const file = await subDir.getFileHandle(fileHandleName, {
75+
create: true,
76+
});
77+
writable = await file.createWritable({});
78+
79+
const encoder = new TextEncoder();
80+
const writeBuffer = encoder.encode("Hello from the second tab!");
81+
await writable.write(writeBuffer);
82+
83+
const keep_open =
84+
params.has("keep_open") && params.get("keep_open");
85+
if (!keep_open) {
86+
await writable.close();
87+
writable = null;
88+
}
89+
}
90+
91+
channel.postMessage("200 OK");
92+
} catch (err) {
93+
channel.postMessage(err.message);
94+
}
95+
});
96+
</script>
97+
</body>
98+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!doctype html>
2+
<html>
3+
<title>Remove dusty-dir-handle</title>
4+
<head>
5+
<script src="/resources/testharness.js"></script>
6+
</head>
7+
<body>
8+
<div id="log"></div>
9+
<script>
10+
const params = new URLSearchParams(window.location.search);
11+
12+
const channelName = params.get("channel");
13+
if (!channelName) {
14+
// On irrecoverable errors, window is closed: parent should check this.
15+
window.close();
16+
17+
throw new Error("Unknown channel name");
18+
}
19+
20+
const opName = params.get("op");
21+
if (!opName || "remove" != opName) {
22+
// On irrecoverable errors, window is closed: parent should check this.
23+
window.close();
24+
25+
throw new Error("Unknown operation name");
26+
}
27+
28+
const channel = new BroadcastChannel(channelName);
29+
const dirHandleName = "dusty-dir-handle-" + channelName;
30+
31+
channel.onmessage = async ev => {
32+
if (ev.data == "cleanup") {
33+
channel.postMessage("done");
34+
}
35+
};
36+
37+
window.addEventListener("load", async () => {
38+
try {
39+
const rootDir = await navigator.storage.getDirectory();
40+
41+
// Let's do some clean up!
42+
await rootDir.removeEntry(dirHandleName, { recursive: true });
43+
44+
channel.postMessage("200 OK");
45+
} catch (err) {
46+
channel.postMessage(err.message);
47+
}
48+
});
49+
</script>
50+
</body>
51+
</html>

0 commit comments

Comments
 (0)
Please sign in to comment.