Documentation
¶
Overview ¶
Package spidermonkey runs untrusted JavaScript on Mozilla's SpiderMonkey engine from pure Go. The engine is exactly ECMA-262: host surfaces like console and setTimeout are not built in — an embedder adds them with Global().DefineFunc.
Index ¶
- func Throw(v Value) error
- type AgentID
- type Agents
- func (a *Agents) Alive() int
- func (a *Agents) Broadcast(v Value) error
- func (a *Agents) Interrupt(id AgentID) (bool, error)
- func (a *Agents) IsAlive(id AgentID) bool
- func (a *Agents) Receive() (from AgentID, v Value, ok bool, err error)
- func (a *Agents) Send(to AgentID, v Value) error
- func (a *Agents) Spawn(glue, src string) (AgentID, error)
- type Config
- type Func
- type JS
- func (js *JS) Agents() *Agents
- func (js *JS) Close() error
- func (js *JS) Eval(ctx context.Context, src string) (Result, error)
- func (js *JS) EvalModule(ctx context.Context, specifier, src string) (ModuleResult, error)
- func (js *JS) Global() *Object
- func (js *JS) NewBytes(data []byte) (*Object, error)
- func (js *JS) NewFunction(name string, fn Func) (*Object, error)
- func (js *JS) NewObject() (*Object, error)
- func (js *JS) RegisterModuleResolver(prefix string, loader ModuleLoader)
- func (js *JS) RunJobs(ctx context.Context) iter.Seq2[Result, error]
- func (js *JS) SetModuleLoader(loader ModuleLoader)
- func (js *JS) TakeUnhandledRejections() ([]Rejection, error)
- type JSError
- type ModuleLoader
- type ModuleResult
- type Object
- func (o *Object) Bool() bool
- func (o *Object) Bytes() ([]byte, error)
- func (o *Object) Call(args ...Value) (Value, error)
- func (o *Object) CallMethod(name string, args ...Value) (Value, error)
- func (o *Object) DefineConstructor(name, key string, fn Func) error
- func (o *Object) DefineFunc(name string, fn Func) error
- func (o *Object) Export() any
- func (o *Object) Float() float64
- func (o *Object) Free() error
- func (o *Object) Get(name string) (Value, error)
- func (o *Object) Int() int
- func (o *Object) IsFunction() bool
- func (o *Object) IsObject() bool
- func (o *Object) IsUndefined() bool
- func (o *Object) New(args ...Value) (Value, error)
- func (o *Object) Object() *Object
- func (o *Object) Set(name string, v Value) error
- func (o *Object) String() string
- type Rejection
- type Result
- type Value
- type WritableFS
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type AgentID ¶ added in v0.2.0
type AgentID uint64
AgentID identifies one spawned agent within a cluster.
type Agents ¶ added in v0.2.0
type Agents struct {
// contains filtered or unexported fields
}
Agents is the interpreter's agent cluster — the host surface over ECMA-262 agents. The spec defines what an agent IS (its own thread of execution and realm, sharing nothing with other agents but SharedArrayBuffer memory) and leaves creation and communication to the host; this type IS that host policy, implemented in Go. The engine bridge contributes only the thread mechanics (Spawn) and structured-clone transport; the queues, the broadcast latch and the lifecycle tracking all live here, so richer topologies ($262.agent, Web Workers, worker_threads with per-agent channels) are adapters over this type rather than engine changes.
Two delivery models, both over the same structured-clone transport:
- Broadcast(v) latches ONE value that every agent's broadcast receive gets (test262's $262.agent). A SharedArrayBuffer in v SHARES its memory with each receiver; everything else is a deep copy.
- Send(to, v) delivers v to ONE agent's FIFO inbox (postMessage to a specific worker). The agent reads it with its inbox receive.
Either direction back to the host is an agent's post(v), popped with Receive() — which reports the sender, so a Worker's onmessage can route.
func (*Agents) Broadcast ¶ added in v0.2.0
Broadcast latches v as THE broadcast and wakes every agent blocked in receive. Agents that call receive later get the same value. A previously latched value is superseded (its clone is retained until Close, since a slow receiver may still be reading it).
func (*Agents) Interrupt ¶ added in v0.5.0
Interrupt stops one agent whatever it is doing: its script ends with an uncatchable exception (guest JS cannot swallow it) and the agent leaves instead of resuming its pump.
This is the forceful counterpart to a cooperative stop. Everything else here is a MESSAGE, which an agent only sees between job-queue drains — and a runaway `while(true){}` never drains, so no message can reach it. Interrupt goes through the agent's own engine context instead, which also wakes one parked in Atomics.wait or idling with nothing to do.
Asynchronous: it returns once the agent is signalled, not once it has gone (the agent may be mid-bytecode on its own thread) — poll IsAlive to wait for the exit. Reports false when no agent with this id is running, in which case there is nothing left to stop.
func (*Agents) IsAlive ¶ added in v0.5.0
IsAlive reports whether the agent with id has not yet exited. A Worker adapter polls this to fire 'exit' even when an agent ends without posting a farewell (e.g. an uncaught error during evaluation).
func (*Agents) Receive ¶ added in v0.2.0
Receive pops the next value an agent posted, with the id of the sender (so a Worker adapter can route it to the right onmessage). ok is false when the queue is empty. The value is deserialized into this interpreter's runtime: an object arrives as an *Object, a SharedArrayBuffer still shares its memory.
func (*Agents) Send ¶ added in v0.2.0
Send delivers v to one agent's FIFO inbox (postMessage to a specific worker) and wakes it if it is blocked in its inbox receive. A SharedArrayBuffer in v shares its memory with the agent; everything else is a deep copy.
func (*Agents) Spawn ¶ added in v0.2.0
Spawn runs an agent on a new thread (a goroutine), its own realm, sharing only SharedArrayBuffer memory with this interpreter. `glue` is trusted adapter setup (evaluated with the agent-primitive prelude, which exposes globalThis.__agent__ = { receive(cb) (blocking broadcast), recv() (blocking inbox), tryRecv()/NO_MSG (non-blocking inbox poll), post(v), sleep(ms), leaving(), monotonicNow() }); `src` is the user source, evaluated as its OWN separate script so its strict directive, line numbers and module-ness are untouched. After both evaluate, the agent drains its job queue until leaving() (or Close). Returns the agent's id, so the host can Send to it and match its posts.
type Config ¶
type Config struct {
// MaxMemoryBytes caps the interpreter's wasm linear memory — the single
// ceiling on everything it can allocate (GC heap, engine data, C stack).
// It sizes the allocation the guest grows into; untouched pages never page
// in, so headroom is nearly free. Hitting it aborts the guest (the host
// gets an error and the instance is spent), so it is a hard host-protection
// backstop, not a limit a script recovers from. Zero means the default.
MaxMemoryBytes int
// Env is the environment the guest sees (and that host functions may read
// via the Config handed to them). nil means an empty environment — the host
// process os.Environ() is NOT leaked.
Env []string
// Stdin, Stdout, Stderr are for HOST FUNCTIONS. SpiderMonkey has no I/O of
// its own, so these do nothing until a host function uses them (a future
// Node-compatible console/process). Unset streams are sandboxed (empty
// stdin, discarded output), never the host process's.
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
// FS is the filesystem the guest sees: it backs the default ES module
// loader and every host-function file operation (the compat packages'
// node:fs). SpiderMonkey itself has no filesystem API, so it does nothing
// on its own. nil means modules must be supplied by a custom
// SetModuleLoader and no filesystem is reachable.
//
// FS is ALSO the single point of filesystem access control: an
// implementation enforces its own policy inside its Open/OpenFile/Stat/…
// methods (deny a path with fs.ErrPermission, hide it with
// fs.ErrNotExist), exactly as a sheena fs.Volume does with its
// Refuse/Hide/Access rules. There is deliberately no separate access
// callback — a plain fs.FS grants read access to everything it exposes
// (the embedder chose to expose it), and a policy-carrying FS restricts
// reads and writes uniformly from one place. An FS that also implements
// WritableFS accepts writes; a plain fs.FS is read-only (writes surface
// as EROFS).
FS fs.FS
// Dial is the outbound-connection whitelist, called before each connect:
// host is the name the guest requested and that resolved to ip (or "" for a
// literal-IP dial with no preceding lookup), ip is the dotted-quad/IPv6
// being dialed, and port is the port. Returning false denies the
// connection; a nil Dial denies ALL outbound connections. Passing host lets
// a policy match host and port jointly (e.g. "example.com only on 443") — an
// IP alone cannot be tied back to the name resolved. The connection is made
// to the exact ip that was approved (the name is resolved once, before the
// hook), so a DNS answer cannot smuggle a different address past the
// allow-list. This mirrors wasm2go's WASI dial hook
// (func(network, host, ip string, port int) bool).
Dial func(network, host, ip string, port int) bool
// Resolve is the name-resolution whitelist. It is called with the host
// being resolved before each lookup; returning false denies it, and a nil
// Resolve denies ALL name resolution (so a hostname connection needs both
// Resolve and Dial; a literal-IP connection needs only Dial). This is where
// a hostname policy such as "block example.com" is enforced.
Resolve func(host string) bool
// Listen gates inbound sockets. It is called with the network and local
// address before each listen; returning false denies it, and a nil Listen
// denies ALL inbound sockets.
Listen func(network, addr string) bool
// Exec is the subprocess whitelist. It is called before every process spawn
// with the executable path and full argv; returning false denies the spawn,
// and a nil Exec denies ALL spawns. Spawning runs a HOST binary, so a
// sandbox that allows subprocess should set this to a strict allow-list.
Exec func(path string, argv []string) bool
}
Config configures a JS interpreter. The zero value is a usable, fully sandboxed interpreter: no host environment leaks, no host stdio is attached, no filesystem is reachable, and the memory cap takes its default.
type Func ¶ added in v0.2.0
Func is a Go function callable from guest JavaScript. It receives the interpreter's Config (so it can reach Env, stdio and FS) and the call's arguments — primitives as data, objects and functions as *Object handles with identity preserved. The returned Value crosses back the same way. A returned error surfaces in the guest as a thrown Error.
fn may re-enter its interpreter — navigate *Object arguments (Get/Set/Call) or even Eval — because the interpreter's invoke lock is released for the callback's duration: the guest is paused waiting for fn's reply, so re-entry continues from the current stack, exactly like a native function calling back into the engine. *Object arguments also stay valid after the evaluation returns (their handles pin the objects), so retaining them for later works too.
type JS ¶ added in v0.2.0
type JS struct {
// contains filtered or unexported fields
}
JS is one isolated SpiderMonkey interpreter: its own wasm instance (its own linear memory) and one runtime. Several run concurrently and in isolation.
func New ¶ added in v0.2.0
New creates a sandboxed interpreter from cfg. The zero Config is a usable, fully sandboxed interpreter.
func (*JS) Close ¶ added in v0.2.0
Close destroys the interpreter and releases its resources. Agents blocked in receive are released first (they unwind with an error) so the engine's shutdown join can complete.
func (*JS) Eval ¶ added in v0.2.0
Eval runs src as a classic script to synchronous completion — draining the microtask queue — and returns the result. ctx aborts a runaway script: on cancellation the script is interrupted and Eval returns ctx.Err(). Eval does NOT drive timers or other async work; use RunJobs for that. A JavaScript throw is reported in Result (OK=false, Error), not as a Go error.
func (*JS) EvalModule ¶ added in v0.2.0
EvalModule compiles and runs src as an ES module registered under specifier, resolving its imports through the module loader (the default Config.FS loader, or one set with SetModuleLoader) and draining the job queue. ctx aborts a runaway module. Like Eval, a JavaScript-level failure is in Result, not a Go error.
func (*JS) Global ¶ added in v0.2.0
Global returns the interpreter's global object. Defining a function on it is the host-surface opt-in: a fresh interpreter is pure ECMA-262 and exposes nothing until the embedder adds something.
js.Global().DefineFunc("print", func(cfg Config, args []Value) (Value, error) {
...
})
func (*JS) NewBytes ¶ added in v0.3.0
NewBytes copies data into a fresh guest Uint8Array and returns its handle. This is the []byte -> JS direction of the binary bridge; the reverse is Object.Bytes. The returned Object is a Value, so it can be passed to Call, Set, or returned from a host Func like any other value. The caller owns the handle (Free releases the host's pin; the guest keeps the array alive as long as it references it).
func (*JS) NewFunction ¶ added in v0.3.0
NewFunction returns a fresh guest function object backed by fn — the Go analogue of syscall/js's FuncOf. Unlike DefineFunc it attaches to nothing: the embedder composes it into any structure — an object property (Set), a callback argument (Call), an underlyingSource.pull — and identity is preserved wherever it flows. name is only the function's `name` property.
The caller owns the handle (Free releases the host's pin; the guest keeps the function alive as long as it references it). The Go-side registration lives for the interpreter's lifetime — it cannot be released earlier, since the guest may hold and call the function long after the host dropped its handle.
func (*JS) NewObject ¶ added in v0.2.0
NewObject creates a fresh, empty JavaScript object owned by this interpreter.
func (*JS) RegisterModuleResolver ¶ added in v0.5.0
func (js *JS) RegisterModuleResolver(prefix string, loader ModuleLoader)
RegisterModuleResolver installs loader for module specifiers beginning with prefix (e.g. "node:"), so several packages can each claim a specifier namespace without clobbering one another or the fallback loader. The longest matching registered prefix wins; specifiers matching no prefix go to the fallback (SetModuleLoader, or the default Config.FS loader). An empty prefix sets the fallback, exactly like SetModuleLoader. Registering nil removes the prefix's resolver. Not safe to call concurrently with evaluation.
func (*JS) RunJobs ¶ added in v0.2.0
RunJobs drives the ECMA-262 job queue, yielding the result of each pump that ran work and ending when the queue is idle or ctx is done. Pending-but-not-due work (a timer, an Atomics.waitAsync timeout) is awaited internally, bounded by ctx, so every yielded Result reflects real progress. Break to stop early:
for r, err := range js.RunJobs(ctx) {
if err != nil { break }
// r.Stdout, r.OK, ...
}
func (*JS) SetModuleLoader ¶ added in v0.2.0
func (js *JS) SetModuleLoader(loader ModuleLoader)
SetModuleLoader installs the fallback loader — the one consulted when no resolver registered with RegisterModuleResolver matches the specifier. It is called on a registry miss and returns the module's source. It replaces the default Config.FS loader. Pass nil to disable fallback loading (imports then fall back to the "module not registered" failure).
func (*JS) TakeUnhandledRejections ¶ added in v0.5.0
TakeUnhandledRejections hands back every rejection still unhandled and forgets them. It is what `unhandledRejection` / `unhandledrejection` are built on: such a rejection is visible ONLY to the engine, since an async function's promise is created by the engine and so escapes any Promise wrapper installed from the host.
Call it at a microtask checkpoint — after Eval or after RunJobs runs dry — so a rejection the guest went on to handle in the same tick is not reported; the engine retracts those as soon as a handler is attached. Draining is destructive: each rejection is reported exactly once, however often this is called.
The caller owns any *Object in the result and must Free it.
type JSError ¶ added in v0.2.0
type JSError struct {
// Message is the exception's stringification plus its stack, or a phrase
// like "JS execution interrupted".
Message string
}
JSError is a JavaScript-level failure: an uncaught throw, a compile error, a failed module import, or an interrupt. It is what Result.Error / ModuleResult .Error hold; it is distinct from a Go error returned by Eval/EvalModule, which signals a host/transport failure (a wasm trap, an encoding problem).
type ModuleLoader ¶ added in v0.5.0
ModuleLoader resolves a module specifier to its source. It receives the interpreter's Config, the resolved module specifier, and the specifier of the importing module (referrer).
type ModuleResult ¶ added in v0.2.0
type ModuleResult struct {
// Error is non-nil when the module threw, failed to compile, failed to
// resolve an import, or was interrupted.
Error error
}
ModuleResult is the outcome of running an ES module with EvalModule. A module has NO completion value — its output is its side effects and (in future) its exports — so there is no Value field.
type Object ¶ added in v0.2.0
type Object struct {
// contains filtered or unexported fields
}
Object is a handle to a JavaScript object (or function) owned by a JS interpreter. An Object IS a Value — it implements the Value interface — so it can be passed anywhere a Value is expected, and property navigation (Get) can return either a primitive Value or another Object.
The handle preserves identity: an Object that crosses the bridge in either direction refers to the SAME guest object, so mutations are visible on both sides. The handle pins the object against garbage collection until Free (or the interpreter's Close).
func (*Object) Bytes ¶ added in v0.3.0
Bytes copies the object's binary contents into a fresh Go []byte. It accepts a Uint8Array, any other ArrayBuffer view (Int32Array, DataView, ...) — read as its raw byte window — an ArrayBuffer, or a SharedArrayBuffer. Any other object is an error. This is the JS -> []byte direction of the binary bridge; the reverse is JS.NewBytes.
func (*Object) Call ¶ added in v0.2.0
Call invokes the object as a function with `this` undefined. A guest throw comes back as a *JSError.
func (*Object) CallMethod ¶ added in v0.2.0
CallMethod invokes o[name](args...) with `this` bound to o.
func (*Object) DefineConstructor ¶ added in v0.2.0
DefineConstructor defines a host-backed CONSTRUCTOR name on the object: `new name(...)` from JS runs fn, and the Value fn returns (typically an *Object built with NewObject) becomes the instance. This is how a real host class — `new Worker(src)` and the like — is defined from Go.
key lets several constructors share a name across objects with distinct Go callbacks; pass name itself for the common case.
func (*Object) DefineFunc ¶ added in v0.2.0
DefineFunc defines a host-backed function name on the object; calling it from JS runs fn with the interpreter's Config and the call arguments. Each definition dispatches under its own hidden key, so defining the same name on two objects never makes their Go callbacks collide. Like NewFunction, the Go-side registration lives for the interpreter's lifetime.
func (*Object) Free ¶ added in v0.2.0
Free releases the object handle (its GC pin). The object itself lives on in the guest; only the host's reference is dropped. The global object (from Global) is freed by Close and must not be freed here.
Freeing twice is a no-op rather than a fault. A handle IS a pointer to the guest's GC root for this object, so a second release would delete it twice and corrupt the guest heap — with no symptom until something much later walks the root list, typically surfacing as an unrelated crash inside teardown. Ownership of a handle is easy to lose track of across the host's close paths, so the primitive absorbs it instead of every caller having to.
func (*Object) Get ¶ added in v0.2.0
Get returns the property name of the object as a Value — a primitive carries its data, an object or function comes back as an *Object with identity preserved. A missing property is Undefined; a property whose access throws (a getter) is returned as a *JSError.
func (*Object) IsFunction ¶ added in v0.2.0
IsFunction reports whether the object is callable (Call will work).
func (*Object) IsUndefined ¶ added in v0.2.0
func (*Object) New ¶ added in v0.3.0
New constructs `new o(args...)` — the Go analogue of syscall/js's Value.New. o must be a constructor (a class or a function); the returned Value is the new instance. A guest throw during construction comes back as a *JSError.
type Rejection ¶ added in v0.5.0
Rejection is one promise rejection that nothing handled: the reason it was rejected with, and the promise itself.
type Result ¶ added in v0.2.0
type Result struct {
// Value is the script's completion value, valid only when Error is nil.
// (Scripts have a completion value; modules do not — see ModuleResult.)
// A primitive completion carries its data and type; an object or function
// completion is an *Object with identity preserved — the same guest object,
// navigable and callable.
Value Value
// Error is non-nil when the script threw, failed to compile, or was
// interrupted.
Error error
}
Result is the outcome of running a script with Eval. Error is nil on success; a non-nil Error (a *JSError) means the script threw, failed to compile, or was interrupted.
There is no Stdout/Stderr: the engine is pure ECMA-262 and has no I/O of its own. Guest output is whatever the embedder wires up — define a `console` or `print` with Global().DefineFunc and route it to any io.Writer.
type Value ¶ added in v0.2.0
type Value interface {
String() string
Float() float64
Int() int
Bool() bool
IsUndefined() bool
IsObject() bool
// Object returns the value as an *Object when IsObject, else nil.
Object() *Object
// Export returns the default Go representation of the value (JS → Go).
Export() any
// contains filtered or unexported methods
}
Value is any JavaScript value — a primitive (number, string, boolean, null, undefined) or an Object. It is an interface precisely because a JS value is a single family spanning all of these: the concrete kinds (primitives and *Object) implement it, and code that holds a Value need not care which it is.
Primitives cross the guest/host boundary as data; objects and functions cross as a handle (an *Object) so they keep their identity and can still be navigated and called. String coerces like JS ToString for primitives; Float, Int and Bool coerce to the Go type (returning the zero value when the Value is not that kind); Export returns the default Go representation.
func ValueOf ¶ added in v0.2.0
ValueOf wraps a Go value as a host Value. An existing Value passes through; bool, string and the numeric types become the matching JS primitive; nil becomes undefined. A composite Go value (slice, array, map, struct — anything encoding/json can marshal) materializes guest-side as a FRESH JS Array/Object each time it crosses: it carries data, not identity. When the guest must see the same object across calls, build it once with NewObject/Set instead.
type WritableFS ¶ added in v0.5.0
type WritableFS interface {
fs.FS
// OpenFile opens name with OS-style flags (os.O_RDONLY, os.O_CREATE,
// os.O_TRUNC, os.O_APPEND, ...). When the flags request write access, the
// returned file also implements io.Writer.
OpenFile(name string, flag int, perm fs.FileMode) (fs.File, error)
// Mkdir creates a directory. The parent must already exist.
Mkdir(name string, perm fs.FileMode) error
// Remove removes a file or an empty directory.
Remove(name string) error
// Rename moves oldname to newname, replacing a non-directory target.
Rename(oldname, newname string) error
}
WritableFS is the interface upgrade a Config.FS may implement to accept writes. Host functions that need to create or modify files assert this interface on Config.FS (the fs.ReadDirFS idiom): when the assertion fails, the filesystem behaves as a read-only mount and writes surface to the guest as permission errors. Reads always go through the plain fs.FS methods.
An implementation is the single point of filesystem access control: it enforces its own policy inside these methods and inside the fs.FS read methods (return fs.ErrPermission to deny, fs.ErrNotExist to hide). A sheena fs.Volume satisfies this interface through a tiny adapter (its OpenFile returns sheena's fs.File, which is an io/fs.File; the rest match by embedding), so a sheena sandbox's Refuse/Hide/Access rules carry straight into the guest.
The memfs package provides an unrestricted in-memory implementation, useful in tests and for isolating instances from the host filesystem entirely.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
compat
|
|
|
cfworkers
Package cfworkers runs Cloudflare-Workers-style modules — `export default { fetch(request, env, ctx) }` — on go-spidermonkey behind Go's net/http.
|
Package cfworkers runs Cloudflare-Workers-style modules — `export default { fetch(request, env, ctx) }` — on go-spidermonkey behind Go's net/http. |
|
internal/eventloop
Package eventloop provides the macro-task loop the compat packages share: host-side timers (setTimeout/setInterval), completions posted from other goroutines (async ops like fetch), and the microtask drain between tasks.
|
Package eventloop provides the macro-task loop the compat packages share: host-side timers (setTimeout/setInterval), completions posted from other goroutines (async ops like fetch), and the microtask drain between tasks. |
|
nodejs
Package nodejs is the Node.js compatibility layer (docs/nodejs-compat-plan.md Phase 3): the node: core modules (path, events, util, buffer, fs, ...), process with Node's nextTick ordering, Buffer, CommonJS require with node_modules resolution, and ESM⇄CJS interop — installed explicitly:
|
Package nodejs is the Node.js compatibility layer (docs/nodejs-compat-plan.md Phase 3): the node: core modules (path, events, util, buffer, fs, ...), process with Node's nextTick ordering, Buffer, CommonJS require with node_modules resolution, and ESM⇄CJS interop — installed explicitly: |
|
web
Package web installs the WinterTC (minimum common Web API) vocabulary on a go-spidermonkey interpreter: console, TextEncoder/TextDecoder, atob/btoa, URL/URLSearchParams, AbortController, queueMicrotask, structuredClone, performance.now, crypto.getRandomValues/randomUUID, ReadableStream, fetch, and setTimeout/setInterval.
|
Package web installs the WinterTC (minimum common Web API) vocabulary on a go-spidermonkey interpreter: console, TextEncoder/TextDecoder, atob/btoa, URL/URLSearchParams, AbortController, queueMicrotask, structuredClone, performance.now, crypto.getRandomValues/randomUUID, ReadableStream, fetch, and setTimeout/setInterval. |
|
Package memfs provides a writable in-memory filesystem implementing spidermonkey.WritableFS.
|
Package memfs provides a writable in-memory filesystem implementing spidermonkey.WritableFS. |