dommy-js-quickjs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,752 @@
1
+ # WPT 適合性 (conformance)
2
+
3
+ `test/fixtures/wpt/` にベンダリングした Web Platform Tests コーパスを、`WptRunner`
4
+ (`test/support/wpt_runner.rb`) でブリッジに対して実行し、適合率を報告する:
5
+
6
+ ```
7
+ bundle exec rake wpt:conformance # コーパス全体
8
+ bundle exec rake "wpt:conformance[url]" # パス部分文字列でフィルタ
9
+ ```
10
+
11
+ `WptRunner` は WPT の 2 つのファイル形式を扱う。`.any.js` / `.window.js` スクリプト
12
+ (`// META: script=` インクルードを解決し、`fetch("resources/…")` のデータファイルを
13
+ fetch スタブ経由でディスクから配信する) と、`.html` テスト (ファイル自体が document
14
+ となり、インライン `<script>` ブロックがテスト本体、testharness 以外の
15
+ `<script src>` ヘルパーはベンダリングしたツリーから解決する)。synthetic な `load`
16
+ イベントが testharness の完了をどう駆動するかは `WptHarness` を参照。
17
+
18
+ ## スナップショット (2026-05-31、pseudo-element drop + null/no-param の後)
19
+
20
+ ```
21
+ dom 7049/7561 (93.2%) — うち querySelector-All 1763/1977, Element-matches 593/672, dom/abort 34/37
22
+ url 1390/1396 (99.6%)
23
+ encoding 174/178 (97.8%)
24
+ domparsing 68/100 (68.0%)
25
+ html 64/95 (67.4%)
26
+ total 8745/9330 (93.7%) — 97 ファイルが完全グリーン
27
+ ```
28
+
29
+ > **セレクタの低リスク回収 → +81 subtests** (querySelector-All 1683→1763)。(1) **pseudo-element subject の節を
30
+ > ドロップ** (+64): `SelectorParser` を拡張して節ごとに「subject が pseudo-element か」を追跡 (`::before`,
31
+ > レガシー一コロン `:first-line` 等)、`matchable_selector` がそれらの節を除去 (全部なら `:not(*)`)。`backend_safe_selector`
32
+ > に折り込んで全クエリ境界で適用 → pseudo-element は要素にマッチしないので空を返す (従来は Nokogiri が `::` を誤パース/
33
+ > "Unregistered function" でエラー)。(2) **null/undefined の WebIDL 強制** (+8): `Internal.css_query_arg!` で
34
+ > querySelector 引数を null→"null"・undefined→"undefined" 強制 (`<null>`/`<undefined>` 要素にマッチ)、no-arg は
35
+ > TypeError。8つの JS dispatch 箇所を統一。(3) **iframe contentWindow に JS 組み込み公開** (+8): `exposeConstructorsOnWindow`
36
+ > が TypeError/Array 等もミラー → `defaultView.TypeError` が解決 (no-param テストの `assert_throws_js(global.TypeError)`)。
37
+ > 回帰ゼロ。残り querySelector ~214 はマッチングギャップ (名前空間, `:lang()`, `[attr=v i]`, 属性空値) = backend 限界。
38
+
39
+ > **CSS セレクタ検証パーサ → +180 subtests** (querySelector-All 1559→1683、Element-matches 536→592)。
40
+ > `Internal::SelectorParser` (新規、dommy gem) は CSS Selectors Level 4 文法の**バリデータ** — トークナイザ
41
+ > + 再帰下降パーサで、無効なセレクタに `DOMException::SyntaxError` を投げる。Nokogiri が黙って受理する/誤った
42
+ > エラーを出すケース (`[*=test]`, `..x`, `div % p`, `:::before`, `::example` 未知 pseudo-element, `ns|div`
43
+ > 未宣言名前空間) を spec 通り弾く。`Internal.validate_selector!` のヒューリスティックを置換。**マッチングは
44
+ > Nokogiri のまま**なので回帰リスクほぼゼロ。WPT の selectors.js から抽出した 34 無効 + 207 有効リストの
45
+ > 両方に対して検証 (34/34 拒否, 207/207 受理)。CSS の「EOF は開いた `[`/`(`/文字列を暗黙に閉じる」規則も実装
46
+ > (`[align="center"`, `::slotted(foo` は有効)。残りの querySelector 失敗 (~294) はマッチングギャップ
47
+ > (名前空間マッチ, `:lang()`, `[attr=v i]` 大小区別フラグ, pseudo-element がマッチしない検証, 属性空値エッジ)
48
+ > = Nokogiri バックエンドの限界で、将来このパーサの AST をカスタムマッチャに使えば解消可能。
49
+
50
+ > **encoding を 118→174/178 (97.8%) に +56** (encodeInto 56→110/111、textdecoder-copy 0→2、
51
+ > encoding 全体 +56)。(1) **WebAssembly.Memory polyfill** (`runtime.rb` の
52
+ > `install_browser_globals`): QuickJS は本物の `SharedArrayBuffer` を持つが `WebAssembly` が
53
+ > 無く、WPT の `common/sab.js` は SAB コンストラクタを
54
+ > `new WebAssembly.Memory({shared:true}).buffer.constructor` から導出する。`.buffer` が
55
+ > SharedArrayBuffer の最小 Memory を生やすだけで、SAB を使う encodeInto/textdecoder-copy が
56
+ > 本物のコーデックロジックで走る。(2) **SharedArrayBuffer の dehydrate** (`host_runtime.js`):
57
+ > SAB は `instanceof ArrayBuffer` が false なので別途バイト列として渡す。(3) **ストリーミング BOM 除去
58
+ > をバイトレベル→コードポイントレベルに** (`text_codec.rb` `decode_utf8`): BOM が複数の `decode()`
59
+ > 呼び出しに跨る (EF BB → BF) と 3 バイト prefix チェックでは捕まらないので、デコード後に先頭 U+FEFF を
60
+ > 除去する「BOM seen」フラグ方式に変更 (ストリーミング跨ぎで持続・flush でリセット)。残り 4 件は backend
61
+ > 上限/範囲外 (ArrayBuffer detach ×2、Big5 ×2)。WebAssembly polyfill はグローバルだが回帰ゼロ。
62
+
63
+ > **querySelector/matches/closest のセレクタ検証を強化 → +68 subtests** (querySelector-All 1505→1559、
64
+ > Element-matches 522→536): (1) **引数なし → TypeError** (args.length で判定)、(2) **空文字列 →
65
+ > SyntaxError**、(3) **先頭がコンビネータ (`>*` 等の相対セレクタ) → SyntaxError**、(4) **未知の
66
+ > pseudo-class → SyntaxError** (既知 pseudo の全集合 `KNOWN_PSEUDOS` と照合; `:hover` 等の既知だが
67
+ > 未実装は valid=空マッチ、`:example` 等の未知は invalid)。Nokogiri が黙って受理する無効セレクタを
68
+ > spec 通り弾く。残りの querySelector 失敗 (~400) は Nokogiri の CSS3 パーサが `[attr*=v]`/`[attr=v i]`
69
+ > 等を parse できない backend 上限 (別のセレクタエンジンが必要)。
70
+ >
71
+ > 直前: **dom/abort 17→34/37** + **不透明 JS 値の identity 保持** (96 green)、**html/dom ARIA reflection**。
72
+
73
+ > **dom/abort 17→34/37 (91.9%)、3 ファイル green** + **ブリッジの identity 制約を解消**: AbortSignal の
74
+ > reason undefined (非abort時)、`abort()`/`abort(null)`/`abort(undefined)` の区別 (args.length)、
75
+ > `AbortSignal.timeout` を win.scheduler に、`onabort` イベントハンドラ、abort イベントの isTrusted。
76
+ > **核心 = 不透明 JS 値の round-trip**: 非 plain な JS オブジェクト (Error/DOMException/クラスインスタンス
77
+ > = prototype ≠ Object.prototype) が Ruby に渡ると Hash 化され **identity を失っていた** (さらに Error は
78
+ > enumerable プロパティ無しで空 Hash に)。dehydrate でこれらを **不透明な JS-side ref** として渡し、
79
+ > Ruby は `Bridge::JSValue` として保持、戻すと元の JS オブジェクトに復元 → `signal.reason === reason` が成立
80
+ > (plain `{}` は従来通り Hash なのでオプションバッグは無影響)。`Bridge::ThrowValue` で throwIfAborted が
81
+ > 正確な reason を throw。**回帰ゼロで +7 subtests・+3 green** (CustomEvent detail 等にも波及)。残り 3 =
82
+ > dependent-signal の abort 順序 (flattening が必要) + iframe (範囲外)。
83
+ >
84
+ > 直前: **html/dom ARIA reflection** (属性 44/44 + 要素 8/27)、**MutationObserver** (6 green)、**dom/lists** (189/189)。
85
+
86
+ > **html/dom ARIA reflection**: **属性 (string) reflection 44/44 green** (aria-attribute-reflection
87
+ > 41/41 + tentative 3/3) — `ariaXxx`↔`aria-xxx`、`role`↔`role` の nullable DOMString。**要素参照
88
+ > reflection** も実装 (aria-element-reflection 0→8/27、disconnected 1/2): `ariaActiveDescendantElement`
89
+ > ↔ `aria-activedescendant` (単数=Element)、`ariaDescribedByElements`↔`aria-describedby` (複数=Element
90
+ > 配列)。explicit ref / IDREF 解決 (要素の root subtree でスコープ、切断後も有効)、setAttribute/
91
+ > removeAttribute で explicit ref クリア。**named element access** (`<div id=foo>`→裸の `foo`) はハーネスで
92
+ > id 要素を globalThis に mirror して近似。途中で**ブリッジ一般バグ修正**: JS `undefined` の **set 値**が
93
+ > `:undefined` シンボルで渡り Bridge::UNDEFINED と別物だった→unwrap で正規化。aria-element の残り 19 は
94
+ > shadow-DOM のアクセシビリティスコープ検証 (複雑な別機能)、labelledby は `test_driver.get_computed_label`
95
+ > 依存で範囲外。`window` に JS 組み込み公開 + accessKeyLabel。reflection.js 系は 60s VM タイムアウトで未 vendor。
96
+ >
97
+ > 直前: **MutationObserver 複数改善** (6 ファイル green)、**dom/lists** (189/189)、**dom/collections** (50/53)。
98
+
99
+ ## (旧) MutationObserver 改善
100
+
101
+ > **MutationObserver を複数改善** (dom/nodes MO 6 ファイル green): (1) **配信タイミングをブリッジ側で修正** —
102
+ > dommy の host-side microtask (Ruby scheduler) を native の promise ジョブキューに統合し、
103
+ > `await Promise.resolve()` と FIFO で interleave。(2) **childList レコードの type フィルタ** —
104
+ > `childList:false` の observer に childList レコードを配信していたバグを修正。(3) **observe を target の
105
+ > document に登録** — DOMParser 製 XML doc の要素の変更も届くように。(4) callback `this`=observer、
106
+ > MutationObserverInit を JS ToBoolean 評価。→ textContent 1→4 (green)、attributes 41/42、sanity・
107
+ > callback-arguments green。残りの MO はタイミングではなく backend 限界 (libxml2 隣接テキスト merge =
108
+ > fragment/normalize、Range op の characterData レコード未発火、unprefixed-ns 属性、parser 観測)。
109
+ >
110
+ > 直前: **dom/lists** (189/189, 100%)、**dom/collections** (50/53)、**dom/observable** (187/241)。
111
+
112
+ ## Landed (2026-05-31 セッション)
113
+
114
+ - **querySelector/matches/closest セレクタ検証** (Dommy)。**+68 subtests** (querySelector-All
115
+ 1505→1559、Element-matches 522→536、回帰ゼロ、total 90.3% 突破)。`Internal.validate_selector!` を
116
+ 全 query 系 (Element/Fragment/Document/NodeWrapperCache の querySelector(_all)、matches?、closest) で
117
+ 呼び、空文字列・先頭コンビネータ・未知 pseudo-class を SyntaxError に。引数なしは各 __js_call__ dispatch
118
+ で `args.empty?` → Bridge::TypeError。未知 pseudo は `KNOWN_PSEUDOS` 全集合と照合 (属性セレクタ/文字列/
119
+ エスケープを含むセレクタは backend に委譲して誤検出回避)。残りは Nokogiri CSS3 パーサ上限。
120
+
121
+ - **dom/abort + 不透明 JS 値の identity 保持** (bridge + Dommy)。dom/abort **17→34/37 (91.9%)、3 green**。
122
+ - **AbortSignal/Controller の細部**: reason は非abort時 undefined; `abort()` (引数なし) はデフォルト
123
+ AbortError、`abort(null)` は null、`abort(undefined)` はデフォルト — `args.length` で区別 (NO_REASON
124
+ センチネル); `AbortSignal.timeout` に win.scheduler を渡す (旧: win で set_timeout 無し); `onabort`
125
+ イベントハンドラプロパティ (add/remove_event_listener 経由); abort イベントの `isTrusted=true`
126
+ (Event#__internal_mark_trusted__)。
127
+ - **核心: 不透明 JS 値の round-trip (ブリッジ一般機能)**: 非 plain な JS オブジェクト (prototype が
128
+ Object.prototype でも null でもない = Error/DOMException/Map/クラスインスタンス) は dehydrate で
129
+ **JS-side レジストリに登録し `{__rb_js_ref: id}` として**渡す。Ruby は `Bridge::JSValue` (id 保持) として
130
+ 扱い、wrap で `{__rb_js_ref}` → rehydrate でレジストリから**元の JS オブジェクトを復元** (identity 保持)。
131
+ plain `{}` データオブジェクトは従来通り key→value マップなのでオプションバッグは無影響。これで
132
+ `controller.abort(error); signal.reason === error` や CustomEvent detail の identity が成立。
133
+ - **`Bridge::ThrowValue`** (< RuntimeError): host メソッドが任意の値を JS に throw する機構
134
+ (`throwIfAborted` が文字列/数値/JSValue の reason を verbatim throw)。dom_guard が `{__rb_throw__}` に
135
+ タグ付けし JS rehydrate が re-throw。Exception reason (デフォルト AbortError) は従来通り直接 raise。
136
+ - 残り 3: AbortSignal.any の abort 順序 (dependent-signal を root に flatten + mark-then-fire が必要、
137
+ Observable への回帰リスクで見送り) + iframe contentWindow (範囲外)。
138
+
139
+ - **html/dom ARIA reflection (属性 + 要素参照) + window グローバル公開** (bridge + Dommy)。
140
+ - **ARIA 要素参照 reflection** (Dommy): aria-element-reflection 0→8/27、disconnected 1/2。単数
141
+ `aria<Xxx>Element`→"aria-xxx" (Element or null)、複数 `aria<Xxx>Elements`→"aria-xxx" (Element 配列)。
142
+ `@aria_element_refs`/`@aria_elements_refs` に explicit ref を保持 (set で content 属性を ""、getter は
143
+ explicit を優先)、IDREF は要素の **root subtree** で解決 (切断時も有効)、set/removeAttribute の
144
+ `aria-*` 直接書き込みで explicit ref をクリア。**named element access**: ハーネスで `document.
145
+ querySelectorAll("[id]")` を globalThis に mirror (既存グローバルは shadow しない) し、裸の id 参照を解決。
146
+ 残り 19 は shadow-DOM のアクセシビリティスコープ検証 (explicit ref が深い shadow で null になる)、
147
+ labelledby は `test_driver.get_computed_label` (a11y ツリー計算) で範囲外。
148
+ - **ARIA / role reflection** (Dommy): **aria-attribute-reflection 0→41/41 + tentative 0→3/3 green**。
149
+ `Element#aria_content_attr` が `ariaXxx`→"aria-"+xxx.downcase、`role`→"role" にマップ。get は
150
+ nullable DOMString (absent→nil/null)、set は null/undefined→属性削除・それ以外→ToString。__js_get__/
151
+ __js_set__ の else に汎用ハンドラを追加。
152
+ - **ブリッジ一般バグ修正** (bridge): JS `undefined` の **プロパティ set 値** が `dehydrate`
153
+ (tagged でない) 経由で Ruby に gem の **`:undefined` シンボル**として渡り、Bridge::UNDEFINED と
154
+ 別物になっていた (args 経路 `dehydrateArgs` は `{__rb_undefined}` でタグ付け→正しく Bridge::UNDEFINED)。
155
+ `unwrap` の case に `when :undefined → Bridge::UNDEFINED` を追加し正規化。これで `el.ariaLabel =
156
+ undefined` 等の nullable setter が undefined を null と同様に扱える。
157
+ - **window に JS 組み込みを公開**: `install_browser_globals` で String/Number/Boolean/Object/Array/
158
+ Math/JSON/… を `window[name] = globalThis[name]` で window プロキシに own プロパティとして付与。
159
+ window は global object なので browser-correct。
160
+ - **`accessKeyLabel`**: `accesskey` 属性が単一の 1-code-point トークンなら `Alt+<KEY>`、
161
+ 空/複数/複数文字なら "" (access-key-label 2/2 green)。
162
+ - **reflection-*.html は意図的に未 vendor**: ブリッジは全 DOM 操作が Ruby 往復で、reflection.js の
163
+ 数千の属性×要素テストは 1 ファイルで 60s VM タイムアウト超過。物量 × overhead が原因で実用不可。
164
+ 残る historical の失敗は permissive な window `has` トラップ (`'HTMLTableDataCellElement' in window`
165
+ が常に true)、未知 HTML 要素が `Dommy::Element` 止まりで HTMLUnknownElement 化されない、parser/all/
166
+ applet 等の obsolete 機能 — いずれも構造的・範囲外。
167
+
168
+ - **MutationObserver 配信を native microtask キューに統合 + MO 改善** (bridge + Dommy)。
169
+ - **タイミング修正 (核心)**: dommy の `scheduler.queue_microtask` (MO 配信等) は Ruby 側の `@microtasks`
170
+ キューに積まれ、native QuickJS の promise ジョブキューとは別ドレインだった。そのため
171
+ `el.textContent="foo"; await Promise.resolve()` でテストが期待する「await 後に配信済み」が成立せず、
172
+ 複数の変更が1コールバックに集約 (「1レコード期待だが3」) されていた。**`Scheduler#native_microtask_scheduler`
173
+ フック**を追加し、ブリッジが `schedule_native_microtask` → `__rbHost.scheduleMicrotask(id)` →
174
+ `Promise.resolve().then(() => __rb_run_microtask(id))` で Ruby proc を native ジョブとして enqueue する
175
+ よう配線 (`HostBridge#window=` で注入)。これで host-side microtask が JS の await/Promise reaction と
176
+ FIFO に並び、textContent 1→3。vanilla CRuby ではフック未設定で従来通り `@microtasks` にフォールバック。
177
+ - **childList レコードの type フィルタ** (実バグ): `notify_child_list_mutation` が observer の登録
178
+ `child_list` フラグを見ずに全 observer へ配信していた (attribute/characterData は確認済みだった)。
179
+ `find_matching_entry` で `entry[:child_list]` を確認 → `observe(t, {childList:false, attributes:true})`
180
+ が childList レコードを受け取らなくなった (attributes 40→41)。
181
+ - **observe を target の document に登録** (実バグ): MutationObserver は `@document = window.document`
182
+ (メイン) 固定で、別 document (DOMParser 製 XML doc) の要素を observe しても、その doc の coordinator に
183
+ 登録されず変更が届かなかった。target の node-document に登録 (`@registered_docs` で追跡し disconnect で
184
+ 全解除) → textContent CDATA (XML-doc MO) が通り textContent 1→4 green。
185
+ - **callback の `this`=observer**: `invokeCallback`/`invoke_callback` に thisArg、HostCallback に
186
+ `__js_call_with_this__`、MutationObserver が observer を receiver に呼ぶ (callback-arguments green)。
187
+ - **`truthy_option` を JS ToBoolean に**: MutationObserverInit メンバは WebIDL boolean なので
188
+ `attributes: ["abc"]` (オブジェクト) も truthy (sanity green)。
189
+ - 残りの MO 失敗はタイミングではなく個別の backend 限界: libxml2 が fragment/normalize の隣接テキストを
190
+ merge (addedNodes 不一致)、Range.deleteContents/extractContents が characterData レコードを発火しない
191
+ (かつ setup の隣接テキストも merge)、unprefixed-ns 属性 (removeAttributeNS) が libxml2 で round-trip
192
+ しない、parser 観測 (ストリーミングパーサ無し)、replaceChild(self,self) の 2レコードセマンティクス。
193
+
194
+ - **dom/lists (DOMTokenList) 完全対応** (Dommy + bridge)。**46→189/189 (100%)、全 5 ファイル green**。
195
+ - **反映 DOMTokenList 属性**: `ClassList` を任意の content attribute に一般化 (`@attribute`)。
196
+ `Element#reflected_token_list` が要素+名前空間に応じて DOMTokenList か **UNDEFINED**(→ JS undefined)
197
+ を返す: relList = a/area/link(HTML) + a(SVG); htmlFor = output(HTML); sandbox = iframe(HTML);
198
+ sizes = link(HTML); 非該当は undefined(null でなく)。`output.htmlFor`/`iframe.sandbox`/`link.sizes`
199
+ はサブクラスの reflect_string を JS 読み取りで token list にインターセプト(Ruby の文字列 accessor は維持)。
200
+ - **iterable メソッド = Array.prototype 由来**: WebIDL の value-iterator interface(indexed getter +
201
+ `iterable<>`)は keys/values/entries/forEach/@@iterator が **%Array.prototype% の関数そのもの**
202
+ (`list.values === Array.prototype.values`)。bridge のカスタムイテレータを Array.prototype のメソッド
203
+ に置換(proxy は array-like なので動作し、Array Iterator を返すので `instanceof Array` は false)。
204
+ forEach も追加。
205
+ - **getElementsByTagNameNS** を Element/Document に追加。
206
+
207
+ - **dom/collections (WebIDL legacy platform object 反映) 実装** (bridge JS + Dommy)。**12→50/53
208
+ (94.3%)、8 ファイル green**。HTMLCollection / DOMStringMap (dataset) / NamedNodeMap の indexed +
209
+ named プロパティを Proxy トラップで spec 通りに反映:
210
+ - **named プロパティ**: Ruby 側が `__js_named_props__` で live な supported property names を公開
211
+ (HTMLCollection = 各要素の id→name を tree 順・重複除去・name は HTML 名前空間のみ; DOMStringMap =
212
+ camelCase した `data-*` キー; NamedNodeMap = 属性の qualified name)。bridge は `__rb_named_props` で
213
+ 毎回ライブ取得し、`ownKeys`/`getOwnPropertyDescriptor`/`has`/`set`/`defineProperty`/`deleteProperty`
214
+ に反映。named は HTMLCollection/NamedNodeMap で**非列挙・読取専用**、DOMStringMap で**列挙可・書込可
215
+ + named deleter** (`__js_delete__`)。
216
+ - **legacy platform [[Set]]/[[Delete]]**: 配列インデックス代入は expando にならず no-op(sloppy)
217
+ /TypeError(strict); 読取専用 named プロパティへの代入も拒否(既に expando が shadow している場合のみ更新)。
218
+ - **インデックス descriptor は writable:false**; 範囲外の配列インデックスは **undefined**(named に
219
+ フォールバックしない — `coll[2147483648]` は undefined だが `namedItem(2147483648)` は要素)。
220
+ - HTMLCollection は **@@iterator のみ**(`iterable<>` 宣言なし → values/keys/entries/forEach を持たない)。
221
+ - **Dommy 修正**: `getElementsByTagNameNS` を Element/Document に追加; `namedItem` の数値引数が
222
+ int32 超で Float として渡る件を整数文字列化; HTMLCollection の数値文字列アクセスを配列インデックス
223
+ (item) と非インデックス (named) で分岐。`StandaloneEventTarget` 同様の名前付き collection マッピング。
224
+
225
+ - **dom/observable (WICG Observable API) 新規実装** (bridge JS + Dommy)。**0→187/241 (77.6%)、10
226
+ ファイル green**。QuickJS は本物の JS エンジンなので、Observable はリアクティブプリミティブを
227
+
228
+ - **dom/observable (WICG Observable API) 新規実装** (bridge JS + Dommy)。**0→187/241 (77.6%)、10
229
+ ファイル green**。QuickJS は本物の JS エンジンなので、Observable はリアクティブプリミティブを
230
+ **JS ポリフィル** (`lib/dommy/js/observable_runtime.js`) として実装し、DOM プロトタイプ seed 後に
231
+ `install!` で eval。host 連携点は EventTarget / AbortController / グローバルエラー報告のみ。
232
+ - **`Observable`/`Subscriber`**: subscribe コールバック、`next/error/complete/addTeardown/active/signal`
233
+ (#private フィールドで receiver-less 呼び出し→TypeError)、ACCEPT 完了/error で signal abort→teardown
234
+ LIFO。**report exception** は `error` ErrorEvent をグローバルに dispatch (Error は stack から
235
+ lineno/colno、文字列等は 0)。
236
+ - **operators**: map/filter/take/drop/flatMap/switchMap/takeUntil/catch/finally/inspect (Observable 返し) +
237
+ first/last/find/some/every/reduce/toArray/forEach (Promise 返し)。take/drop の負値は unsigned long long
238
+ として最大値 (Infinity) に。first/last は空完了で RangeError。takeUntil は notifier の next **も error も**
239
+ complete を呼ぶ (error 非伝播)、sync 発火時は source を購読しない。
240
+ - **`Observable.from`**: Observable/async-iterable/iterable/Promise。TC39 GetMethod 準拠 (present-but-not-
241
+ callable は TypeError、@@iterator は from() 時と subscribe 時に**再読**しキャッシュしない)。
242
+ - **`EventTarget.prototype.when`**: `addEventListener(type, …, {signal})` を購読/購読解除に橋渡し。
243
+ `new EventTarget()` (StandaloneEventTarget) が "EventTarget" として crossing するよう NAME_OVERRIDES 追加。
244
+ - **基盤バグ修正 (Dommy、全領域に有益)**: (1) **`queueMicrotask`** が window scheduler 経由で native
245
+ promise キューと別だったため Promise reaction と FIFO 順序がずれていた → `Promise.resolve().then` に。
246
+ (2) **`setTimeout`/`setInterval` の delay** が undefined/非数値で `to_i` クラッシュ → 0 に強制。
247
+ (3) **`AbortSignal` の abort 理由**が無指定/undefined で `null` だった → spec 通り **AbortError
248
+ DOMException** を既定に。(4) **`AbortSignal`** をグローバル公開 (static `abort`/`any`/`timeout`)。
249
+ - 残り 54: 連鎖タイマー依存の async-iterable (harness pump 制約)、detached-document (.window.js)、
250
+ 高度な teardown/abort 順序、microtask タイミング細部。
251
+
252
+ - **dom/traversal (TreeWalker / NodeIterator) 代表コーパス + 掘り下げ修正** (Dommy + bridge)。
253
+ **1568/1584 (99.0%)、8 ファイル green**。修正した実バグ:
254
+ - **`invoke_filter` が JS フィルタを呼べていなかった**: 関数フィルタ (HostCallback) も
255
+ `{acceptNode}` オブジェクト (Hash) も Ruby の `accept_node`/`call` に respond せず**常に
256
+ FILTER_ACCEPT** を返していた。`__js_call__("call", …)` 経由で呼び、戻り値を WebIDL 強制
257
+ (boolean→0/1)。acceptNode が callable でなければ TypeError。
258
+ - **TreeWalker の SKIP/REJECT セマンティクス**: `traverse_children`/`traverse_siblings`/`next_node`/
259
+ `previous_node` を WHATWG アルゴリズムに書き換え (SKIP=子へ降りる、REJECT=サブツリーごと
260
+ スキップ)。previousNode はルートも返しうる (parentNode と非対称)。
261
+ - **NodeIterator の commit セマンティクス**: `next_node`/`previous_node` を**ローカル変数**で
262
+ 走査し、**ACCEPT 時のみ** referenceNode/pointerBeforeReferenceNode をコミット (null を返す際は
263
+ 位置を変えない)。これが NodeIterator.html を 118→757 に押し上げた決定打。
264
+ - **`NodeIterator` pre-removing steps**: live iterator を Document に登録し、`remove_node_with_notify`
265
+ の unlink 前に各 iterator の referenceNode を WHATWG 手順で調整。NodeIterator-removal 5→15/16。
266
+ - **`hasChildNodes()` / `hasAttributes()` の欠落**: Element/Document/Fragment/ProcessingInstruction/
267
+ DocumentType/CharacterData に未実装で、common.js の `previousNode`/`nextNode` ヘルパーが
268
+ `node.hasChildNodes()` で**"not a function"** を投げていた。全ノード型に追加。
269
+ - **`NodeFilter.bitmask_for` を `1 << (nodeType-1)` に**: PI/CDATASection/Attr を扱えず PI ルートの
270
+ iterator が空になっていた。
271
+ - **whatToShow / currentNode の強制**: `createTreeWalker`/`createNodeIterator` で undefined→既定
272
+ 0xFFFFFFFF・null→0・それ以外 ToUint32。currentNode setter は非 Node に TypeError (`is_a?(Node)`)。
273
+ - bridge: `NodeFilter` 定数を `INTERFACE_CONSTANTS` で公開、`NodeFilter`/`TreeWalker`/`NodeIterator`
274
+ を BASE_CHAINS に seed。残り 16 件は assert_readonly (Proxy descriptor ギャップ)、フィルタ例外の
275
+ 同一性、再入 InvalidStateError で構造的・範囲外。
276
+
277
+ - **Range (`dom/ranges/`) 代表サブセット + 掘り下げ修正** (Dommy)。**310/319** (7 ファイル中 3 green;
278
+ 計測した全 dom/ranges は 20524/23304 = 88.1%)。3 つの実バグ: (1) **`Document.createCDATASection`**
279
+ 追加 (`CDATASectionNode` = Text サブタイプ nodeType 4 + Backend.create_cdata/cdata_class +
280
+ wrapper マッピング)。common.js の `setupRangeTests()` がこれ無しで死亡し全 Range テストが setup で
281
+ error していた。(2) **CharacterDataNode に `ownerDocument`** (テキストノードが null を返し
282
+ `rangeFromEndpoints` の createRange が全滅)。(3) **`Range#comparePoint`/`isPointInRange` 実装 +
283
+ `compareBoundaryPoints` の検証**: offset は WebIDL unsigned long (負値 wrap → IndexSizeError)、
284
+ 無効 `how`→NotSupportedError、別ツリー→WrongDocumentError、doctype→InvalidNodeTypeError。
285
+ 巨大な組み合わせテスト (compareBoundaryPoints 9313 / comparePoint 5580 / isPointInRange 5733 /
286
+ intersectsNode 2356) は Range がコーパスを支配するため vendor せず (実装は保持)。content 操作
287
+ (deleteContents/extractContents/insertNode/surroundContents/cloneContents) は srcless iframe を
288
+ 2 つ作り両方で操作して比較するパターンで範囲外。
289
+ - **MutationObserver の record 詳細** (Dommy)。**98→109/133** (childList 19→28、attributes
290
+ 38→40)。(1) **ノード移動 = removal + addition** (`detach_with_notify`): insertBefore/appendChild/
291
+ append/prepend で既存ノード (親あり) を挿入する際、旧親へ childList removal レコードを先に発火
292
+ (within-parent の並べ替えは同一親に 2 レコード)。(2) **削除前の sibling 捕捉**
293
+ (`Document#remove_node_with_notify`): 全 remove 経路 (Element/CharacterData の `remove`、
294
+ `remove_child`、Range `delete_contents`) が unlink 前に previous/next sibling を**ラップして**
295
+ 捕捉し record に渡す (コーディネータは明示 sibling を verbatim 記録するので raw Nokogiri ノードだと
296
+ undefined になっていた)。(3) **attributeNamespace** をレコードに記録 (setAttributeNS/
297
+ removeAttributeNS が `namespace:` を渡す; namespaced 属性は局所名を小文字化しない)。残り: Range
298
+ "child and data removal"・`Node.normalize`・fragment-text は **libxml2 の隣接テキスト結合**で
299
+ ブロック (テスト設定が 3 つの別テキストノードを作るが 1 つに融合) — バックエンド根本制約。
300
+ callback の `this`=observer はブリッジのコールバック receiver 対応が要るため見送り (1 件)。
301
+ - **History API (pushState/replaceState) 自己完結サブセット** (Dommy)。
302
+ `the-history-interface` の自己完結テスト **7/7 green** (pushState/replaceState の基本・引数省略・
303
+ state・cross-origin SecurityError)。`History#push`/`replace` が URL を文書 URL に対して解決し、
304
+ parse 失敗 or **cross-origin なら SecurityError** を投げる (`resolve_url!`)。history/location の
305
+ WPT は大半が navigation (back/forward/popstate)・実 iframe・cross-origin 依存で、no-network・単一
306
+ 文書の Dommy では本質的に動かせないため、それらは vendor しない (url_rewriting は iframe 生成 +
307
+ blob URL + origin ルール、numbered 001-012 は実ナビゲーション)。
308
+ - **MutationObserver 取り込み** (ハーネス + Dommy)。**17→98/133**、5 ファイル green (disconnect /
309
+ inner-outer / takeRecords / + attributes/characterData/sanity が大幅前進)。(1) **ハーネスの強制
310
+ timeout** (最大レバー、再利用可能): 配信されないミューテーション (Range 操作など) で async_test が
311
+ 1つでもハングすると testharness の completion が発火せず**そのファイルの全結果が失われていた**
312
+ (childList/attributes/characterData/textContent が 0/0)。`WptHarness#run` が pump 後に
313
+ `__wptResults === null` なら決定論クロックを 11s×2 進め、testharness の harness-timeout (10s,
314
+ `setTimeout(()=>tests.timeout())`) を発火させて未完了テストを TIMEOUT 扱いにし completion を回す。
315
+ → 4 ファイルが 0/0 から復活。(2) **CharacterData メソッド** (Dommy): `Text`/`Comment` に
316
+ `appendData`/`insertData`/`deleteData`/`replaceData`/`substringData`/`length` を実装 (各変更は
317
+ `write_data` 経由で characterData レコードを発火、offset 範囲外は IndexSizeError)。(3) **observe()
318
+ 検証** (Dommy): `attributes`/`characterData` の暗黙 true は companion (attributeOldValue/Filter/
319
+ characterDataOldValue) が**あり、かつメンバー省略時のみ**。明示 false + companion は `Bridge::TypeError`、
320
+ 3 種いずれも無しも TypeError (JS TypeError にマーシャル)。(4) **コールバック引数**: `(records,
321
+ observer)` を渡す。残り: Range 操作のレコード未配信 (timeout)、record の nextSibling/addedNodes 詳細、
322
+ `Node.normalize`、パーサ挿入の document 監視、複数 observer の textContent 累積タイミング。
323
+ - **Selectors (querySelector/matches/closest) 取り込み** (ハーネス + ブリッジ + Dommy)。
324
+ Element-matches **0→522/672**、ParentNode-querySelector-All **0→1505/1977**、Element-closest
325
+ **24→28/29**、querySelector-scope **0→4/4** (dom 2200→4262)。(1) **動的 iframe 読込**
326
+ (ハーネス、最大レバー): 包括スイートは `document.createElement("iframe")` + `frame.src=` +
327
+ `frame.onload=` で実行時に iframe を生成し、その contentDocument に対して全テストを定義する。
328
+ `WptRunner` が inline script の `.src="…\.html"` 代入を検出し content を vendor (フラグメント除去
329
+ キー)、`BrowserHarness#wire_dynamic_iframes` が pump 各ラウンドで未配線 iframe を
330
+ `Dommy.parse` → contentDocument 設定 → `expose_constructors_on` → `load` イベント発火
331
+ (Dommy は `iframe.onload=` を load で呼ぶ) → `init()` が走り ~1900 subtests 解錠。
332
+ (2) **`:scope`** (Dommy): `ScopedCSSPseudoHandlers` で Nokogiri の `nokogiri:scope(.)` を
333
+ コンテキスト要素に解決。Nokogiri は `el.css` を子孫 (`.//`) に限定し要素自身を含めないため、
334
+ `:scope` 含有クエリは文書から評価して el の子孫に絞る。(3) **closest 書き直し** (Dommy):
335
+ scope ハンドラでセレクタ評価 → 最近接の inclusive ancestor を返す。(4) **無効セレクタ →
336
+ SyntaxError** (Dommy): Nokogiri の CSS *パース* エラー ("unexpected … after …") は
337
+ SyntaxError、"Unregistered function" (未対応疑似 `:hover` 等) は空マッチに degrade。
338
+ (5) **`DocumentFragment.ownerDocument`** (Dommy): null だったのを所有文書に (interface チェックの
339
+ `ownerDocument.defaultView.NodeList` が解決)。(6) **`NodeList`/`HTMLCollection` を BASE_CHAINS に**
340
+ (ブリッジ): `result instanceof NodeList` 用にグローバルコンストラクタをシード。残りの大半は
341
+ **Nokogiri の CSS パーサが多くの CSS3 セレクタを解析できない**根本制約 (別エンジンが必要)、
342
+ および未対応疑似・名前空間・case-insensitive 属性フラグ。
343
+ - **DOM Parsing & Serialization (`/domparsing/`) 取り込み** (ブリッジ + Dommy)。**29→68/100**、
344
+ 5 ファイル green (insert-adjacent / innerhtml-06 / innerhtml-li-autoclosing / outerhtml-01 /
345
+ domparser-spurious-attributes)。(1) **insertAdjacentHTML** (Dommy): 位置文字列を ASCII
346
+ case-insensitive に (`beforeBegin` 等が no-op していた — 6→30/31)、`add_previous_sibling` は
347
+ forward 反復で順序保持 (afterend のみ reverse)、無効位置→SyntaxError、親なし/親が Document の
348
+ beforebegin/afterend→NoModificationAllowedError。(2) **set 経路を `dom_guard` で包む** (ブリッジ):
349
+ `__rb_host_set` が DOMException を投げる setter (例 `documentElement.outerHTML=` →
350
+ NoModificationAllowedError) でタグ付き例外を返し、JS set トラップが re-throw — 従来は生 Ruby
351
+ 例外が漏れていた潜在バグ。(3) **outerHTML setter** (Dommy): `Element#__js_set__` に `outerHTML`
352
+ を配線 (従来未処理 = expando 化していた)。(4) **[LegacyNullToEmptyString] ToString** (ブリッジ):
353
+ set トラップが innerHTML/outerHTML を JS 側で null→""、他は `String(value)` 強制 (`= 42` /
354
+ `{toString}` / throwing toString が正しく; innerhtml-07・outerhtml-02 完全グリーン)。
355
+ (5) **`compatMode`** (Dommy): doctype 有無で BackCompat/CSS1Compat。(6) **DOMParser の無効 enum**:
356
+ `Bridge::TypeError` (WebIDL enum)。(7) **XML パース/直列化** (Dommy): `Backend.parse_xml` (Nokogiri
357
+ XML パーサ) で `parseFromString(…, "text/xml")` が正しい XML 文書に、`document_element` を
358
+ `at_css("html")` から `nokogiri_doc.root` に (XML 文書の root を返す)、`XMLSerializer` を `to_xml`
359
+ ベースに (自己閉じ + XML エスケープ)。dommy 側テスト 3 件を spec 準拠に更新 (DOMParser enum →
360
+ TypeError、insertAdjacentHTML 親なし → throw)。残り: XMLSerializer の WHATWG 名前空間
361
+ シリアライズアルゴリズム (25、Nokogiri の to_xml は名前空間 prefix 管理が WHATWG と不一致)、
362
+ style 属性の CSS 直列化 (4)、Nokogiri が隣接テキストを結合する件 (insert_adjacent_html /
363
+ innerhtml-04 各 1)。
364
+ - **Encoding (`/encoding/`) 取り込み + 型付き配列マーシャリング** (ブリッジ + Dommy)。encoding
365
+ **4→118/178**、5 ファイル green (api-basics / api-surrogates-utf8 / textdecoder-fatal /
366
+ textdecoder-ignorebom / textencoder-utf16-surrogates)。(1) **型付き配列マーシャリング層**: JS の
367
+ ArrayBuffer / TypedArray は `dehydrate` で `{__rb_bytes:[…]}` に (従来は Object.keys で
368
+ index→値の Hash になり "no implicit conversion of Array into Integer")、Ruby 側は
369
+ `Bridge::Bytes`(< Array) に。逆向きは host が `Bytes` を返すと `rehydrate` が `new Uint8Array`
370
+ に。`TextEncoder#encode` が Uint8Array を返すように。(2) **WHATWG UTF-8 デコーダ** (Dommy
371
+ `text_codec.rb`): バイト単位のステートマシン (bytesNeeded/lower-upper boundary) で streaming
372
+ (`{stream:true}` 跨ぎの state)、正確な U+FFFD 配置 (`[0xF0,0x9F,0x41]`→"�A" 等)、
373
+ `fatal`→TypeError、EOF flush (不完全列は 1 個の U+FFFD)、BOM ストリップ/ignoreBOM を実装。
374
+ `fatal`/`ignoreBOM` 属性も公開。(3) **`encodeInto`** (ブリッジ JS 側): TextEncoder プロトタイプ
375
+ に定義し destination Uint8Array を in-place 変更 (host 往復ではコピーになるため)。スカラー値を
376
+ UTF-8 に、入りきらない code point の手前で停止、`{read, written}` を返す。(4) **単独サロゲートの
377
+ スクラブ** (ブリッジ): `dehydrate` が JS 文字列の unpaired surrogate を U+FFFD に置換 (Ruby 文字列は
378
+ lone surrogate を保持できないため、spec の USVString 変換と等価)。残り ~60 は ASCII/UTF-8 スコープ外
379
+ (SharedArrayBuffer は WebAssembly.Memory 依存・54 件、Big5、ArrayBuffer detach)。
380
+ - **C: ライブコレクション / イテレータ** (ブリッジ + Dommy)。`urlsearchparams-foreach`
381
+ **2→6/6 (green)**、`Node-childNodes` **2→4/6**、`Element-getElementsByClassName` **1→2/3**。
382
+ (1) **array-like proxy の own-index 反映** (ブリッジ): `makeHandler` に `getOwnPropertyDescriptor`
383
+ / `ownKeys` / `has` トラップを追加し、`ARRAY_LIKE_COLLECTIONS` (NodeList/HTMLCollection/…) の
384
+ インデックス 0..length-1 を own enumerable configurable プロパティとして公開 (length はライブに
385
+ ホストへ問い合わせ)。testharness の `assert_array_equals` が `hasOwnProperty(i)` で要素の有無を
386
+ 見るため必須。`2 in nodeList` も範囲外で false に。(2) **childNodes をキャッシュ済みライブ
387
+ NodeList に** (Dommy): Element は既に `@live_child_nodes`、Fragment / Document も同様に
388
+ キャッシュ (`fragment.childNodes === fragment.childNodes` + ミューテーション反映)。
389
+ `LiveNodeList#__js_get__` の範囲外インデックス→`UNDEFINED`。(3) **本物のイテレータ** (ブリッジ):
390
+ array-like プロトタイプに keys/values/entries/Symbol.iterator を JS 側で定義 (Ruby メソッドから
391
+ 外す) → `list.keys() instanceof Array` が false、length/[i] をライブに読む。URLSearchParams の
392
+ Symbol.iterator は各 `.next()` で `entries()` を再読込するライブ版に (`for…of` 中の delete/
393
+ search 変更が見える)。**残り**: childNodes のテキストノード識別 (Nokogiri が `add_child` で
394
+ テキストノードを再生成 → wrapper の backend 参照が古くなる) と new Document() のクロスドキュメント
395
+ identity — 深い wrapper-cache の課題で別途。
396
+ - **B: WebIDL イベント引数 / 辞書変換** (ブリッジ + Dommy)。`Event-constructors` **8→14**、
397
+ `Event-initEvent` **9→12**、`CustomEvent` **2→3**、`AddEventListenerOptions-once` **3→4**
398
+ (4 ファイル green)。(1) **コンストラクタ引数の WebIDL 強制** (ブリッジ `coerceConstructorArgs`):
399
+ `CONSTRUCTOR_DICTS` (Event/CustomEvent の宣言メンバー + 型) を持ち、`constructInterface` で
400
+ type を `String()` 強制 (throwing toString が伝播、引数なしは TypeError)、init は宣言メンバー
401
+ のみを宣言順で読む (stray getter を呼ばない、null バイト入りキー `"bubbles\0…"` も読まない、
402
+ boolean は JS truthiness)。完全に Ruby 側へ渡る前に正規化するので `is_a?(Hash)` 等の既存挙動を
403
+ 壊さない。(2) **イベントの未知プロパティ→undefined** (Dommy): `Event#__js_get__` の `else` が
404
+ `Bridge::UNDEFINED` を返す (`ev.sweet` は undefined、`target`/`srcElement`/`currentTarget` 等の
405
+ 真に null な属性は明示 case で nil)。(3) **initEvent** (Dommy): dispatch 中は no-op (dispatch
406
+ フラグ)、type 引数必須、`initCustomEvent` を追加。(4) **once は呼び出し前に除去** (Dommy): ネスト
407
+ dispatch で二重発火しない。(5) **`new Document()` 構築可能化** (Dommy): 空の application/xml 文書。
408
+ 残り: capture の dummy-getter (addEventListener options は host_call 経路で辞書変換が未適用) と
409
+ GC 圧力下のコールバック ID 再利用、Event-isTrusted の記述子検査、bubbles の cloneNode。
410
+ - **イベント伝播フラグ + capture オプション** (Dommy)。`Event-propagation.html` **4→7/7**、
411
+ `EventListenerOptions-capture.html` **0→2/4**。(1) dispatch 末尾で stop-propagation /
412
+ stop-immediate フラグをクリア (canceled フラグは保持) — 同じ Event を再 dispatch できる
413
+ ように。dispatch 前に立てた `stopPropagation()` は引き続き尊重。(2) `deliver_at` が配信
414
+ *前* にもフラグ確認 (祖先が無い AT_TARGET だけのケースでも pre-set フラグを尊重)。
415
+ (3) capture フラグを **JS truthiness** で判定 (`EventTarget.js_truthy?` / `.capture_flag`):
416
+ Ruby では `0`/`""` が truthy だが JS では falsy、`Bridge::UNDEFINED` センチネルも falsy。
417
+ `{capture:0}`/`undefined` third-arg を正しく非 capture に。(4) `remove_event_listener` が
418
+ `options` を取り (callback, capture) でマッチ (旧実装は callback だけで全削除) — 全
419
+ ディスパッチ箇所が `args[2]` を渡す。残り 2 (capture): dummy getter を読まない遅延 dict
420
+ 変換 (Event-constructors と同根) と、GC 圧力下のコールバック ID 再利用による stale
421
+ capture リスナー (深いブリッジのライフサイクル問題)。
422
+ - **createElement の文書型対応** (Dommy + ハーネス)。`Document-createElement.html`
423
+ **59→123/147**。createElement は HTML 文書のみ ASCII 小文字化し、HTML 名前空間
424
+ (HTML/XHTML 文書) か null 名前空間 (非 XHTML の XML 文書) を付与。`Element#tagName` は
425
+ 「HTML 名前空間 **かつ** HTML 文書」のときだけ ASCII 大文字化 (XHTML 要素 = HTML 名前空間
426
+ だが XML 文書 → 大文字化しない)。`Document#html_document?` (content_type == "text/html")
427
+ を追加。`create_element` は名前空間メタデータを `__internal_set_namespace__` で保持し
428
+ localName/tagName/namespaceURI が一貫。ハーネス側: iframe の XML/XHTML 文書に
429
+ `content_type` を設定し (`text/xml` / `application/xhtml+xml`)、各 iframe に**専用の Window**
430
+ (`Dommy.parse` 産) を defaultView として与え、`Runtime#expose_constructors_on` で seed 済み
431
+ constructor を sub window proxy にも公開 (cross-window `instanceof` / `contentWindow.document`
432
+ が正しい sub 文書を返す)。ブリッジは sub window proxy を JS グローバル配列で保持し GC で
433
+ ハンドルが失われないようにする。残り ~24 は古い緩いケース (`f}oo`/先頭結合文字/`￿` —
434
+ XML Name production 的に無効で実ブラウザ/jsdom も落ちる、追わない)。
435
+ - **classList を順序付き集合に** (Dommy)。`Element-classlist.html` **1235→1420/1420 (完全
436
+ グリーン)**。DOMTokenList の token set を「class 属性を ASCII 空白で分割し重複排除した
437
+ 順序集合」に (`class_tokens` に `.uniq`) — length/item/iteration/contains が集合を見る
438
+ (`value`/`toString` は属性の生値)。indexed getter `classList[i]` は範囲外/負で `undefined`
439
+ (`item(i)` メソッドは null) を `Bridge::UNDEFINED` で返す。`update_tokens` は add/remove/
440
+ replace で**常に**集合を再シリアライズ (重複 collapse + 空白正規化) — 唯一の例外は「空集合
441
+ かつ属性が存在しない」で属性を作らない (空集合だが属性が在れば `""` を設定、削除しない)。
442
+ `toggle(token, force)` は force 一致の no-op では更新せず属性を不変に。`replace` の検証順を
443
+ spec 準拠に (両引数の空チェック→SyntaxError が両引数の空白チェック→InvalidCharacterError
444
+ より先、`replace(" ", "")` は SyntaxError)。dommy 側テスト 1 件を spec 準拠に更新
445
+ (最後のトークン削除は属性を `""` にする、削除しない)。
446
+ - **`url-constructor` / `url-origin` のデータ供給を解消** (ハーネス + ブリッジ + Dommy)。
447
+ 両ファイルは `fetch(urltestdata.json).json()` でコーパスを流し込み 1 つの `promise_test` で
448
+ ~1290 ケースを回すが、ずっと 0/1 だった。原因は 3 段の連鎖:
449
+ 1. **testharness の DOM 出力が NUL でクラッシュ** (ハーネス)。完了時に testharness が各
450
+ subtest 名を `document.createTextNode` で DOM に描画するが、URL ケースのテスト名には
451
+ 入力の NUL/制御文字がそのまま入る。libxml2 バックエンドの Text ノードは NUL を拒否
452
+ (`ArgumentError: string contains null byte`) → 完了処理ごと落ちてそのファイルの結果が
453
+ 0 件になっていた。結果は `add_completion_callback` でプログラム的に回収しているので、
454
+ `WptHarness` 初期化で **`setup({ output: false })`** を呼び testharness の視覚出力を無効化。
455
+ 2. **`Response#json` が lone surrogate を拒否** (Dommy `fetch.rb`)。`urltestdata-javascript-
456
+ only.json` は `\uD800` 等の単独サロゲートを含み、Ruby の `JSON.parse` が "invalid
457
+ surrogate pair" で例外 → `Promise.all` が reject → メイン 998 ケースが 1 件も走らない。
458
+ `scrub_lone_surrogates` を追加し、単独サロゲートのエスケープを U+FFFD に置換してから
459
+ parse (有効なペアはそのまま保持)。これは URL パーサが行う置換と等価で spec 準拠。
460
+ 3. **URL コンストラクタが TypeError でなく DOMException を投げていた** (Dommy + ブリッジ)。
461
+ WHATWG では `new URL(bad)` / `url.href=bad` は **`TypeError`** を投げるが、Dommy は
462
+ `DOMException::SyntaxError` を投げ、`assert_throws_js(TypeError, …)` (instanceof 検査) が
463
+ 275/276 件で失敗。`Dommy::Bridge::TypeError` (専用例外、bare な Ruby `TypeError` とは別物
464
+ なので本物の型バグをマスクしない) を新設し、URL の constructor/href= がこれを投げる
465
+ (`URL.parse`/`canParse`/`blob_inner_origin` の rescue も追従)。ブリッジは `dom_guard` で
466
+ これを捕捉し `{name:"TypeError", js_native:true}` でタグ付け → `makeHostError` が
467
+ `info.js_native` のとき本物の JS コンストラクタ (`new globalThis[name](msg)`) で再生成。
468
+ あわせて: blob URL の origin を内側スキームが http/https/file のときだけ内側 origin に
469
+ (それ以外は opaque origin) — `blob:ftp://`/`blob:ws://`/`blob:blob:https://` 系の 4 件。
470
+ URLSearchParams が **owner-backed (URL の query から初期化) のときは先頭 `?` を除去しない**
471
+ (`??a=b` の query "?a=b" は先頭 `?` がデータで最初の名前が "?a") — 1 件。
472
+ → url-constructor **0/1→888/888**、url-origin **0/1→401/401**、url **89.0%→99.3%**、
473
+ **total 83.6%→89.3% (3317/3714)**、19 ファイル green。dom は不変。
474
+ - **イベント伝播の WHATWG 準拠化 + Event 定数** (Dommy + ブリッジ)。`dom/events` を
475
+ 取り込み 22→34/57。(1) `dispatch_event` を capturing→at-target→bubbling の3フェーズに
476
+ 書き直し: 祖先パスを常に構築 (非 bubbling でも capture フェーズあり)、`eventPhase`
477
+ (NONE/CAPTURING/AT_TARGET/BUBBLING) を明示設定、capture リスナーは capturing 相、
478
+ 非 capture は bubbling 相 (target では両方)、`stopPropagation` は `catch(:stop_…)` で
479
+ 打ち切り。リスナー dedup を (listener, **capture**) に修正し、`addEventListener` の
480
+ 3 引数 boolean / `{capture:}` を解釈。`EventTarget` に `__internal_event_parent__` の
481
+ 既定 (nil) を追加し、`send`→`__send__` で XHR 等の `send` オーバーライドとの衝突を回避。
482
+ (2) **Event 定数** (`Event.CAPTURING_PHASE` 等) をブリッジの `INTERFACE_CONSTANTS` で
483
+ Event の interface オブジェクト+prototype に公開 (Node 定数と同機構)。(3) `Event#
484
+ returnValue` (= !defaultPrevented、setter で cancel) と `Event#isTrusted` (false) を追加。
485
+ - **Node.isEqualNode + DOMImplementation 一式** (Dommy)。
486
+ `Node-isEqualNode.html` **0→9/9 (完全グリーン)**。`Internal::NodeEquality` が WHATWG の
487
+ "equals" (型別データ + 順序付き子孫の再帰比較) を実装し、`Node` モジュールの
488
+ `is_equal_node` から全ノードクラス (Element / CharacterData→Text・Comment / Fragment /
489
+ Document / DocumentType / ProcessingInstruction) に配線。比較はラッパーの公開アクセサ
490
+ 経由 (`__js_get__` の型別プロパティ + `child_nodes` + `attributes`) なので不均一なノード
491
+ クラス間で一様。あわせて: `DocumentType` に publicId/systemId; `document.implementation`
492
+ (`DOMImplementation`) の `createDocumentType` / `createDocument` (独立 XML 文書、任意で
493
+ document element) / `createHTMLDocument` (doctype + html>head,body); `ProcessingInstruction`
494
+ + `document.createProcessingInstruction`; `Document#appendChild` (ドキュメント直下への
495
+ ノード追加)。
496
+ - **ブリッジの undefined / null 区別** (ブリッジ + Dommy)。JS `undefined` と `null` が
497
+ 両方 Ruby `nil` に畳まれていたのを、トップレベルの呼び出し引数に限り区別:
498
+ `dehydrateArgs` が明示的 `undefined` を `{__rb_undefined:true}` でタグ付け →
499
+ `unwrap` が `Dommy::Bridge::UNDEFINED` センチネルに (`UNDEFINED` は symbol から
500
+ `to_s`→"undefined" の object に。void 戻り値と同じセンチネルを双方向で再利用)。
501
+ ネストした undefined (オプションバッグ等) は従来通り null のままで既存挙動を保護。
502
+ 消費側: `URL`/`URL.parse`/`canParse` の base `undefined`→base なし;
503
+ `URLSearchParams#has`/`delete` の 2 番目 `undefined`→一引数形; `createElement(NS)` /
504
+ `createAttribute(NS)` の WebIDL DOMString 強制変換 (`undefined`→"undefined"、
505
+ `null`→"null"、namespace は nullable で `undefined`→null)。→ createElementNS
506
+ **486→534/596**、url-statics-parse/canparse と urlsearchparams-delete/has が完全
507
+ グリーン (total →**83.8%**、14 ファイル green)。
508
+ - **createElementNS の名前空間 + Document#childNodes + Node 定数** (Dommy +
509
+ ブリッジ)。`Document-createElementNS.html` **316→486/596** (dom 1666→1839、total
510
+ →**81.4%**)。3 つの連鎖した修正:
511
+ - **Node の数値定数** (ブリッジ): `Node.ELEMENT_NODE` 等を Node の interface
512
+ オブジェクトと prototype に定義 (`host_runtime.js` の `NODE_CONSTANTS`)。インスタンス
513
+ は proxy get の `prop in target` フォールバックで継承値に届く。`assert_equals(el.
514
+ nodeType, Node.ELEMENT_NODE)` が通るように。
515
+ - **`Document#childNodes`** (Dommy): nil を返していた → 全ノードの NodeList を返す。
516
+ testharness の `format_value` が DOCUMENT で `childNodes.length` を読むため、これが
517
+ 無いと assert 記録 (`AssertRecord`→`format_value`) が例外を投げ、"cannot set
518
+ property status of undefined" として全 success ケースを潰していた。
519
+ - **createElementNS の名前空間メタデータ** (Dommy): 作成時に (namespace, prefix,
520
+ localName, qualifiedName) を Element に保持 (`__internal_set_namespace__`)。
521
+ `tagName`/`localName`/`prefix`/`namespaceURI` がこれを優先。tagName は HTML 名前空間
522
+ のときだけ大文字化 (非 HTML/SVG は case 保持 — `createElementNS(SVG,"svg").tagName`
523
+ は "svg")。
524
+ - **classList (DOMTokenList) の void 戻り値ほか** (ブリッジ + Dommy)。
525
+ `Element-classlist.html` **965→1235/1420**。`classList.add`/`remove` は
526
+ `undefined` を返すべきだが Ruby `nil` が JS `null` にマーシャルされ全滅していた
527
+ (260 件)。**`Dommy::Bridge::UNDEFINED` センチネル**を新設し (`__js_call__` が void op で
528
+ 返す)、ブリッジの `wrap` が `{__rb_undefined:true}` に、JS `rehydrate` が `undefined`
529
+ に変換。あわせて `item(-1)` → `null` (負 index で Ruby が末尾要素を返していた)、
530
+ `toString` を追加 (`String(classList)` が値を返す、stringifier)、トークンの `null`→
531
+ `"null"` 強制変換 (`add(null)`/`contains(null)`)。
532
+ - **iframe `contentDocument` ロード** (ハーネス + Dommy)。createElementNS/
533
+ createElement は各ケースを HTML / XML / XHTML の 3 document に対して走らせるが、
534
+ XML/XHTML 変種は `<iframe src=/common/dummy.xml>` の `contentDocument` を使い、
535
+ ハーネスが未対応で null → 全滅していた。`common/dummy.xml`・`dummy.xhtml` を vendor し、
536
+ `WptRunner` が `<iframe src=…>` を検出して中身を読み (`iframe_docs`)、`BrowserHarness`
537
+ が各 iframe を `Dommy.parse` でネスト文書に起こして `HTMLIFrameElement#
538
+ __internal_set_content_document__` で配線 (defaultView は最上位 window なので
539
+ `doc.defaultView.DOMException` も解決)。`HTMLIFrameElement#content_document` を
540
+ settable 化。createElementNS の検証/生成ロジックは文書非依存なので、HTML 変種で通る
541
+ ケースが XML/XHTML でもそのまま通る (XML 変種は全て非 HTML 名前空間 = 大文字化なし)。
542
+ → createElementNS **106→316/596**、createElement **37→58/147** (dom 1165→1396、
543
+ total →**62.7%**)。
544
+ - **createElement(NS) の検証を spec 準拠に** (Dommy)。`create_element_ns` は
545
+ `Internal::Namespaces.validate_and_extract` を呼ばず独自の `NAME_RE` (コロン不可) で
546
+ 検証し名前空間抽出もしていなかった (prefixed 名・xml/xmlns 規則を全滅させていた)。
547
+ `validate_and_extract` 使用に書き換え。さらに `Internal::Namespaces` の Name/QName を
548
+ **canonical な `xml-name-validator` 相当の正規表現** (XML 1.0 NameStartChar/NameChar の
549
+ Unicode 範囲) に置換し、`create_element` / `create_attribute` も `NAME` production で
550
+ 検証 (旧 `NAME_RE` は ASCII 限定で `İnput` 等を誤拒否)。→ createElementNS
551
+ **72→106/596**、createElement **20→37/147** (dom 1113→1165)。残りは iframe variant +
552
+ vendored コーパスの緩い旧ケース (`0:a`/`f:o:o` を valid 扱い) + null/undefined 強制変換。
553
+ - **window 上の interface コンストラクタ公開** (ブリッジ)。ブラウザでは window が
554
+ グローバルオブジェクトそのものだが、ここでは window は別の host プロキシで、host
555
+ get がこれらに `null` を返していた (`typeof null === "object"` で気付きにくい)。その
556
+ ため `window.Node` / `document.defaultView.DOMException` 等が `null` になり、
557
+ `assert_throws_dom(type, doc.defaultView.DOMException, …)` が `null.name` を読んで
558
+ クラッシュ ("cannot read property 'name' of null")。`__rbHost.exposeConstructors
559
+ OnWindow` を追加し (`window=` 束縛時に `attachStatics` 直後で呼ぶ)、seed 済み
560
+ interface コンストラクタ + `DOMException` を `Object.defineProperty` で window プロキシ
561
+ の *ターゲット* に own プロパティとして定義 (get トラップの own-property 高速パスで
562
+ Ruby 往復なし、識別子も一致: `window.DOMException === DOMException`)。host が既に
563
+ 解決する名前 (Event 等の host 製コンストラクタ) は上書きしない。→
564
+ Document-createElementNS **1→72/596**、Document-createElement **0→20/147**
565
+ (dom 1022→1113)。再発パターン (他テストの `window.X` も恩恵)。
566
+ - **WHATWG basic URL パーサー** (Dommy, `Internal::UrlParser`)。`Dommy::URL` の
567
+ Ruby-`URI` コアを仕様準拠のステートマシンパーサーに置き換えた (`Record` 構造体、
568
+ 階層パス vs opaque パス、ホスト解析は既存の `Internal::IDNA` / `Ipv4Parser` /
569
+ `Punycode` を再利用、percent-encode セット一式)。`Dommy::URL` は `@record` を
570
+ ラップするようになった。`urltestdata.json` に対する純 Ruby 計測:
571
+ **496/887 → 887/887 (100%)**。100% に到達する過程で取り込んだバグ修正:
572
+ opaque パスの末尾スペースのエンコード (次が `?`/`#` のときのみ `%20`)、path
573
+ percent-encode セットに `^` (0x5E) を含める、`parse_host` で UTF-8 デコード時に
574
+ U+FFFD で置換 (`%80`/`%A0` ホストが `ArgumentError` でなく綺麗に失敗する)、
575
+ `IDNA.to_ascii` に WHATWG パラメータ追加 (`check_hyphens: false`,
576
+ `verify_dns_length: false` → 空 / 過長 / ハイフン端のラベルを許容、例
577
+ `http://./`、`http://foo.09..`)、`Ipv4Parser` が裸の基数接頭辞 (`0x`、`0`) を 0 と
578
+ みなす (`https://0x.0x.0x.0x` → `0.0.0.0`)。dommy 全スイートグリーン (2507) +
579
+ quickjs (113)。→ url-statics-tojson 1/1、urlsearchparams-stringifier 14/14;
580
+ `url-constructor`/`url-origin` は依然 0/1 (データ駆動、後述)。
581
+ - **URLSearchParams `sort`** (Dommy): 名前を UTF-16 *コードユニット* 順でソートする
582
+ ようにした (サロゲートペア文字は先頭の 0xD800–0xDBFF ユニットで並ぶ)。同名は
583
+ index タイブレークで安定 (Ruby の `sort_by` は安定でないため)。
584
+ → urlsearchparams-sort 13/17 → **17/17**。
585
+ - **Element `*AttributeNS` + Attr ノード一式** (Dommy)。attributes.html を
586
+ **7→47/67** に。内訳:
587
+ - `getAttributeNS` / `setAttributeNS` / `hasAttributeNS` / `removeAttributeNS` /
588
+ `getAttributeNodeNS` / `setAttributeNodeNS` を実装し `__js_call__` に配線。新しい
589
+ `Internal::Namespaces.validate_and_extract` が WHATWG DOM の "validate and
590
+ extract" を実装 (NCName/QName 検証 → InvalidCharacterError、prefix と namespace
591
+ の整合性 → NamespaceError、`xml`/`xmlns` の特例)。両バックエンドが NS ストレージを
592
+ 実装 (Nokogiri はフル NS、Nokolexbor は null 名前空間に縮退)。(7→22)
593
+ - **set-an-attribute-value の prefix 保持**: 既存の (namespace, localName) 属性を
594
+ 別 prefix で再 set したとき、値のみ変更し prefix は保持 (以前は prefix が変わって
595
+ いた)。
596
+ - **`Attr.textContent` / `Attr.specified`**: `Attr#__js_get__`/`__js_set__` に追加
597
+ (textContent は value を返す/書く、specified は常に true)。テストヘルパー
598
+ `attr_is` がこれらを参照するため大量の "(object) null" 失敗を解消。(22→34)
599
+ - **非 NS の `setAttribute`/`toggleAttribute` の空名チェック**: 空 qualifiedName で
600
+ InvalidCharacterError (corpus は空文字のみ無効を要求; `"0"`/`":"` 等は valid)。(34→36)
601
+ - **Attr ノードの同一性キャッシュ**: `NamedNodeMap` が `[namespace, localName]` を
602
+ キーに Attr インスタンスをキャッシュし、`el.attributes[i]` /
603
+ `getAttributeNode(NS)` がすべて同一オブジェクトを返す。`setAttributeNode(NS)` は
604
+ WHATWG "set an attribute" 準拠 (渡されたオブジェクトをそのまま採用、旧 Attr を
605
+ detach して返す、別要素にバインド済みなら InUseAttributeError)。`Element#
606
+ removeAttribute(NS)` は backend 削除の*前*に cached Attr を detach するので、保持
607
+ された参照は `ownerElement === null` になり値を保つ。(36→47)
608
+ - **Promise thenable adoption** (ブリッジ + Dommy)。`HostBridge#invoke_callback` /
609
+ `invoke_lifecycle` が戻り値を `unwrap` するようになり、JS の `.then` コールバックが
610
+ 返した Promise プロキシが生きた `PromiseValue` として戻る。また
611
+ `Dommy::PromiseValue` の adoption 継続が `self` を返さなくなった (`run_handler`
612
+ が無限に再 adopt していた — microtask 無限ループ)。結果:
613
+ `fetch().then(r => r.json()).then(data …)` チェーンが値を届けるようになった。
614
+ - **URLSearchParams** (`Dommy::URLSearchParams`):
615
+ `append`/`set`/`has`/`get`/`getAll`/`delete` 全体で `null`→`"null"` の USVString
616
+ 強制変換; 2 引数の `has(name, value)` / `delete(name, value)`; パーサが空の `&`
617
+ トークンをスキップ; `forEach` は JS コールバックをブリッジ ABI 経由で呼ぶ
618
+ (`&block` ではなく); WHATWG `application/x-www-form-urlencoded` シリアライザ
619
+ (`*-._` を保持、スペース→`+`)。
620
+ → append 4/4、stringifier 14/14、has 3/4、delete 5/8、foreach 2/6; url
621
+ 68.8%→77.1%。
622
+ - **`URL.parse` がもう throw しない** ("both relative" ケース): `parse_with_base` が
623
+ `URI::Error` (`URI::BadURIError` 含む) を rescue → `SyntaxError` → `nil`。
624
+ - **DOMException マーシャリング** (ブリッジ)。ホスト RPC の本体
625
+ (`__rb_host_get/call`、`__rb_construct`、`__rb_static_call`) を `dom_guard` 内で実行
626
+ し、`Dommy::DOMException` を捕捉してタグ付きマーカーを返す; JS 側の `rehydrate` が
627
+ それを本物の `DOMException` として再 throw する (name + legacy code、`instanceof
628
+ DOMException`)。以前は quickjs gem がプレーンな `Error` に潰していたため、すべての
629
+ `assert_throws_dom` が失敗していた。非子要素の `removeChild` (NotFoundError、code 8)
630
+ をはじめ、あらゆる DOM のエラー契約を修正。(その `Element-classlist` への効果は、下記
631
+ の PutForwards 修正までマスクされていた — throw する呼び出し自体がメソッドを解決でき
632
+ なかった。)
633
+ - **`classList` PutForwards** (Dommy)。`el.classList = x` が class 属性へ転送される
634
+ ようになった (`Element#__js_set__` が `"classList"` を扱う)、WHATWG
635
+ `[PutForwards=value]` 準拠。以前は未処理の write が JS 側の文字列 expando になり、
636
+ その要素の生涯にわたって **`classList` ゲッターをシャドウ** していたため、以後の
637
+ `el.classList.add(…)` がすべて文字列を見ていた (`list[fn]` が undefined)。→
638
+ **Element-classlist 20→965/1420**、dom 1.5%→43.4%、total 4.9%→**45.0%**。
639
+ - **`nodeName`** (Dommy): `Text`→`"#text"`、`Comment`→`"#comment"` (および
640
+ `#cdata-section`)、`DocumentFragment`→`"#document-fragment"`、`DocumentType`→
641
+ その name。→ Node-nodeName 1→5/6 (残り: 外来名前空間の要素の大文字小文字)。
642
+
643
+ ### `url-constructor` / `url-origin` — **完了** (888/888 + 401/401、Landed 参照)
644
+ かつて 0/1 だった原因 (testharness DOM 出力の NUL クラッシュ、`Response#json` の
645
+ lone-surrogate、URL の TypeError マーシャリング) はすべて解消。当時メモしていた
646
+ "Node object of unknown type" は、testharness が NUL 入りテスト名で createTextNode に
647
+ 失敗 → 完了処理が落ち、`promise_test` が壊れた object を reject していたもの (proxy の
648
+ `has` トラップが全 true なので testharness が node と誤判定して整形) で、根本は上記 3 点。
649
+
650
+ ## 残りのギャップバックログ (ROI 順)
651
+
652
+ 大半は Dommy 側; ブリッジ側は明記する。
653
+
654
+ ### dom/events — 伝播 + Event 定数 + 構築/辞書/once は完了 (Landed)
655
+ `Event-propagation` 7/7、`Event-constructors` 14/14、`Event-initEvent` 12/12、
656
+ `CustomEvent` 3/3、`AddEventListenerOptions-once` 4/4 は完了。残り:
657
+ - **`EventListenerOptions-capture` (2/4)**: addEventListener の options 辞書を遅延読みして
658
+ `dummy` getter を呼ばないようにする (Event-constructors と同根だが host_call 経路なので
659
+ `coerceConstructorArgs` が未適用)、および GC 圧力下のコールバック ID 再利用で残る stale
660
+ capture リスナー (深いブリッジのライフサイクル問題)。
661
+ - **`Event-dispatch-bubbles-true/false` (各 3/5)**: `document.cloneNode(true)` (文書の
662
+ ディープクローン) と `new Document()` へのクロスドキュメント append + クエリ。
663
+ - `Event-isTrusted` (0/1): プロパティ記述子の検査 (合成プロトタイプの構造的ギャップ)。
664
+
665
+ ### URL コア — WHATWG basic URL パーサー — **完了** (Landed 参照)
666
+ - Ruby-`URI` を `Internal::UrlParser` に置換; `urltestdata.json` で 887/887。
667
+ - これ単体では解消しなかったもの: `url-constructor`/`url-origin` は依然 0/1。今は
668
+ ハーネスのデータ供給の問題 (`Response#json` の lone-surrogate + "Node object of
669
+ unknown type" マーシャリング — 上記) がボトルネックで、パーサではない。これらの修正が
670
+ 次に大きい `url` レバー (1 つの `promise_test` の裏に ~887 ケースが隠れている)。
671
+
672
+ ### Attributes (attributes.html 47/67 — 残り 20 件)
673
+ Element `*AttributeNS`、Attr ノードの同一性、textContent/specified、空名検証は
674
+ **完了** (Landed 参照)。残りは難度・リスクの高いものが中心:
675
+ - **NamedNodeMap の own-property 列挙** (6 件)。`Object.getOwnPropertyNames(
676
+ el.attributes)` がインデックス (`"0".."n-1"`、enumerable) + qualified name
677
+ (non-enumerable) を返すべき。ブリッジの `makeHandler` には C バッチで array-like 用の
678
+ `ownKeys` + `getOwnPropertyDescriptor` トラップが入った (インデックスは own 化済み) が、
679
+ NamedNodeMap の **named property (qualified name) を non-enumerable own として公開** する
680
+ 部分と、HTML 文書中の HTML 要素は「全小文字の qualified name のみ」を named property に
681
+ するカーブアウトが未対応。
682
+ - **同名属性が複数あるケース** (`First set attribute is returned` / `setAttribute should
683
+ set the first attribute …`、~6 件)。Nokogiri/HTML は要素ごとに同名属性を 1 つしか
684
+ 持てないため、重複属性 (パース由来等) を表現できない。
685
+ - **libxml2 の `xmlns` 特殊扱い** (1 件)。`setAttributeNS(XMLNS, "xmlns", …)` の
686
+ namespaceURI が取れない (libxml2 が名前空間宣言として扱う)。
687
+ - **`removeAttribute` の qualifiedName 横断マッチング** (1-2 件)。`setAttributeNS("x",
688
+ "foo", …)` した属性を `removeAttribute("foo")` (qualifiedName 一致) で消す挙動 +
689
+ cached Attr の detach キー整合。
690
+ - `document.implementation.createDocument` 未実装 (1 件)、inline style の toggle、
691
+ 非 HTML 要素の大文字属性 (createElementNS の case 保持に依存) 等。
692
+
693
+ ### `Element-classlist` — **完了** (1420/1420、Landed 参照)
694
+ 順序付き集合化で完全グリーン。
695
+
696
+ ### createElement / createElementNS (createElement 123/147, createElementNS 534/596)
697
+ 文書型対応 + window コンストラクタ + 検証 + iframe + 名前空間メタデータ + Node 定数で
698
+ 大きく前進 (Landed 参照)。残り (両ファイル計 ~86) はほぼ **緩い古いケース** で、追わない:
699
+ `f}oo` / `;foo` / 先頭結合文字 / `￿` (XML Name production 的に無効) や `f:o:o` (コロン 2 個)
700
+ / `0:a` (prefix が数字始まり) を valid 扱いする古い WPT 期待値。canonical 実装 (および実
701
+ ブラウザ/jsdom) でも落ちる。
702
+
703
+ ### ノードの命名 / ノードタイプ — 完了 (Landed 参照)
704
+ - `Text`/`Comment`/`DocumentFragment`/`DocumentType` の `nodeName` は修正済み。残る
705
+ Node-nodeName の失敗 1 件は外来名前空間の *要素* の大文字小文字で、上記の
706
+ createElementNS 項目に属する。
707
+
708
+ ### URLSearchParams (残り = urlsearchparams-constructor 21/27)
709
+ - `sort` (17/17) と ライブ `for…of` (`urlsearchparams-foreach` 6/6) は **完了** (Landed 参照)。
710
+ - 残るは `urlsearchparams-constructor` の 6 件: sequence-of-sequences のバリデーション
711
+ (`new URLSearchParams([[1]])` は throw すべき)、unpaired-surrogate の置換 (`U+d835` →
712
+ `�`)、レコード引数が全プロパティを読む件 (DOMException 引数)、NUL バイト入りキーの
713
+ 切り詰め、カスタム `[Symbol.iterator]`。
714
+
715
+ ### `undefined` vs `null` (ブリッジ) — **完了** (Landed 参照)
716
+ - `dehydrateArgs` + `Dommy::Bridge::UNDEFINED` センチネルでトップレベル引数の `undefined`
717
+ を `null` と区別。URL の base、`has`/`delete` の値、`createElement(NS)` の DOMString
718
+ 強制変換に適用。url-statics + searchparams-delete/has が完全グリーンに。
719
+
720
+ ### 計測 (後で、今はやらない)
721
+ - **スコープを絞った `idlharness` 試行。** WPT の `idlharness.js` は WebIDL の正準的な
722
+ クロスチェックだが、測るのは *構造的* な忠実度 (インターフェース/継承、属性/メソッドの
723
+ 存在、**プロパティ記述子 + プロトタイプ同一性**) — ES6-Proxy + 合成プロトタイプの設計は
724
+ 後者を意図的に近似しているため、フル実行は記述子ノイズが支配的になる。値の強制変換の
725
+ バケット (`ToString(null)`、`long`/`USVString`/enum) はメソッド単位のテスト (すでに
726
+ 走らせているもの) でより良く測れる。いつか構造的バケットをデータで定量化するために
727
+ スコープを絞った実行 (URL/URLSearchParams か Node/Element/Event) をする価値はあるが、
728
+ メソッド単位のバックログより優先ではない。
729
+
730
+ ### インターフェース / より大きな機能 (優先度低)
731
+ - `NodeList` インターフェースのシード (getElementsByClassName のインターフェースチェック)。
732
+ - `implementation.createDocumentType` / `createDocument` / `createHTMLDocument`、
733
+ `Node.isEqualNode`、`createProcessingInstruction` は **完了** (Landed; Node-isEqualNode
734
+ 9/9)。`createDocument`/`createHTMLDocument` は window 非依存の独立 Document を生成する
735
+ ので、まだ vendor していない `dom/common.js` ベースのテスト群のセットアップ要件も
736
+ 満たせるようになった (それらの追加 vendor は今後の作業)。
737
+ - **ノード wrapper の identity** (childNodes の残り 2 件): Nokogiri の `add_child` がテキスト
738
+ ノードを再生成するため、`createTextNode` で得た wrapper の backend 参照が append 後に古く
739
+ なり `childNodes[i] === kid` が崩れる。`append_child` が add_child の戻り値で wrapper を
740
+ re-key する必要がある。`new Document()` へのクロスドキュメント append も同様。
741
+ - **Proxy `has` トラップがすべてに `true` を返す** (`host_runtime.js`)。array-like
742
+ コレクションのインデックスは C バッチで範囲チェックするようにした (`2 in nodeList` は
743
+ false) が、それ以外の name は依然 `true`。`"nodeType" in anyProxy` 等のダックタイピングを
744
+ 欺く潜在バグ。実メンバーシップ (symbols/HKEY/メソッド/expando/プロトタイプ) への厳格化は
745
+ 正当に null な DOM プロパティ (`firstChild`) に注意が要る。
746
+ - **Proxy `set` トラップが読み取り専用のホストゲッターを expando でシャドウする。** Dommy
747
+ が write を処理しないとき、ブリッジはターゲットに JS 側 expando を退避する; そのプロパティ
748
+ が実際にはホストの *ゲッター* (例 `classList`、`tagName`) だと、expando が以後それを恒久的
749
+ にシャドウする。`classList` は Dommy の PutForwards で回避したが、一般則として誤り —
750
+ ホスト公開の読み取り専用 name への未処理 write はドロップ (または strict モードで throw)
751
+ すべきで、シャドウすべきでない。ホストが「この name は自分が所有する (読み取り専用)」 vs
752
+ 「自由な expando スロット」を通知する必要がある。