Skip to content

Commit 8aecab1

Browse files
authored
Fix mounting WORKERFS images from the main thread using the JS API (#488)
* Always hook FS.mount() and revive from ArrayBuffer * Update NEWS.md
1 parent bf4d142 commit 8aecab1

File tree

3 files changed

+37
-27
lines changed

3 files changed

+37
-27
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
* When explicitly creating a list using the `RList` constructor, nested JavaScript objects at a deeper level are also converted into R list objects. This does not affect the generic `RObject` constructor, as the default is for JavaScript objects to map to R `data.frame` objects using the `RDataFrame` constructor.
88

9+
## Bug Fixes
10+
11+
* (Regression) Mounting filesystem images using the `WORKERFS` filesystem type now works again in the browser using the JavaScript API from the main thread (#488).
12+
913
# webR 0.4.2
1014

1115
## New features

src/webR/mount.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,31 @@ type WorkerFileSystemType = Emscripten.FileSystemType & {
1616
contents: ArrayBufferView, mtime?: Date) => FS.FSNode;
1717
};
1818

19+
/**
20+
* Hooked FS.mount() for using WORKERFS under Node.js or with `Blob` objects
21+
* replaced with Uint8Array over the communication channel.
22+
* @internal
23+
*/
24+
export function mountFS(type: Emscripten.FileSystemType, opts: FSMountOptions, mountpoint: string) {
25+
// For non-WORKERFS filesystem types, just call the original FS.mount()
26+
if (type !== Module.FS.filesystems.WORKERFS) {
27+
return Module.FS._mount(type, opts, mountpoint) as void;
28+
}
29+
30+
// Otherwise, handle `packages` using our own internal mountImageData()
31+
if ('packages' in opts && opts.packages) {
32+
opts.packages.forEach((pkg) => {
33+
mountImageData(pkg.blob as ArrayBufferLike, pkg.metadata, mountpoint);
34+
});
35+
} else {
36+
// TODO: Handle `blobs` and `files` keys.
37+
throw new Error(
38+
"Can't mount data. You must use the `packages` key when mounting with `WORKERFS` in webR."
39+
);
40+
}
41+
}
42+
43+
1944
/**
2045
* Download an Emscripten FS image and mount to the VFS
2146
* @internal
@@ -88,29 +113,6 @@ export function mountImagePath(path: string, mountpoint: string) {
88113
}
89114
}
90115

91-
/**
92-
* An implementation of FS.mount() for WORKERFS under Node.js
93-
* @internal
94-
*/
95-
export function mountFSNode(type: Emscripten.FileSystemType, opts: FSMountOptions, mountpoint: string) {
96-
if (!IN_NODE || type !== Module.FS.filesystems.WORKERFS) {
97-
return Module.FS._mount(type, opts, mountpoint) as void;
98-
}
99-
100-
if ('packages' in opts && opts.packages) {
101-
opts.packages.forEach((pkg) => {
102-
// Main thread communication casts `Blob` to Uint8Array
103-
// FIXME: Use a replacer + reviver to handle `Blob`s
104-
mountImageData(pkg.blob as ArrayBufferLike, pkg.metadata, mountpoint);
105-
});
106-
} else {
107-
throw new Error(
108-
"Can't mount data under Node. " +
109-
"Mounting with `WORKERFS` under Node must use the `packages` key."
110-
);
111-
}
112-
}
113-
114116
// Mount the filesystem image `data` and `metadata` to the VFS at `mountpoint`
115117
function mountImageData(data: ArrayBufferLike | Buffer, metadata: FSMetaData, mountpoint: string) {
116118
if (IN_NODE) {
@@ -140,7 +142,9 @@ function mountImageData(data: ArrayBufferLike | Buffer, metadata: FSMetaData, mo
140142
WORKERFS.createNode(dirNode, file, WORKERFS.FILE_MODE, 0, contents);
141143
});
142144
} else {
143-
Module.FS.mount(Module.FS.filesystems.WORKERFS, {
145+
// Main thread communication casts `Blob` to Uint8Array
146+
// FIXME: Use a replacer + reviver to handle `Blob`s
147+
Module.FS._mount(Module.FS.filesystems.WORKERFS, {
144148
packages: [{
145149
blob: new Blob([data]),
146150
metadata,

src/webR/webr-worker.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { WebRPayloadRaw, WebRPayloadPtr, WebRPayloadWorker, isWebRPayloadPtr } f
1010
import { RPtr, RType, RCtor, WebRData, WebRDataRaw } from './robj';
1111
import { protect, protectInc, unprotect, parseEvalBare, UnwindProtectException, safeEval } from './utils-r';
1212
import { generateUUID } from './chan/task-common';
13-
import { mountFSNode, mountImageUrl, mountImagePath } from './mount';
13+
import { mountFS, mountImageUrl, mountImagePath } from './mount';
1414
import type { parentPort } from 'worker_threads';
1515

1616
import {
@@ -840,8 +840,6 @@ function init(config: Required<WebROptions>) {
840840

841841
Module.preRun.push(() => {
842842
if (IN_NODE) {
843-
Module.FS._mount = Module.FS.mount;
844-
Module.FS.mount = mountFSNode;
845843
globalThis.FS = Module.FS;
846844
(globalThis as any).chan = chan;
847845
}
@@ -852,6 +850,10 @@ function init(config: Required<WebROptions>) {
852850
Module.ENV.HOME = _config.homedir;
853851
Module.FS.chdir(_config.homedir);
854852
Module.ENV = Object.assign(Module.ENV, env);
853+
854+
// Hook Emscripten's FS.mount() to handle ArrayBuffer data from the channel
855+
Module.FS._mount = Module.FS.mount;
856+
Module.FS.mount = mountFS;
855857
});
856858

857859
chan?.setDispatchHandler(dispatch);

0 commit comments

Comments
 (0)