FEATURE: opt-in JavaScript host namespace (Context.new(host_namespace:))#426
Open
ursm wants to merge 2 commits into
Open
FEATURE: opt-in JavaScript host namespace (Context.new(host_namespace:))#426ursm wants to merge 2 commits into
ursm wants to merge 2 commits into
Conversation
…pace:)) Expose mini_racer's non-standard JS helpers through a single opt-in host namespace object (in the spirit of Deno's `Deno` / Bun's `Bun`) instead of polluting globalThis. Context.new(host_namespace: "MiniRacer") installs globalThis.MiniRacer with a drainMicrotasks() method; by default nothing is injected and the global stays clean. drainMicrotasks() is a native, rendezvous-free microtask checkpoint -- the inline JS-callable companion to Context#perform_microtask_checkpoint -- for draining the queue between synchronous JS operations (e.g. dispatchEvent listener chains) without the Ruby<->V8 round-trip. It uses MicrotasksScope::PerformCheckpoint (a no-op while a checkpoint is already in progress), takes no v8::Locker (safe in single-threaded mode), and leaves termination active so watchdog/OOM surfaces to the enclosing eval/call. The namespace holds native code pointers so it is not serialized into snapshots; it is (re)installed on every fresh context. It is non-enumerable on globalThis (matching built-in globals like Math/JSON), while its methods are ordinary enumerable properties. host_namespace accepts a String (the global name), true (default name "MiniRacer"), or nil/false (off). V8-only, like perform_microtask_checkpoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A host_namespace that is not a valid identifier (e.g. "foo-bar", "foo.bar") would otherwise be installed as a literal globalThis property key reachable only via globalThis["..."], never as the intended `<name>.drainMicrotasks()`. Reject such names up front with ArgumentError on both the V8 and TruffleRuby backends. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds an opt-in JavaScript host namespace:
Context.new(host_namespace: "MiniRacer")installs a singleglobalThis.MiniRacerobject and hangs mini_racer's non-standard JS helpers off it. The first such helper isMiniRacer.drainMicrotasks().By default (
host_namespaceunset) nothing is injected and the global stays clean.Why
drainMicrotasks()is the inline, JS-callable companion toContext#perform_microtask_checkpoint(#418 / #419). That Ruby method was added for spec-compliantdispatchEventordering, but in practice the drain often has to happen from within JavaScript — e.g. between each listener in a synchronousdispatchEventchain. Doing that today requiresattach-ing a Ruby callback that round-trips JS→Ruby→V8 on every drain.drainMicrotasks()runs the sameMicrotasksScope::PerformCheckpointinline on the isolate thread with no rendezvous.Design — opt-in namespace (Deno/Bun model)
Rather than injecting a bare global, helpers live under one opt-in namespace object, in the spirit of Deno's
Deno/ Bun'sBun. This keeps the default global clean (nothing injected unless asked) and gives a single, named extension point for future host helpers instead of introducing a new top-level global each time.host_namespace:accepts a String (the global name),true(the default name"MiniRacer"), ornil/false(off).The namespace object is installed non-enumerable — matching every built-in global (in a bare context
Object.keys(globalThis)is[];Math/JSONare non-enumerable). Its methods are ordinary enumerable own-properties, soObject.keys(MiniRacer)→["drainMicrotasks"].drainMicrotasks()semanticsMicrotasksScope::PerformCheckpoint— a guarded no-op while a checkpoint is already in progress; it never force-nests microtask runs.v8_reply/ rendezvous.v8::Locker(safe under:single_threaded, where V8 shares the Ruby thread).eval/call.perform_microtask_checkpoint), not propagated to the caller.The namespace closes over native code pointers, so it is not serialized into snapshots; it is (re)installed on every fresh context (multi-threaded, single-threaded, and snapshot-backed).
TruffleRuby
V8-only, like
perform_microtask_checkpoint. The option is accepted/validated but the namespace is not installed on the TruffleRuby backend.Tests / docs
New tests cover: default-off;
true/ custom name; off-paths (false, empty string) and wrong-type rejection; non-enumerable namespace + discoverable methods; inline drain order; nested-call guarded no-op (constructed to distinguish a true no-op from re-entry); microtask-exception isolation; snapshot-backed install; and watchdog + OOM termination surfacing (asserted via a trailing side effect so the tests fail if the drain becomes a no-op). A single-threaded test guards the no-Lockerinvariant. README and CHANGELOG updated.Known edge cases
host_namespace, the install is silently skipped (V8DefineOwnPropertyreturnsfalse) with no diagnostic. Only reachable by an embedder colliding their own snapshot global with their own chosen name.String::kMaxLength) likewise skips installation silently.host_namespaceraisesTypeErroron CRuby vsArgumentErroron TruffleRuby — consistent with the existing per-backend validation split for other options.🤖 Generated with Claude Code