feat: add TextEncoder/TextDecoder and atob/btoa on a lazy-global tier - #2026
Draft
edusperoni wants to merge 2 commits into
Draft
feat: add TextEncoder/TextDecoder and atob/btoa on a lazy-global tier#2026edusperoni wants to merge 2 commits into
edusperoni wants to merge 2 commits into
Conversation
Mirrors NativeScript/ios#448: native WHATWG TextEncoder/TextDecoder (utf-8, utf-16le, utf-16be, windows-1252 with full label sets, streaming decode, exact replacement semantics) and forgiving-base64 atob/btoa, registered through a new lazy-global tier (LazyGlobals): each global is a SetLazyDataProperty on the global template, so the builtin behind it is compiled and run only on first read, once per isolate, with sibling names sharing the run through a RuntimeState slot. Unlike ios there is no metadata-interceptor decline hook — android has no global named-property interceptor, so none is needed. encodeInto registers a V8 Fast API overload (NATIVESCRIPT_ENABLE_FAST_API, default on), live on android's JIT tiers. Bumps the shared test suite for the 94 TextEncoding conformance specs and wires it into mainpage.js.
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Matches the updated NativeScript/ios#448. The lazy tier's private exports cache generalizes into BuiltinLoader::GetExports, one per-isolate cache every entry point to a builtin shares — the ns:/node: module registry (whose per-specifier exports map it replaces), the lazy globals, and any binding factory. ns:util re-exports TextEncoder/TextDecoder as the very class objects the globals hold, lazily end to end (SetLazyDataProperty on the binding, getters in ns-util.js/node-util.js), and node:util forwards them as Node does.
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.
Mirrors NativeScript/ios#448.
Adds native, WHATWG-conformant
TextEncoder,TextDecoder,atobandbtoaglobals — and, more importantly, the lazy-global tier they ride on, which is the foundation for bringing further web globals (Blob,fetch,crypto,DOMException, …) into the runtime with zero cost when unused.Lazy-global tier (
LazyGlobals)SetLazyDataPropertybeforeContext::New: the builtin behind it is not compiled, run, or allocated until app code first reads the name, and V8 then replaces the property with a plain data property so later reads cost nothing.BuiltinLoader::GetExports(backed by aRuntimeStateslot — the android analog of ios'Cachesstate slot), which every entry point to a builtin shares: sibling names from one builtin (TextEncoder+TextDecoder) cost one run, and so does a module re-exporting the same interfaces. Thens:/node:registry's private per-specifier exports map is folded into this cache.test-app/runtime/src/main/cpp/js/README.md: lazy builtins run at arbitrary times, so they may only consumeinternalskeys published by eager builtins.LazyGlobals::Initruns inPrepareV8Runtimefor every isolate; assignment-before-first-read correctly replaces a lazy global (V8 gives setter-less API accessors a reconfigure-to-data setter).Divergence from ios: no
LazyGlobals::IsLazyGlobalinterceptor hook. On ios the global metadata interceptor must decline these names so an ObjC symbol sharing a name can't shadow a runtime global; android has no global named-property interceptor (top-level Java namespaces are installed eagerly byMetadataNode::CreateTopLevelNamespaces), so there is nothing to decline.TextEncoder / TextDecoder
Node's split:
js/text-encoding.jsowns the WebIDL surface (brand checks via private fields, enumerable prototype members,Symbol.toStringTag),TextEncoding.cppowns the bytes.RangeError. (Precedent: Node without ICU ships utf-8/utf-16le; utf-16be and windows-1252 are cheap, and windows-1252 covers theascii/latin1/iso-8859-1aliases web code actually uses.)decode(…, {stream}): incomplete sequences (including split BOMs and split utf-16 code units) carry across calls in a 16-byteUint8Arraythe builtin owns — no per-instance native handle, no finalizer.fatalthrowsTypeError;ignoreBOMhonored.encode()/encodeInto()with correct USV conversion and partial-write boundaries (never splits an encoded code point).String::NewFromOneByte; results downgrade to one-byte strings when possible.ns:util / node:util exposure
Node exposes the encoding interfaces on
util, sons:utilandnode:utilre-export them — as the very class objects the globals hold:require("ns:util").TextDecoder === globalThis.TextDecoder, whichever entry point is reached first, main isolate or worker. The members stay lazy end to end (SetLazyDataPropertyon thens:utilbinding, getters inns-util.js/node-util.js), so requiring either module still doesn't run the text-encoding builtin.atob / btoa
WHATWG forgiving-base64 in
Base64.cpp(whitespace stripping, padding rules, alphabet validation). With noDOMExceptionin the runtime yet, failures throw the name-patchedError(InvalidCharacterError) stand-in the other builtins already use — a follow-up PR will introduceDOMExceptionand upgrade these plusAbortSignal's reasons.V8 Fast API
encodeIntoregisters av8::CFunctionfast-call overload behindNATIVESCRIPT_ENABLE_FAST_API(default on, defined inUtil.h). Unlike ios (lite/jitless), android runs the optimizing tiers, so the overload is live here once a call site tiers up. This build's V8 restricts fast returns to scalars, so the string-returning ops (decode,atob,btoa) have no fast overload — current Node makes the same call in its encoding binding.Notes for reviewers
docs/text-encoding.mdfollows the android docs convention (ios#448 has no docs directory to mirror);docs/ns-builtin-modules.mdgets the same util-surface updates as ios'.types/ns-util.d.tsupdate has no android counterpart (no such types directory here).Tests
common-runtime-tests-appto0f45dc8(94 feature-detecting specs; pending, not failing, on runtimes without these globals; per-encoding sub-suites probe constructor support). Independently validated against Node 24 (full ICU) as a conformance reference: 94/94. Wired up viashared.runTextEncodingTests()in mainpage.js.NsUtilTestsadditions: identity of the module exports and the globals (including a fresh-isolate worker probing both access orders), round trips, and thenode:utilsurface.