ReviewOS

zig-utils/zig-js

exact DOMURL object boundary

#313
Closed chris opened this 9 days ago ·
9 days ago

Parent: #140 Tracked by: #134, #163, #164

Symbols (URL cluster sub-slice 4 — DOMURL object boundary, final URL-cluster slice)

SymbolHome (packages/runtime/src/... @ 7ed99c02)Bun C++ (upstream/src/jsc/bindings/...)
BunString__toJSDOMURLjsc/bun_string_jsc.zig:9 — (globalObject, in: *String) JSValueBunString.cpp:580 — DOMURL::create(str, String()) + toJSNewlyCreated; RETURN_IF_EXCEPTION → {}
BunString__toURLjsc/ZigString.zig:148 — (this: *const ZigString, *JSGlobalObject) JSValue (string-first arg order)not vendored in the upstream C++ subset; semantics pinned to the same DOMURL::create(url, "") path as toJSDOMURL with a ZigString input
WebCore__DOMURL__cast_jsc/DOMURL.zig:26 — (JSValue, *VM) ?*DOMURLbindings.cpp:2132 — WebCoreCast<JSDOMURL, DOMURL>
WebCore__DOMURL__href_jsc/DOMURL.zig:40 — (*DOMURL, out: *ZigString) void (Home-only)bindings.cpp:2137 — domURL->href().string() as ZigString
WebCore__DOMURL__pathname_jsc/DOMURL.zig:71 — (*DOMURL, out: *ZigString) void (Home-only)bindings.cpp:2142 — domURL->href().path() as ZigString
WebCore__DOMURL__fileSystemPathjsc/DOMURL.zig:53 — (*DOMURL, error_code: *c_int) Stringbindings.cpp:2149 — error 1 non-empty host, error 2 %2f in path, error 3 not file:; else url.fileSystemPath() (decoded path)

Key semantics pinned from the vendored sources:

  • DOMURL::create(url, base) returns ExceptionOr<Ref<DOMURL>> (webcore/DOMURL.h:45): invalid input raises a TypeError that toJSNewlyCreated propagates through the throw scope — so toJSDOMURL/toURL publish the exception and return .empty on invalid input; they do not create empty-href objects.
  • The created object IS the realm's URL-interface object (instanceof URL true): URL prototype, the hidden URL component slots, and the live searchParams snapshot — identical in shape to new URL(str) minus the new-target check.
  • The VM* in cast_ is the VM identity zig-js already publishes from JSC__JSGlobalObject__vm/bunVM (the context group). Cast is VM-scoped: any same-VM realm's URL object qualifies; foreign-VM values, non-URL objects, and primitives return null. The returned DOMURL* borrows the live JS object (Bun returns the wrapper-owned impl pointer) — valid while the caller's value keeps it alive; no allocation, no ownership transfer.
  • href_/pathname_ return borrowed ZigString views that must outlive the call without a free hook: views are interned in a process-global latin1/UTF-16 store (mirroring the existing group-level zig_string_views), latin1 for 8-bit content, UTF-16 otherwise.
  • fileSystemPath is context-free: file: scheme required (else error 3 Dead), non-empty host → error 1 Dead, case-insensitive %2f in the serialized path → error 2 Dead, else the percent-decoded path as an owned BunString. A null DOMURL* yields error 2 Dead (zig-js sentinel where Bun dereferences unconditionally).

Acceptance criteria

  • Engine: one allocator-first urlCreateDomUrlObject helper next to the URL constructor (proto from the realm's URL.prototype, hidden component slots, live searchParams snapshot, TypeError on invalid input); urlLoad made pub. Zero behavior change to the JS URL constructor.
  • BunString__toJSDOMURL/BunString__toURL decode their BunString/ZigString input with the established same-VM preamble, build the object, and return its encoded handle; invalid input publishes the TypeError and returns .empty.
  • WebCore__DOMURL__cast_ validates the VM handle (alignment + group self-consistency), decodes with VM affinity, applies the URL-slot predicate, and returns the borrowing pointer.
  • href_/pathname_/fileSystemPath accept the borrowing pointer with alignment + slot revalidation; href_/pathname_ zero the out-param on null/stale input; fileSystemPath writes error codes only on failure (success leaves the caller's c_int untouched, matching Bun).
  • Compiled runtime fixture 314 → 320: object creation parity (instanceof URL, href/searchParams vs new URL), invalid-input exception publication for both creators, cast acceptance of native-created AND new URL-constructed objects, cast rejection (non-URL object, primitive, foreign VM, null VM), href_/pathname_ round-trips, fileSystemPath success + all three error codes, null tolerance throughout.
  • Home inventory 317/431, Bun inventory 308/421; bun-private-abi-audit + c-api-audit green; full zig build test zero new failures (worktree baseline-vs-candidate against the same pinned base).

Design constraints

  • No new object model: the DOMURL object is the existing URL-builtin object shape; cast_ is a predicate, not a conversion.
  • The process-global ZigString view store reuses the exact latin1/UTF-16 conversion of the group-level store (shared helper), guarded by a mutex since it is not thread-affine.
  • fileSystemPath performs no GC allocation (private allocator only), so the borrowed object needs no active realm during the call.
9 days ago

Evidence — landed on main as runtime 7b7cad40 + docs 7aeb2193. The URL cluster is now complete.

Implementation:

  • src/interpreter.zig: one allocator-first urlCreateDomUrlObject helper next to the URL constructor (URL prototype from the realm, hidden component slots, live searchParams snapshot, TypeError on invalid input); urlLoad made pub. Zero behavior change to the JS URL constructor.
  • src/c_api.zig: the six exports. BunString__toJSDOMURL/BunString__toURL decode with the established same-VM preamble and build the object via the helper — invalid input publishes the TypeError and returns .empty (ExceptionOr semantics), null input returns .empty with no exception. WebCore__DOMURL__cast_ validates the VM handle (alignment + primary.c_api_group self-consistency), decodes with VM affinity (privateValueFrom on the group primary), and applies the hidden-slot predicate; the returned pointer borrows the live JS object. href_/pathname_ serialize through urlLoad/urlSerialize and intern borrowed views in a new process-global latin1/UTF-16 store — the group-level zig_string_views logic was factored into a shared privateInternZigStringView (identical conversion, mutex-guarded globally, unchanged per-group behavior). fileSystemPath allocates nothing from the GC: scheme/host/%2f checks map to error codes 3/1/2 (written only on failure), and the percent-decoded path returns as an owned BunString.

Validation:

  • Compiled runtime fixture 314 → 320/320: instanceof URL + href/searchParams parity with new URL for both creators, invalid-input TypeError publication (both creators), null-input empty-without-exception, cast acceptance of native-created and new URL-constructed objects across same-VM realms, cast rejection (plain object, string primitive, null VM, foreign-VM value), href_/pathname_ exact strings, fileSystemPath success + error codes 1/2/3 + null tolerance.
  • bun-private-abi-audit: implemented-private 308/421 (pending 113). c-api-audit: 326 private profile exports, 117/117 public complete. test-home-public-abi: 50/50.
  • Worktree A/B at base a44593a7: baseline 1152 passed / 3 failed vs candidate 1152 passed / 3 failed — identical failure sets (the three known pre-existing: gc slab size, wasm.decode malformed, wasm.validate GC canonical recursive type identity). Zero new failures.

Inventories refreshed: Home 317/431 implemented (114 pending), Bun 308/421 (113 pending). Docs boundary evidence published in docs/abi/README.md.

Sign in to comment on this issue.