Skip to content

Add opt-in TextEncoder, TextDecoder, atob and btoa - #3091

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:webapi/encoding
Aug 20, 2026
Merged

lahma merged 1 commit into
sebastienros:mainfrom
lahma:webapi/encoding

Conversation

@lahma

@lahma lahma commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Part of the opt-in web API series (#3079): TextEncoder/TextDecoder behind WebApiFeatures.Encoding and atob/btoa behind WebApiFeatures.Base64 (two independent flags, both in Default). Usual rules: BCL only, net8+, opt-in, default engine unchanged.

  • TextEncoder (https://encoding.spec.whatwg.org/#interface-textencoder): UTF-8 only per spec; encode (USVString semantics — unpaired surrogates → U+FFFD in one pass via the encoder's replacement fallback) and encodeInto, whose rune-walk never splits a code point at the destination boundary and counts read in UTF-16 code units. The result object uses a shared JsObjectLayout, so a loop reading .written keeps a monomorphic inline cache.
  • TextDecoder: labels for UTF-8/UTF-16LE/UTF-16BE from the spec's label table — matched ASCII-case-insensitively as the spec defines it, not OrdinalIgnoreCase, which would fold U+017F ſ onto s and accept cſunicode (pinned as refused); any other label is a RangeError. fatal (→ TypeError, decoder reset to a clean stream afterwards), ignoreBOM, and streaming decode(chunk, { stream: true }) via a per-instance BCL Decoder — a multi-byte code point and a BOM split across two chunks both decode correctly (pinned). Detached/out-of-bounds buffer sources contribute the empty byte sequence per WebIDL's get-buffer-source-copy.
  • btoa takes a DOMString, not USVString — a code unit above 0xFF (including a lone surrogate) is an InvalidCharacterError DOMException, per https://html.spec.whatwg.org/multipage/webappapis.html#dom-btoa.
  • atob is a hand-written forgiving-base64 decode (https://infra.spec.whatwg.org/#forgiving-base64) — Convert.FromBase64String is not that function in either direction ("YQ" must decode; "YQ=" must not), and the padding rule (strip at most two =, only when the whitespace-stripped length divides by four) is implemented exactly as written.

WebIDL attribute/operation shapes follow the precedent set in #3079 (attributes as enumerable brand-checked accessors; operations non-enumerable, documented simplification). 97 new tests plus public-surface pins (default engine has neither global; host-registered names win). Verified: all-TFM build, both suites × both frameworks × both host-contract-verification configurations.

🤖 Generated with Claude Code

Two more features on the opt-in web-API surface, both still installing
nothing unless a host names them.

- WebApiFeatures.Encoding installs TextEncoder and TextDecoder per the
  Encoding Standard. The encoder is UTF-8 only, as the standard requires:
  encode() replaces an unpaired surrogate with U+FFFD (USVString), and
  encodeInto() fills a Uint8Array without ever splitting a code point,
  reporting UTF-16 code units read and bytes written.
- TextDecoder reads UTF-8, UTF-16LE and UTF-16BE, resolved through the
  spec's label table for exactly those three - the legacy single-byte and
  multi-byte encodings each need their own index and are out of scope, so
  their labels are a RangeError rather than a silent substitution. Label
  matching is ASCII case-insensitive after ASCII-whitespace trimming, and
  deliberately spelled out rather than handed to OrdinalIgnoreCase, which
  folds U+017F onto s.
- Streaming keeps one BCL Decoder per instance for as long as the stream
  lasts, so a code point split across two chunks decodes once its last
  byte arrives; fatal is DecoderExceptionFallback reported as a TypeError,
  and ignoreBOM=false drops one leading U+FEFF per stream, which works
  across a chunk boundary because the BOM is dropped from scalar values
  rather than from bytes.
- WebApiFeatures.Base64 installs atob and btoa. btoa maps each UTF-16 code
  unit to a byte and raises an InvalidCharacterError DOMException above
  U+00FF; atob is Infra's forgiving-base64 decode written out, because
  Convert.FromBase64String is a different function - it rejects unpadded
  input the algorithm accepts and accepts padding the algorithm refuses.
- Both features are in WebApiFeatures.Default, and a host may enable
  either without the other.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lahma
lahma merged commit d2893ad into sebastienros:main Aug 20, 2026
5 checks passed
@lahma
lahma deleted the webapi/encoding branch August 20, 2026 16:26
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