Skip to content

feat: Node-style primordials for runtime builtins - #1990

Open
edusperoni wants to merge 1 commit into
feat/js-builtinsfrom
feat/primordials
Open

feat: Node-style primordials for runtime builtins#1990
edusperoni wants to merge 1 commit into
feat/js-builtinsfrom
feat/primordials

Conversation

@edusperoni

@edusperoni edusperoni commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Description

Android mirror of NativeScript/ios#415. Stacked on #1989 (feat/js-builtins) — review only the last commit.

The runtime's builtin JavaScript installs globals and leaves closures behind that run for the lifetime of the app: event dispatch, the URL/blob glue, console's stringify, the JS→org.json serializer. Until now those closures reached for intrinsics (Array.prototype.slice, JSON.stringify, Object.defineProperty, …) through the live globals, so app code replacing one could break runtime internals or observe them.

internal/primordials.js captures exactly the intrinsics the other builtins need into a frozen, null-prototype namespace, Node-style — trimmed to what the Android builtins actually reach (not a mirror of Node's or iOS's list).

The (binding, primordials) contract

  • Builtins now compile with four fixed parameters: exports, module, binding, primordials.
  • The snapshot is built lazily on the first RunBuiltin of an isolate — during runtime init, before any user code — and cached per isolate (mutex-guarded, released on isolate disposal; worker isolates snapshot their own realm's intrinsics automatically).
  • Instance methods are uncurried exactly as Node does it (ArrayPrototypeSlice(list, 1), via Function.prototype.bind.bind(Function.prototype.call)); statics keep their path (JSONStringify). Plain constructor calls made once at init time stay direct — the rule targets closures that outlive init.
  • Deliberately left live: Blob/File in blob-url.js (app-layer provided), global.__requireOverride in require-factory.js (app-layer hook), org.json.* in json-helper.js (metadata interceptor, not an intrinsic).

Enforcement: ESLint no-restricted-properties for every captured static and no-restricted-globals for every captured constructor, each message naming the replacement; primordials.js itself is exempted. Both rule classes verified to fire.

Tests: new tests/testPrimordials.js (8 specs) tampers with the intrinsics and checks the runtime keeps working — a guard spec proving the tampering is observable, global dispatchEvent/add/remove/once under broken Array.prototype.* + Function.prototype.call, reportError delivering a correct ErrorEvent, circular console.log staying non-fatal, plus Android-specific specs for the searchParams re-sync, the blob store under broken Map.prototype.*, and org.json serialization under broken Array/Object/Date intrinsics. Each spec keeps the tampered window synchronous and assertion-free, restoring originals in finally.

Related Pull Requests

Does your pull request have unit tests?

Yes — 8 new device specs. Full suite: 613 specs, 0 failures (605 baseline + 8), including worker suites, which exercise per-isolate snapshot creation and disposal on worker threads.

Runtime builtins install closures that outlive init and are then reachable
from app code, so every intrinsic they use at call time is something the app
can replace. internal/primordials.js snapshots exactly the intrinsics the
builtins need into a frozen null-prototype namespace, taken on the first
RunBuiltin of an isolate (during runtime init) and cached per isolate;
builtins now compile with a fourth fixed parameter, `primordials`.

Instance methods are uncurried Node-style, so the receiver becomes the first
argument. ESLint fails the lint on direct use of the captured statics and
constructors.

Mirrors NativeScript/ios#415.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c658ec4d-42a3-432b-a2da-cd23b8686ae3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant