Skip to content

Commit 7b1af56

Browse files
jasnelljoshthoward
authored andcommitted
Implement webfs file/directory locator property (#4532)
1 parent 73d2668 commit 7b1af56

File tree

3 files changed

+56
-55
lines changed

3 files changed

+56
-55
lines changed

src/workerd/api/filesystem.c++

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,22 +2022,15 @@ jsg::Ref<jsg::DOMException> fsErrorToDomException(jsg::Lock& js, workerd::FsErro
20222022
jsg::Promise<jsg::Ref<FileSystemDirectoryHandle>> StorageManager::getDirectory(
20232023
jsg::Lock& js, const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& exception) {
20242024
auto& vfs = workerd::VirtualFileSystem::current(js);
2025-
return js.resolvedPromise(js.alloc<FileSystemDirectoryHandle>(jsg::USVString(), vfs.getRoot(js)));
2025+
return js.resolvedPromise(js.alloc<FileSystemDirectoryHandle>(
2026+
KJ_ASSERT_NONNULL(jsg::Url::tryParse("file:///"_kj)), jsg::USVString(), vfs.getRoot(js)));
20262027
}
20272028

20282029
FileSystemDirectoryHandle::FileSystemDirectoryHandle(
2029-
jsg::USVString name, kj::Rc<workerd::Directory> inner)
2030-
: FileSystemHandle(kj::mv(name)),
2030+
jsg::Url locator, jsg::USVString name, kj::Rc<workerd::Directory> inner)
2031+
: FileSystemHandle(kj::mv(locator), kj::mv(name)),
20312032
inner(kj::mv(inner)) {}
20322033

2033-
jsg::Promise<bool> FileSystemDirectoryHandle::isSameEntry(
2034-
jsg::Lock& js, jsg::Ref<FileSystemHandle> other) {
2035-
KJ_IF_SOME(other, kj::dynamicDowncastIfAvailable<FileSystemDirectoryHandle>(*other)) {
2036-
return js.resolvedPromise(other.inner.get() == inner.get());
2037-
}
2038-
return js.resolvedPromise(false);
2039-
}
2040-
20412034
jsg::Promise<jsg::Ref<FileSystemFileHandle>> FileSystemDirectoryHandle::getFileHandle(jsg::Lock& js,
20422035
jsg::USVString name,
20432036
jsg::Optional<FileSystemGetFileOptions> options,
@@ -2057,7 +2050,9 @@ jsg::Promise<jsg::Ref<FileSystemFileHandle>> FileSystemDirectoryHandle::getFileH
20572050
})) {
20582051
KJ_SWITCH_ONEOF(node) {
20592052
KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {
2060-
return js.resolvedPromise(js.alloc<FileSystemFileHandle>(kj::mv(name), kj::mv(file)));
2053+
auto locator = KJ_ASSERT_NONNULL(getLocator().tryResolve(name));
2054+
return js.resolvedPromise(
2055+
js.alloc<FileSystemFileHandle>(kj::mv(locator), kj::mv(name), kj::mv(file)));
20612056
}
20622057
KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {
20632058
auto ex =
@@ -2100,7 +2095,9 @@ jsg::Promise<jsg::Ref<FileSystemDirectoryHandle>> FileSystemDirectoryHandle::get
21002095
})) {
21012096
KJ_SWITCH_ONEOF(node) {
21022097
KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {
2103-
return js.resolvedPromise(js.alloc<FileSystemDirectoryHandle>(kj::mv(name), kj::mv(dir)));
2098+
auto locator = KJ_ASSERT_NONNULL(getLocator().tryResolve(kj::str(name, "/")));
2099+
return js.resolvedPromise(
2100+
js.alloc<FileSystemDirectoryHandle>(kj::mv(locator), kj::mv(name), kj::mv(dir)));
21042101
}
21052102
KJ_CASE_ONEOF(dir, kj::Rc<workerd::File>) {
21062103
auto ex = js.domException(kj::str("TypeMismatchError"), kj::str("File name is a file"));
@@ -2158,17 +2155,19 @@ jsg::Promise<kj::Array<jsg::USVString>> FileSystemDirectoryHandle::resolve(
21582155

21592156
namespace {
21602157
kj::Array<jsg::Ref<FileSystemHandle>> collectEntries(
2161-
jsg::Lock& js, kj::Rc<workerd::Directory>& inner) {
2158+
jsg::Lock& js, kj::Rc<workerd::Directory>& inner, const jsg::Url& parentLocator) {
21622159
kj::Vector<jsg::Ref<FileSystemHandle>> entries;
21632160
for (auto& entry: *inner.get()) {
21642161
KJ_SWITCH_ONEOF(entry.value) {
21652162
KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {
2166-
entries.add(
2167-
js.alloc<FileSystemFileHandle>(js.accountedUSVString(entry.key), file.addRef()));
2163+
auto locator = KJ_ASSERT_NONNULL(parentLocator.tryResolve(entry.key));
2164+
entries.add(js.alloc<FileSystemFileHandle>(
2165+
kj::mv(locator), js.accountedUSVString(entry.key), file.addRef()));
21682166
}
21692167
KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {
2170-
entries.add(
2171-
js.alloc<FileSystemDirectoryHandle>(js.accountedUSVString(entry.key), dir.addRef()));
2168+
auto locator = KJ_ASSERT_NONNULL(parentLocator.tryResolve(kj::str(entry.key, "/")));
2169+
entries.add(js.alloc<FileSystemDirectoryHandle>(
2170+
kj::mv(locator), js.accountedUSVString(entry.key), dir.addRef()));
21722171
}
21732172
KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {
21742173
SymbolicLinkRecursionGuardScope guardScope;
@@ -2179,12 +2178,14 @@ kj::Array<jsg::Ref<FileSystemHandle>> collectEntries(
21792178
KJ_IF_SOME(res, link->resolve(js)) {
21802179
KJ_SWITCH_ONEOF(res) {
21812180
KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {
2182-
entries.add(
2183-
js.alloc<FileSystemFileHandle>(js.accountedUSVString(entry.key), file.addRef()));
2181+
auto locator = KJ_ASSERT_NONNULL(parentLocator.tryResolve(entry.key));
2182+
entries.add(js.alloc<FileSystemFileHandle>(
2183+
kj::mv(locator), js.accountedUSVString(entry.key), file.addRef()));
21842184
}
21852185
KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {
2186+
auto locator = KJ_ASSERT_NONNULL(parentLocator.tryResolve(kj::str(entry.key, "/")));
21862187
entries.add(js.alloc<FileSystemDirectoryHandle>(
2187-
js.accountedUSVString(entry.key), dir.addRef()));
2188+
kj::mv(locator), js.accountedUSVString(entry.key), dir.addRef()));
21882189
}
21892190
KJ_CASE_ONEOF(_, workerd::FsError) {
21902191
JSG_FAIL_REQUIRE(DOMOperationError, "Symbolic link recursion detected"_kj);
@@ -2200,16 +2201,16 @@ kj::Array<jsg::Ref<FileSystemHandle>> collectEntries(
22002201

22012202
jsg::Ref<FileSystemDirectoryHandle::EntryIterator> FileSystemDirectoryHandle::entries(
22022203
jsg::Lock& js) {
2203-
return js.alloc<EntryIterator>(IteratorState(JSG_THIS, collectEntries(js, inner)));
2204+
return js.alloc<EntryIterator>(IteratorState(JSG_THIS, collectEntries(js, inner, getLocator())));
22042205
}
22052206

22062207
jsg::Ref<FileSystemDirectoryHandle::KeyIterator> FileSystemDirectoryHandle::keys(jsg::Lock& js) {
2207-
return js.alloc<KeyIterator>(IteratorState(JSG_THIS, collectEntries(js, inner)));
2208+
return js.alloc<KeyIterator>(IteratorState(JSG_THIS, collectEntries(js, inner, getLocator())));
22082209
}
22092210

22102211
jsg::Ref<FileSystemDirectoryHandle::ValueIterator> FileSystemDirectoryHandle::values(
22112212
jsg::Lock& js) {
2212-
return js.alloc<ValueIterator>(IteratorState(JSG_THIS, collectEntries(js, inner)));
2213+
return js.alloc<ValueIterator>(IteratorState(JSG_THIS, collectEntries(js, inner, getLocator())));
22132214
}
22142215

22152216
void FileSystemDirectoryHandle::forEach(jsg::Lock& js,
@@ -2225,23 +2226,16 @@ void FileSystemDirectoryHandle::forEach(jsg::Lock& js,
22252226
}
22262227
callback.setReceiver(js.v8Ref(receiver));
22272228

2228-
for (auto& entry: collectEntries(js, inner)) {
2229+
for (auto& entry: collectEntries(js, inner, getLocator())) {
22292230
callback(js, js.accountedUSVString(entry->getName(js)), entry.addRef(), JSG_THIS);
22302231
}
22312232
}
22322233

2233-
FileSystemFileHandle::FileSystemFileHandle(jsg::USVString name, kj::Rc<workerd::File> inner)
2234-
: FileSystemHandle(kj::mv(name)),
2234+
FileSystemFileHandle::FileSystemFileHandle(
2235+
jsg::Url locator, jsg::USVString name, kj::Rc<workerd::File> inner)
2236+
: FileSystemHandle(kj::mv(locator), kj::mv(name)),
22352237
inner(kj::mv(inner)) {}
22362238

2237-
jsg::Promise<bool> FileSystemFileHandle::isSameEntry(
2238-
jsg::Lock& js, jsg::Ref<FileSystemHandle> other) {
2239-
KJ_IF_SOME(other, kj::dynamicDowncastIfAvailable<FileSystemFileHandle>(*other)) {
2240-
return js.resolvedPromise(other.inner.get() == inner.get());
2241-
}
2242-
return js.resolvedPromise(false);
2243-
}
2244-
22452239
jsg::Promise<jsg::Ref<File>> FileSystemFileHandle::getFile(
22462240
jsg::Lock& js, const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler) {
22472241
// TODO(node-fs): Currently this copies the file data into the new File object.

src/workerd/api/filesystem.h

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,10 @@ class FileSystemHandle: public jsg::Object {
270270
// file handle. It would still likely be useful tho. As a follow up
271271
// step, we will implement serialization/deserialization of these objects.
272272
public:
273-
FileSystemHandle(jsg::USVString name): name(kj::mv(name)) {}
273+
FileSystemHandle(jsg::Url&& locator, jsg::USVString name)
274+
: locator(kj::mv(locator)),
275+
name(kj::mv(name)) {}
276+
274277
const jsg::USVString& getName(jsg::Lock& js) {
275278
return name;
276279
}
@@ -279,9 +282,16 @@ class FileSystemHandle: public jsg::Object {
279282
// Implemented by subclasses.
280283
KJ_UNIMPLEMENTED("getKind() not implemented");
281284
}
282-
virtual jsg::Promise<bool> isSameEntry(jsg::Lock& js, jsg::Ref<FileSystemHandle> other) {
283-
// Implemented by subclasses.
284-
KJ_UNIMPLEMENTED("isSameEntry() not implemented");
285+
286+
jsg::Promise<bool> isSameEntry(jsg::Lock& js, jsg::Ref<FileSystemHandle> other) {
287+
// Per the spec, two handles are the same if they refer to the same entry (that is,
288+
// have the same locator). It does not matter if they are different actual entries.
289+
return locator.equal(other->getLocator(),
290+
jsg::Url::EquivalenceOption::IGNORE_FRAGMENTS |
291+
jsg::Url::EquivalenceOption::IGNORE_SEARCH |
292+
jsg::Url::EquivalenceOption::NORMALIZE_PATH)
293+
? js.resolvedPromise(true)
294+
: js.resolvedPromise(false);
285295
}
286296

287297
JSG_RESOURCE_TYPE(FileSystemHandle) {
@@ -290,20 +300,23 @@ class FileSystemHandle: public jsg::Object {
290300
JSG_METHOD(isSameEntry);
291301
}
292302

303+
const jsg::Url& getLocator() const {
304+
return locator;
305+
}
306+
293307
private:
308+
const jsg::Url locator;
294309
jsg::USVString name;
295310
};
296311

297312
class FileSystemFileHandle: public FileSystemHandle {
298313
public:
299-
FileSystemFileHandle(jsg::USVString name, kj::Rc<workerd::File> inner);
314+
FileSystemFileHandle(jsg::Url locator, jsg::USVString name, kj::Rc<workerd::File> inner);
300315

301316
kj::StringPtr getKind(jsg::Lock& js) override {
302317
return "file"_kj;
303318
}
304319

305-
jsg::Promise<bool> isSameEntry(jsg::Lock& js, jsg::Ref<FileSystemHandle> other) override;
306-
307320
struct FileSystemCreateWritableOptions {
308321
jsg::Optional<bool> keepExistingData;
309322
JSG_STRUCT(keepExistingData);
@@ -379,14 +392,13 @@ class FileSystemDirectoryHandle final: public FileSystemHandle {
379392
};
380393

381394
public:
382-
FileSystemDirectoryHandle(jsg::USVString name, kj::Rc<workerd::Directory> inner);
395+
FileSystemDirectoryHandle(
396+
jsg::Url locator, jsg::USVString name, kj::Rc<workerd::Directory> inner);
383397

384398
kj::StringPtr getKind(jsg::Lock& js) override {
385399
return "directory"_kj;
386400
}
387401

388-
jsg::Promise<bool> isSameEntry(jsg::Lock& js, jsg::Ref<FileSystemHandle> other) override;
389-
390402
struct FileSystemGetFileOptions {
391403
bool create = false;
392404
JSG_STRUCT(create);

src/wpt/fs-test.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ const root = await navigator.storage.getDirectory();
66
const tmp = await root.getDirectoryHandle('tmp');
77
// The root is read-only, so we need to use a writable subdirectory
88
// to actually run the tests.
9-
navigator.storage.getDirectory = async () => tmp;
9+
navigator.storage.getDirectory = async () => {
10+
// Let's create a new random tmp subdirectory for each test run to avoid interference
11+
return tmp.getDirectoryHandle(crypto.randomUUID(), { create: true });
12+
};
1013

1114
export default {
1215
'FileSystemBaseHandle-buckets.https.any.js': {
@@ -23,12 +26,7 @@ export default {
2326
},
2427
'FileSystemBaseHandle-isSameEntry.https.any.js': {
2528
comment: '...',
26-
expectedFailures: [
27-
// TODO(node-fs): Fix these tests
28-
'isSameEntry comparing two files pointing to the same path returns true',
29-
'isSameEntry comparing two directories pointing to the same path returns true',
30-
'isSameEntry comparing a file to a directory of the same path returns false',
31-
],
29+
expectedFailures: [],
3230
},
3331
'FileSystemBaseHandle-postMessage-BroadcastChannel.https.window.js': {
3432
comment: 'BroadcastChannel is not implemented in workers',
@@ -81,7 +79,6 @@ export default {
8179
comment: '...',
8280
expectedFailures: [
8381
// TODO(node-fs): Fix these tests
84-
'getDirectoryHandle(create=true) creates an empty directory',
8582
'getDirectoryHandle() with empty name',
8683
'getDirectoryHandle() with "." name',
8784
'getDirectoryHandle() with ".." name',
@@ -115,9 +112,7 @@ export default {
115112
// TODO(node-fs): Fix these tests
116113
'removeEntry() to remove a file',
117114
'removeEntry() on an already removed file should fail',
118-
'removeEntry() to remove an empty directory',
119115
'removeEntry() on a non-empty directory should fail',
120-
'removeEntry() on a directory recursively should delete all sub-items',
121116
'removeEntry() with empty name should fail',
122117
'removeEntry() with "." name should fail',
123118
'removeEntry() with ".." name should fail',

0 commit comments

Comments
 (0)