Skip to content

Add opt-in Web API foundation: Options.WebApi, DOMException and console - #3079

Merged
lahma merged 2 commits into
sebastienros:mainfrom
lahma:webapi/foundation
Aug 20, 2026
Merged

lahma merged 2 commits into
sebastienros:mainfrom
lahma:webapi/foundation

Conversation

@lahma

@lahma lahma commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

This is the first PR of a planned series that gives Jint an opt-in, WHATWG-faithful set of web platform APIs — console, timers, TextEncoder, URL, AbortController and ultimately fetch — guided by WinterTC's Minimum Common Web Platform API. Users have asked for this since 2017 (#442, discussion #1253), and every embedder that wanted logging or HTTP has had to hand-roll its own shims.

Three ground rules hold for the whole series, and this PR lays them down:

  • BCL only. No new package dependencies, which is why the surface is uniformly #if NET8_0_OR_GREATER (the same shape as the Float16 APIs and the System.Text.Json interop): on net462/netstandard2.x the options and types simply do not exist.
  • Opt-in, and nothing else changes. A default engine is byte-for-byte the engine it was before: no globals, no construction cost (the options group itself is allocated on first touch), no behaviour change. UseWebApis() enables the non-network set; fetch, when it lands, will always require its own explicit UseFetch(...).
  • The host wins. Globals are installed as lazy descriptors from Options.Apply, after the host's own configuration, and the install is non-clobbering — a console the host registered itself is left exactly as the host left it (checked with a probe, so a host's lazy global is not even materialized by the look). Only the principal realm is touched, so a ShadowRealm gets none of this.

What's in this PR

  • Options.WebApi + [Flags] WebApiFeatures + UseWebApis/UseConsole extensions, and WebApiRegistration — the plumbing every later feature rides on. Flags are declared only once the feature behind them exists; the bit layout is reserved up front.
  • DOMException per WebIDL: prototype chained to %Error.prototype% (so instanceof Error holds and stack works), name/message/code as brand-checked prototype accessors, the 25 legacy constants on both the interface object and the prototype. It has no feature flag — it is how every web API reports failure, so it exists whenever any of them does.
  • console per the Console Standard: the log family, assert, trace, dir, group, count and time, with the %s/%d/%i/%f/%o/%O/%c/%% formatter, group indentation, and a bounded, cycle-safe, getter-free object inspection. Output goes to a host-supplied ConsoleSink (one finished line per record); the default sink discards everything, so enabling the feature never surprises a server host with stdout writes.
  • Jint.Repl adopts UseConsole(Console.Out) and drops its hand-rolled JsConsole — first dogfooding consumer.
  • One core fix the work surfaced: Error.prototype.toString passed %Error.prototype% as the receiver for its name/message reads where spec steps 3 and 5 say Get(O, …). Unobservable for ordinary errors (data properties); wrong for any error whose attributes are prototype accessors that brand-check their receiver, which DOMException's are. Full test262 stays green.
  • README gains a "Web APIs (opt-in)" section with the roadmap table; AGENTS.md scopes the unconditional-registration rule to TC39 features and documents the new subtree's conventions.

Verification

  • dotnet build -c Release — all five TFMs, 0 errors (the #if gate is proven by the downlevel targets compiling).
  • Jint.Tests and Jint.Tests.PublicInterface green on net10.0 and net472, in both the default and the JINT_HOST_CONTRACT_VERIFICATION=1 configurations.
  • Full test262 green (run because a core Error file was touched; the Error-filtered subset was run repeatedly).
  • Pins included for: a default engine has no web globals; a host-registered global wins; ShadowRealm gets nothing; a snapshot restore returns the globals to their unmaterialized state.

The next PRs in the series, in dependency order: timers + queueMicrotask (the one event-loop change, benchmark-gated), then Event/AbortController, TextEncoder/TextDecoder, atob/btoa, structuredClone, crypto, performance, WHATWG URL, Blob/File/FormData, and finally fetch itself with bounded default policy (scheme allowlist, redirect-hop re-validation, response-size cap, timeout) and a threat-model entry.

🤖 Generated with Claude Code

lahma and others added 2 commits August 20, 2026 11:36
Introduces the plumbing for a WHATWG-faithful web platform surface, plus the
first two APIs on it. Nothing is installed unless a host asks: an engine that
does not is byte-for-byte the engine it was before.

- Options.WebApi (net8+ only, whole-file gated) with WebApiFeatures, the
  ConsoleSink abstraction and UseWebApis/UseConsole extensions. The options
  group is allocated on first touch, so a default engine allocates nothing
  for it and Options.Apply reads the backing field.
- WebApiRegistration installs one LazyPropertyDescriptor<Engine> per global
  through GlobalObject.SetProperty, from the one gated block in Options.Apply.
  Non-clobbering: a name the host already registered is left alone, checked
  with a probe so a host's own lazy global is not materialized by the look.
  Only the principal realm is touched, so a ShadowRealm gets nothing.
- DOMException per WebIDL: prototype chained to %Error.prototype%, name/message/
  code as brand-checked prototype accessors, the 25 legacy constants on both the
  interface object and the prototype, a lazy own `stack`, @@toStringTag.
- console per the Console Standard: the log family, assert, trace, dir, group,
  count and time, with the %s/%d/%i/%f/%o/%O/%c/%% formatter, group indentation
  and a bounded, cycle-safe, getter-free object inspection. Exactly one
  ConsoleSink.Write per emitted record.
- Jint.Repl adopts UseConsole(Console.Out) and drops its hand-rolled JsConsole.
- Error.prototype.toString now performs Get(O, "name"/"message") with O as the
  receiver rather than %Error.prototype%, as steps 3 and 5 specify. It is
  unobservable for an ordinary error, whose name and message are data
  properties, but a WebIDL-shaped error whose attributes are brand-checking
  prototype accessors was handed the wrong receiver and rightly refused.
- README gains a "Web APIs (opt-in)" section; AGENTS.md scopes the
  unconditional-registration rule to TC39 features, documents the Jint/WebApi
  subtree and adds the WHATWG citation URLs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lahma
lahma merged commit afa3f4d into sebastienros:main Aug 20, 2026
5 checks passed
@lahma
lahma deleted the webapi/foundation branch August 20, 2026 09:06
This was referenced Aug 20, 2026
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