Skip to main content

atmos/os_lib/js/
selftest.rs

1//! JS エンジン自己テスト(mod.rs から分割。2026-07-16 リファクタ フェーズ4)。
2//! ロジック不変。`super::*` で mod.rs の JsRuntime/eval/Value 等を取り込む。
3use super::*;
4
5/// JS エンジンの自己テスト。各ケースを評価し、最後の式の文字列化が期待値と一致するか確認。
6/// 起動時に呼ばれ `JS_SELFTEST: PASS n/n` をシリアルへ出す(CI スモークの判定マーカー)。
7pub fn selftest() -> (usize, usize) {
8    let cases: &[(&str, &str)] = &[
9        // 演算子・優先順位
10        ("1 + 2 * 3", "7"),
11        ("(1 + 2) * 3", "9"),
12        ("2 ** 10", "1024"),
13        ("7 % 3", "1"),
14        ("10 / 4", "2.5"),
15        ("true && false || true", "true"),
16        ("null ?? 'x'", "x"),
17        ("1 < 2 ? 'a' : 'b'", "a"),
18        // テンプレートリテラル `${}` 補間(ES2015)。以前はレキサーが `${...}` を
19        // 単なる生文字列として保持するだけで実際の補間評価が行われず(Phase 4 として
20        // 未実装のまま放置されていた)、自己テストも一件も存在しない状態だった。
21        ("var name='World'; `Hello, ${name}!`", "Hello, World!"),
22        ("`1+2=${1+2}`", "1+2=3"),
23        ("var a=1,b=2; `${a}-${b}-${a+b}`", "1-2-3"),
24        ("`no interpolation here`", "no interpolation here"),
25        ("``", ""),
26        ("`${'a'+'b'}${'c'}`", "abc"),
27        // 補間式の中に文字列リテラル(波括弧に見える文字を含む可能性がある)や
28        // オブジェクトリテラル(ネストした `{}`)があっても深さ計算が壊れないこと。
29        ("`x=${'{not a brace}'}`", "x={not a brace}"),
30        ("`obj=${JSON.stringify({a:1})}`", "obj={\"a\":1}"),
31        // 補間式内でのネストしたテンプレートリテラル(二重ネストではない単純な形)。
32        ("`outer ${`inner`}`", "outer inner"),
33        // 文字列
34        ("'a' + 'b' + 'c'", "abc"),
35        ("'Hello'.toUpperCase()", "HELLO"),
36        ("'a,b,c'.split(',').length", "3"),
37        ("'  hi  '.trim()", "hi"),
38        ("'abc'.slice(1)", "bc"),
39        // 変数・制御構文
40        ("var s = 0; for (var i = 1; i <= 5; i++) s += i; s", "15"),
41        // 【2026-10-16】`var` の巻き上げ。宣言より前の代入は関数ローカルへ入り、外側の
42        // 同名変数を書き換えない(Cloudflare beacon.min.js の `bi:(a=st,...)...; var a;` が
43        // 外側の関数 `a` を上書きして `a is not a function` になっていた)。
44        ("var x=1; function f(){ x=2; var x; return x; } f() + ',' + x", "2,1"),
45        ("function a(){ return 'fn'; } function g(){ var o={b:(a={}, 1)}; var a; return o.b; } g(); a()", "fn"),
46        ("function f(){ return typeof v; var v = 1; } f()", "undefined"),
47        ("function h(p){ var p; return p; } h(5)", "5"),
48        ("var q='outer'; function k(){ q=3; if (false) { var q; } return q; } k() + ',' + q", "3,outer"),
49        ("var r='outer'; function m(){ r=1; try { var r; } finally {} return r; } m() + ',' + r", "1,outer"),
50        ("var t='outer'; function n(){ t=1; for (var t in {}) {} return t; } n() + ',' + t", "1,outer"),
51        ("var u='outer'; function w(){ u=1; switch (0) { case 0: var u; } return u; } w() + ',' + u", "1,outer"),
52        // 入れ子の関数の中の var は外側の関数へ巻き上げない。
53        ("var z='outer'; function y(){ z=1; function inner(){ var z; } return z; } y(); z", "1"),
54        // window のプロパティはグローバル変数として typeof でも見える(`window.$ = jQuery`)。
55        ("window.zzTypeofProbe = 5; typeof zzTypeofProbe", "number"),
56        ("typeof zzNeverDefinedAnywhere", "undefined"),
57        // 初期値の無い var の再宣言は既存の値を変えない。
58        ("var vvRedecl = 3; var vvRedecl; vvRedecl", "3"),
59        // 巻き上げは let/const に影響しない(ブロック外では見えない)。
60        ("function lc(){ { let k2 = 1; } return typeof k2; } lc()", "undefined"),
61        ("let n = 0; while (n < 3) n++; n", "3"),
62        ("let x = 10; if (x > 5) { x = 1 } else { x = 2 } x", "1"),
63        // ラベル付き break/continue(ES3以降の基礎機能。以前は AST に Label 相当の
64        // variant 自体が存在せず完全に未サポートだった)。
65        (
66            "var out=''; outer: for(var i=0;i<3;i++){ for(var j=0;j<3;j++){ if(j===1) continue outer; out+=i+''+j; } } out",
67            "001020",
68        ),
69        (
70            "var found=null; outer: for(var i=0;i<3;i++){ for(var j=0;j<3;j++){ if(i===1&&j===1){ found=i+''+j; break outer; } } } found",
71            "11",
72        ),
73        // ラベル無しの break/continue は従来通り最も内側のループのみに作用する
74        // (ラベル対応追加による回帰が無いことの確認)。
75        (
76            "var out=''; for(var i=0;i<3;i++){ for(var j=0;j<3;j++){ if(j===1) break; out+=i+''+j; } } out",
77            "001020",
78        ),
79        // 三項演算子の `:` とラベル構文の衝突が無いこと(`x ? 1 : 2` の `:` は
80        // ラベル判定の対象にならない)。
81        ("var x=5; x > 0 ? 1 : 2", "1"),
82        // `**` の右結合性(`2**3**2` は `2**(3**2)`=512。左結合だと `(2**3)**2`=64になり誤り)。
83        ("2 ** 3 ** 2", "512"),
84        // 論理代入演算子(ES2021): 値としての結果。
85        ("var a=0; a ||= 5; a", "5"),
86        ("var a=1; a ||= 5; a", "1"),
87        ("var a=1; a &&= 5; a", "5"),
88        ("var a=0; a &&= 5; a", "0"),
89        ("var a=null; a ??= 5; a", "5"),
90        ("var a=0; a ??= 5; a", "0"),
91        // 論理代入演算子は仕様上の短絡評価が必須: 条件を満たさない場合、右辺の副作用は
92        // 一切実行されてはならない(以前は関数の先頭で右辺を無条件に評価してから分岐して
93        // いたため、代入されない場合でも副作用が毎回発生してしまう仕様違反バグがあった)。
94        (
95            "var calls=0; function rhs(){calls++; return 9;} var a=1; a ||= rhs(); calls",
96            "0",
97        ),
98        (
99            "var calls=0; function rhs(){calls++; return 9;} var a=0; a &&= rhs(); calls",
100            "0",
101        ),
102        (
103            "var calls=0; function rhs(){calls++; return 9;} var a=5; a ??= rhs(); calls",
104            "0",
105        ),
106        // 条件を満たす場合は右辺が(1回だけ)評価される。
107        (
108            "var calls=0; function rhs(){calls++; return 9;} var a=0; a ||= rhs(); calls+','+a",
109            "1,9",
110        ),
111        // 関数・クロージャ・再帰
112        ("function add(a, b) { return a + b } add(3, 4)", "7"),
113        ("let f = (a, b) => a + b; f(5, 6)", "11"),
114        ("function fib(n){ return n < 2 ? n : fib(n-1) + fib(n-2) } fib(10)", "55"),
115        ("let mk = () => { let c = 0; return () => ++c }; let g = mk(); g(); g(); g()", "3"),
116        // 配列
117        ("[1,2,3].map(x => x * 2).join(',')", "2,4,6"),
118        ("[1,2,3,4].filter(x => x % 2 === 0).join(',')", "2,4"),
119        ("[1,2,3,4].reduce((s, x) => s + x, 0)", "10"),
120        ("[3,1,2].sort((a,b) => a - b).join('')", "123"),
121        ("let a = [1]; a.push(2); a.push(3); a.length", "3"),
122        // オブジェクト
123        ("var o = {a:1, b:2}; o.a + o.b", "3"),
124        ("var o = {}; o['k'] = 9; o.k", "9"),
125        ("Object.keys({x:1, y:2, z:3}).length", "3"),
126        // typeof / 例外
127        ("typeof undefinedVar", "undefined"),
128        ("typeof 42", "number"),
129        ("try { throw 'boom' } catch (e) { e }", "boom"),
130        // JSON
131        ("JSON.stringify({x:[1,2], y:'z'})", "{\"x\":[1,2],\"y\":\"z\"}"),
132        ("JSON.parse('{\"n\":5}').n", "5"),
133        // Math
134        ("Math.max(3, 7, 2)", "7"),
135        ("Math.floor(3.9)", "3"),
136        ("Math.abs(-5)", "5"),
137        // switch(フォールスルー・default 含む)
138        ("function f(x){ switch(x){ case 1: return 'one'; case 2: return 'two'; default: return 'other' } } f(2)", "two"),
139        ("function f(x){ switch(x){ case 1: return 'one'; default: return 'd' } } f(9)", "d"),
140        ("var r=''; switch(1){ case 1: r+='a'; case 2: r+='b'; break; case 3: r+='c' } r", "ab"),
141        // スプレッド/レスト
142        ("var a=[1,2]; var b=[...a,3,4]; b.join(',')", "1,2,3,4"),
143        ("function sum(...xs){ return xs.reduce((s,x)=>s+x,0) } sum(1,2,3,4)", "10"),
144        ("function add(a,b,c){ return a+b+c } add(...[1,2,3])", "6"),
145        ("var o={a:1}; var p={...o, b:2}; p.a + p.b", "3"),
146        ("Math.max(...[5,2,9,1])", "9"),
147        ("function tail(first, ...rest){ return rest.join('-') } tail(1,2,3,4)", "2-3-4"),
148        // Map
149        ("var m=new Map(); m.set('a',1).set('b',2); m.get('a') + m.size", "3"),
150        ("var m=new Map([['x',10],['y',20]]); m.has('y') + ',' + m.get('y')", "true,20"),
151        ("var m=new Map(); m.set(1,'one'); m.delete(1); m.has(1)", "false"),
152        // `new Map(iterable)` の各要素が非オブジェクト(ペアでない)場合は仕様どおり
153        // TypeError を投げる必要があるが、以前は黙って {undefined: undefined} という
154        // 意味のない1エントリだけの Map が静かに作られていた。
155        (
156            "try { new Map([1,2,3]); 'no throw'; } catch(e) { 'threw'; }",
157            "threw",
158        ),
159        (
160            "try { new Map(['ab']); 'no throw'; } catch(e) { 'threw'; }",
161            "threw",
162        ),
163        // Set
164        ("var s=new Set([1,2,2,3,3,3]); s.size", "3"),
165        ("var s=new Set(); s.add(5).add(5).add(6); s.has(5) + ',' + s.size", "true,2"),
166        ("var s=new Set([1,2,3]); var t=0; s.forEach(v=>t+=v); t", "6"),
167        ("[...new Set([1,1,2,3,3])].join(',')", "1,2,3"),
168        // 分割代入(オブジェクト)
169        ("var {a, b} = {a:1, b:2}; a + b", "3"),
170        ("var {a: x, b: y} = {a:10, b:20}; x + y", "30"),
171        ("var {a, c = 9} = {a:1}; a + c", "10"),
172        ("var {a, ...rest} = {a:1, b:2, c:3}; rest.b + rest.c", "5"),
173        ("var {p: {q}} = {p:{q:42}}; q", "42"),
174        // 分割代入(配列)
175        ("var [a, b] = [1, 2]; a + b", "3"),
176        ("var [a, , c] = [1, 2, 3]; a + c", "4"),
177        ("var [a, ...rest] = [1, 2, 3, 4]; rest.join('-')", "2-3-4"),
178        ("var [a = 5, b = 6] = [1]; a + b", "7"),
179        ("var [[a], [b]] = [[1],[2]]; a + b", "3"),
180        ("var t=0; for (const [k,v] of [['a',1],['b',2]]) t += v; t", "3"),
181        // デフォルト引数
182        ("function f(a, b = 10) { return a + b } f(5)", "15"),
183        ("function f(a, b = 10) { return a + b } f(5, 7)", "12"),
184        // 引数の分割代入
185        ("function f({x, y}) { return x * y } f({x:3, y:4})", "12"),
186        ("function f([a, b]) { return a - b } f([9, 2])", "7"),
187        // 算出キー
188        ("var k = 'dyn'; var o = {[k]: 99}; o.dyn", "99"),
189        ("var o = {['a' + 'b']: 1}; o.ab", "1"),
190        // class(基本)
191        ("class A { constructor(x){ this.x = x } get(){ return this.x } } new A(7).get()", "7"),
192        ("class A { constructor(){ this.n = 0 } inc(){ this.n++; return this.n } } var a=new A(); a.inc(); a.inc()", "2"),
193        ("class P { area(){ return 0 } } class C extends P { area(){ return 5 } } new C().area()", "5"),
194        // class(継承・super)
195        ("class A { constructor(x){ this.x = x } } class B extends A { constructor(x,y){ super(x); this.y = y } sum(){ return this.x + this.y } } new B(3,4).sum()", "7"),
196        ("class A { greet(){ return 'A' } } class B extends A { greet(){ return super.greet() + 'B' } } new B().greet()", "AB"),
197        ("class A { constructor(){ this.v = 1 } } class B extends A {} new B().v", "1"),
198        // class field 宣言(ES2022。コンストラクタ本体の先頭で this.x = init される)
199        ("class C { x = 5; } new C().x", "5"),
200        ("class C { x; } typeof new C().x", "undefined"),
201        ("class C { x = 1; y = this.x + 1; } var c = new C(); c.x + ',' + c.y", "1,2"),
202        (
203            "class C { x = 1; constructor(){ this.x = this.x + 10 } } new C().x",
204            "11",
205        ),
206        (
207            "class A { x = 1; } class B extends A { y = 2; } var b = new B(); b.x + ',' + b.y",
208            "1,2",
209        ),
210        // private field(#x)。'#' が識別子の一部として扱われ、通常のプロパティアクセス
211        // 経路をそのまま通る(真の隠蔽ではないが、構文としては正しく動作する)。
212        (
213            "class Counter { #n = 0; inc(){ this.#n++; return this.#n } } var c = new Counter(); c.inc(); c.inc()",
214            "2",
215        ),
216        (
217            "class C { #secret = 'hidden'; reveal(){ return this.#secret } } new C().reveal()",
218            "hidden",
219        ),
220        // private メソッド(#method())
221        (
222            "class C { #double(n){ return n * 2 } calc(n){ return this.#double(n) } } new C().calc(21)",
223            "42",
224        ),
225        // プライベートフィールドのブランドチェック `#x in obj`(ES2022)が丸ごと
226        // 未対応で、`#x is not defined` の例外に必ずなっていた。
227        (
228            "class C { #x = 1; static has(o){ return #x in o; } } C.has(new C()) + ',' + C.has({})",
229            "true,false",
230        ),
231        // static field
232        ("class C { static count = 7; } C.count", "7"),
233        // static メソッド
234        ("class M { static twice(n){ return n * 2 } } M.twice(21)", "42"),
235        // メソッドからメソッド呼び出し
236        ("class C { a(){ return 2 } b(){ return this.a() * 10 } } new C().b()", "20"),
237        // Promise / async / await(await はマイクロタスクを駆動して同期解決)
238        ("await Promise.resolve(42)", "42"),
239        // **重要**: 汎用 thenable(本物の Promise ではないが呼び出し可能な `.then` を
240        // 持つオブジェクト。古いライブラリの独自 Promise 実装等でよく使われる)の
241        // 同化が丸ごと未対応で、`await`/`Promise.resolve` がオブジェクト自体を
242        // 履行値にしてしまうバグだった。
243        (
244            "await Promise.resolve({ then(resolve){ resolve(99); } })",
245            "99",
246        ),
247        (
248            "await { then(resolve){ resolve(7); } }",
249            "7",
250        ),
251        (
252            "try { await { then(resolve, reject){ reject('bad'); } } } catch(e) { e }",
253            "bad",
254        ),
255        // thenable の連鎖(thenable の then が別の thenable で resolve する)も
256        // 再帰的に同化されること。
257        (
258            "await { then(resolve){ resolve({ then(r){ r(5); } }); } }",
259            "5",
260        ),
261        // **重要**: thenable 検出が own プロパティしか見ていなかったため、
262        // `then` メソッドが `.prototype` 側にある class インスタンスの thenable
263        // を取りこぼすバグだった(自己検証で発見)。
264        (
265            "class MyThenable { then(resolve){ resolve(42); } } await new MyThenable()",
266            "42",
267        ),
268        (
269            "class MyThenable { then(_, reject){ reject('bad'); } } try { await new MyThenable() } catch(e) { e }",
270            "bad",
271        ),
272        ("await Promise.resolve(1).then(v => v + 1)", "2"),
273        ("await Promise.resolve(1).then(v => v + 1).then(v => v * 10)", "20"),
274        ("await Promise.reject('e').catch(e => 'caught:' + e)", "caught:e"),
275        ("async function f(){ return 7 } await f()", "7"),
276        ("async function f(){ let x = await Promise.resolve(10); return x * 2 } await f()", "20"),
277        ("async function f(){ try { await Promise.reject('x') } catch(e){ return 'C' + e } } await f()", "Cx"),
278        ("await (async () => 5)()", "5"),
279        ("await new Promise((res) => res(99))", "99"),
280        ("await new Promise((res, rej) => rej('bad')).catch(e => 'got:' + e)", "got:bad"),
281        ("await Promise.all([Promise.resolve(1), Promise.resolve(2), 3]).then(a => a.join(','))", "1,2,3"),
282        // **重要**: `Promise.all` の配列要素に汎用 thenable(本物の Promise ではないが
283        // 呼び出し可能な `.then` を持つオブジェクト)が混ざっていると、`Promise.resolve`/
284        // `await` 単体では既に同化対応済みだったのに、要素がそのままオブジェクトとして
285        // 混入してしまう非対称なバグだった。
286        (
287            "await Promise.all([1, { then(r){ r(2); } }, Promise.resolve(3)]).then(a => a.join(','))",
288            "1,2,3",
289        ),
290        (
291            "await Promise.all([{ then(resolve, reject){ reject('bad'); } }]).catch(e => 'caught:' + e)",
292            "caught:bad",
293        ),
294        ("typeof Promise.resolve(1)", "object"),
295        ("var log=''; await Promise.resolve(1).finally(() => log = 'F'); log", "F"),
296        // `finally` は元の値/理由をそのまま次へ渡す(コールバックの戻り値は無視)。
297        ("await Promise.resolve(1).finally(() => 2)", "1"),
298        // `finally` のコールバックが例外を投げた場合、それが settle を上書きすべき
299        // (以前は副作用としてだけ実行し戻り値/例外を完全に無視していたバグ)。
300        (
301            "await Promise.resolve(1).finally(() => { throw 'x'; }).catch(e => 'caught:' + e)",
302            "caught:x",
303        ),
304        // `finally` のコールバックが reject する Promise を返した場合も同様。
305        (
306            "await Promise.resolve(1).finally(() => Promise.reject('y')).catch(e => 'caught:' + e)",
307            "caught:y",
308        ),
309        // reject 経路でも `finally` はコールバック実行後、元の reason で reject し続ける。
310        (
311            "await Promise.reject('orig').finally(() => 'ignored').catch(e => 'caught:' + e)",
312            "caught:orig",
313        ),
314        ("async function sq(n){ return n*n } var s=0; for (const n of [1,2,3]) s += await sq(n); s", "14"),
315        // generator(遅延評価 / replay 駆動)
316        ("function* g(){ yield 1; yield 2; yield 3 } [...g()].join(',')", "1,2,3"),
317        ("function* g(){ yield* [1,2]; yield 3 } [...g()].join(',')", "1,2,3"),
318        ("function* g(){ for (let i=0;i<3;i++) yield i*i } [...g()].join(',')", "0,1,4"),
319        ("function* g(){ yield 1; yield 2; yield 3 } var s=0; for (const v of g()) s += v; s", "6"),
320        ("function* g(){ yield 'a'; yield 'b' } var it=g(); it.next().value + it.next().value + it.next().done", "abtrue"),
321        ("function* g(){ yield 1; return 99; } var it=g(); it.next(); it.next().done", "true"),
322        // 無限 generator を有限個だけ消費(真の遅延評価。eager だと無限ループ)
323        ("function* nat(){ let i=0; while(true) yield i++; } var it=nat(); it.next().value + ',' + it.next().value + ',' + it.next().value", "0,1,2"),
324        ("function* nat(){ let i=1; while(true) yield i++; } var it=nat(); var s=0; for(let k=0;k<5;k++) s += it.next().value; s", "15"),
325        // .next(v) による双方向の値受け渡し
326        ("function* g(){ var a = yield 1; var b = yield a + 10; return a + b; } var it=g(); it.next(); var r1=it.next(5); var r2=it.next(100); r1.value + ',' + r2.value", "15,105"),
327        ("function* g(){ var x = yield 'first'; yield 'got:' + x; } var it=g(); it.next(); it.next('hello').value", "got:hello"),
328        // yield* で別の generator へ委譲(generator 同士のネスト)
329        ("function* inner(){ yield 1; yield 2; } function* outer(){ yield 0; yield* inner(); yield 3; } [...outer()].join(',')", "0,1,2,3"),
330        // **重要**: `async function*`(非同期 generator。ES2018)の `.next()` が
331        // 仕様上必ず Promise を返すべきところ、`is_generator` の分岐が `is_async` を
332        // 完全に無視しており、普通の同期 generator と同じ `{value,done}` プレーン
333        // オブジェクトを直接返してしまっていたバグ。
334        (
335            "async function* g(){ yield 1; yield 2; } typeof g().next().then",
336            "function",
337        ),
338        (
339            "async function* g(){ yield 1; yield 2; } var it = g(); await it.next().then(r => r.value + ',' + r.done)",
340            "1,false",
341        ),
342        (
343            "async function* g(){ yield await Promise.resolve(42); } var it = g(); await it.next().then(r => r.value)",
344            "42",
345        ),
346        ("function* g(){ yield* 'abc'; } [...g()].join('-')", "a-b-c"),
347        // return() で早期終了
348        ("function* g(){ yield 1; yield 2; yield 3; } var it=g(); it.next(); var r=it.return(99); r.value + ',' + r.done + ',' + it.next().done", "99,true,true"),
349        // return() と try...finally の連携(return() 時に finally が実行される)
350        ("var log=''; function* g(){ try { yield 1; } finally { log+='f'; } } var it=g(); it.next(); var r=it.return(99); r.value + ',' + r.done + ',' + log", "99,true,f"),
351        // finally の中で return がある場合はそちらが優先される
352        ("function* g(){ try { yield 1; } finally { return 42; } } var it=g(); it.next(); it.return(99).value", "42"),
353        // finally の中で yield がある場合はそこで一時停止し、次の再開で強制Return値で完了する。
354        // value/done は正しいが、下の replay 特性(368行目)どおり finally 冒頭の
355        // `log+='f'` は forced-return 契機の replay と次の .next() の replay の
356        // 計2回実行されるため 'f' が2つになる(2026-07-14、期待値の計算ミスを
357        // QEMU JS_SELFTEST FAIL で発見・修正。本体側の finally 実行ロジックは正しい)。
358        ("var log=''; function* g(){ try { yield 1; } finally { log+='f'; yield 2; log+='end'; } } var it=g(); it.next(); var r1=it.return(99); var r2=it.next(); r1.value+','+r1.done+','+r2.value+','+r2.done+','+log", "2,false,99,true,ffend"),
359        // replay 方式の帰結: resume のたびに本体を先頭から再実行するため、外部副作用は
360        // 複数回発生する(yield される値・.next(v) の受け渡しは正しい)。この再実行特性を
361        // 明示的に検証する(2 回目の next で log+='a' が再実行され 'aab' になる)。
362        ("var log=''; function* g(){ log+='a'; yield 1; log+='b'; yield 2; } var it=g(); it.next(); it.next(); log", "aab"),
363        // Array.from が generator を消費できる(+ mapFn)
364        ("function* g(){ yield 1; yield 2; yield 3; } Array.from(g()).join(',')", "1,2,3"),
365        ("function* g(){ yield 1; yield 2; yield 3; } Array.from(g(), x => x * 10).join(',')", "10,20,30"),
366        // try/finally を跨ぐ yield(中断中は finally を実行せず、resume で継続)
367        ("function* g(){ try { yield 1; yield 2; } finally { } yield 3; } [...g()].join(',')", "1,2,3"),
368        // 【2026-10-19】EventTarget.prototype の native メソッド(ShadyDOM が __shady_native_* として写す)
369        ("typeof EventTarget.prototype.addEventListener + ',' + typeof EventTarget.prototype.removeEventListener + ',' + typeof EventTarget.prototype.dispatchEvent + ',' + !!Object.getOwnPropertyDescriptor(EventTarget.prototype, 'addEventListener')", "function,function,function,true"),
370        ("Object.defineProperty(EventTarget.prototype, '__x7', {value: function(){ return 7; }, configurable: true}); var d = document.createElement('div'); (typeof d.__x7) + ',' + (typeof window.__x7) + ',' + (typeof document.__x7)", "function,function,function"),
371        ("var d = document.createElement('div'); var hit = 0; EventTarget.prototype.addEventListener.call(d, 'foo', function(){ hit++; }); EventTarget.prototype.dispatchEvent.call(d, new Event('foo')); hit", "1"),
372        ("var hit = 0; var f = function(){ hit++; }; EventTarget.prototype.addEventListener.call(window, 'foo2', f); window.dispatchEvent(new Event('foo2')); EventTarget.prototype.removeEventListener.call(window, 'foo2', f); window.dispatchEvent(new Event('foo2')); hit", "1"),
373        ("(Object.getPrototypeOf(Node.prototype) === EventTarget.prototype) + ',' + (Object.getPrototypeOf(Window.prototype) === EventTarget.prototype) + ',' + (document.createElement('div') instanceof EventTarget)", "true,true,true"),
374        // 【2026-10-19】RegExp.prototype から直接読むメソッド(XRegExp)
375        ("var Yb = {exec: RegExp.prototype.exec, test: RegExp.prototype.test}; Yb.exec.call(/\\/([a-z]*)$/i, RegExp.prototype.toString.call(/x/gi))[1] + ',' + Yb.test.call(/b/, 'abc') + ',' + (RegExp.prototype.constructor === RegExp)", "gi,true,true"),
376        // 【2026-10-20】ラベル付きブロックの break(入れ子のループから抜ける)と、ループのラベル
377        ("var r; a:{ for(var q=3;q>=1;q--) for(var c=[1];c.length;){ c.shift(); r='found'; break a } r='after' } var n = 0; outer: for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (j == 1) continue outer; n++; } } r + ',' + n", "found,3"),
378        // 【2026-10-20】代入は左辺の参照を右辺より先に 1 回だけ評価する(Node で確認: LRAKV,1,2,1,1)
379        ("var log = []; ((log.push('L'), {})).x = (log.push('R'), 1); var o = {}; (log.push('A'), o)[(log.push('K'), 'k')] = (log.push('V'), 2); var a = [0, 0, 0], i = 0; a[i++] += 1; var j = 0; a[j++]++; var n = 0; var f = function(){ n++; return {}; }; f().z = 1; log.join('') + ',' + i + ',' + a[0] + ',' + j + ',' + n", "LRAKV,1,2,1,1"),
380        // 【2026-10-20】BotGuard(www.google.com/js/th)の状態機械。ul(true,24,S1) が S1.kR を作る
381        ("var ul=function(m,K,a,J,b,F,R){for(F=49;F!=34;)if(F==30)F=(K+7&16)<10&&K-5>=-36?20:21;else{if(F==45)return R;if(F==80)F=K-7&6?30:99;else if(F==21)F=(K|48)==K?56:45;else if(F==99)a.YR=void 0,a.kR=function(){return a.YR?a.YR:a.YR=new a},F=30;else if(F==20){F=21}else F==56?(R=7,F=45):F==49&&(F=80)}}; var S1=function(){}; ul(true,24,S1); typeof S1.kR", "function"),
382        // 【2026-10-19】sort はマージソート(安定・O(n log n))
383        ("var a = []; for (var i = 0; i < 300; i++) a.push((i * 7919) % 300); a.sort(function(x, y){ return x - y; }); var ok = true; for (var i = 0; i < 300; i++) if (a[i] !== i) ok = false; ok", "true"),
384        ("[{k:1,v:'a'},{k:0,v:'b'},{k:1,v:'c'},{k:0,v:'d'}].sort(function(x, y){ return x.k - y.k; }).map(function(o){ return o.v; }).join('') + ',' + [10, 9, 1, undefined, 2].sort().join(',') + ',' + ['b','a','c'].sort(function(x, y){ return x < y ? 1 : -1; }).join('')", "bdac,1,10,2,9,,cba"),
385        // 【2026-10-19】正規表現の自前プロパティと差し替えた原型(XRegExp)
386        ("var re = /a/g; re.xregexp = re.xregexp || {}; re.xregexp.gy = 5; var P = function(){}; P.prototype = {hello: function(){ return 'hi'; }}; re.__proto__ = P.prototype; re.xregexp.gy + ',' + re.hello() + ',' + re.global + ',' + re.test('cat')", "5,hi,true,true"),
387        // 【2026-10-19】配列の穴: Array(n) の要素は穴で、map/forEach/filter/reduce 等は飛ばす(D3 の配色表)
388        ("var n = 0; Array(3).concat('a', 'b').map(function(x){ n++; return x + '!'; }).join(',') + '|' + n", ",,,a!,b!|2"),
389        ("var a = Array(3); a[1] = 'x'; var n = 0; a.forEach(function(){ n++; }); n + ',' + (0 in a) + ',' + (1 in a) + ',' + a.length", "1,false,true,3"),
390        ("Array(3).filter(function(){ return true; }).length + ',' + Array(2).reduce(function(a, b){ return a + b; }, 5) + ',' + Array(3).indexOf(undefined) + ',' + [undefined].indexOf(undefined) + ',' + Array(3).some(function(){ return true; }) + ',' + Array(3).every(function(){ return false; })", "0,5,-1,0,false,true"),
391        ("var m = 0; new Array(2).forEach(function(){ m++; }); var b = Array(2); b.fill(1); var k = 0; b.forEach(function(){ k++; }); m + ',' + k", "0,2"),
392        // 【2026-10-19】Window(大域オブジェクトの型)
393        ("typeof Window + ',' + (window instanceof Window) + ',' + (typeof window.setTimeout)", "function,true,function"),
394        // 【2026-10-18】onclick ハンドラの currentTarget は自分(子で起きたクリックの泡立ちでも)
395        ("var p = document.createElement('div'); var c = document.createElement('span'); p.appendChild(c); var r = 'none'; p.onclick = function(e){ r = (e.currentTarget && e.currentTarget.tagName) + ',' + (e.target && e.target.tagName) + ',' + e.eventPhase; }; c.click(); r", "DIV,SPAN,3"),
396        // 【2026-10-18】YouTube のレンダラが古い中身を外す操作(外れずに操作部が二重になっていた)
397        ("var p = document.createElement('ytm-x'); var a = document.createElement('div'); var b = document.createElement('span'); p.appendChild(a); p.replaceChild(b, a); p.childNodes.length + ',' + p.firstChild.tagName + ',' + (a.parentNode === null) + ',' + a.isConnected", "1,SPAN,true,false"),
398        ("var p = document.createElement('div'); p.appendChild(document.createElement('i')); p.appendChild(document.createElement('b')); p.textContent = ''; p.appendChild(document.createElement('u')); p.childNodes.length + ',' + p.firstChild.tagName", "1,U"),
399        ("var p = document.createElement('div'); var a = document.createElement('i'); p.appendChild(a); (a.parentElement === p) + ',' + (document.createElement('ytm-y').appendChild(a), a.parentElement.tagName) + ',' + p.childNodes.length", "true,YTM-Y,0"),
400        ("var p = document.createElement('div'); var c = document.createComment('x'); var a = document.createElement('i'); p.appendChild(c); p.appendChild(a); p.removeChild(c); p.insertBefore(document.createElement('b'), a); p.childNodes.length + ',' + p.firstChild.tagName", "2,B"),
401        ("var f = document.createDocumentFragment(); var a = document.createElement('i'); f.appendChild(a); var p = document.createElement('div'); p.appendChild(f); (a.parentElement === p) + ',' + f.childNodes.length", "true,0"),
402        // 【2026-10-18】DOM 要素の組み込みメソッドを自前のプロパティで上書きできる(YouTube の player API は
403        // 要素に addEventListener 等を代入する)
404        ("var d = document.createElement('div'); d.addEventListener = function(){ return 42; }; d.addEventListener('x', null)", "42"),
405        ("var d = document.createElement('div'); d.focus = function(){ return 'mine'; }; d.focus()", "mine"),
406        // 【2026-10-18】生テキスト要素の中身は outerHTML でエスケープしない
407        ("var s = document.createElement('style'); s.textContent = 'a>b{color:red}'; var t = document.createElement('p'); t.textContent = 'a>b'; s.outerHTML + '|' + t.outerHTML", "<style>a>b{color:red}</style>|<p>a&gt;b</p>"),
408        // 【2026-10-18】Symbol をキーにしたプロパティ(YouTube の Wiz の文脈トークン `d[_.nla]=!0` → Object.assign → `_.nla in a`)
409        ("var S = Symbol('Hb'); var d = {}; d[S] = true; (S in d) + ',' + d[S]", "true,true"),
410        ("var S = Symbol('Hb'); var d = {}; d[S] = true; var o = Object.assign({}, {x: 1}, d); (S in o) + ',' + o[S]", "true,true"),
411        ("var S = Symbol('a'), T = Symbol('a'); var o = {}; o[S] = 1; (T in o) + ',' + (S in o)", "false,true"),
412        ("var S = Symbol('Hb'); var d = {}; d[S] = true; var o = {...d, y: 2}; (S in o) + ',' + o.y", "true,2"),
413        ("var t = Symbol('t'); var m = new Map(); m.set(t, 5); m.get(t) + ',' + m.get(Symbol('t'))", "5,undefined"),
414        ("var S = Symbol('k'); var o = {}; o[S] = 1; Object.hasOwn(o, S) + ',' + o.hasOwnProperty(S)", "true,true"),
415        // Symbol.match/replace/search/split のオブジェクト委譲
416        ("var custom = { [Symbol.match](s) { return s === 'hello' ? 'yes' : 'no'; } }; 'hello'.match(custom) + ',' + 'world'.match(custom)", "yes,no"),
417        ("var custom = { [Symbol.replace](s, r) { return s + '-' + r; } }; 'abc'.replace(custom, '123')", "abc-123"),
418        ("var custom = { [Symbol.search](s) { return s.length; } }; 'abc'.search(custom)", "3"),
419        ("var custom = { [Symbol.split](s, lim) { return [s.length, lim]; } }; 'abc'.split(custom, 5).join(',')", "3,5"),
420        // RegExp.prototype[Symbol.*] 直接呼び出し & RegExp 委譲動作
421        ("typeof /a/[Symbol.match]", "function"),
422        ("/b/[Symbol.match]('abc').join(',')", "b"),
423        ("/b/[Symbol.replace]('abc', 'X')", "aXc"),
424        ("/b/[Symbol.search]('abc')", "1"),
425        ("/b/[Symbol.split]('abc').join(',')", "a,c"),
426        // Symbol.species 静的アクセサ
427        ("Array[Symbol.species] === Array", "true"),
428        ("RegExp[Symbol.species] === RegExp", "true"),
429        ("Promise[Symbol.species] === Promise", "true"),
430        // ArraySpeciesCreate (Symbol.species による派生生成)
431        ("class SubArr extends Array {}; const sa = new SubArr(1, 2, 3); sa.map(x => x*2) instanceof SubArr", "true"),
432        ("class SubArr extends Array {}; const sa = new SubArr(1, 2, 3); sa.filter(x => x > 1) instanceof SubArr", "true"),
433        ("class SubArr extends Array {}; const sa = new SubArr(1, 2, 3); sa.slice(1) instanceof SubArr", "true"),
434        ("class SubArr extends Array {}; const sa = new SubArr(1, 2, 3); sa.concat([4]) instanceof SubArr", "true"),
435        ("class SubArr extends Array {}; const sa = new SubArr(1, 2, 3); sa.splice(1, 1) instanceof SubArr", "true"),
436        ("class SubArr extends Array {}; const sa = new SubArr(1, 2, 3); sa.flatMap(x => [x]) instanceof SubArr", "true"),
437        ("class CustomArr extends Array { static get [Symbol.species]() { return Array; } }; const ca = new CustomArr(1, 2); !(ca.map(x => x) instanceof CustomArr) && ca.map(x => x) instanceof Array", "true"),
438        // Array.prototype[Symbol.unscopables]
439        ("Array.prototype[Symbol.unscopables].includes", "true"),
440        ("Array.prototype[Symbol.unscopables].at", "true"),
441        // setTimeout(マクロタスク。Promise を介して await で駆動)
442        ("var x=0; await new Promise(r => setTimeout(() => { x = 5; r(); }, 0)); x", "5"),
443        ("var done=false; await new Promise(r => setTimeout(() => { done = true; r(); }, 0)); done", "true"),
444        ("await new Promise(res => setTimeout(() => res(7), 0))", "7"),
445        // requestAnimationFrame(マクロタスク。timestamp 引数 + cancelAnimationFrame)
446        ("typeof requestAnimationFrame", "function"),
447        ("var x=0; await new Promise(r => requestAnimationFrame(() => { x = 5; r(); })); x", "5"),
448        ("var ty=''; await new Promise(r => requestAnimationFrame(t => { ty = typeof t; r(); })); ty", "number"),
449        ("var L=''; requestAnimationFrame(() => L+='a'); var id=requestAnimationFrame(() => L+='b'); cancelAnimationFrame(id); await new Promise(r => requestAnimationFrame(() => r())); L", "a"),
450        // localStorage / sessionStorage(静的マップ共有のため各テスト先頭で clear)
451        ("localStorage.clear(); localStorage.setItem('a','1'); localStorage.getItem('a')", "1"),
452        ("localStorage.clear(); localStorage.getItem('missing')", "null"),
453        ("localStorage.clear(); localStorage.setItem('x','9'); localStorage.length", "1"),
454        ("localStorage.clear(); localStorage.setItem('a','1'); localStorage.removeItem('a'); localStorage.getItem('a')", "null"),
455        ("localStorage.clear(); localStorage.foo='bar'; localStorage.getItem('foo')", "bar"),
456        ("sessionStorage.clear(); sessionStorage.setItem('s','2'); sessionStorage.getItem('s')", "2"),
457        // `delete localStorage.foo` も同じ種類の黙殺バグだった(`removeItem` と同義のはずが
458        // Host プロキシが `.props` を持たないため何も起きていなかった)。
459        (
460            "localStorage.clear(); localStorage.foo='bar'; delete localStorage.foo; localStorage.getItem('foo')",
461            "null",
462        ),
463        // `Object.keys/values/entries(localStorage)` と `for...in localStorage` が実データ
464        // (専用マップ側にある) を一切見ておらず常に空になっていたバグの修正確認。
465        (
466            "localStorage.clear(); localStorage.setItem('a','1'); localStorage.setItem('b','2'); Object.keys(localStorage).sort().join(',')",
467            "a,b",
468        ),
469        (
470            "localStorage.clear(); localStorage.setItem('a','1'); localStorage.setItem('b','2'); Object.values(localStorage).sort().join(',')",
471            "1,2",
472        ),
473        (
474            "localStorage.clear(); localStorage.setItem('a','1'); Object.entries(localStorage)[0].join(':')",
475            "a:1",
476        ),
477        (
478            "localStorage.clear(); localStorage.setItem('a','1'); localStorage.setItem('b','2'); var out=[]; for (var k in localStorage) out.push(k); out.sort().join(',')",
479            "a,b",
480        ),
481        (
482            "localStorage.clear(); localStorage.setItem('a','1'); Object.assign({}, localStorage).a",
483            "1",
484        ),
485        // `Object.keys/values/entries(el.dataset)` と `for...in el.dataset` も同種の
486        // バグで実データ(DOM 要素側の `data-*` 属性)を一切見ておらず常に空になって
487        // いた(`localStorage`/`sessionStorage` で修正済みのバグと同型)。
488        (
489            "var e=document.createElement('div'); e.dataset.userId='42'; e.dataset.fooBar='x'; Object.keys(e.dataset).sort().join(',')",
490            "fooBar,userId",
491        ),
492        (
493            "var e=document.createElement('div'); e.dataset.a='1'; e.dataset.b='2'; Object.values(e.dataset).sort().join(',')",
494            "1,2",
495        ),
496        (
497            "var e=document.createElement('div'); e.dataset.a='1'; Object.entries(e.dataset)[0].join(':')",
498            "a:1",
499        ),
500        (
501            "var e=document.createElement('div'); e.dataset.a='1'; e.dataset.b='2'; var out=[]; for (var k in e.dataset) out.push(k); out.sort().join(',')",
502            "a,b",
503        ),
504        (
505            "var e=document.createElement('div'); e.dataset.a='1'; Object.assign({}, e.dataset).a",
506            "1",
507        ),
508        // タグ付きテンプレート `` tag`...${e}...` ``(ES2015)が丸ごと未対応で、`Template`
509        // トークンが式の直後に来ると静かにパースが崩れるバグだった。基本形・複数補間・
510        // `this` 束縛(メソッド呼出形)・組込み `String.raw` を確認。
511        (
512            "function tag(s, ...v){ return s.join('|') + '::' + v.join(','); } tag`a${1}b${2}c`",
513            "a|b|c::1,2",
514        ),
515        (
516            "var o={ tag(s){ return this===o ? s[0] : 'bad'; } }; o.tag`hi`",
517            "hi",
518        ),
519        ("String.raw`a\\nb`", "a\\nb"),
520        ("String.raw`x${1+1}y`", "x2y"),
521        // `strings.raw` はエスケープ未解決のまま渡る(cooked とは別)ことの確認。
522        (
523            "function tag(s){ return s[0] + '|' + s.raw[0]; } tag`a\\nb`",
524            "a\nb|a\\nb",
525        ),
526        // `Object.is(a, b)`(ES2015、SameValue)が丸ごと欠落していた。`===` と違い
527        // `NaN` 同士は等しく、`+0`/`-0` は区別する。
528        ("Object.is(NaN, NaN)", "true"),
529        ("NaN === NaN", "false"),
530        ("Object.is(0, -0)", "false"),
531        ("0 === -0", "true"),
532        ("Object.is(1, 1)", "true"),
533        ("Object.is('a', 'a')", "true"),
534        ("Object.is({}, {})", "false"),
535        // `Reflect.setPrototypeOf`/`isExtensible`/`preventExtensions`/
536        // `getOwnPropertyDescriptor` が丸ごと欠落していた。
537        (
538            "var o={}; var p={x:1}; Reflect.setPrototypeOf(o,p); o.x",
539            "1",
540        ),
541        ("var o={}; Reflect.isExtensible(o)", "true"),
542        (
543            "var o={}; Reflect.preventExtensions(o); Reflect.isExtensible(o)",
544            "false",
545        ),
546        (
547            "Reflect.getOwnPropertyDescriptor({x:1}, 'x').value",
548            "1",
549        ),
550        // `Array.prototype.fill(value, start, end)` が第2/第3引数を完全に無視し、
551        // 常に配列全体を上書きしていた。
552        ("[1,2,3,4,5].fill(0,1,3).join(',')", "1,0,0,4,5"),
553        ("[1,2,3,4,5].fill(9).join(',')", "9,9,9,9,9"),
554        ("[1,2,3,4,5].fill(0,-2).join(',')", "1,2,3,0,0"),
555        ("[1,2,3].fill(0,1).join(',')", "1,0,0"),
556        // `Array.prototype.indexOf`/`includes` の `fromIndex`(第2引数)が完全に無視され、
557        // 常に先頭から探索していた。
558        ("[1,2,3,2,1].indexOf(2, 2)", "3"),
559        ("[1,2,3].indexOf(1, 1)", "-1"),
560        ("[1,2,3].indexOf(2, -2)", "1"),
561        ("[1,2,3].includes(1, 1)", "false"),
562        ("[1,2,3].includes(2, 1)", "true"),
563        // `lastIndexOf` にも同じ `fromIndex` 無視バグがあった。
564        ("[1,2,1,2,1].lastIndexOf(2, 2)", "1"),
565        ("[1,2,3].lastIndexOf(3, 1)", "-1"),
566        ("[1,2,3].lastIndexOf(1, -1)", "0"),
567        ("[1,2,3,2].lastIndexOf(2)", "3"),
568        // `fromIndex` に NaN を渡すと仕様上 `ToIntegerOrInfinity(NaN)=0` として扱われ
569        // index 0 のみを見るはずだが、以前は無条件に -1 を返していた。
570        ("[5,1,2].lastIndexOf(5, NaN)", "0"),
571        ("[5,1,2].lastIndexOf(1, NaN)", "-1"),
572        // URLSearchParams
573        ("new URLSearchParams('a=1&b=2').get('a')", "1"),
574        ("new URLSearchParams('?a=1&b=2').get('b')", "2"),
575        ("new URLSearchParams('a=1&a=2').getAll('a').join(',')", "1,2"),
576        ("new URLSearchParams('a=1').has('a') + ',' + new URLSearchParams('a=1').has('z')", "true,false"),
577        ("var p=new URLSearchParams('a=1'); p.append('b','2'); p.toString()", "a=1&b=2"),
578        ("var p=new URLSearchParams('a=1&b=2'); p.delete('a'); p.toString()", "b=2"),
579        ("var p=new URLSearchParams('a=1'); p.set('a','9'); p.get('a')", "9"),
580        // `new URLSearchParams(init)` の init がオブジェクトの場合(sequence of
581        // pairs / record / 既存インスタンス)が丸ごと壊れていたバグ(`to_js_string`
582        // 丸投げで常に `"[object Object]"` 相当に落ちていた)。
583        ("new URLSearchParams([['a','1'],['b','2']]).toString()", "a=1&b=2"),
584        ("new URLSearchParams({a:'1',b:'2'}).toString()", "a=1&b=2"),
585        (
586            "var p1=new URLSearchParams('a=1&b=2'); new URLSearchParams(p1).toString()",
587            "a=1&b=2",
588        ),
589        // URLSearchParams: %XX / + デコード(form-urlencoded)
590        ("new URLSearchParams('a=hello%20world').get('a')", "hello world"),
591        ("new URLSearchParams('a=hello+world').get('a')", "hello world"),
592        ("var p=new URLSearchParams(); p.set('a','hello world'); p.toString()", "a=hello+world"),
593        ("var p=new URLSearchParams(); p.set('a','a&b=c'); p.get('a')", "a&b=c"),
594        // `entries`/`keys`/`values`/`forEach` が `URLSearchParams` に丸ごと欠落していた
595        // (`FormData` は既にあったが、内部表現が同じなのに移植されていなかった)。
596        (
597            "[...new URLSearchParams('a=1&b=2').entries()].map(e=>e[0]+':'+e[1]).join(',')",
598            "a:1,b:2",
599        ),
600        ("new URLSearchParams('a=1&b=2').keys().join(',')", "a,b"),
601        ("new URLSearchParams('a=1&b=2').values().join(',')", "1,2"),
602        (
603            "var s=''; new URLSearchParams('a=1&b=2').forEach(function(v,k){ s+=k+'='+v+';'; }); s",
604            "a=1;b=2;",
605        ),
606        // `Symbol.iterator` 未登録で `for (const [k,v] of params)`(`entries()` と同義の
607        // 既定イテレーション)が丸ごと非対応だったバグ。`FormData` にも同じ修正を適用。
608        (
609            "var out=[]; for (const [k,v] of new URLSearchParams('a=1&b=2')) out.push(k+':'+v); out.join(',')",
610            "a:1,b:2",
611        ),
612        (
613            "var fd=new FormData(); fd.set('x','9'); var out=[]; for (const [k,v] of fd) out.push(k+':'+v); out.join(',')",
614            "x:9",
615        ),
616        // URLPattern API (HTML Standard)
617        ("new URLPattern('/users/:id').test('/users/123')", "true"),
618        ("new URLPattern({ pathname: '/about' }).test('/about')", "true"),
619        ("new URLPattern('/users/:id').exec('/users/123').pathname.input", "/users/123"),
620        ("new URLPattern('/admin/*').test('/users/123')", "false"),
621        // encodeURIComponent / decodeURIComponent
622        ("encodeURIComponent('hello world')", "hello%20world"),
623        ("encodeURIComponent('a=1&b=2')", "a%3D1%26b%3D2"),
624        ("decodeURIComponent('hello%20world')", "hello world"),
625        ("decodeURIComponent(encodeURIComponent('日本語'))", "日本語"),
626        ("encodeURIComponent(\"!'()*\")", "!'()*"),
627        // encodeURI / decodeURI(予約文字は残す)
628        ("encodeURI('https://example.com/a b?x=1&y=2')", "https://example.com/a%20b?x=1&y=2"),
629        ("decodeURI('https://example.com/a%20b')", "https://example.com/a b"),
630        // `escape`/`unescape`(Annex B.2.1)が丸ごと未対応だった。
631        ("escape('a b?c')", "a%20b%3Fc"),
632        ("unescape(escape('a b?c'))", "a b?c"),
633        // ASCII 範囲外は `%uXXXX` 形式になる。
634        ("escape('日')", "%u65E5"),
635        ("unescape('%u65E5')", "日"),
636        // Annex B.2.3 の `String.prototype` HTML ラッパーメソッド群が丸ごと未対応だった。
637        ("'hi'.bold()", "<b>hi</b>"),
638        ("'hi'.italics()", "<i>hi</i>"),
639        ("'hi'.big()", "<big>hi</big>"),
640        ("'hi'.small()", "<small>hi</small>"),
641        ("'hi'.strike()", "<strike>hi</strike>"),
642        ("'hi'.sub()", "<sub>hi</sub>"),
643        ("'hi'.sup()", "<sup>hi</sup>"),
644        ("'hi'.fixed()", "<tt>hi</tt>"),
645        ("'hi'.blink()", "<blink>hi</blink>"),
646        ("'hi'.anchor('top')", "<a name=\"top\">hi</a>"),
647        ("'hi'.link('x.html')", "<a href=\"x.html\">hi</a>"),
648        ("'hi'.fontcolor('red')", "<font color=\"red\">hi</font>"),
649        ("'hi'.fontsize(3)", "<font size=\"3\">hi</font>"),
650        // 属性値の `\"` は `&quot;` へエスケープする(仕様どおり)。
651        ("'hi'.anchor('a\"b')", "<a name=\"a&quot;b\">hi</a>"),
652        // `trimLeft`/`trimRight`(Annex B.2.2。`trimStart`/`trimEnd` の別名)が
653        // 丸ごと未対応だった。
654        ("'  hi  '.trimLeft()", "hi  "),
655        ("'  hi  '.trimRight()", "  hi"),
656        // FormData (空生成 + API)
657        ("var f=new FormData(); f.append('a','1'); f.append('b','2'); f.get('a')", "1"),
658        ("var f=new FormData(); f.append('a','1'); f.append('a','2'); f.getAll('a').join(',')", "1,2"),
659        ("var f=new FormData(); f.append('a','1'); f.append('b','2'); f.toString()", "a=1&b=2"),
660        ("var f=new FormData(); f.append('a','1'); f.set('a','9'); f.get('a')", "9"),
661        ("var f=new FormData(); f.append('a','1'); f.has('a')+','+f.has('z')", "true,false"),
662        ("var f=new FormData(); f.append('a','1'); f.append('b','2'); f.entries().map(function(e){return e[0]+'='+e[1]}).join('&')", "a=1&b=2"),
663        ("var f=new FormData(); f.append('a','1'); f.append('b','2'); f.keys().join(',')", "a,b"),
664        ("var f=new FormData(); f.append('a','1'); f.append('b','2'); var s=''; f.forEach(function(v,k){s+=k+':'+v+';'}); s", "a:1;b:2;"),
665        // URL
666        ("new URL('https://e.com/p?x=1#h').pathname", "/p"),
667        ("new URL('https://e.com/p?x=1').searchParams.get('x')", "1"),
668        // `new URL(url, base)` の第2引数 base が丸ごと無視され、相対URL解決という
669        // URL API 最も基本的な使い方が機能しなかったバグ(既存の `resolve_url`
670        // ヘルパは `fetch`/XHR では既に使われていたが `URL` コンストラクタには
671        // 配線されていなかった。`.`/`..` セグメントの解決は非対応の簡略実装
672        // という既存の制約はそのまま)。
673        ("new URL('/a/b', 'https://e.com/x/y').href", "https://e.com/a/b"),
674        ("new URL('c', 'https://e.com/a/b').href", "https://e.com/a/c"),
675        // `user:pass@host` 形式のユーザー情報(userinfo)が丸ごと未対応で、
676        // `hostname`(引いては `host`/`port`/`origin`)にそのまま混入し
677        // 壊れた値になるバグだった(`user` が `hostname` の一部に化ける)。
678        ("new URL('https://user:pass@example.com/p').hostname", "example.com"),
679        ("new URL('https://user:pass@example.com/p').username", "user"),
680        ("new URL('https://user:pass@example.com/p').password", "pass"),
681        ("new URL('https://user:pass@example.com/p').origin", "https://example.com"),
682        // ユーザー情報無しの通常URLでは既定値が空文字列のまま(回帰確認)。
683        ("new URL('https://example.com/p').username", ""),
684        // `url.href = '...'`(URL全体を再パースして全コンポーネントを書き換える
685        // 定番の再代入パターン)が丸ごと未対応で、セッターが無いため代入が
686        // 黙って無視されていた。
687        (
688            "var u=new URL('https://a.com/x'); u.href='https://b.com/y?z=1'; u.hostname",
689            "b.com",
690        ),
691        (
692            "var u=new URL('https://a.com/x'); u.href='https://b.com/y?z=1'; u.pathname",
693            "/y",
694        ),
695        (
696            "var u=new URL('https://a.com/x'); u.href='https://b.com/y?z=1'; u.searchParams.get('z')",
697            "1",
698        ),
699        (
700            // オブジェクト自体は再代入せず同じインスタンスを書き換えるため、
701            // 元の変数参照からも新しい値が見える。
702            "var u=new URL('https://a.com/x'); var u2=u; u.href='https://b.com/y'; u2.href",
703            "https://b.com/y",
704        ),
705        // `url.pathname`/`.search`/`.hash`の個別再代入が丸ごと未対応だった。
706        // 特に`.search`は`url.toString()`/`.href`が`searchParams`だけを見て
707        // `search`プロパティ自体は読まないため、代入しても何も反映されない
708        // という`.href`丸ごと差し替えとは別種の静かな不整合があった。
709        (
710            "var u=new URL('https://a.com/x'); u.pathname='y'; u.href",
711            "https://a.com/y",
712        ),
713        (
714            "var u=new URL('https://a.com/x'); u.search='z=1'; u.href",
715            "https://a.com/x?z=1",
716        ),
717        (
718            "var u=new URL('https://a.com/x'); u.search='z=1'; u.searchParams.get('z')",
719            "1",
720        ),
721        (
722            "var u=new URL('https://a.com/x?old=1'); u.search='new=2'; u.href",
723            "https://a.com/x?new=2",
724        ),
725        (
726            "var u=new URL('https://a.com/x'); u.hash='frag'; u.href",
727            "https://a.com/x#frag",
728        ),
729        (
730            // 空文字列への代入はコンポーネントを除去する(クエリ/フラグメント無しに戻る)。
731            "var u=new URL('https://a.com/x?y=1#z'); u.search=''; u.hash=''; u.href",
732            "https://a.com/x",
733        ),
734        // `url.protocol`/`.host`/`.hostname`/`.port`の個別再代入も丸ごと
735        // 未対応だった。これら4つは互いに依存する派生値(`host`=`hostname`+
736        // `:`+`port`、`origin`=`protocol`+`//`+`host`)を持つため、どれを
737        // 代入しても`host`/`origin`を再計算し直す必要がある。
738        (
739            "var u=new URL('https://a.com:8080/x'); u.hostname='b.com'; u.href",
740            "https://b.com:8080/x",
741        ),
742        (
743            "var u=new URL('https://a.com:8080/x'); u.hostname='b.com'; u.origin",
744            "https://b.com:8080",
745        ),
746        (
747            "var u=new URL('https://a.com/x'); u.port='9090'; u.host",
748            "a.com:9090",
749        ),
750        (
751            "var u=new URL('https://a.com:8080/x'); u.host='c.com:7070'; u.hostname + ',' + u.port",
752            "c.com,7070",
753        ),
754        (
755            "var u=new URL('http://a.com/x'); u.protocol='https'; u.href",
756            "https://a.com/x",
757        ),
758        // クエリのみ/フラグメントのみの相対参照(`?x=2`/`#frag`)が、他の
759        // 相対パス解決と同じ「ディレクトリ相対」ロジックに落ちてベースの
760        // パスの最後のセグメントごと消えるバグだった。
761        ("new URL('?x=2', 'https://e.com/a/b?x=1').href", "https://e.com/a/b?x=2"),
762        ("new URL('#frag', 'https://e.com/a/b?x=1').href", "https://e.com/a/b?x=1#frag"),
763        // プロトコル相対URL(`//host/path`)が、ベースのホスト配下の相対パスと
764        // して誤解決されるバグだった(`//cdn.example.com/x.js` は別ホストへの
765        // 参照であり、ベースホスト配下のパスにしてはいけない)。
766        ("new URL('//cdn.example.com/x.js', 'https://e.com/a/b').href", "https://cdn.example.com/x.js"),
767        // `URL.canParse(url, base?)`(ES2023/WHATWG)が丸ごと未対応だった。
768        ("URL.canParse('https://example.com')", "true"),
769        ("URL.canParse('not a url')", "false"),
770        ("URL.canParse('/path', 'https://example.com')", "true"),
771        ("URL.canParse('/path')", "false"),
772        // `URL.parse(url, base?)`(ES2024/WHATWG)が丸ごと未対応だった。
773        // `try { new URL(x) } catch { null }` の1メソッド版。
774        ("URL.parse('https://example.com/p').pathname", "/p"),
775        ("URL.parse('not a url') === null", "true"),
776        ("URL.parse('/path', 'https://example.com').host", "example.com"),
777        ("URL.parse('/path') === null", "true"),
778        // `URL.createObjectURL(blob)`/`.revokeObjectURL(url)` が丸ごと未対応だった。
779        ("URL.createObjectURL(new Blob(['x'])).startsWith('blob:')", "true"),
780        // 呼び出す毎に一意な URL を発行する。
781        (
782            "URL.createObjectURL(new Blob(['x'])) === URL.createObjectURL(new Blob(['x']))",
783            "false",
784        ),
785        ("typeof URL.revokeObjectURL('blob:x')", "undefined"),
786        // `URLSearchParams.prototype.size`(ES2023)が丸ごと未対応だった。
787        ("new URLSearchParams('a=1&b=2').size", "2"),
788        (
789            "var p=new URLSearchParams('a=1'); p.append('b','2'); p.size",
790            "2",
791        ),
792        (
793            "var p=new URLSearchParams('a=1&b=2'); p.delete('a'); p.size",
794            "1",
795        ),
796        ("new URLSearchParams('').size", "0"),
797        // `has(name, value)`/`delete(name, value)`(ES2023 の第2引数)が丸ごと
798        // 無視され、値を問わず同名エントリを判定/削除してしまうバグだった。
799        ("new URLSearchParams('a=1&a=2').has('a', '1')", "true"),
800        ("new URLSearchParams('a=1&a=2').has('a', '9')", "false"),
801        (
802            "var p=new URLSearchParams('a=1&a=2'); p.delete('a','1'); p.getAll('a').join(',')",
803            "2",
804        ),
805        (
806            "var p=new URLSearchParams('a=1&a=2'); p.delete('a'); p.getAll('a').join(',')",
807            "",
808        ),
809        // `URLSearchParams.prototype.sort()`(ES2020)が丸ごと未対応だった。
810        (
811            "var p=new URLSearchParams('c=3&a=1&b=2'); p.sort(); p.toString()",
812            "a=1&b=2&c=3",
813        ),
814        (
815            "var p=new URLSearchParams('b=2&a=1&a=0'); p.sort(); p.getAll('a').join(',')",
816            "1,0",
817        ),
818        // `url.toString()`(暗黙の文字列化 `fetch(url)` 等でも使われる)が `href` の
819        // 構築時点スナップショットのままで、`searchParams` の変更を反映しないバグ。
820        (
821            "var u=new URL('https://e.com/p?x=1'); u.searchParams.set('x','9'); u.toString()",
822            "https://e.com/p?x=9",
823        ),
824        (
825            "var u=new URL('https://e.com/p'); u.searchParams.append('a','1'); u.searchParams.append('b','2'); u.toString()",
826            "https://e.com/p?a=1&b=2",
827        ),
828        (
829            "var u=new URL('https://e.com/p?x=1#h'); u.searchParams.delete('x'); u.toString()",
830            "https://e.com/p#h",
831        ),
832        // `URL.prototype.toJSON`(仕様上 `toString()` と同じ href を返すだけの
833        // エイリアス)が丸ごと未対応で、`JSON.stringify(url)` がプロパティ丸ごとの
834        // JSON になってしまうバグだった。
835        (
836            "JSON.stringify({u: new URL('https://e.com/p?x=1')})",
837            "{\"u\":\"https://e.com/p?x=1\"}",
838        ),
839        // base64
840        ("btoa('hello')", "aGVsbG8="),
841        ("atob('aGVsbG8=')", "hello"),
842        ("atob(btoa('AtmOS roundtrip'))", "AtmOS roundtrip"),
843        // navigator / performance
844        ("navigator.platform", "AtmOS"),
845        ("typeof navigator.userAgent", "string"),
846        ("navigator.onLine", "true"),
847        // `navigator.cookieEnabled`/`.hardwareConcurrency`/`.maxTouchPoints`/
848        // `.languages`/`.webdriver`(丸ごと未対応だった)。
849        ("navigator.cookieEnabled", "true"),
850        ("navigator.hardwareConcurrency", "4"),
851        ("navigator.maxTouchPoints", "0"),
852        ("navigator.languages[0]", "ja-JP"),
853        ("navigator.webdriver", "false"),
854        // `navigator.userAgentData`(Client Hints API。丸ごと未対応だった。
855        // `navigator.userAgent`文字列パースに代わる構造化版の定番フィー
856        // チャー検出パターン。2026-07-17 発見・実装)。
857        (
858            "navigator.userAgentData.brands[0].brand + ',' + navigator.userAgentData.mobile + ',' + \
859             navigator.userAgentData.platform",
860            "AtmOSBrowser,false,AtmOS",
861        ),
862        (
863            "await navigator.userAgentData.getHighEntropyValues(['architecture','bitness']).then(v => \
864             v.architecture + ',' + v.bitness + ',' + v.platform)",
865            "arm,64,AtmOS",
866        ),
867        // `navigator.storage`(StorageManager API。`.estimate()`/`.persist()`/
868        // `.persisted()`が丸ごと未対応だった。2026-07-17 発見・実装。実際の
869        // ディスク使用量計測機構が無いため`usage:0`の誠実な簡略値、
870        // `persist`/`persisted`は常に`true`)。
871        (
872            "await navigator.storage.estimate().then(e => typeof e.usage + ',' + typeof e.quota)",
873            "number,number",
874        ),
875        ("await navigator.storage.persist()", "true"),
876        ("await navigator.storage.persisted()", "true"),
877        ("typeof performance.now()", "number"),
878        // Performance Timeline / User Timing API(`mark`/`measure`/
879        // `getEntriesByType`/`getEntriesByName`/`clearMarks`/`clearMeasures`)
880        // が `now()` 以外丸ごと未対応だった。
881        ("performance.mark('a').entryType", "mark"),
882        (
883            "performance.mark('s1'); performance.mark('e1'); \
884             performance.measure('m1','s1','e1').entryType",
885            "measure",
886        ),
887        // `performance.timeOrigin`(丸ごと未対応だった。2026-07-16 発見・
888        // 実装)。自己テストはブート初期段階(壁時計がまだ NTP 同期前)に
889        // 実行されるため `epoch_ms_now()` が `0` を返しうる。数値型である
890        // ことのみ検証し、正の値は断定しない。
891        ("typeof performance.timeOrigin", "number"),
892        ("performance.timeOrigin >= 0", "true"),
893        // `performance.getEntries()`(フィルタ無し全件取得。丸ごと未対応
894        // だった。2026-07-16 発見・実装)。
895        (
896            "performance.mark('ge1'); performance.getEntries().some(e => e.name === 'ge1')",
897            "true",
898        ),
899        ("performance.mark('a'); performance.getEntriesByType('mark').length > 0", "true"),
900        ("performance.mark('a'); performance.getEntriesByName('a', 'mark').length", "1"),
901        (
902            "performance.mark('to-clear'); performance.clearMarks('to-clear'); \
903             performance.getEntriesByName('to-clear').length",
904            "0",
905        ),
906        (
907            "performance.mark('sX'); performance.mark('eX'); \
908             performance.measure('mX','sX','eX'); performance.clearMeasures(); \
909             performance.getEntriesByType('measure').length",
910            "0",
911        ),
912        // `PerformanceObserver`(丸ごと未対応だった。`observe({entryTypes})`
913        // で監視中の種別に一致する `mark`/`measure` を同期通知する)。
914        (
915            "let got; new PerformanceObserver((list) => { got = list.getEntries()[0].name; }) \
916             .observe({entryTypes:['mark']}); performance.mark('obs1'); got",
917            "obs1",
918        ),
919        (
920            "let n = 0; new PerformanceObserver((list) => { n += list.getEntries().length; }) \
921             .observe({entryTypes:['measure']}); performance.mark('s2'); \
922             performance.mark('e2'); performance.measure('m2','s2','e2'); n",
923            "1",
924        ),
925        (
926            "let hit = false; let ob = new PerformanceObserver(() => { hit = true; }); \
927             ob.observe({entryTypes:['mark']}); ob.disconnect(); performance.mark('after-disconnect'); hit",
928            "false",
929        ),
930        // `PerformanceObserver.supportedEntryTypes`(丸ごと未対応だった。
931        // 2026-07-16 発見・実装)。
932        (
933            "PerformanceObserver.supportedEntryTypes.join(',')",
934            "mark,measure",
935        ),
936        // `observe({type: '...', buffered: true})`(単一種別+既存エントリ
937        // 即時配信の新形式)が丸ごと未対応だった(`entryTypes`しか読んで
938        // おらず`type`単体では一切発火しない黙殺バグだった。2026-07-17
939        // 発見・実装)。
940        (
941            "let got; new PerformanceObserver((list) => { got = list.getEntries()[0].name; }) \
942             .observe({type:'mark'}); performance.mark('obs2'); got",
943            "obs2",
944        ),
945        (
946            "performance.mark('pre-existing'); let got=null; \
947             new PerformanceObserver((list) => { got = list.getEntries()[0].name; }) \
948             .observe({type:'mark', buffered:true}); got",
949            "pre-existing",
950        ),
951        (
952            "let n=0; new PerformanceObserver((list) => { n += list.getEntries().length; }) \
953             .observe({type:'mark'}); n",
954            "0",
955        ),
956        // MessageChannel/MessagePort(丸ごと未対応だった。`.port1`/`.port2` 間の
957        // 双方向メッセージング)。
958        (
959            "let mc = new MessageChannel(); let got; \
960             mc.port2.onmessage = e => { got = e.data; }; \
961             mc.port1.postMessage('hi'); got",
962            "hi",
963        ),
964        (
965            "let mc2 = new MessageChannel(); let n = 0; \
966             mc2.port2.addEventListener('message', e => { n += e.data; }); \
967             mc2.port1.postMessage(1); mc2.port1.postMessage(2); n",
968            "3",
969        ),
970        (
971            "let mc3 = new MessageChannel(); let hit = false; \
972             mc3.port2.onmessage = () => { hit = true; }; \
973             mc3.port2.close(); mc3.port1.postMessage('x'); hit",
974            "false",
975        ),
976        // BroadcastChannel(丸ごと未対応だった。同名インスタンス全体への配送、
977        // 自分自身への配送除外、他名チャネルへの非配送を確認)。
978        (
979            "let a1 = new BroadcastChannel('ch'); let b1 = new BroadcastChannel('ch'); \
980             let got; b1.onmessage = e => { got = e.data; }; \
981             a1.postMessage('hi'); got",
982            "hi",
983        ),
984        (
985            "let a2 = new BroadcastChannel('ch2'); let selfHit = false; \
986             a2.onmessage = () => { selfHit = true; }; \
987             a2.postMessage('x'); selfHit",
988            "false",
989        ),
990        (
991            "let a3 = new BroadcastChannel('chX'); let b3 = new BroadcastChannel('chY'); \
992             let hit = false; b3.onmessage = () => { hit = true; }; \
993             a3.postMessage('x'); hit",
994            "false",
995        ),
996        // `window.screen`(丸ごと未対応だった。`screen.width`/`.height`/
997        // `.availWidth`/`.availHeight`/`.colorDepth`/`.orientation.type` という
998        // レスポンシブ分岐の定番パターン)。
999        ("typeof screen.width", "number"),
1000        ("screen.width === window.innerWidth", "true"),
1001        ("screen.availHeight === screen.height", "true"),
1002        ("screen.colorDepth", "24"),
1003        ("screen.orientation.type", "landscape-primary"),
1004        ("window.screen === screen", "true"),
1005        // `screen.orientation.lock()`/`.unlock()`(丸ごと未対応だった。実際に
1006        // 画面の向きを固定する機構は無いため状態のみ追跡する簡略実装)。
1007        (
1008            "typeof screen.orientation.lock('portrait-primary').then",
1009            "function",
1010        ),
1011        (
1012            "screen.orientation.lock('portrait-primary'); screen.orientation.type",
1013            "portrait-primary",
1014        ),
1015        (
1016            "screen.orientation.lock('portrait-primary'); screen.orientation.unlock(); \
1017             screen.orientation.type",
1018            "landscape-primary",
1019        ),
1020        // `reportError()`(丸ごと未対応だった。`window.onerror`/
1021        // `window.addEventListener('error', fn)` へ未捕捉例外と同じ経路で
1022        // 通知する標準グローバル関数)。
1023        (
1024            "let got; window.onerror = (msg) => { got = msg; }; \
1025             reportError(new Error('boom')); got",
1026            "boom",
1027        ),
1028        (
1029            "let ev; window.addEventListener('error', e => { ev = e; }); \
1030             reportError(new Error('e2')); ev.error.message",
1031            "e2",
1032        ),
1033        ("typeof reportError('plain string')", "undefined"),
1034        // `window.confirm`/`window.prompt`(丸ごと未対応だった。`alert`は
1035        // 既に`console_log`で代替されていたのに、対になるこの2つだけ
1036        // グローバルにも`window`にも未登録で`TypeError`になっていた。
1037        // ブロッキングUIが無いこの処理系では常に「OK」相当を返す簡略実装。
1038        // 2026-07-16 発見・実装)。
1039        ("confirm('are you sure?')", "true"),
1040        ("window.confirm('are you sure?')", "true"),
1041        ("prompt('name?', 'bob')", "bob"),
1042        ("window.prompt('name?')", "null"),
1043        // `window.open`/`.close`/`.focus`/`.blur`(丸ごと未対応だった。同じ
1044        // 監査で発見。呼び出すと`TypeError`になっていた。この処理系には
1045        // 複数ウィンドウ機構が無いため`open`はポップアップブロック相当の
1046        // `null`、他は無害なno-op。2026-07-16 発見・実装)。
1047        ("typeof open", "function"),
1048        ("open('https://example.com')", "null"),
1049        ("window.open('https://example.com', '_blank')", "null"),
1050        ("typeof close", "function"),
1051        ("close(); window.close(); 'ok'", "ok"),
1052        // `window.stop()`(丸ごと未対応だった。ページ読み込み中止ボタン
1053        // 相当の定番API。2026-07-17 発見・実装。`close`/`.print`と同じ
1054        // 無害なno-op)。
1055        ("typeof stop", "function"),
1056        ("stop(); window.stop(); 'ok'", "ok"),
1057        ("typeof focus", "function"),
1058        ("focus(); window.focus(); blur(); window.blur(); 'ok'", "ok"),
1059        // `window.getSelection()`(丸ごと未対応だった。`document.
1060        // getSelection()`は既に実装済みだったが、仕様上こちらが本来の
1061        // 正規の場所で、より一般的に使われる`window.getSelection().
1062        // toString()`等のイディオムが`TypeError`になっていた。
1063        // 2026-07-16 発見・実装。同じ実装を再利用するため戻り値も
1064        // `document.getSelection()`と同一の空文字列)。
1065        ("typeof window.getSelection", "function"),
1066        ("typeof getSelection", "function"),
1067        ("window.getSelection().toString()", ""),
1068        // `self`/`top`/`parent`/`frames`(丸ごと未対応だった。`globalThis`
1069        // は既に実装済みだったのに、同じ「windowを指す別名」仲間のこの4つ
1070        // だけ登録が漏れていた。この処理系には独立JSレルムを持つiframeが
1071        // 無く常に単一フレームのため、仕様どおり自分自身を指す。
1072        // 2026-07-16 発見・実装)。
1073        ("self === window", "true"),
1074        ("window.top === window", "true"),
1075        ("parent === window", "true"),
1076        ("window.frames === window", "true"),
1077        ("typeof window.name", "string"),
1078        // `iframe.contentWindow`/`.contentDocument`(同じ「フレームが無い
1079        // ので自分自身を指す」規定。丸ごと未対応だった。2026-07-17
1080        // 発見・実装)。
1081        (
1082            "document.createElement('iframe').contentWindow === window",
1083            "true",
1084        ),
1085        (
1086            "document.createElement('iframe').contentDocument === document",
1087            "true",
1088        ),
1089        (
1090            "document.createElement('div').contentWindow",
1091            "undefined",
1092        ),
1093        // `object.contentDocument`(丸ごと未対応だった。`HTMLObjectElement`
1094        // は`contentWindow`を持たず`contentDocument`のみが仕様上の対象。
1095        // 2026-07-17 発見・実装)。
1096        (
1097            "document.createElement('object').contentDocument === document",
1098            "true",
1099        ),
1100        (
1101            "document.createElement('object').contentWindow",
1102            "undefined",
1103        ),
1104        // `navigator.sendBeacon(url, data)`(丸ごと未対応だった。ページ離脱時の
1105        // 計測データ送信に使われる定番パターン。既存の `fetch()` POST経路を
1106        // 再利用して送信し常に `true` を返す)。
1107        ("typeof navigator.sendBeacon", "function"),
1108        ("navigator.sendBeacon('data:,x')", "true"),
1109        ("navigator.sendBeacon('data:,x', 'payload')", "true"),
1110        // `XMLSerializer`(丸ごと未対応だった。`.serializeToString(node)` は
1111        // 既存の `outerHTML` ゲッターと同じ `get_outer_html` を再利用する)。
1112        (
1113            "var e=document.createElement('div'); e.setAttribute('id','x'); \
1114             e.setAttribute('class','a b'); e.innerHTML='hi2'; \
1115             new XMLSerializer().serializeToString(e) === e.outerHTML",
1116            "true",
1117        ),
1118        (
1119            "var e=document.createElement('span'); e.textContent='z'; \
1120             new XMLSerializer().serializeToString(e) === e.outerHTML",
1121            "true",
1122        ),
1123        ("typeof new XMLSerializer().serializeToString", "function"),
1124        // `navigator.clipboard`(丸ごと未対応だった。`writeText`/`readText` という
1125        // コピー&ペースト UI の定番パターン)。実クリップボードは無いため単純な
1126        // プロセス内文字列保持で近似する。
1127        (
1128            "await navigator.clipboard.writeText('hello'); await navigator.clipboard.readText()",
1129            "hello",
1130        ),
1131        ("typeof navigator.clipboard.writeText('x')", "object"),
1132        // Array: at / flatMap / findLast / findLastIndex
1133        ("[10,20,30].at(-1)", "30"),
1134        ("[10,20,30].at(0)", "10"),
1135        ("[1,2,3].flatMap(x => [x, x*10]).join(',')", "1,10,2,20,3,30"),
1136        ("[1,2,3,4].findLast(x => x % 2 === 1)", "3"),
1137        ("[1,2,3,4].findLastIndex(x => x % 2 === 0)", "3"),
1138        // String: at
1139        ("'hello'.at(-1)", "o"),
1140        ("'hello'.at(1)", "e"),
1141        // 通常の呼び出しは影響を受けないことも確認。
1142        ("'hello world'.includes('world')", "true"),
1143        ("'hello'.startsWith('he')", "true"),
1144        ("'hello'.endsWith('lo')", "true"),
1145        // **重要**: `includes`/`startsWith`/`endsWith` は仕様上、第1引数に
1146        // `RegExp` を渡すと `TypeError` を投げる必要がある(うっかり正規表現を
1147        // 渡した誤用に対する意図的なガード)が、丸ごと未対応で常に無言で
1148        // リテラル文字列 `"/x/"` として検索してしまっていた。
1149        (
1150            "try { 'x'.includes(/x/); 'no-throw' } catch(e) { 'threw' }",
1151            "threw",
1152        ),
1153        (
1154            "try { 'x'.startsWith(/x/); 'no-throw' } catch(e) { 'threw' }",
1155            "threw",
1156        ),
1157        (
1158            "try { 'x'.endsWith(/x/); 'no-throw' } catch(e) { 'threw' }",
1159            "threw",
1160        ),
1161        // Promise: allSettled / race / any
1162        ("(await Promise.allSettled([Promise.resolve(1), Promise.reject('e')])).map(r => r.status).join(',')", "fulfilled,rejected"),
1163        ("await Promise.race([Promise.resolve('first'), Promise.resolve('second')])", "first"),
1164        ("await Promise.any([Promise.reject('x'), Promise.resolve('ok')])", "ok"),
1165        // `allSettled`/`race`/`any` も `Promise.all` と同じく配列要素の汎用 thenable を
1166        // 同化しない非対称なバグだった(共通ヘルパ `resolve_maybe_thenable` で解消)。
1167        (
1168            "(await Promise.allSettled([{ then(r){ r(1); } }, { then(_,rj){ rj('e'); } }])).map(r => r.status).join(',')",
1169            "fulfilled,rejected",
1170        ),
1171        (
1172            "await Promise.race([{ then(r){ r('first'); } }, Promise.resolve('second')])",
1173            "first",
1174        ),
1175        (
1176            "await Promise.any([{ then(_,rj){ rj('x'); } }, { then(r){ r('ok'); } }])",
1177            "ok",
1178        ),
1179        // TextEncoder / TextDecoder
1180        ("new TextEncoder().encode('Hi').join(',')", "72,105"),
1181        ("new TextDecoder().decode([72,105])", "Hi"),
1182        ("new TextDecoder().decode(new TextEncoder().encode('AtmOS'))", "AtmOS"),
1183        // `new TextDecoder(label)` が丸ごとラベル引数を無視し常に `utf-8` 決め打ち
1184        // だったバグ(`utf-16le`/`utf-16be` データを渡すと静かに文字化けしていた)。
1185        ("new TextDecoder('utf-16le').encoding", "utf-16le"),
1186        ("new TextDecoder('utf-16be').encoding", "utf-16be"),
1187        // 'H'=0x48 'i'=0x69 を UTF-16LE(下位バイト→上位バイト)で並べたバイト列。
1188        ("new TextDecoder('utf-16le').decode([0x48,0x00,0x69,0x00])", "Hi"),
1189        ("new TextDecoder('utf-16be').decode([0x00,0x48,0x00,0x69])", "Hi"),
1190        ("new TextDecoder('utf-16').encoding", "utf-16le"),
1191        // `decode(chunk, {stream: true})`(丸ごと未対応だった。チャンク境界で
1192        // マルチバイト文字が分断された場合の定番パターン。2026-07-14 発見・
1193        // 実装)。'€'(U+20AC)の UTF-8 表現 [0xE2,0x82,0xAC] を先頭2バイトと
1194        // 残り1バイトに分割して渡す。
1195        (
1196            "var d=new TextDecoder(); d.decode(new Uint8Array([0xE2,0x82]), {stream:true}) + \
1197             d.decode(new Uint8Array([0xAC]))",
1198            "€",
1199        ),
1200        // stream:true 無し(既定 false)で不完全なバイト列を渡すと、従来どおり
1201        // 即座に置換文字化される(持ち越されない)。
1202        ("new TextDecoder().decode(new Uint8Array([0xE2,0x82]))", "\u{FFFD}"),
1203        // crypto
1204        ("crypto.randomUUID().length", "36"),
1205        ("crypto.randomUUID().charAt(14)", "4"),
1206        ("crypto.getRandomValues([0,0,0]).length", "3"),
1207        // `crypto.getRandomValues` が種別を無視して常に `& 0xff`(0-255)で埋めており、
1208        // 8bit より広い型(`Uint16Array` 等)では値域のごく一部しか使わないバグだった。
1209        // 十分な試行数の中に 255 を超える値が1つでも現れれば、下位8bitマスクではなく
1210        // 実際に16bit幅を使っている証拠になる。
1211        (
1212            "crypto.getRandomValues(new Uint16Array(200)).some(v => v > 255)",
1213            "true",
1214        ),
1215        // `Uint8Array` は元々の挙動(0-255 の範囲内)を維持する。
1216        (
1217            "crypto.getRandomValues(new Uint8Array(50)).every(v => v >= 0 && v <= 255)",
1218            "true",
1219        ),
1220        // structuredClone(深いコピー: 元を変えても複製は不変)
1221        ("var o={a:{b:1}}; var c=structuredClone(o); o.a.b=9; c.a.b", "1"),
1222        ("structuredClone([1,[2,3]])[1][0]", "2"),
1223        // structuredClone(Error) — 汎用の plain object 複製に落ちると proto が
1224        // 引き継がれず `instanceof Error` を失っていたバグ(2026-07-14 発見・修正。
1225        // message/name/stack は元々コピーされていたが、プロトタイプ連鎖が
1226        // 途切れていた)。サブタイプ(TypeError 等)も正しいプロトタイプで
1227        // 複製されることを確認。
1228        (
1229            "var c=structuredClone(new TypeError('x')); c instanceof Error && c instanceof TypeError",
1230            "true",
1231        ),
1232        ("structuredClone(new Error('boom')).message", "boom"),
1233        ("structuredClone(new TypeError('x')).stack", "TypeError: x"),
1234        // `structuredClone(function)`が仕様上の`DataCloneError`を投げず、
1235        // 汎用plain object複製に落ちて「呼出不能な壊れたオブジェクト」に
1236        // 静かに化けていたバグ(`deep_clone_value`を`Result`化して修正。
1237        // 2026-07-17 発見・実装)。
1238        (
1239            "try { structuredClone(function(){}); 'no-throw' } catch(e) { e.name }",
1240            "DataCloneError",
1241        ),
1242        (
1243            "try { structuredClone({fn: () => 1}); 'no-throw' } catch(e) { e.name }",
1244            "DataCloneError",
1245        ),
1246        ("structuredClone({a: 1, b: [2, 3]}).a", "1"),
1247        // 上記`DataCloneError`修正の副次効果として、`URLSearchParams`/
1248        // `FormData`/`Headers`(メソッド群が`.props`上の生の関数値として
1249        // 実装されている「疑似クラス」オブジェクト)を`structuredClone`
1250        // すると、以前は関数プロパティごと汎用複製されて壊れたオブジェクト
1251        // (メソッド呼び出し不能)に静かに化けていたのが、今回の修正で
1252        // 正しく`DataCloneError`を投げるようになったことを確認・記録する
1253        // (2026-07-17)。
1254        (
1255            "try { structuredClone(new URLSearchParams('a=1')); 'no-throw' } catch(e) { e.name }",
1256            "DataCloneError",
1257        ),
1258        (
1259            "try { structuredClone(new FormData()); 'no-throw' } catch(e) { e.name }",
1260            "DataCloneError",
1261        ),
1262        (
1263            "try { structuredClone(new Headers()); 'no-throw' } catch(e) { e.name }",
1264            "DataCloneError",
1265        ),
1266        // `structuredClone(Promise)`も仕様上構造化複製不可(`DataCloneError`)
1267        // だが未対応で、`.then`/`.catch`が消えた呼出不能な壊れたオブジェクト
1268        // に静かに化けていた(丸ごと未対応だった。2026-07-17 発見・実装)。
1269        (
1270            "try { structuredClone(Promise.resolve(1)); 'no-throw' } catch(e) { e.name }",
1271            "DataCloneError",
1272        ),
1273        // `WeakMap`/`WeakSet`も`URLSearchParams`/`FormData`/`Headers`と同じ
1274        // 「メソッド群が`.props`上の生の関数値」パターンの疑似クラスで、
1275        // 仕様上も構造化複製不可(`DataCloneError`)。前サイクルまでの
1276        // `deep_clone_value`関数検出修正の副次効果で既に正しく動作している
1277        // ことを確認・記録する(2026-07-17)。
1278        (
1279            "try { structuredClone(new WeakMap()); 'no-throw' } catch(e) { e.name }",
1280            "DataCloneError",
1281        ),
1282        (
1283            "try { structuredClone(new WeakSet()); 'no-throw' } catch(e) { e.name }",
1284            "DataCloneError",
1285        ),
1286        // `AbortController`/`AbortSignal`/`MessagePort`/`MessageChannel`も
1287        // 同じ「メソッド群が`.props`上の生の関数値」パターンの疑似クラス
1288        // で、仕様上も構造化複製不可。前サイクルまでの`deep_clone_value`
1289        // 関数検出修正の副次効果で正しく動作していることを確認・記録し、
1290        // `DataCloneError`検証の横断監査を完了する(2026-07-17)。
1291        (
1292            "try { structuredClone(new AbortController()); 'no-throw' } catch(e) { e.name }",
1293            "DataCloneError",
1294        ),
1295        (
1296            "try { structuredClone(new AbortController().signal); 'no-throw' } catch(e) { e.name }",
1297            "DataCloneError",
1298        ),
1299        (
1300            "try { structuredClone(new MessageChannel()); 'no-throw' } catch(e) { e.name }",
1301            "DataCloneError",
1302        ),
1303        (
1304            "try { structuredClone(new MessageChannel().port1); 'no-throw' } catch(e) { e.name }",
1305            "DataCloneError",
1306        ),
1307        // structuredClone(Map/Set) — 中身(エントリ/要素)まで正しく複製され、元を変えても
1308        // 複製に影響しないこと(以前は ObjKind::MapObj/SetObj 自体が複製されず消えていたバグ)。
1309        (
1310            "var m=new Map([['a',1],['b',2]]); var c=structuredClone(m); m.set('a',99); c.get('a')+','+c.get('b')+','+c.size",
1311            "1,2,2",
1312        ),
1313        (
1314            "var s=new Set([1,2,3]); var c=structuredClone(s); s.add(4); c.has(4)+','+c.size",
1315            "false,3",
1316        ),
1317        // structuredClone(TypedArray) — TypedArray も内部表現は ObjKind::Array 流用
1318        // (`_ta_kind` タグで区別)なため、素の配列複製経路に落ちると型情報
1319        // (`_ta_kind`/`BYTES_PER_ELEMENT`/`set`/`slice`/`fill`)が丸ごと失われる
1320        // バグだった(Date/RegExp/Map/Set と同種)。
1321        ("var a=new Int8Array([200,-100]); var c=structuredClone(a); c.join(',')", "-56,-100"),
1322        (
1323            "var a=new Int8Array(1); var c=structuredClone(a); c.BYTES_PER_ELEMENT",
1324            "1",
1325        ),
1326        ("var a=new Int8Array(1); var c=structuredClone(a); c[0]=200; c[0]", "-56"),
1327        (
1328            "var a=new Int8Array([1,2]); var c=structuredClone(a); a[0]=9; c[0]",
1329            "1",
1330        ),
1331        // structuredClone(buf, {transfer:[buf]}) — 第2引数 transfer が丸ごと
1332        // 無視されており、転送指定した ArrayBuffer が detached にならなかった
1333        // バグ。
1334        (
1335            "var b=new ArrayBuffer(4); var c=structuredClone(b,{transfer:[b]}); b.detached+','+c.byteLength",
1336            "true,4",
1337        ),
1338        // structuredClone(Blob/DataView) — どちらも `Obj::plain()` + 隠しプロパティで
1339        // 実バイト列を持つ実装のため、汎用の plain object 複製に落ちると
1340        // `slice`/`text`/`getUint8` 等のネイティブ関数プロパティが呼出不能な壊れた
1341        // オブジェクトに複製されてしまうバグだった(TypedArray と同種)。
1342        (
1343            "var b=new Blob(['hi']); var c=structuredClone(b); await c.text()",
1344            "hi",
1345        ),
1346        ("var b=new Blob(['hi']); var c=structuredClone(b); c.size", "2"),
1347        (
1348            "var dv=new DataView(new ArrayBuffer(1)); dv.setUint8(0,65); var c=structuredClone(dv); c.getUint8(0)",
1349            "65",
1350        ),
1351        // structuredClone(ArrayBuffer) — `slice` がネイティブ関数プロパティのため
1352        // 同種のバグで複製後は呼出不能になっていた。
1353        (
1354            "var buf=new ArrayBuffer(4); var c=structuredClone(buf); c.byteLength",
1355            "4",
1356        ),
1357        (
1358            "var buf=new ArrayBuffer(4); var c=structuredClone(buf); typeof c.slice",
1359            "function",
1360        ),
1361        // structuredClone(Proxy) — 実データが `.props` ではなく `ObjKind::Proxy`
1362        // 側にあるため、汎用の plain object 複製に落ちると常に空の `{}` になる
1363        // バグだった(`Date`/`Map`/`Set` 等と同種)。仕様上は本来複製不可
1364        // (`DataCloneError`)だが、他の Proxy 透過性修正と同じ「target へ
1365        // フォワード」方針で target を複製する簡略実装とした。
1366        (
1367            "var p=new Proxy({a:1,b:2},{}); var c=structuredClone(p); c.a+','+c.b",
1368            "1,2",
1369        ),
1370        (
1371            "var t={a:1}; var p=new Proxy(t,{}); var c=structuredClone(p); t.a=99; c.a",
1372            "1",
1373        ),
1374        (
1375            "var buf = await new Blob(['A']).arrayBuffer(); var c=structuredClone(buf); new DataView(c).getUint8(0)",
1376            "65",
1377        ),
1378        // Object: fromEntries / getOwnPropertyNames / freeze
1379        ("Object.fromEntries([['a',1],['b',2]]).b", "2"),
1380        ("Object.getOwnPropertyNames({x:1,y:2}).join(',')", "x,y"),
1381        ("var o=Object.freeze({a:1}); o.a", "1"),
1382        // Number 静的
1383        ("Number.isInteger(5)", "true"),
1384        ("Number.isInteger(5.5)", "false"),
1385        ("Number.isInteger('5')", "false"),
1386        ("Number.isSafeInteger(9007199254740991)", "true"),
1387        ("Number.MAX_SAFE_INTEGER", "9007199254740991"),
1388        ("Number.isNaN(NaN) + ',' + Number.isNaN(5)", "true,false"),
1389        // `Number.MIN_VALUE`/`Number.NaN`(ES1 以来の定数)が丸ごと未登録だった。
1390        // `MIN_VALUE` は「0に最も近い正の値」(非正規化数まで含む)であり
1391        // `MIN_SAFE_INTEGER`(大きな負の整数)とは別物であることに注意。
1392        ("Number.MIN_VALUE > 0", "true"),
1393        ("Number.MIN_VALUE < Number.EPSILON", "true"),
1394        ("Number.isNaN(Number.NaN)", "true"),
1395        // Math 追加
1396        ("Math.hypot(3,4)", "5"),
1397        ("Math.log2(8)", "3"),
1398        ("Math.round(Math.log10(1000))", "3"),
1399        ("Math.round(Math.cbrt(27))", "3"),
1400        ("Math.atan2(0,5)", "0"),
1401        // Math 追加2(ES2015。fround/clz32/imul/sinh系/expm1/log1p/定数群が丸ごと未対応だった)。
1402        ("Math.fround(1.5)", "1.5"),
1403        ("Math.fround(1.1) !== 1.1", "true"),
1404        ("Math.f16round(1.5)", "1.5"),
1405        ("Math.f16round(1.337) !== 1.337", "true"),
1406        ("Math.sumPrecise([1, 2, 3])", "6"),
1407        ("Math.sumPrecise([0.1, 0.2])", "0.30000000000000004"),
1408        ("Math.sumPrecise([1, Infinity])", "Infinity"),
1409        ("isNaN(Math.sumPrecise([Infinity, -Infinity]))", "true"),
1410        ("isNaN(Math.sumPrecise([1, NaN]))", "true"),
1411        ("new Headers({'Set-Cookie':'a=1'}).getSetCookie().join(',')", "a=1"),
1412        ("var h=new Headers(); h.append('Set-Cookie','a=1'); h.append('Set-Cookie','b=2'); h.getSetCookie().join('; ')", "a=1; b=2"),
1413        ("Math.clz32(1)", "31"),
1414        ("Math.clz32(0)", "32"),
1415        ("Math.clz32(1000)", "22"),
1416        ("Math.imul(3,4)", "12"),
1417        ("Math.imul(0xffffffff, 5)", "-5"),
1418        ("Math.round(Math.sinh(0))", "0"),
1419        ("Math.cosh(0)", "1"),
1420        ("Math.tanh(0)", "0"),
1421        ("Math.asinh(0)", "0"),
1422        ("Math.acosh(1)", "0"),
1423        ("Math.atanh(0)", "0"),
1424        ("Math.round(Math.expm1(0))", "0"),
1425        ("Math.log1p(0)", "0"),
1426        ("Math.round(Math.LN2*1000)", "693"),
1427        ("Math.round(Math.LN10*1000)", "2303"),
1428        ("Math.round(Math.SQRT2*1000)", "1414"),
1429        ("Math.round(Math.SQRT1_2*1000)", "707"),
1430        // AbortController
1431        ("var c=new AbortController(); var b=c.signal.aborted; c.abort(); b+','+c.signal.aborted", "false,true"),
1432        // `signal.addEventListener('abort', fn)` が丸ごと no-op で、`abort()` を呼んでも
1433        // 登録済みリスナが一切発火しないバグだった。`signal.reason` も未対応だった。
1434        (
1435            "var c=new AbortController(); var fired=false; c.signal.addEventListener('abort', function(){fired=true;}); c.abort(); fired",
1436            "true",
1437        ),
1438        ("var c=new AbortController(); c.abort(); c.signal.reason.name", "AbortError"),
1439        ("var c=new AbortController(); c.abort('custom'); c.signal.reason", "custom"),
1440        // 二重 abort() は2回目以降が黙殺される(リスナが2回発火しない)。
1441        (
1442            "var c=new AbortController(); var n=0; c.signal.addEventListener('abort', function(){n++;}); c.abort(); c.abort(); n",
1443            "1",
1444        ),
1445        // `AbortSignal.abort(reason)`(ES2022)が、`AbortController` 経由でしか
1446        // signal を得られず丸ごと未対応だった。
1447        ("AbortSignal.abort().aborted", "true"),
1448        ("AbortSignal.abort('why').reason", "why"),
1449        ("AbortSignal.abort().reason.name", "AbortError"),
1450        // `DOMException`(丸ごと未対応だった。`new DOMException(message, name)`
1451        // というユーザーコードからの直接構築、レガシー `.code` 数値定数)。
1452        ("new DOMException('boom', 'NotFoundError').message", "boom"),
1453        ("new DOMException('boom', 'NotFoundError').name", "NotFoundError"),
1454        ("new DOMException('boom', 'NotFoundError').code", "8"),
1455        ("DOMException.NOT_FOUND_ERR", "8"),
1456        ("new DOMException().name", "Error"),
1457        ("new DOMException('x', 'CustomError').code", "0"),
1458        // 既存の AbortError も同じ DOMException 経路で構築されるようになった
1459        // (`.code` が正しく `20`=`ABORT_ERR` になることを確認)。
1460        ("AbortSignal.abort().reason.code", "20"),
1461        // `signal.removeEventListener('abort', fn)` が `addEventListener` は対応済み
1462        // なのに対をなす削除側だけ無条件 no-op のままだったバグ。
1463        (
1464            "var c=new AbortController(); var n=0; var fn=function(){n++;}; c.signal.addEventListener('abort', fn); c.signal.removeEventListener('abort', fn); c.abort(); n",
1465            "0",
1466        ),
1467        (
1468            "var c=new AbortController(); var n=0; c.signal.addEventListener('abort', function(){n++;}); c.signal.removeEventListener('abort', function(){}); c.abort(); n",
1469            "1",
1470        ),
1471        // `AbortSignal.any(iterable)`(ES2024)— 複数の中断条件を1つの signal にまとめる。
1472        (
1473            "var c1=new AbortController(); var c2=new AbortController(); var s=AbortSignal.any([c1.signal, c2.signal]); var before=s.aborted; c2.abort('two'); before+','+s.aborted+','+s.reason",
1474            "false,true,two",
1475        ),
1476        (
1477            "var c1=new AbortController(); c1.abort('already'); AbortSignal.any([c1.signal]).reason",
1478            "already",
1479        ),
1480        // `signal.throwIfAborted()` が丸ごと未対応だった。中断前は no-op。
1481        ("var c=new AbortController(); c.signal.throwIfAborted()", "undefined"),
1482        // 中断後は `reason` を投げる。
1483        (
1484            "var c=new AbortController(); c.abort('boom'); \
1485             var caught; try { c.signal.throwIfAborted(); } catch (e) { caught = e; } caught",
1486            "boom",
1487        ),
1488        // `EventTarget` が丸ごと未対応だった(DOM 要素以外で独自のイベント発行/購読の
1489        // 基盤として使う定番パターン)。他の EventTarget 風オブジェクトと違い型ごとに
1490        // 複数リスナを保持する。
1491        (
1492            "var t=new EventTarget(); var log=''; t.addEventListener('x', ()=>log+='a'); t.addEventListener('x', ()=>log+='b'); t.dispatchEvent(new Event('x')); log",
1493            "ab",
1494        ),
1495        (
1496            "var t=new EventTarget(); var n=0; var fn=()=>n++; t.addEventListener('x', fn); t.addEventListener('x', fn); t.dispatchEvent(new Event('x')); n",
1497            "1",
1498        ),
1499        (
1500            "var t=new EventTarget(); var n=0; var fn=()=>n++; t.addEventListener('x', fn); t.removeEventListener('x', fn); t.dispatchEvent(new Event('x')); n",
1501            "0",
1502        ),
1503        // 同一関数を capture/bubble 両方で登録すると重複扱いされず2件とも
1504        // 保持される(仕様上 `(listener, capture)` の組で区別すべきところ、
1505        // 以前は `capture` を見ずに `cb` だけで重複判定していたため2回目の
1506        // 登録が黙って無視されるバグだった。`document`のcapture対応
1507        // 〔2026-07-16〕で顕在化・同日発見・修正)。
1508        (
1509            "var t=new EventTarget(); var n=0; var fn=()=>n++; t.addEventListener('x', fn, true); t.addEventListener('x', fn, false); t.dispatchEvent(new Event('x')); n",
1510            "2",
1511        ),
1512        // `removeEventListener(type, fn)`(capture省略=既定false)はcapture
1513        // 登録済みの同一関数を巻き込んで消してはいけない。
1514        (
1515            "var t=new EventTarget(); var n=0; var fn=()=>n++; t.addEventListener('x', fn, true); t.addEventListener('x', fn, false); t.removeEventListener('x', fn); t.dispatchEvent(new Event('x')); n",
1516            "1",
1517        ),
1518        // `addEventListener(type, fn, {signal})`(丸ごと未対応だった。
1519        // `AbortController` でリスナを一括解除する定番パターン。2026-07-14
1520        // 発見・実装)。abort 後は発火しなくなる。
1521        (
1522            "var t=new EventTarget(); var c=new AbortController(); var n=0; \
1523             t.addEventListener('x', ()=>n++, {signal: c.signal}); \
1524             t.dispatchEvent(new Event('x')); c.abort(); t.dispatchEvent(new Event('x')); n",
1525            "1",
1526        ),
1527        // 登録前に既に abort 済みの signal を渡した場合は登録自体が行われない。
1528        (
1529            "var t=new EventTarget(); var c=new AbortController(); c.abort(); var n=0; \
1530             t.addEventListener('x', ()=>n++, {signal: c.signal}); \
1531             t.dispatchEvent(new Event('x')); n",
1532            "0",
1533        ),
1534        // DOM 要素側(`Element.prototype.addEventListener`)でも同じく対応。
1535        (
1536            "var el=document.createElement('div'); var c=new AbortController(); var n=0; \
1537             el.addEventListener('click', ()=>n++, {signal: c.signal}); \
1538             el.click(); c.abort(); el.click(); n",
1539            "1",
1540        ),
1541        // `event.preventDefault()` が丸ごと no-op で `defaultPrevented` を一切
1542        // 追跡せず、`dispatchEvent()` の戻り値が常に `true` になるバグだった。
1543        (
1544            "var t=new EventTarget(); t.addEventListener('x', e => e.preventDefault()); t.dispatchEvent(new Event('x', {cancelable:true}))",
1545            "false",
1546        ),
1547        (
1548            "var t=new EventTarget(); t.addEventListener('x', e => e.preventDefault()); t.dispatchEvent(new Event('x'))",
1549            "true",
1550        ),
1551        // `event.isTrusted` が丸ごと未対応だった。スクリプトから発火した
1552        // イベントは仕様上常に `false`。
1553        (
1554            "var t=new EventTarget(); var r; t.addEventListener('x', e => { r = e.isTrusted; }); t.dispatchEvent(new Event('x')); r",
1555            "false",
1556        ),
1557        // `event.composed`(丸ごと未対応だった。`Event`/`CustomEvent`両方の
1558        // コンストラクタで`options.composed`が一切読まれず常に`undefined`に
1559        // なっていた。2026-07-16 発見・実装)。
1560        ("new Event('x', {composed: true}).composed", "true"),
1561        ("new Event('x').composed", "false"),
1562        (
1563            "new CustomEvent('x', {detail: 1, composed: true}).composed",
1564            "true",
1565        ),
1566        // `EventTarget` のイベントに `stopImmediatePropagation` が丸ごと
1567        // 未対応で、呼び出すと TypeError になっていた(そもそも呼び出しようが
1568        // ないため残りのリスナーを止める効果も一切なかった)。
1569        (
1570            "var t=new EventTarget(); var log=''; \
1571             t.addEventListener('x', e => { log+='a'; e.stopImmediatePropagation(); }); \
1572             t.addEventListener('x', () => { log+='b'; }); \
1573             t.dispatchEvent(new Event('x')); log",
1574            "a",
1575        ),
1576        // `EventTarget.addEventListener` の第3引数 `{once:true}` が丸ごと
1577        // 読まれておらず、一度発火しても解除されず何度でも呼ばれ続けていた。
1578        (
1579            "var t=new EventTarget(); var n=0; t.addEventListener('x', ()=>n++, {once:true}); \
1580             t.dispatchEvent(new Event('x')); t.dispatchEvent(new Event('x')); n",
1581            "1",
1582        ),
1583        // once 指定でも removeEventListener で明示的に解除できる。
1584        (
1585            "var t=new EventTarget(); var n=0; var fn=()=>n++; t.addEventListener('x', fn, {once:true}); \
1586             t.removeEventListener('x', fn); t.dispatchEvent(new Event('x')); n",
1587            "0",
1588        ),
1589        // `EventTarget` 発の `Event`/`CustomEvent` に `composedPath()` が丸ごと
1590        // 未対応で、呼び出すと TypeError になっていた(DOM 要素側は既に対応済み)。
1591        (
1592            "var t=new EventTarget(); var p; t.addEventListener('x', e => { p = e.composedPath(); }); \
1593             t.dispatchEvent(new Event('x')); p.length === 1 && p[0] === t",
1594            "true",
1595        ),
1596        // 発火前(target 未設定)は composedPath() が空配列を返す。
1597        ("new CustomEvent('x').composedPath().length", "0"),
1598        // `KeyboardEvent`/`MouseEvent`(丸ごと未対応だった。`el.dispatchEvent
1599        // (new KeyboardEvent('keydown', {key:'a'}))` というシミュレーション
1600        // イベント構築の定番パターン)。
1601        ("new KeyboardEvent('keydown', {key:'a', code:'KeyA'}).key", "a"),
1602        ("new KeyboardEvent('keydown', {key:'a', code:'KeyA'}).code", "KeyA"),
1603        (
1604            "var t=new EventTarget(); var got; \
1605             t.addEventListener('keydown', e => { got = e.ctrlKey + ':' + e.key; }); \
1606             t.dispatchEvent(new KeyboardEvent('keydown', {key:'Enter', ctrlKey:true})); got",
1607            "true:Enter",
1608        ),
1609        ("new MouseEvent('click', {clientX:10, clientY:20}).clientX", "10"),
1610        (
1611            "var t=new EventTarget(); var got; \
1612             t.addEventListener('click', e => { got = e.clientY + ':' + e.button; }); \
1613             t.dispatchEvent(new MouseEvent('click', {clientY:5, button:2})); got",
1614            "5:2",
1615        ),
1616        ("new MouseEvent('click').bubbles", "false"),
1617        ("new MouseEvent('click', {bubbles:true}).bubbles", "true"),
1618        // `WheelEvent`/`FocusEvent`(丸ごと未対応だった。`MouseEvent` を継承
1619        // する `WheelEvent` は座標系プロパティも合わせて持つべき仕様)。
1620        ("new WheelEvent('wheel', {deltaY:100}).deltaY", "100"),
1621        ("new WheelEvent('wheel', {deltaY:1, clientX:5}).clientX", "5"),
1622        ("new WheelEvent('wheel').deltaMode", "0"),
1623        ("new FocusEvent('focusout', {relatedTarget: null}).relatedTarget === null", "true"),
1624        (
1625            "var t=new EventTarget(); var r=new EventTarget(); var got; \
1626             t.addEventListener('focusout', e => { got = e.relatedTarget; }); \
1627             t.dispatchEvent(new FocusEvent('focusout', {relatedTarget: r})); got === r",
1628            "true",
1629        ),
1630        // `Touch`/`TouchEvent`(丸ごと未対応だった。この処理系にはタッチ
1631        // スクリーン入力機構自体が無いが、コンストラクタでのシミュレーション
1632        // パターンには対応する。2026-07-15 発見・実装)。
1633        ("new Touch({identifier:1, clientX:10, clientY:20}).clientX", "10"),
1634        ("new Touch({}).force", "1"),
1635        (
1636            "var t=new Touch({identifier:1}); \
1637             new TouchEvent('touchstart', {touches:[t], changedTouches:[t]}).touches[0] === t",
1638            "true",
1639        ),
1640        // `CommandEvent`/`ToggleEvent`/`FormDataEvent`(WHATWG HTML Standard / Invoker Commands API)
1641        ("new CommandEvent('command', {command:'show-modal'}).command", "show-modal"),
1642        ("new CommandEvent('command', {source:null}).source === null", "true"),
1643        ("new ToggleEvent('toggle', {oldState:'closed', newState:'open'}).newState", "open"),
1644        ("new ToggleEvent('toggle').oldState", "closed"),
1645        ("new FormDataEvent('formdata', {formData:null}).formData === null", "true"),
1646
1647        ("new TouchEvent('touchend').changedTouches.length", "0"),
1648        // `HashChangeEvent`/`PageTransitionEvent`(丸ごと未対応だった。
1649        // 2026-07-15 発見・実装)。
1650        (
1651            "new HashChangeEvent('hashchange', {oldURL:'a', newURL:'b'}).oldURL + ',' + \
1652             new HashChangeEvent('hashchange', {oldURL:'a', newURL:'b'}).newURL",
1653            "a,b",
1654        ),
1655        (
1656            "new HashChangeEvent('hashchange').type",
1657            "hashchange",
1658        ),
1659        (
1660            "new PageTransitionEvent('pageshow', {persisted:true}).persisted",
1661            "true",
1662        ),
1663        // `PopStateEvent`(丸ごと未対応だった。実ナビゲーション経由の発火は
1664        // 既に`state`付きで動いていたが、手動シミュレーション用の専用
1665        // コンストラクタが無かった。2026-07-17 発見・実装)。
1666        (
1667            "new PopStateEvent('popstate', {state:{a:1}}).state.a",
1668            "1",
1669        ),
1670        ("new PopStateEvent('popstate').state", "null"),
1671        (
1672            "var got=null; window.addEventListener('popstate', e => got = e.state); \
1673             window.dispatchEvent(new PopStateEvent('popstate', {state:'s'})); got",
1674            "s",
1675        ),
1676        // `DOMRect`/`DOMRectReadOnly`コンストラクタ(丸ごと未対応だった。
1677        // `getBoundingClientRect()`と同じ形状で手動構築する定番パターン。
1678        // 2026-07-17 発見・実装)。
1679        (
1680            "var r=new DOMRect(10,20,30,40); \
1681             [r.x,r.y,r.width,r.height,r.left,r.top,r.right,r.bottom].join(',')",
1682            "10,20,30,40,10,20,40,60",
1683        ),
1684        ("new DOMRect().width", "0"),
1685        ("new DOMRectReadOnly(1,2,3,4).right", "4"),
1686        ("JSON.stringify(new DOMRect(1,2,3,4).toJSON()).includes('\"width\":3')", "true"),
1687        // `DOMPoint`/`DOMPointReadOnly`コンストラクタおよび DOMMatrix/DOMQuad (Geometry Interfaces)
1688        (
1689            "var p=new DOMPoint(1,2); p.x+','+p.y+','+p.z+','+p.w",
1690            "1,2,0,1",
1691        ),
1692        ("new DOMPoint(1,2,3,4).w", "4"),
1693        ("new DOMPointReadOnly().x", "0"),
1694        ("JSON.stringify(new DOMPoint(1,2).toJSON()).includes('\"y\":2')", "true"),
1695        ("new DOMPoint(10, 20).matrixTransform(new DOMMatrix().translate(5, 5)).x", "15"),
1696        ("new DOMMatrix().isIdentity", "true"),
1697        ("new DOMMatrix().is2D", "true"),
1698        ("new DOMMatrix([1,0,0,1,10,20]).toString()", "matrix(1, 0, 0, 1, 10, 20)"),
1699        ("new DOMMatrix().translate(10, 20).e", "10"),
1700        ("new DOMMatrix().scale(2, 3).d", "3"),
1701        ("DOMMatrix.fromFloat32Array(new Float32Array([1,0,0,1,10,20])).e", "10"),
1702        ("new DOMMatrix().rotate(90).b", "1"),
1703        ("new DOMMatrix([1,0,0,1,10,20]).invert().e", "-10"),
1704        ("new DOMMatrix().translateSelf(10, 20).e", "10"),
1705        ("new DOMMatrix().rotateSelf(90).b", "1"),
1706        ("new DOMMatrix().scaleSelf(2, 3).d", "3"),
1707        ("new DOMMatrix([1,0,0,1,10,20]).invertSelf().e", "-10"),
1708        ("DOMQuad.fromRect(new DOMRect(10, 20, 30, 40)).getBounds().width", "30"),
1709        ("DOMRect.fromRect({x: 5, y: 10, width: 15, height: 20}).width", "15"),
1710        ("DOMPoint.fromPoint({x: 3, y: 4, z: 5, w: 1}).z", "5"),
1711        ("'가'.normalize('NFD').normalize('NFC')", "가"),
1712
1713        // `window.dispatchEvent(event)`(丸ごと未対応だった。前サイクルで
1714        // 発見した既知ギャップを本サイクルで解消。2026-07-15 発見・実装)。
1715        (
1716            "var got=null; window.addEventListener('hashchange', e => got = e.newURL); \
1717             window.dispatchEvent(new HashChangeEvent('hashchange', {newURL:'x'})); got",
1718            "x",
1719        ),
1720        (
1721            "window.dispatchEvent(new Event('unknown-type'))",
1722            "true",
1723        ),
1724        // `window.dispatchEvent()` は `addEventListener` 登録分だけでなく
1725        // `window.on<type>`(IDL属性スタイル)も合わせて呼び出す(丸ごと
1726        // 未対応だった。2026-07-15 発見・実装)。
1727        (
1728            "var n=0; window.onhashchange = () => n++; \
1729             window.dispatchEvent(new HashChangeEvent('hashchange')); n",
1730            "1",
1731        ),
1732        (
1733            "var order=[]; window.onresize = () => order.push('on'); \
1734             window.addEventListener('resize', () => order.push('listener')); \
1735             window.dispatchEvent(new Event('resize')); order.join(',')",
1736            "on,listener",
1737        ),
1738        // `window.removeEventListener()`(丸ごと未対応だった。以前は無害な
1739        // no-op のままで、`AbortSignal` 経由以外ではリスナが二度と解除
1740        // できなかった。2026-07-15 発見・実装)。
1741        (
1742            "var n=0; var cb = () => n++; window.addEventListener('resize', cb); \
1743             window.removeEventListener('resize', cb); \
1744             window.dispatchEvent(new Event('resize')); n",
1745            "0",
1746        ),
1747        (
1748            "var log=[]; var cb1 = () => log.push('1'); var cb2 = () => log.push('2'); \
1749             window.addEventListener('resize', cb1); window.addEventListener('resize', cb2); \
1750             window.removeEventListener('resize', cb1); \
1751             window.dispatchEvent(new Event('resize')); log.join(',')",
1752            "2",
1753        ),
1754        // `document.removeEventListener()`(丸ごと未対応だった。`addEventListener`
1755        // は `window` 版を再利用していたが、対になる `removeEventListener`
1756        // 自体が未登録で「関数ではない」`TypeError` になっていた。2026-07-15
1757        // 発見・実装)。
1758        (
1759            "typeof document.removeEventListener",
1760            "function",
1761        ),
1762        (
1763            "var n=0; var cb = () => n++; document.addEventListener('hashchange', cb); \
1764             document.removeEventListener('hashchange', cb); \
1765             window.dispatchEvent(new Event('hashchange')); n",
1766            "0",
1767        ),
1768        // `document.dispatchEvent`(丸ごと未対応だった。`addEventListener`/
1769        // `removeEventListener`と同じ兄弟ギャップ。2026-07-15 発見・実装)。
1770        (
1771            "var got=null; document.addEventListener('hashchange', e => got = e.newURL); \
1772             document.dispatchEvent(new HashChangeEvent('hashchange', {newURL:'y'})); got",
1773            "y",
1774        ),
1775        // `window.dispatchEvent()`/`document.dispatchEvent()` の戻り値が仕様
1776        // どおり `!defaultPrevented` になること(以前は無条件 `true` を返し、
1777        // リスナが `preventDefault()` を呼んでも常に `true` だった。要素用
1778        // `dom_dispatch_event`/`event_target_dispatch_event` は正しく実装済みで
1779        // window/document 経路にだけ同型バグが残っていた。2026-07-16 発見・修正)。
1780        (
1781            "window.addEventListener('resize', e => e.preventDefault()); \
1782             window.dispatchEvent(new Event('resize', {cancelable:true}))",
1783            "false",
1784        ),
1785        (
1786            "document.addEventListener('scroll', e => e.preventDefault()); \
1787             document.dispatchEvent(new Event('scroll', {cancelable:true}))",
1788            "false",
1789        ),
1790        // `preventDefault()` を呼ばなければ従来どおり `true` を返す。
1791        (
1792            "window.addEventListener('popstate', () => {}); \
1793             window.dispatchEvent(new Event('popstate'))",
1794            "true",
1795        ),
1796        // dispatch 中に `event.target` が dispatch 対象へ設定されること(以前は
1797        // window/document 経路では一切設定されず `undefined` だった。2026-07-16
1798        // 発見・修正)。
1799        (
1800            "var t='none'; window.addEventListener('message', e => t = (e.target === window)); \
1801             window.dispatchEvent(new Event('message')); t",
1802            "true",
1803        ),
1804        // `window.dispatchEvent(new ErrorEvent(...))` 経由でも `window.onerror`
1805        // がレガシー5引数シグネチャ `(message, source, lineno, colno, error)` で
1806        // 呼ばれること(以前は Event オブジェクトを1引数でそのまま渡していて
1807        // `reportError()`〔既に正しい5引数呼び出し〕と経路によって呼び出し形が
1808        // 食い違っていた。2026-07-16 発見・修正)。
1809        (
1810            "var got=''; window.onerror = (msg, src, line, col) => { got = msg+'|'+src+'|'+line+'|'+col; }; \
1811             window.dispatchEvent(new ErrorEvent('error', {message:'boom', filename:'a.js', lineno:3, colno:7})); got",
1812            "boom|a.js|3|7",
1813        ),
1814        // `online`/`offline`(丸ごと未対応だった。`addEventListener('online',
1815        // ...)` が登録経路自体無く黙って何もしなかった。2026-07-16 発見・実装)。
1816        (
1817            "var n=0; window.addEventListener('online', () => n++); \
1818             window.dispatchEvent(new Event('online')); n",
1819            "1",
1820        ),
1821        (
1822            "var n=0; var cb = () => n++; window.addEventListener('offline', cb); \
1823             window.removeEventListener('offline', cb); \
1824             window.dispatchEvent(new Event('offline')); n",
1825            "0",
1826        ),
1827        // `beforeunload`/`unload`/`pageshow`/`pagehide`/`visibilitychange`/
1828        // `languagechange`(丸ごと未対応だった。2026-07-16 発見・実装)。
1829        (
1830            "var log=[]; \
1831             window.addEventListener('beforeunload', () => log.push('bu')); \
1832             window.addEventListener('unload', () => log.push('u')); \
1833             window.addEventListener('pageshow', () => log.push('ps')); \
1834             window.addEventListener('pagehide', () => log.push('ph')); \
1835             document.addEventListener('visibilitychange', () => log.push('vc')); \
1836             window.addEventListener('languagechange', () => log.push('lc')); \
1837             window.dispatchEvent(new Event('beforeunload')); \
1838             window.dispatchEvent(new Event('unload')); \
1839             window.dispatchEvent(new Event('pageshow')); \
1840             window.dispatchEvent(new Event('pagehide')); \
1841             document.dispatchEvent(new Event('visibilitychange')); \
1842             window.dispatchEvent(new Event('languagechange')); \
1843             log.join(',')",
1844            "bu,u,ps,ph,vc,lc",
1845        ),
1846        // `document.visibilityState`/`.hidden`(丸ごと未対応だった。2026-07-16
1847        // 発見・実装)。
1848        ("document.visibilityState", "visible"),
1849        ("document.hidden", "false"),
1850        // `document.characterSet`/`.compatMode`/`.contentType`/`.doctype`
1851        // (丸ごと未対応だった。2026-07-16 発見・実装)。
1852        ("document.characterSet", "UTF-8"),
1853        ("document.compatMode", "CSS1Compat"),
1854        ("document.contentType", "text/html"),
1855        ("document.doctype", "null"),
1856        // `document.currentScript`(丸ごと未対応だった。2026-07-16 発見・
1857        // 実装。実行中スクリプト要素の追跡機構が無いため常に `null` を返す
1858        // 誠実な簡略実装)。
1859        ("document.currentScript", "null"),
1860        // `document.implementation`(`DOMImplementation`。丸ごと未対応
1861        // だった。2026-07-16 発見・実装。`hasFeature()`は仕様どおり常に
1862        // `true`を返すレガシー no-op、`createDocumentType()`は独立した
1863        // 読み取り専用簡略オブジェクトを返す)。
1864        ("document.implementation.hasFeature('x','1.0')", "true"),
1865        (
1866            "var dt=document.implementation.createDocumentType('html',' ',''); dt.name+','+dt.nodeType",
1867            "html,10",
1868        ),
1869        (
1870            "var doc=document.implementation.createHTMLDocument('MyDoc'); doc.title+','+doc.nodeType",
1871            "MyDoc,9",
1872        ),
1873        (
1874            "var doc=document.implementation.createDocument('http://www.w3.org/1999/xhtml','html'); doc.qualifiedName+','+doc.nodeType",
1875            "html,9",
1876        ),
1877        // `storage` イベント(丸ごと未対応だった。`StorageEvent` コンストラクタ
1878        // 自体は既に対応済みだったが登録経路が無かった。2026-07-16 発見・実装)。
1879        (
1880            "var got=null; window.addEventListener('storage', e => got = e.key); \
1881             window.dispatchEvent(new StorageEvent('storage', {key:'k'})); got",
1882            "k",
1883        ),
1884        // `document.addEventListener('readystatechange', ...)`(丸ごと未対応
1885        // だった。`document.readyState` は常に `"complete"` のため
1886        // `load`/`DOMContentLoaded` と同じ即時発火。2026-07-16 発見・実装)。
1887        (
1888            "var n=0; document.addEventListener('readystatechange', () => n++); n",
1889            "1",
1890        ),
1891        // `window.print()`/`beforeprint`/`afterprint`(丸ごと未対応だった。
1892        // 2026-07-16 発見・実装)。
1893        ("window.print(); true", "true"),
1894        (
1895            "var log=[]; \
1896             window.addEventListener('beforeprint', () => log.push('bp')); \
1897             window.addEventListener('afterprint', () => log.push('ap')); \
1898             window.dispatchEvent(new Event('beforeprint')); \
1899             window.dispatchEvent(new Event('afterprint')); \
1900             log.join(',')",
1901            "bp,ap",
1902        ),
1903        // `PointerEvent`/`InputEvent`(丸ごと未対応だった。`PointerEvent` は
1904        // `MouseEvent` を継承するため座標系プロパティも合わせて持つべき仕様)。
1905        ("new PointerEvent('pointerdown', {pointerId:7, pointerType:'touch'}).pointerId", "7"),
1906        ("new PointerEvent('pointerdown', {pointerType:'touch', clientX:9}).clientX", "9"),
1907        ("new PointerEvent('pointerdown').isPrimary", "false"),
1908        ("new PointerEvent('pointerdown', {isPrimary:true}).width", "1"),
1909        ("new InputEvent('input', {data:'a', inputType:'insertText'}).data", "a"),
1910        ("new InputEvent('input', {data:'a', inputType:'insertText'}).inputType", "insertText"),
1911        ("new InputEvent('beforeinput').isComposing", "false"),
1912        // `setPointerCapture`/`releasePointerCapture`/`hasPointerCapture`
1913        // (Pointer Events。丸ごと未対応だった)。
1914        (
1915            "var e=document.createElement('div'); e.hasPointerCapture(1)",
1916            "false",
1917        ),
1918        (
1919            "var e=document.createElement('div'); e.setPointerCapture(1); e.hasPointerCapture(1)",
1920            "true",
1921        ),
1922        (
1923            "var e=document.createElement('div'); e.setPointerCapture(1); \
1924             e.releasePointerCapture(1); e.hasPointerCapture(1)",
1925            "false",
1926        ),
1927        // 別 pointerId には影響しない。
1928        (
1929            "var e=document.createElement('div'); e.setPointerCapture(1); e.hasPointerCapture(2)",
1930            "false",
1931        ),
1932        // `ProgressEvent`/`StorageEvent`(丸ごと未対応だった)。
1933        ("new ProgressEvent('progress', {loaded:50, total:100}).loaded", "50"),
1934        ("new ProgressEvent('progress', {lengthComputable:true}).lengthComputable", "true"),
1935        ("new ProgressEvent('progress').total", "0"),
1936        ("new StorageEvent('storage', {key:'k', newValue:'v'}).key", "k"),
1937        ("new StorageEvent('storage', {key:'k', newValue:'v'}).newValue", "v"),
1938        ("new StorageEvent('storage').oldValue === null", "true"),
1939        // `CompositionEvent`/`ClipboardEvent`(丸ごと未対応だった)。
1940        ("new CompositionEvent('compositionupdate', {data:'あ'}).data", "あ"),
1941        ("new CompositionEvent('compositionstart').data", ""),
1942        (
1943            "var t=new EventTarget(); var got; \
1944             t.addEventListener('paste', e => { got = e.clipboardData; }); \
1945             t.dispatchEvent(new ClipboardEvent('paste', {clipboardData:'X'})); got",
1946            "X",
1947        ),
1948        ("new ClipboardEvent('copy').clipboardData === null", "true"),
1949        // `AnimationEvent`/`TransitionEvent`/`DragEvent`/`SubmitEvent`(丸ごと
1950        // 未対応だった。`DragEvent` は `MouseEvent` を継承するため座標系
1951        // プロパティも合わせて持つべき仕様)。
1952        ("new AnimationEvent('animationend', {animationName:'spin'}).animationName", "spin"),
1953        ("new AnimationEvent('animationend').elapsedTime", "0"),
1954        ("new TransitionEvent('transitionend', {propertyName:'opacity'}).propertyName", "opacity"),
1955        ("new DragEvent('drop', {dataTransfer:'X'}).dataTransfer", "X"),
1956        ("new DragEvent('drop', {clientX:3}).clientX", "3"),
1957        ("new SubmitEvent('submit', {submitter:'BTN'}).submitter", "BTN"),
1958        ("new SubmitEvent('submit').submitter === null", "true"),
1959        // `MessageEvent`/`ErrorEvent`(丸ごと未対応だった。`window.
1960        // postMessage()`/`reportError()` の内部イベント形と同じ形をユーザー
1961        // コードから直接構築できる)。
1962        ("new MessageEvent('message', {data:'hi', origin:'https://a.com'}).data", "hi"),
1963        ("new MessageEvent('message', {data:'hi', origin:'https://a.com'}).origin", "https://a.com"),
1964        ("new MessageEvent('message').ports.length", "0"),
1965        ("new ErrorEvent('error', {message:'boom', lineno:5}).message", "boom"),
1966        ("new ErrorEvent('error', {message:'boom', lineno:5}).lineno", "5"),
1967        ("new ErrorEvent('error').error === null", "true"),
1968        // `EventTarget` 発のイベントで `currentTarget`/`eventPhase` が丸ごと
1969        // 未設定で常に undefined だった(DOM 要素側は既に対応済み)。
1970        (
1971            "var t=new EventTarget(); var ct, ph; \
1972             t.addEventListener('x', e => { ct = e.currentTarget; ph = e.eventPhase; }); \
1973             t.dispatchEvent(new Event('x')); ct === t && ph === 2",
1974            "true",
1975        ),
1976        // 発火完了後は currentTarget が null、eventPhase が 0 に戻る。
1977        (
1978            "var t=new EventTarget(); var e=new Event('x'); t.dispatchEvent(e); \
1979             e.currentTarget === null && e.eventPhase",
1980            "0",
1981        ),
1982        // `Event.timeStamp` がこのエンジン全体に丸ごと未対応で常に undefined
1983        // だった(DOM要素・EventTarget どちらの経路でも欠落していた)。
1984        ("typeof new Event('x').timeStamp", "number"),
1985        (
1986            "var b=document.createElement('button'); var ts; \
1987             b.addEventListener('click', e => { ts = e.timeStamp; }); \
1988             b.dispatchEvent({type:'click'}); typeof ts",
1989            "number",
1990        ),
1991        // レガシー DOM Level 0 の `returnValue`/`cancelBubble`/`srcElement` が
1992        // 丸ごと未対応だった。`e.returnValue = false` は `preventDefault()` と
1993        // 等価であるべき。
1994        (
1995            "var t=new EventTarget(); var r; \
1996             t.addEventListener('x', e => { e.returnValue = false; }); \
1997             r = t.dispatchEvent(new Event('x', {cancelable:true})); r",
1998            "false",
1999        ),
2000        // `e.cancelBubble = true` は `stopPropagation()` と等価であるべき。
2001        (
2002            "var b=document.createElement('button'); var p=document.createElement('div'); \
2003             p.appendChild(b); var s=''; \
2004             p.addEventListener('click', ()=>s+='P'); \
2005             b.addEventListener('click', e => { s+='B'; e.cancelBubble = true; }); \
2006             b.dispatchEvent({type:'click', bubbles:true}); s",
2007            "B",
2008        ),
2009        // `srcElement` は `target` と同じ値の別名。
2010        (
2011            "var t=new EventTarget(); var se; \
2012             t.addEventListener('x', e => { se = e.srcElement; }); \
2013             t.dispatchEvent(new Event('x')); se === t",
2014            "true",
2015        ),
2016        // `Event.NONE`/`.CAPTURING_PHASE`/`.AT_TARGET`/`.BUBBLING_PHASE`
2017        // (`Node.ELEMENT_NODE`と同型の標準定数)が丸ごと未対応だった。
2018        (
2019            "Event.NONE + ',' + Event.CAPTURING_PHASE + ',' + Event.AT_TARGET + ',' + Event.BUBBLING_PHASE",
2020            "0,1,2,3",
2021        ),
2022        (
2023            "var t=new EventTarget(); var ph; \
2024             t.addEventListener('x', e => { ph = e.eventPhase === Event.AT_TARGET; }); \
2025             t.dispatchEvent(new Event('x')); ph",
2026            "true",
2027        ),
2028        // `form.requestSubmit()`/`form.submit()`(HTML5)が丸ごと未対応だった。
2029        // `requestSubmit()` は `submit` イベントを発火する。
2030        (
2031            "var f=document.createElement('form'); var n=0; \
2032             f.addEventListener('submit', ()=>n++); f.requestSubmit(); n",
2033            "1",
2034        ),
2035        // `preventDefault()` で中止可能。
2036        (
2037            "var f=document.createElement('form'); \
2038             f.addEventListener('submit', e=>e.preventDefault()); \
2039             typeof f.requestSubmit()",
2040            "undefined",
2041        ),
2042        // `submit()` は無害な no-op(実際のフォーム送信/ページ遷移パイプラインが
2043        // 無いため)で、少なくとも例外を投げない。
2044        ("var f=document.createElement('form'); f.submit(); true", "true"),
2045        // `input.showPicker()`(丸ごと未対応だった。ネイティブピッカー UI 自体が
2046        // 無いため `submit()` と同じ無害な no-op。2026-07-15 発見・実装)。
2047        (
2048            "var i=document.createElement('input'); i.type='date'; i.showPicker(); true",
2049            "true",
2050        ),
2051        // `input.stepUp(n?)`/`.stepDown(n?)`(HTML5)が丸ごと未対応だった。
2052        (
2053            "var i=document.createElement('input'); i.type='number'; i.value='5'; \
2054             i.stepUp(); i.value",
2055            "6",
2056        ),
2057        ("var i=document.createElement('input'); i.type='number'; i.value='5'; i.stepDown(3); i.value", "2"),
2058        // `step` 属性を刻み幅として使う。
2059        (
2060            "var i=document.createElement('input'); i.type='number'; i.value='0'; i.step='0.5'; \
2061             i.stepUp(); i.value",
2062            "0.5",
2063        ),
2064        // `max` があればクランプする。
2065        (
2066            "var i=document.createElement('input'); i.type='number'; i.value='9'; i.max='10'; \
2067             i.stepUp(5); i.value",
2068            "10",
2069        ),
2070        // String / Array 静的・console・globalThis
2071        ("String.fromCharCode(72,105)", "Hi"),
2072        ("String.fromCodePoint(65)", "A"),
2073        ("Array.of(1,2,3).join(',')", "1,2,3"),
2074        // Array イテレータ系 + reduceRight
2075        ("[...[10,20].keys()].join(',')", "0,1"),
2076        ("[...[10,20].values()].join(',')", "10,20"),
2077        // entries() は本物の Array Iterator(.map 等の Array メソッドは持たない。仕様どおり)
2078        // を返すため、配列メソッドを使うにはまず spread/Array.from で配列化する必要がある。
2079        ("[...[5,6].entries()].map(e => e[0]+':'+e[1]).join(',')", "0:5,1:6"),
2080        ("[1,2,3].reduceRight((a,b) => a+'-'+b)", "3-2-1"),
2081        ("[1,2,3,4].reduceRight((a,b) => a+b, 0)", "10"),
2082        // document.title 読み書き
2083        ("document.title = 'AtmOS'; document.title", "AtmOS"),
2084        ("document.readyState", "complete"),
2085        // `document.compatMode`/`.characterSet`/`.charset`/`.contentType`/
2086        // `.scrollingElement`(HTML5。丸ごと未対応だった)。
2087        ("document.compatMode", "CSS1Compat"),
2088        ("document.characterSet", "UTF-8"),
2089        ("document.charset", "UTF-8"),
2090        ("document.contentType", "text/html"),
2091        ("document.scrollingElement === document.documentElement", "true"),
2092        // `document.children`/`.firstElementChild`/`.lastElementChild`/
2093        // `.childElementCount`(`ParentNode`ミックスインの`Document`側配線
2094        // 漏れ。丸ごと未対応だった。2026-07-17 発見・実装。この配列は
2095        // bare `JsRuntime::new()` で `<html>` 自体が未構築のため、実際の
2096        // 検証は `dom_cases`(HTML フィクスチャ付き)側で行う)。
2097        ("document.firstElementChild === document.documentElement", "true"),
2098        ("document.lastElementChild === document.documentElement", "true"),
2099        ("document.children.length", "0"),
2100        ("document.childElementCount", "0"),
2101        // `document.referrer`/`.lastModified`/`.domain`(HTML5。丸ごと未対応
2102        // だった)。
2103        ("document.referrer", ""),
2104        (
2105            "/^\\d{2}\\/\\d{2}\\/\\d{4} \\d{2}:\\d{2}:\\d{2}$/.test(document.lastModified)",
2106            "true",
2107        ),
2108        ("typeof document.domain", "string"),
2109        // `navigator.geolocation`(丸ごと未対応だった。実ハードウェアが無いため
2110        // 常に `PERMISSION_DENIED` で `error` コールバックを同期的に呼ぶ簡略実装)。
2111        ("typeof navigator.geolocation.getCurrentPosition", "function"),
2112        (
2113            "var code; navigator.geolocation.getCurrentPosition(() => {}, e => { code = e.code; }); \
2114             code === 1",
2115            "true",
2116        ),
2117        (
2118            "typeof navigator.geolocation.watchPosition(() => {}, () => {})",
2119            "number",
2120        ),
2121        ("typeof navigator.geolocation.clearWatch(1)", "undefined"),
2122        // `navigator.wakeLock.request(type)`(Screen Wake Lock API。丸ごと
2123        // 未対応だった。実際の電源管理機構は無いため状態のみ追跡する簡略実装)。
2124        (
2125            "(await navigator.wakeLock.request('screen')).released",
2126            "false",
2127        ),
2128        ("(await navigator.wakeLock.request('screen')).type", "screen"),
2129        (
2130            "var s = await navigator.wakeLock.request('screen'); await s.release(); s.released",
2131            "true",
2132        ),
2133        // `navigator.share(data)`/`navigator.canShare(data)`(Web Share API。
2134        // 丸ごと未対応だった。実際の共有先 UI が無いため `canShare` は常に
2135        // `false`、`share` は仕様どおり `AbortError` で拒否する)。
2136        ("navigator.canShare({title:'x'})", "false"),
2137        (
2138            "try { await navigator.share({title:'x'}); 'resolved' } \
2139             catch(e) { e.name }",
2140            "AbortError",
2141        ),
2142        // `new EyeDropper().open()`(丸ごと未対応だった。実際のピッカー UI が
2143        // 無いため、ユーザーがキャンセルした場合と同じ `AbortError` で拒否)。
2144        (
2145            "try { await new EyeDropper().open(); 'resolved' } \
2146             catch(e) { e.name }",
2147            "AbortError",
2148        ),
2149        // `navigator.vibrate(pattern)`(Vibration API。丸ごと未対応だった。
2150        // 戻り値はリクエスト受理の可否のみを表しハードウェアの有無とは
2151        // 無関係なため、常に `true`)。
2152        ("navigator.vibrate(200)", "true"),
2153        ("navigator.vibrate([100,50,100])", "true"),
2154        // `navigator.getBattery()`(Battery Status API。丸ごと未対応だった。
2155        // バッテリー非搭載・常時 AC 電源動作というこの OS のターゲット
2156        // 実態に即した「常時満充電で給電中」を返す簡略実装)。
2157        ("(await navigator.getBattery()).charging", "true"),
2158        ("(await navigator.getBattery()).level", "1"),
2159        ("(await navigator.getBattery()).chargingTime", "0"),
2160        // `navigator.connection`(Network Information API。丸ごと未対応
2161        // だった。実測機構は無いため一般的なブロードバンド相当の代表値を返す
2162        // 簡略実装)。
2163        ("navigator.connection.saveData", "false"),
2164        ("navigator.connection.effectiveType", "4g"),
2165        ("typeof navigator.connection.downlink", "number"),
2166        // `navigator.locks.request(name, callback)`(Web Locks API。丸ごと
2167        // 未対応だった。複数タブ間の実際の排他機構が無いため、コールバックを
2168        // 即座に同期呼び出しし戻り値で解決する簡略実装。2026-07-15 発見・実装)。
2169        ("await navigator.locks.request('x', lock => lock.name)", "x"),
2170        ("await navigator.locks.request('x', lock => lock.mode)", "exclusive"),
2171        (
2172            "var ran=false; await navigator.locks.request('x', () => { ran=true; }); ran",
2173            "true",
2174        ),
2175        ("(await navigator.locks.query()).held.length", "0"),
2176        // Storage Access API(`document.hasStorageAccess()`/
2177        // `.requestStorageAccess()`。丸ごと未対応だった。サードパーティ
2178        // Cookie 分離機構自体が無いため常に成功する簡略実装)。
2179        ("await document.hasStorageAccess()", "true"),
2180        ("typeof await document.requestStorageAccess()", "undefined"),
2181        // `navigator.clipboard.write(items)`/`.read()`(`ClipboardItem` 配列版。
2182        // 丸ごと未対応だった。`text/plain` エントリのみ対応する簡略実装で、
2183        // `writeText`/`readText` と同じ内部状態を共有する)。
2184        (
2185            "await navigator.clipboard.write([new ClipboardItem({'text/plain': \
2186             new Blob(['hello-clip'], {type:'text/plain'})})]); \
2187             await navigator.clipboard.readText()",
2188            "hello-clip",
2189        ),
2190        (
2191            "await navigator.clipboard.writeText('via-text'); \
2192             (await navigator.clipboard.read())[0].types[0]",
2193            "text/plain",
2194        ),
2195        (
2196            "await navigator.clipboard.writeText('via-text2'); \
2197             var items = await navigator.clipboard.read(); \
2198             var blob = await items[0].getType('text/plain'); await blob.text()",
2199            "via-text2",
2200        ),
2201        // `navigator.permissions.query({name})`(丸ごと未対応だった。`geolocation`
2202        // は常に `PERMISSION_DENIED` を返す実装と、`notifications` は
2203        // `Notification.permission === 'granted'` と、それぞれ整合させる)。
2204        (
2205            "(await navigator.permissions.query({name:'geolocation'})).state",
2206            "denied",
2207        ),
2208        (
2209            "(await navigator.permissions.query({name:'notifications'})).state",
2210            "granted",
2211        ),
2212        (
2213            "(await navigator.permissions.query({name:'camera'})).state",
2214            "denied",
2215        ),
2216        // IntersectionObserver(コールバック即時発火・isIntersecting=true)
2217        ("var seen=false; var io=new IntersectionObserver(entries => { seen=entries[0].isIntersecting; }); io.observe(document.body); seen", "true"),
2218        // `IntersectionObserverEntry` の `boundingClientRect`/`intersectionRect`/
2219        // `rootBounds`/`time`(丸ごと未対応だった。2026-07-16 発見・実装)。
2220        (
2221            "var e=null; var io=new IntersectionObserver(entries => { e=entries[0]; }); \
2222             io.observe(document.body); \
2223             typeof e.boundingClientRect.width + ',' + \
2224             (e.intersectionRect === e.boundingClientRect ? 'same' : 'diff') + ',' + \
2225             (e.rootBounds === null) + ',' + (typeof e.time)",
2226            "number,same,true,number",
2227        ),
2228        // `IntersectionObserver`のコンストラクタ第2引数`options`
2229        // (`root`/`rootMargin`/`threshold`)が丸ごと無視されており、対応
2230        // する`.root`/`.rootMargin`/`.thresholds`読み取り専用プロパティも
2231        // 存在しなかった(丸ごと未対応だった。2026-07-17 発見・実装)。
2232        (
2233            "var io=new IntersectionObserver(()=>{}); \
2234             (io.root===null)+','+io.rootMargin+','+io.thresholds.join(',')",
2235            "true,0px 0px 0px 0px,0",
2236        ),
2237        (
2238            "var r=document.body; \
2239             var io=new IntersectionObserver(()=>{}, {root:r, rootMargin:'10px', threshold:0.5}); \
2240             (io.root===r)+','+io.rootMargin+','+io.thresholds.join(',')",
2241            "true,10px,0.5",
2242        ),
2243        (
2244            "var io=new IntersectionObserver(()=>{}, {threshold:[0,0.25,1]}); io.thresholds.join(',')",
2245            "0,0.25,1",
2246        ),
2247        (
2248            "var io=new IntersectionObserver(()=>{}, {rootMargin:'-5px 10px'}); io.rootMargin",
2249            "-5px 10px",
2250        ),
2251        (
2252            "var io=new IntersectionObserver(()=>{}); typeof io.unobserve === 'function' && typeof io.disconnect === 'function'",
2253            "true",
2254        ),
2255        (
2256            "var r=null; var io=new IntersectionObserver(entries => { r=entries[0].intersectionRatio; }); io.observe(document.body); typeof r === 'number'",
2257            "true",
2258        ),
2259        // `ResizeObserverEntry.contentRect` が常に 0/0/0/0 固定だったバグと、
2260        // `borderBoxSize`/`contentBoxSize`(丸ごと未対応だった)を修正・追加。
2261        // 2026-07-16 発見・実装。
2262        (
2263            "var e=null; var ro=new ResizeObserver(entries => { e=entries[0]; }); \
2264             ro.observe(document.createElement('div')); \
2265             typeof e.contentRect.width + ',' + \
2266             Array.isArray(e.borderBoxSize) + ',' + \
2267             typeof e.borderBoxSize[0].inlineSize + ',' + \
2268             Array.isArray(e.contentBoxSize) + ',' + \
2269             typeof e.contentBoxSize[0].blockSize",
2270            "number,true,number,true,number",
2271        ),
2272        // `ResizeObserverEntry.devicePixelContentBoxSize`が丸ごと未対応
2273        // だった(`borderBoxSize`/`contentBoxSize`のみ対応済みで漏れて
2274        // いた。2026-07-17 発見・実装。`devicePixelRatio`が常に`1.0`固定
2275        // のため数値上は`contentBoxSize`と同一になる簡略実装)。
2276        (
2277            "var e=null; var ro=new ResizeObserver(entries => { e=entries[0]; }); \
2278             ro.observe(document.createElement('div')); \
2279             Array.isArray(e.devicePixelContentBoxSize) + ',' + \
2280             typeof e.devicePixelContentBoxSize[0].inlineSize",
2281            "true,number",
2282        ),
2283        // window.matchMedia(max-width 判定)
2284        ("window.matchMedia('(max-width: 9999px)').matches", "true"),
2285        ("window.matchMedia('(max-width: 0px)').matches", "false"),
2286        ("window.matchMedia('(min-width: 1px)').matches", "true"),
2287        // `window.matchMedia()` が `max-width`/`min-width` しか判定できず、CSS 側の
2288        // `@media` 評価(`prefers-color-scheme` 等)と食い違う非対称なバグだった。
2289        // 同じ判定ロジックへ統一した後の確認(既定は light テーマ)。
2290        ("window.matchMedia('(prefers-color-scheme: light)').matches", "true"),
2291        ("window.matchMedia('(prefers-color-scheme: dark)').matches", "false"),
2292        // window.scrollY / scrollTo
2293        ("typeof scrollY", "number"),
2294        ("scrollTo({top:100}); scrollY", "0"),
2295        // window.addEventListener load → 即時コールバック
2296        ("var loaded=false; window.addEventListener('load', () => { loaded=true; }); loaded", "true"),
2297        // `document.addEventListener('DOMContentLoaded', fn)` が丸ごと no-op だった
2298        // バグ(`window` 版は既に即時発火していたが `document` 側の配線が漏れていた)。
2299        // `window` 版よりもむしろよく使われる定番パターン。
2300        (
2301            "var ready=false; document.addEventListener('DOMContentLoaded', () => { ready=true; }); ready",
2302            "true",
2303        ),
2304        // `input.checked`(チェックボックス/ラジオボタンの選択状態)が JS プロパティとして
2305        // 丸ごと未実装で、`checkbox.checked`/`checkbox.checked = true` が常に
2306        // `undefined`/黙殺になっていた。
2307        (
2308            "var c=document.createElement('input'); c.type='checkbox'; c.checked",
2309            "false",
2310        ),
2311        (
2312            "var c=document.createElement('input'); c.type='checkbox'; c.checked=true; c.checked",
2313            "true",
2314        ),
2315        (
2316            "var c=document.createElement('input'); c.type='checkbox'; c.checked=true; c.checked=false; c.checked",
2317            "false",
2318        ),
2319        // `input.files`(`<input type="file">`のFileList。丸ごと未対応
2320        // だった。2026-07-17 発見・実装。実際のファイルピッカーUIが無い
2321        // ため常に空配列を返す誠実な簡略実装)。
2322        (
2323            "var f=document.createElement('input'); f.type='file'; f.files.length",
2324            "0",
2325        ),
2326        (
2327            "var f=document.createElement('input'); f.type='text'; f.files",
2328            "undefined",
2329        ),
2330        // `input[type=file].value`はセキュリティ上重要な仕様(`value`content
2331        // 属性は常に無視/未反映で、選択済みファイルが無ければ常に空文字列を
2332        // 返すべき)だが、以前は汎用の属性直結フォールバックに落ちており、
2333        // `<input type="file" value="...">`の属性値がそのまま漏れる
2334        // 静かなバグだった。
2335        (
2336            "var f=document.createElement('input'); f.type='file'; \
2337             f.setAttribute('value','c:\\\\secret.txt'); f.value",
2338            "",
2339        ),
2340        (
2341            "var f=document.createElement('input'); f.type='file'; f.value='x'; f.value",
2342            "",
2343        ),
2344        // `a`/`area`要素のURL分解プロパティ(`URLUtils`ミックスイン。
2345        // `.protocol`/`.host`/`.hostname`/`.port`/`.pathname`/`.search`/
2346        // `.hash`/`.origin`)が丸ごと未対応だった(`location`/`URL`は
2347        // 既に対応済みだったが`<a>`側への配線漏れ。2026-07-17 発見・実装)。
2348        (
2349            "var a=document.createElement('a'); a.href='https://sub.example.org:8080/x/y?z=1#top'; \
2350             [a.protocol,a.host,a.hostname,a.port,a.pathname,a.search,a.hash,a.origin].join('|')",
2351            "https:|sub.example.org:8080|sub.example.org|8080|/x/y|?z=1|#top|https://sub.example.org:8080",
2352        ),
2353        (
2354            "var a=document.createElement('area'); a.href='http://x.com/'; a.protocol",
2355            "http:",
2356        ),
2357        // `checkbox.indeterminate`(不確定表示状態。HTML 属性としては反映されない
2358        // JS 専用プロパティ)が丸ごと未対応だった。
2359        (
2360            "var c=document.createElement('input'); c.type='checkbox'; c.indeterminate",
2361            "false",
2362        ),
2363        (
2364            "var c=document.createElement('input'); c.type='checkbox'; c.indeterminate=true; c.indeterminate",
2365            "true",
2366        ),
2367        (
2368            "var c=document.createElement('input'); c.type='checkbox'; c.indeterminate=true; c.hasAttribute('indeterminate')",
2369            "false",
2370        ),
2371        // `input`/`textarea.selectionStart`/`.selectionEnd`/
2372        // `.selectionDirection`/`textarea.textLength`(丸ごと未対応
2373        // だった。2026-07-17 発見・実装。実際のキャレット機構が無いため
2374        // 状態追跡のみの簡略実装。未設定時は仕様どおり`0`/`"none"`)。
2375        (
2376            "var i=document.createElement('input'); i.selectionStart + ',' + i.selectionEnd + ',' + i.selectionDirection",
2377            "0,0,none",
2378        ),
2379        (
2380            "var i=document.createElement('input'); i.selectionStart=2; i.selectionEnd=5; i.selectionDirection='forward'; \
2381             i.selectionStart + ',' + i.selectionEnd + ',' + i.selectionDirection",
2382            "2,5,forward",
2383        ),
2384        (
2385            "var t=document.createElement('textarea'); t.value='hello'; t.textLength",
2386            "5",
2387        ),
2388        // `setSelectionRange`/`setRangeText`(丸ごと未対応だった。
2389        // `selectionStart`等と対になる操作メソッド。2026-07-17 発見・
2390        // 実装)。
2391        (
2392            "var i=document.createElement('input'); i.setSelectionRange(1,3,'forward'); \
2393             i.selectionStart + ',' + i.selectionEnd + ',' + i.selectionDirection",
2394            "1,3,forward",
2395        ),
2396        (
2397            "var i=document.createElement('input'); i.value='hello world'; \
2398             i.setRangeText('there', 6, 11); i.value",
2399            "hello there",
2400        ),
2401        (
2402            "var i=document.createElement('input'); i.value='hello world'; \
2403             i.setRangeText('there', 6, 11, 'select'); \
2404             i.value + '|' + i.selectionStart + ',' + i.selectionEnd",
2405            "hello there|6,11",
2406        ),
2407        (
2408            "var i=document.createElement('input'); i.value='hello world'; \
2409             i.selectionStart=0; i.selectionEnd=5; i.setRangeText('hi'); i.value",
2410            "hi world",
2411        ),
2412        // `checkbox.click()` は仕様上、リスナ実行前に自身の `checked` を切り替える。
2413        (
2414            "var c=document.createElement('input'); c.type='checkbox'; c.click(); c.checked",
2415            "true",
2416        ),
2417        (
2418            "var c=document.createElement('input'); c.type='checkbox'; c.click(); c.click(); c.checked",
2419            "false",
2420        ),
2421        (
2422            "var r=document.createElement('input'); r.type='radio'; r.click(); r.checked",
2423            "true",
2424        ),
2425        // `node.ownerDocument`(`element.ownerDocument.createElement(...)` という
2426        // 定番イディオムで使われる)が丸ごと未対応だった。
2427        (
2428            "document.createElement('div').ownerDocument === document",
2429            "true",
2430        ),
2431        (
2432            "typeof document.createElement('div').ownerDocument.createElement",
2433            "function",
2434        ),
2435        // **重要**: ラジオボタンの最も基本的な仕様上の挙動(同じ `name` を持つグループ内で
2436        // 1つだけが `checked` になる排他選択)が以前は非対応の簡略化として明示的に
2437        // 見送られていた。`element.click()` と `.checked = true` 代入の両方で効くこと。
2438        (
2439            "var r1=document.createElement('input'); r1.type='radio'; r1.name='g'; \
2440             var r2=document.createElement('input'); r2.type='radio'; r2.name='g'; \
2441             r1.click(); r2.click(); r1.checked + ',' + r2.checked",
2442            "false,true",
2443        ),
2444        (
2445            "var r1=document.createElement('input'); r1.type='radio'; r1.name='g2'; \
2446             var r2=document.createElement('input'); r2.type='radio'; r2.name='g2'; \
2447             r1.checked=true; r2.checked=true; r1.checked + ',' + r2.checked",
2448            "false,true",
2449        ),
2450        // 異なる `name` を持つ radio 同士は互いに影響しない。
2451        (
2452            "var r1=document.createElement('input'); r1.type='radio'; r1.name='ga'; \
2453             var r2=document.createElement('input'); r2.type='radio'; r2.name='gb'; \
2454             r1.click(); r2.click(); r1.checked + ',' + r2.checked",
2455            "true,true",
2456        ),
2457        // `input.disabled`/`option.selected`/`input.placeholder`/`input.name` も
2458        // `checked`/`type` と同じ理由で JS プロパティとして丸ごと未実装だった。
2459        (
2460            "var b=document.createElement('button'); b.disabled=true; b.disabled",
2461            "true",
2462        ),
2463        (
2464            "var b=document.createElement('button'); b.disabled=true; b.disabled=false; b.disabled",
2465            "false",
2466        ),
2467        (
2468            "var o=document.createElement('option'); o.selected=true; o.selected",
2469            "true",
2470        ),
2471        // `option.text`(`textContent` の別名。`new Option(...)`/`option.text = '...'`
2472        // という定番パターン)が丸ごと未対応だった。
2473        (
2474            "var o=document.createElement('option'); o.text='Label'; o.text",
2475            "Label",
2476        ),
2477        (
2478            "var o=document.createElement('option'); o.text='Label'; o.textContent",
2479            "Label",
2480        ),
2481        // `option`/`optgroup.label`(丸ごと未対応だった。`option.label`は
2482        // 仕様上`label`属性が無ければテキスト内容にフォールバックする。
2483        // 2026-07-17 発見・実装)。
2484        (
2485            "var o=document.createElement('option'); o.textContent='hi'; o.label",
2486            "hi",
2487        ),
2488        (
2489            "var o=document.createElement('option'); o.textContent='hi'; o.label='explicit'; o.label",
2490            "explicit",
2491        ),
2492        (
2493            "var g=document.createElement('optgroup'); g.textContent='hi'; g.label",
2494            "",
2495        ),
2496        (
2497            "var g=document.createElement('optgroup'); g.label='Group A'; g.label",
2498            "Group A",
2499        ),
2500        // `input.required`/`input.readOnly`/`input.multiple`/`img.src`/`img.alt`/
2501        // `a.href` も同じ理由で JS プロパティとして丸ごと未実装だった。
2502        (
2503            "var i=document.createElement('input'); i.required=true; i.required",
2504            "true",
2505        ),
2506        (
2507            "var i=document.createElement('input'); i.readOnly=true; i.readOnly",
2508            "true",
2509        ),
2510        (
2511            "var s=document.createElement('select'); s.multiple=true; s.multiple",
2512            "true",
2513        ),
2514        (
2515            "var img=document.createElement('img'); img.src='a.png'; img.alt='pic'; img.src+','+img.alt",
2516            "a.png,pic",
2517        ),
2518        (
2519            "var a=document.createElement('a'); a.href='/x'; a.href",
2520            "/x",
2521        ),
2522        // `label.htmlFor`/`form.action`/`form.method`/`input.min`/`max`/`step`/
2523        // `maxLength`/`a.target`/`rel`/`download` も同種の未実装だった。
2524        (
2525            "var l=document.createElement('label'); l.htmlFor='name'; l.htmlFor",
2526            "name",
2527        ),
2528        (
2529            "var f=document.createElement('form'); f.action='/submit'; f.method='post'; f.action+','+f.method",
2530            "/submit,post",
2531        ),
2532        // `form.method`(`method`属性が無いか既知の列挙値以外なら既定
2533        // `"get"`にフォールバックする。`fieldset.type`等と同じバグ
2534        // パターンで丸ごと未対応だった。2026-07-17 発見・実装)。
2535        ("document.createElement('form').method", "get"),
2536        (
2537            "var f=document.createElement('form'); f.setAttribute('method','DIALOG'); f.method",
2538            "dialog",
2539        ),
2540        (
2541            "var f=document.createElement('form'); f.setAttribute('method','bogus'); f.method",
2542            "get",
2543        ),
2544        // `form.enctype`/`.encoding`(丸ごと未対応だった。`encoding`は
2545        // `enctype`のレガシー別名で同じ属性を指す。既知の列挙値3種以外
2546        // なら既定`"application/x-www-form-urlencoded"`にフォールバック
2547        // する。2026-07-17 発見・実装)。
2548        (
2549            "document.createElement('form').enctype",
2550            "application/x-www-form-urlencoded",
2551        ),
2552        (
2553            "var f=document.createElement('form'); f.enctype='multipart/form-data'; f.enctype+','+f.encoding",
2554            "multipart/form-data,multipart/form-data",
2555        ),
2556        (
2557            "var f=document.createElement('form'); f.encoding='text/plain'; f.enctype",
2558            "text/plain",
2559        ),
2560        // `form.acceptCharset`(`accept-charset`属性のcamelCase IDL
2561        // プロパティ版。`htmlFor`と同じパターンだが丸ごと未対応だった。
2562        // 2026-07-17 発見・実装)。
2563        (
2564            "var f=document.createElement('form'); f.acceptCharset='UTF-8'; f.getAttribute('accept-charset')",
2565            "UTF-8",
2566        ),
2567        (
2568            "var f=document.createElement('form'); f.setAttribute('accept-charset','UTF-8'); f.acceptCharset",
2569            "UTF-8",
2570        ),
2571        // `button`/`input`の「送信オーバーライド」IDLプロパティ(`formAction`/
2572        // `formEnctype`/`formMethod`/`formNoValidate`/`formTarget`)が丸ごと
2573        // 未対応だった。`form.action`/`.method`/`.enctype`と同じ既定値
2574        // フォールバックパターンを踏襲する。2026-07-17 発見・実装)。
2575        (
2576            "var b=document.createElement('button'); b.formAction='/x'; b.getAttribute('formaction')",
2577            "/x",
2578        ),
2579        (
2580            "document.createElement('input').formEnctype",
2581            "application/x-www-form-urlencoded",
2582        ),
2583        (
2584            "var b=document.createElement('button'); b.formEnctype='multipart/form-data'; b.formEnctype",
2585            "multipart/form-data",
2586        ),
2587        ("document.createElement('button').formMethod", "get"),
2588        (
2589            "var i=document.createElement('input'); i.formMethod='post'; i.formMethod",
2590            "post",
2591        ),
2592        (
2593            "document.createElement('button').formNoValidate",
2594            "false",
2595        ),
2596        (
2597            "var b=document.createElement('button'); b.formNoValidate=true; b.getAttribute('formnovalidate')",
2598            "formnovalidate",
2599        ),
2600        (
2601            "var i=document.createElement('input'); i.formTarget='_blank'; i.formTarget",
2602            "_blank",
2603        ),
2604        (
2605            "var i=document.createElement('input'); i.defaultValue='hello'; i.defaultValue+','+i.getAttribute('value')",
2606            "hello,hello",
2607        ),
2608        (
2609            "var i=document.createElement('input'); i.defaultChecked=true; i.defaultChecked+','+i.hasAttribute('checked')",
2610            "true,true",
2611        ),
2612        (
2613            "var o=document.createElement('option'); o.defaultSelected=true; o.defaultSelected+','+o.hasAttribute('selected')",
2614            "true,true",
2615        ),
2616        // `img.loading`/`.decoding`(既知の列挙値以外なら仕様上の既定値
2617        // `"auto"`にフォールバックする必要があるが、`fieldset.type`等と
2618        // 同じバグパターンで空文字列になっていた。2026-07-17 発見・実装)。
2619        ("document.createElement('img').loading", "auto"),
2620        (
2621            "var i=document.createElement('img'); i.loading='lazy'; i.loading",
2622            "lazy",
2623        ),
2624        (
2625            "var i=document.createElement('img'); i.setAttribute('loading','bogus'); i.loading",
2626            "auto",
2627        ),
2628        ("document.createElement('img').decoding", "auto"),
2629        (
2630            "var i=document.createElement('img'); i.decoding='sync'; i.decoding",
2631            "sync",
2632        ),
2633        (
2634            "var i=document.createElement('img'); i.setAttribute('decoding','bogus'); i.decoding",
2635            "auto",
2636        ),
2637        // `audio`/`video.preload`(欠落時既定値`"metadata"`、不正値既定値
2638        // `"auto"`。`fieldset.type`等と同じバグパターンで空文字列になって
2639        // いた。2026-07-17 発見・実装)。
2640        ("document.createElement('audio').preload", "metadata"),
2641        (
2642            "var v=document.createElement('video'); v.preload='none'; v.preload",
2643            "none",
2644        ),
2645        (
2646            "var v=document.createElement('video'); v.setAttribute('preload','bogus'); v.preload",
2647            "auto",
2648        ),
2649        // `textarea.wrap`(`"soft"`/`"hard"`。既知値以外・欠落時は仕様上の
2650        // 既定値`"soft"`にフォールバックする必要があるが、丸ごと未対応
2651        // だった。2026-07-17 発見・実装)。
2652        ("document.createElement('textarea').wrap", "soft"),
2653        (
2654            "var t=document.createElement('textarea'); t.wrap='hard'; t.wrap",
2655            "hard",
2656        ),
2657        (
2658            "var t=document.createElement('textarea'); t.setAttribute('wrap','bogus'); t.wrap",
2659            "soft",
2660        ),
2661        // `track.kind`(欠落時既定`"subtitles"`/不正値既定`"metadata"`の
2662        // 2段階フォールバック。プロパティ自体が丸ごと未対応だった。
2663        // 2026-07-17 発見・実装)。
2664        ("document.createElement('track').kind", "subtitles"),
2665        (
2666            "var t=document.createElement('track'); t.kind='captions'; t.kind",
2667            "captions",
2668        ),
2669        (
2670            "var t=document.createElement('track'); t.setAttribute('kind','bogus'); t.kind",
2671            "metadata",
2672        ),
2673        // `track.label`/`.srclang`(単純な文字列反映)と`track.default`
2674        // (ブール型)が丸ごと未対応だった。あわせて`option`/`optgroup`/
2675        // `track`共通の`label`セッターが丸ごと存在しなかった重複バグも
2676        // 解消。2026-07-18 発見・実装)。
2677        (
2678            "var t=document.createElement('track'); t.label='English'; t.getAttribute('label')",
2679            "English",
2680        ),
2681        (
2682            "var t=document.createElement('track'); t.srclang='en'; t.srclang",
2683            "en",
2684        ),
2685        ("document.createElement('track').default", "false"),
2686        (
2687            "var t=document.createElement('track'); t.default=true; t.getAttribute('default')",
2688            "default",
2689        ),
2690        // `link.as`(`rel="preload"`/`"prefetch"`の取得先種別ヒント。単純な
2691        // 文字列反映だが丸ごと未対応だった。2026-07-18 発見・実装)。
2692        (
2693            "var l=document.createElement('link'); l.as='script'; l.getAttribute('as')",
2694            "script",
2695        ),
2696        (
2697            "var l=document.createElement('link'); l.setAttribute('as','style'); l.as",
2698            "style",
2699        ),
2700        // `video.videoWidth`/`.videoHeight`(実デコードパイプラインが
2701        // 無いため常に`0`)と`audio`/`video.seeking`(実際の非同期シーク
2702        // 処理が無いため常に`false`)が丸ごと未対応だった。2026-07-18
2703        // 発見・実装)。
2704        (
2705            "document.createElement('video').videoWidth+','+document.createElement('video').videoHeight",
2706            "0,0",
2707        ),
2708        ("document.createElement('video').seeking", "false"),
2709        ("document.createElement('audio').seeking", "false"),
2710        // `select.size`(欠落・不正値時の既定値`0`)と`input.size`(欠落・
2711        // 不正値時の既定値`20`)が丸ごと未対応だった。2026-07-18 発見・
2712        // 実装)。
2713        ("document.createElement('select').size", "0"),
2714        (
2715            "var s=document.createElement('select'); s.size=5; s.size",
2716            "5",
2717        ),
2718        ("document.createElement('input').size", "20"),
2719        (
2720            "var i=document.createElement('input'); i.size=10; i.size",
2721            "10",
2722        ),
2723        (
2724            "var i=document.createElement('input'); i.setAttribute('size','0'); i.size",
2725            "20",
2726        ),
2727        // `textarea.rows`/`.cols`(欠落・不正値時の既定値`2`/`20`)が
2728        // 丸ごと未対応だった。2026-07-18 発見・実装)。
2729        ("document.createElement('textarea').rows", "2"),
2730        (
2731            "var t=document.createElement('textarea'); t.rows=8; t.rows",
2732            "8",
2733        ),
2734        ("document.createElement('textarea').cols", "20"),
2735        (
2736            "var t=document.createElement('textarea'); t.cols=40; t.cols",
2737            "40",
2738        ),
2739        (
2740            "var t=document.createElement('textarea'); t.setAttribute('rows','0'); t.rows",
2741            "2",
2742        ),
2743        // `dialog.requestClose(returnValue?)`(`.close()`と違い、閉じる
2744        // 直前に取消可能な`cancel`イベントを発火する。丸ごと未対応
2745        // だった。2026-07-18 発見・実装)。
2746        (
2747            "var d=document.createElement('dialog'); d.open=true; d.requestClose('x'); \
2748             d.open+','+d.returnValue",
2749            "false,x",
2750        ),
2751        (
2752            "var d=document.createElement('dialog'); d.open=true; \
2753             d.addEventListener('cancel', e=>e.preventDefault()); d.requestClose(); d.open",
2754            "true",
2755        ),
2756        (
2757            "var d=document.createElement('dialog'); d.open=true; var fired=false; \
2758             d.addEventListener('close', ()=>fired=true); d.requestClose(); fired",
2759            "true",
2760        ),
2761        // `progress.position`(廃止予定だが仕様に残る読み取り専用IDL。
2762        // `value`未設定なら不確定状態`-1`、それ以外は`value/max`。丸ごと
2763        // 未対応だった。2026-07-18 発見・実装)。
2764        ("document.createElement('progress').position", "-1"),
2765        (
2766            "var p=document.createElement('progress'); p.max=10; p.value=5; p.position",
2767            "0.5",
2768        ),
2769        (
2770            "var p=document.createElement('progress'); p.value=1; p.position",
2771            "1",
2772        ),
2773        // `output.htmlFor`(`for`属性を空白区切りトークンとして扱う
2774        // `DOMTokenList`。`label.htmlFor`(単一文字列)とは異なる意味。
2775        // `relList`/`sandbox`と同じ読み取り専用トークン配列の簡略実装。
2776        // 丸ごと未対応だった。2026-07-18 発見・実装)。
2777        (
2778            "var o=document.createElement('output'); o.htmlFor='a b c'; \
2779             o.htmlFor.length+','+o.htmlFor[1]",
2780            "3,b",
2781        ),
2782        (
2783            "var o=document.createElement('output'); o.setAttribute('for','x y'); \
2784             Array.from(o.htmlFor).join(',')",
2785            "x,y",
2786        ),
2787        ("document.createElement('output').htmlFor.length", "0"),
2788        // `input[type=checkbox/radio].value`(`value`属性が無い場合の仕様上
2789        // の既定値は`""`ではなく`"on"`。フォーム送信ロジック
2790        // 〔`collect_form_data`〕は既に正しくこの既定値を使っていたが、
2791        // JSプロパティの`.value`読み出し側だけ空文字列になる不整合が
2792        // あった。2026-07-18 発見・実装)。
2793        (
2794            "var c=document.createElement('input'); c.type='checkbox'; c.value",
2795            "on",
2796        ),
2797        (
2798            "var r=document.createElement('input'); r.type='radio'; r.value",
2799            "on",
2800        ),
2801        (
2802            "var c=document.createElement('input'); c.type='checkbox'; c.value='yes'; c.value",
2803            "yes",
2804        ),
2805        (
2806            "document.createElement('input').value",
2807            "",
2808        ),
2809        // `input[type=color].value`(`value`属性が無いか7文字の`#rrggbb`
2810        // 小文字16進形式でない不正値の場合の既定値は`"#000000"`。
2811        // `checkbox`/`radio`の`"on"`と同じバグパターンで空文字列に
2812        // なっていた。2026-07-18 発見・実装)。
2813        (
2814            "var c=document.createElement('input'); c.type='color'; c.value",
2815            "#000000",
2816        ),
2817        (
2818            "var c=document.createElement('input'); c.type='color'; c.value='#ff00aa'; c.value",
2819            "#ff00aa",
2820        ),
2821        // `input[type=color].value = v`の書き込み側sanitizationが丸ごと
2822        // 未対応だった。ゲッター側は「既に小文字16進形式か」しか見ておらず、
2823        // セッター側で大文字→小文字への正規化が行われていなかったため、
2824        // 仕様上は妥当な色(大文字16進)である`'#FF00AA'`を代入しても
2825        // 小文字化されずゲッターの検証に弾かれ`#000000`になってしまう
2826        // 食い違いがあった(2026-07-18 発見・修正)。
2827        (
2828            "var c=document.createElement('input'); c.type='color'; c.value='#FF00AA'; c.value",
2829            "#ff00aa",
2830        ),
2831        (
2832            // 16進形式として無効な値(色名等)は仕様どおり`#000000`へ正規化される。
2833            "var c=document.createElement('input'); c.type='color'; c.value='red'; c.value",
2834            "#000000",
2835        ),
2836        // `input[type=range].value`(`value`属性が無いか不正値の場合の
2837        // 既定値は`min`と`max`の中点。`min`/`max`省略時は既定`0`/`100`
2838        // のため無指定時は`50`。`checkbox`/`color`と同じバグパターンで
2839        // 空文字列になっていた。2026-07-18 発見・実装)。
2840        (
2841            "var r=document.createElement('input'); r.type='range'; r.value",
2842            "50",
2843        ),
2844        (
2845            "var r=document.createElement('input'); r.type='range'; r.min='0'; r.max='10'; r.value",
2846            "5",
2847        ),
2848        (
2849            "var r=document.createElement('input'); r.type='range'; r.value='7'; r.value",
2850            "7",
2851        ),
2852        (
2853            "var r=document.createElement('input'); r.type='range'; r.min='5'; r.max='2'; r.value",
2854            "5",
2855        ),
2856        // `input[type=range].value`の仕様上のvalue sanitization algorithmは
2857        // 数値変換できない不正値の既定値フォールバックだけでなく`min`/`max`
2858        // へのクランプも含むが、パース自体には成功する範囲外の値
2859        // (`max=10`で`value=999`等)はクランプされずそのまま漏れていた。
2860        (
2861            "var r=document.createElement('input'); r.type='range'; r.min='0'; r.max='10'; r.value='999'; r.value",
2862            "10",
2863        ),
2864        (
2865            "var r=document.createElement('input'); r.type='range'; r.min='0'; r.max='10'; r.value='-5'; r.value",
2866            "0",
2867        ),
2868        (
2869            // `setAttribute`経由(IDLセッターを介さない直接の属性書き込み)でも
2870            // 同じくクランプされる。
2871            "var r=document.createElement('input'); r.type='range'; r.min='0'; r.max='10'; \
2872             r.setAttribute('value','999'); r.value",
2873            "10",
2874        ),
2875        // `table.tHead`/`.tFoot`/`.caption`のセッター(読み出し側は既に
2876        // 実装済みだったが、対になる書き込み側が丸ごと未対応だった。
2877        // 代入すると既存の同名セクションを除去し、新しい要素を正しい
2878        // 位置へ挿入する。2026-07-18 発見・実装)。
2879        (
2880            "var t=document.createElement('table'); var h=document.createElement('thead'); \
2881             t.tHead=h; t.tHead===h",
2882            "true",
2883        ),
2884        (
2885            "var t=document.createElement('table'); var h1=document.createElement('thead'); \
2886             var h2=document.createElement('thead'); t.tHead=h1; t.tHead=h2; \
2887             (t.tHead===h2)+','+t.children.length",
2888            "true,1",
2889        ),
2890        (
2891            "var t=document.createElement('table'); var h=document.createElement('thead'); \
2892             t.tHead=h; t.tHead=null; t.tHead",
2893            "null",
2894        ),
2895        (
2896            "var t=document.createElement('table'); var c=document.createElement('caption'); \
2897             var b=document.createElement('tbody'); t.appendChild(b); t.caption=c; \
2898             t.firstChild===c",
2899            "true",
2900        ),
2901        // `select.add(element, before)`(`select.options.add`と同じ意味の
2902        // 便利メソッド。丸ごと未対応だった。2026-07-18 発見・実装)。
2903        (
2904            "var s=document.createElement('select'); var o=document.createElement('option'); \
2905             s.add(o); s.options.length+','+(s.options[0]===o)",
2906            "1,true",
2907        ),
2908        (
2909            "var s=document.createElement('select'); var o1=document.createElement('option'); \
2910             var o2=document.createElement('option'); s.add(o1); s.add(o2, o1); \
2911             s.options[0]===o2",
2912            "true",
2913        ),
2914        // `audio`/`video.played`(`TimeRanges`。`buffered`/`.seekable`と
2915        // 同じ理由で常に空。丸ごと未対応だった。2026-07-18 発見・実装)。
2916        ("document.createElement('video').played.length", "0"),
2917        ("document.createElement('audio').played.length", "0"),
2918        // `element.role`/`.ariaLabel`等(`ARIAMixin`。`role`/`aria-*`属性の
2919        // IDLプロパティ版)が丸ごと未対応だった。`aria`接頭辞後の
2920        // PascalCase部分を全て小文字化して`aria-`と連結する命名規則を
2921        // 汎用アームで一括対応。2026-07-18 発見・実装)。
2922        (
2923            "var d=document.createElement('div'); d.role='button'; d.getAttribute('role')",
2924            "button",
2925        ),
2926        (
2927            "var d=document.createElement('div'); d.setAttribute('role','tab'); d.role",
2928            "tab",
2929        ),
2930        (
2931            "var d=document.createElement('div'); d.ariaLabel='close'; d.getAttribute('aria-label')",
2932            "close",
2933        ),
2934        (
2935            "var d=document.createElement('div'); d.setAttribute('aria-hidden','true'); d.ariaHidden",
2936            "true",
2937        ),
2938        (
2939            "var d=document.createElement('div'); d.ariaValueNow='5'; d.getAttribute('aria-valuenow')",
2940            "5",
2941        ),
2942        (
2943            "var d=document.createElement('div'); d.ariaLabel='x'; d.ariaLabel=null; \
2944             d.getAttribute('aria-label')",
2945            "null",
2946        ),
2947        ("document.createElement('div').ariaLabel", "null"),
2948        // `element.outerText`(読み出しは`innerText`と同じだが、書き込みは
2949        // 自身の中身ではなく自身「そのもの」をテキストノードへ置き換える
2950        // 点が違う。丸ごと未対応だった。2026-07-18 発見・実装)。
2951        (
2952            "var d=document.createElement('div'); d.textContent='hi'; d.outerText",
2953            "hi",
2954        ),
2955        (
2956            "var p=document.createElement('div'); var c=document.createElement('span'); \
2957             c.textContent='x'; p.appendChild(c); c.outerText='y'; p.textContent",
2958            "y",
2959        ),
2960        (
2961            "var p=document.createElement('div'); var c=document.createElement('span'); \
2962             p.appendChild(c); c.outerText='z'; p.children.length",
2963            "0",
2964        ),
2965        // `element.part`(CSS Shadow Parts。`relList`/`sandbox`と同じ
2966        // 読み取り専用トークン配列の簡略実装。丸ごと未対応だった。
2967        // 2026-07-18 発見・実装)。
2968        (
2969            "var d=document.createElement('div'); d.setAttribute('part','a b'); \
2970             d.part.length+','+d.part[1]",
2971            "2,b",
2972        ),
2973        ("document.createElement('div').part.length", "0"),
2974        // `link.sizes`は`img`/`source.sizes`(単純なDOMString)とは異なり
2975        // 仕様上`DOMTokenList`。`part`/`relList`と同じ読み取り専用トークン
2976        // 配列の簡略実装で区別する。以前は同名の文字列反映アームに
2977        // 一括で落ちて型が誤っていた。2026-07-18 発見・実装)。
2978        (
2979            "var l=document.createElement('link'); l.setAttribute('sizes','16x16 32x32'); \
2980             l.sizes.length+','+l.sizes[1]",
2981            "2,32x32",
2982        ),
2983        (
2984            "var i=document.createElement('img'); i.sizes='(min-width: 600px) 50vw, 100vw'; i.sizes",
2985            "(min-width: 600px) 50vw, 100vw",
2986        ),
2987        // `input.accept`(`type="file"`が許可するファイル種別フィルタ。
2988        // 単純な文字列反映だが丸ごと未対応だった。2026-07-18 発見・実装)。
2989        (
2990            "var i=document.createElement('input'); i.accept='image/*'; i.getAttribute('accept')",
2991            "image/*",
2992        ),
2993        (
2994            "var i=document.createElement('input'); i.setAttribute('accept','.pdf,.doc'); i.accept",
2995            ".pdf,.doc",
2996        ),
2997        // `script.charset`(廃止予定だが仕様に残る単純な文字列反映。
2998        // ゲッターが`meta`タグ限定のままで他タグでは未対応だった。
2999        // 2026-07-18 発見・実装)。
3000        (
3001            "var s=document.createElement('script'); s.charset='utf-8'; s.getAttribute('charset')",
3002            "utf-8",
3003        ),
3004        (
3005            "var s=document.createElement('script'); s.setAttribute('charset','shift-jis'); s.charset",
3006            "shift-jis",
3007        ),
3008        // `a`/`area.protocol`/`.host`/`.hostname`/`.port`/`.pathname`/
3009        // `.search`/`.hash`のセッター(`URLUtils`ミックスイン。ゲッターは
3010        // 既に実装済みだったが対になるセッターが丸ごと未対応だった。
3011        // 2026-07-18 発見・実装)。
3012        (
3013            "var a=document.createElement('a'); a.href='https://x.com/p'; a.hostname='y.com'; a.href",
3014            "https://y.com/p",
3015        ),
3016        (
3017            "var a=document.createElement('a'); a.href='https://x.com/p'; a.protocol='http'; a.protocol",
3018            "http:",
3019        ),
3020        (
3021            "var a=document.createElement('a'); a.href='https://x.com/p'; a.pathname='q'; a.pathname",
3022            "/q",
3023        ),
3024        (
3025            "var a=document.createElement('a'); a.href='https://x.com/p'; a.search='x=1'; a.search",
3026            "?x=1",
3027        ),
3028        (
3029            "var a=document.createElement('a'); a.href='https://x.com/p'; a.hash='frag'; a.hash",
3030            "#frag",
3031        ),
3032        (
3033            "var a=document.createElement('a'); a.href='https://x.com:80/p'; a.host='y.com:90'; \
3034             a.hostname+','+a.port",
3035            "y.com,90",
3036        ),
3037        (
3038            "var a=document.createElement('a'); a.href='https://x.com/p'; a.username='u'; \
3039             a.password='pw'; a.href",
3040            "https://u:pw@x.com/p",
3041        ),
3042        // `element.willValidate`がタグを一切見ておらず、`<output>`/`<div>`
3043        // のような本来常に`false`であるべき非フォームコントロールでも
3044        // `true`を返してしまうバグだった。2026-07-18 発見・修正)。
3045        ("document.createElement('output').willValidate", "false"),
3046        ("document.createElement('div').willValidate", "false"),
3047        ("document.createElement('fieldset').willValidate", "false"),
3048        ("document.createElement('input').willValidate", "true"),
3049        (
3050            "var b=document.createElement('button'); b.disabled=true; b.willValidate",
3051            "false",
3052        ),
3053        // `validity_flags`(`.validity`が内部で使う共通ロジック)が
3054        // `willValidate`と同根の「タグを見ない」バグを持っていた。
3055        // `<output required>`のような本来検証対象外の要素へ`required`
3056        // 属性を付けても`valueMissing`が立たないことを確認。2026-07-18
3057        // 発見・修正)。
3058        (
3059            "var o=document.createElement('output'); o.setAttribute('required','required'); \
3060             o.validity.valid",
3061            "true",
3062        ),
3063        (
3064            "var d=document.createElement('div'); d.setAttribute('required','required'); \
3065             d.validity.valueMissing",
3066            "false",
3067        ),
3068        (
3069            "var i=document.createElement('input'); i.setAttribute('required','required'); \
3070             i.validity.valueMissing",
3071            "true",
3072        ),
3073        // `validate_field`(`element.checkValidity()`が直接呼ぶ経路)も
3074        // 同根の「タグを見ない」バグを持っていた。`<output required>`へ
3075        // `checkValidity()`を直接呼んでも常に`true`のままであることを
3076        // 確認。`form.checkValidity()`側は既に`input`/`textarea`/
3077        // `select`へ絞り込み済みのため実害が無かった経路との違いに
3078        // 注意。2026-07-18 発見・修正)。
3079        (
3080            "var o=document.createElement('output'); o.setAttribute('required','required'); \
3081             o.checkValidity()",
3082            "true",
3083        ),
3084        (
3085            "var i=document.createElement('input'); i.setAttribute('required','required'); \
3086             i.checkValidity()",
3087            "false",
3088        ),
3089        // `video.disableRemotePlayback`(`disablePictureInPicture`と同じ
3090        // 単純なブール属性反映だが丸ごと未対応だった。2026-07-18
3091        // 発見・実装)。
3092        (
3093            "document.createElement('video').disableRemotePlayback",
3094            "false",
3095        ),
3096        (
3097            "var v=document.createElement('video'); v.disableRemotePlayback=true; \
3098             v.getAttribute('disableremoteplayback')",
3099            "disableremoteplayback",
3100        ),
3101        // `link`/`script.blocking`(`part`/`relList`/`sandbox`と同じ読み
3102        // 取り専用トークン配列。丸ごと未対応だった。2026-07-18 発見・
3103        // 実装)。
3104        (
3105            "var l=document.createElement('link'); l.setAttribute('blocking','render'); \
3106             l.blocking.length+','+l.blocking[0]",
3107            "1,render",
3108        ),
3109        ("document.createElement('script').blocking.length", "0"),
3110        // `output.value`/`.defaultValue`(`<output>`は`value`content属性を
3111        // 持たず、未書込み時は`.textContent`にフォールバックする。
3112        // `textarea.value`と同じパターンだが丸ごと未対応だった。
3113        // 2026-07-18 発見・実装)。
3114        (
3115            "var o=document.createElement('output'); o.textContent='42'; o.value",
3116            "42",
3117        ),
3118        (
3119            "var o=document.createElement('output'); o.textContent='42'; o.value='7'; o.value",
3120            "7",
3121        ),
3122        (
3123            "var o=document.createElement('output'); o.textContent='42'; o.value='7'; \
3124             o.textContent",
3125            "42",
3126        ),
3127        // `video.getVideoPlaybackQuality()`(丸ごと未対応だった。実
3128        // デコードパイプラインが無いため全カウンタ`0`の誠実な簡略実装。
3129        // 2026-07-18 発見・実装)。
3130        (
3131            "var q=document.createElement('video').getVideoPlaybackQuality(); \
3132             q.totalVideoFrames+','+q.droppedVideoFrames+','+q.corruptedVideoFrames",
3133            "0,0,0",
3134        ),
3135        // `element.namespaceURI`/`.localName`/`.prefix`(丸ごと未対応
3136        // だった。XML名前空間を一切モデル化していないため常にHTML
3137        // 名前空間定数/タグ名そのもの/`null`を返す誠実な簡略実装。
3138        // 2026-07-18 発見・実装)。
3139        (
3140            "document.createElement('div').namespaceURI",
3141            "http://www.w3.org/1999/xhtml",
3142        ),
3143        ("document.createElement('SPAN').localName", "span"),
3144        ("document.createElement('div').prefix", "null"),
3145        (
3146            "document.createTextNode('x').namespaceURI",
3147            "null",
3148        ),
3149        (
3150            "document.createTextNode('x').localName",
3151            "null",
3152        ),
3153        // `HTMLScriptElement.supports(type)`(静的メソッド。丸ごと未対応
3154        // だった。`classic`/`module`は実サポート済みで`true`、
3155        // `importmap`はインポートマップ自体が未実装のため`false`)。
3156        (
3157            "HTMLScriptElement.supports('classic')+','+HTMLScriptElement.supports('module')",
3158            "true,true",
3159        ),
3160        (
3161            "document.createElement('div').ariaActiveDescendantElement",
3162            "null",
3163        ),
3164        (
3165            "(function(){var a=document.createElement('div');a.setAttribute('aria-activedescendant','nope');return a.ariaActiveDescendantElement;})()",
3166            "null",
3167        ),
3168        // `element.getHTML()`/`.setHTMLUnsafe()`(`innerHTML`ゲッター/
3169        // セッターの明示的メソッド版。丸ごと未対応だった。この処理系には
3170        // Trusted Types自体が無いため`innerHTML`と可視的な違いは無い
3171        // 簡略実装。2026-07-18 発見・実装)。
3172        (
3173            "var d=document.createElement('div'); d.innerHTML='<b>x</b>'; d.getHTML()",
3174            "<b>x</b>",
3175        ),
3176        (
3177            "var d=document.createElement('div'); d.setHTMLUnsafe('<i>y</i>'); d.innerHTML",
3178            "<i>y</i>",
3179        ),
3180        // `CSS.number()`/`.percent()`/`.px()`/`.em()`/`.rem()`/`.deg()`
3181        // (CSS Typed OMの値+単位ファクトリ関数群。丸ごと未対応だった。
3182        // 2026-07-18 発見・実装)。
3183        ("CSS.px(5).value+','+CSS.px(5).unit+','+CSS.px(5).toString()", "5,px,5px"),
3184        ("CSS.percent(50).toString()", "50%"),
3185        ("CSS.number(3).toString()", "3"),
3186        ("CSS.em(1.5).toString()", "1.5em"),
3187        ("CSS.rem(2).toString()", "2rem"),
3188        ("CSS.deg(90).toString()", "90deg"),
3189        ("HTMLScriptElement.supports('importmap')", "false"),
3190        ("HTMLScriptElement.supports('bogus')", "false"),
3191        // `link.imageSrcset`/`.imageSizes`(`rel="preload" as="image"`用の
3192        // レスポンシブ画像プリロードヒント。`link.as`と同じ単純な文字列
3193        // 反映だが丸ごと未対応だった。2026-07-18 発見・実装)。
3194        (
3195            "(function(){var l=document.createElement('link');l.imageSrcset='a.jpg 1x, b.jpg 2x';l.imageSizes='100vw';return l.imageSrcset+'|'+l.imageSizes;})()",
3196            "a.jpg 1x, b.jpg 2x|100vw",
3197        ),
3198        (
3199            "document.createElement('link').imageSrcset+'|'+document.createElement('link').imageSizes",
3200            "|",
3201        ),
3202        // `iframe.credentialless`(COEP credentialless。`allowFullscreen`と
3203        // 同じ単純なブール属性反映だが丸ごと未対応だった。2026-07-18
3204        // 発見・実装)。
3205        (
3206            "(function(){var f=document.createElement('iframe');var before=f.credentialless;f.credentialless=true;var after=f.credentialless;f.credentialless=false;return before+','+after+','+f.credentialless;})()",
3207            "false,true,false",
3208        ),
3209        // `area.coords`/`.shape`(`shape`は既知値以外・欠落時は仕様上の
3210        // 既定値`"rect"`にフォールバックし、レガシー別名`"circ"`/
3211        // `"polygon"`も正規化する。プロパティ自体が丸ごと未対応だった。
3212        // 2026-07-17 発見・実装)。
3213        (
3214            "var a=document.createElement('area'); a.coords='0,0,10,10'; a.coords",
3215            "0,0,10,10",
3216        ),
3217        ("document.createElement('area').shape", "rect"),
3218        (
3219            "var a=document.createElement('area'); a.shape='circle'; a.shape",
3220            "circle",
3221        ),
3222        (
3223            "var a=document.createElement('area'); a.setAttribute('shape','circ'); a.shape",
3224            "circle",
3225        ),
3226        (
3227            "var a=document.createElement('area'); a.setAttribute('shape','polygon'); a.shape",
3228            "poly",
3229        ),
3230        // `ol.type`/`.start`/`.reversed`(`type`は既知値以外・欠落時は
3231        // 仕様上の既定値`"1"`にフォールバックする。`start`は欠落・不正値
3232        // 時の既定値`1`。どちらも丸ごと未対応だった。2026-07-17 発見・
3233        // 実装)。
3234        ("document.createElement('ol').type", "1"),
3235        (
3236            "var o=document.createElement('ol'); o.type='A'; o.type",
3237            "A",
3238        ),
3239        (
3240            "var o=document.createElement('ol'); o.setAttribute('type','bogus'); o.type",
3241            "1",
3242        ),
3243        ("document.createElement('ol').start", "1"),
3244        (
3245            "var o=document.createElement('ol'); o.start=5; o.start",
3246            "5",
3247        ),
3248        (
3249            "document.createElement('ol').reversed",
3250            "false",
3251        ),
3252        (
3253            "var o=document.createElement('ol'); o.reversed=true; o.getAttribute('reversed')",
3254            "reversed",
3255        ),
3256        // `meter.low`/`.high`/`.optimum`(`min`/`max`と同じく丸ごと未対応
3257        // だった。既定値はそれぞれ`min`/`max`/`min`と`max`の中点。
3258        // 2026-07-17 発見・実装)。
3259        (
3260            "var m=document.createElement('meter'); m.min='2'; m.max='10'; m.low+','+m.high+','+m.optimum",
3261            "2,10,6",
3262        ),
3263        (
3264            "var m=document.createElement('meter'); m.max='10'; m.low='4'; m.low",
3265            "4",
3266        ),
3267        (
3268            "var m=document.createElement('meter'); m.max='10'; m.high='20'; m.high",
3269            "10",
3270        ),
3271        // `td`/`th.colSpan`/`.rowSpan`(欠落・不正値時の既定値`1`、
3272        // `colSpan`は最大`1000`、`rowSpan`は最大`65534`にクランプ。
3273        // どちらも丸ごと未対応だった。2026-07-17 発見・実装)。
3274        ("document.createElement('td').colSpan", "1"),
3275        (
3276            "var c=document.createElement('td'); c.colSpan=3; c.colSpan",
3277            "3",
3278        ),
3279        (
3280            "var c=document.createElement('td'); c.setAttribute('colspan','9999'); c.colSpan",
3281            "1000",
3282        ),
3283        ("document.createElement('th').rowSpan", "1"),
3284        (
3285            "var c=document.createElement('th'); c.rowSpan=0; c.rowSpan",
3286            "0",
3287        ),
3288        // `td`/`th.headers`(単純な文字列反映)と`th.scope`(既知値以外・
3289        // 欠落時は仕様上の既定値`""`にフォールバック)、`th.abbr`(単純な
3290        // 文字列反映)が丸ごと未対応だった。2026-07-17 発見・実装)。
3291        (
3292            "var c=document.createElement('td'); c.headers='h1 h2'; c.headers",
3293            "h1 h2",
3294        ),
3295        ("document.createElement('th').scope", ""),
3296        (
3297            "var t=document.createElement('th'); t.scope='col'; t.scope",
3298            "col",
3299        ),
3300        (
3301            "var t=document.createElement('th'); t.setAttribute('scope','bogus'); t.scope",
3302            "",
3303        ),
3304        (
3305            "var t=document.createElement('th'); t.abbr='Description'; t.abbr",
3306            "Description",
3307        ),
3308        // `col`/`colgroup.span`(欠落・不正値時の既定値`1`、最大`1000`に
3309        // クランプ。丸ごと未対応だった。2026-07-17 発見・実装)。
3310        ("document.createElement('col').span", "1"),
3311        (
3312            "var c=document.createElement('colgroup'); c.span=3; c.span",
3313            "3",
3314        ),
3315        (
3316            "var c=document.createElement('col'); c.setAttribute('span','9999'); c.span",
3317            "1000",
3318        ),
3319        // `object.data`(`href`/`src`/`action`と同じくURL属性を反映し、
3320        // 現在のページURL基準の絶対URLを返す。テキストノードの
3321        // `CharacterData.data`と同名で衝突するためタグ限定で扱う必要が
3322        // あったが、プロパティ自体が丸ごと未対応だった。2026-07-17
3323        // 発見・実装)。
3324        (
3325            "var o=document.createElement('object'); o.data='/x'; o.getAttribute('data')",
3326            "/x",
3327        ),
3328        (
3329            "var t=document.createTextNode('hi'); t.data",
3330            "hi",
3331        ),
3332        // `meta.httpEquiv`/`.content`/`.charset`(`httpEquiv`は`http-equiv`
3333        // 属性の camelCase IDL プロパティ版で`htmlFor`と同じマッピング
3334        // パターン、`content`/`.charset`は単純な属性反映。3つとも丸ごと
3335        // 未対応だった。2026-07-17 発見・実装)。
3336        (
3337            "var m=document.createElement('meta'); m.httpEquiv='refresh'; m.getAttribute('http-equiv')",
3338            "refresh",
3339        ),
3340        (
3341            "var m=document.createElement('meta'); m.setAttribute('http-equiv','refresh'); m.httpEquiv",
3342            "refresh",
3343        ),
3344        (
3345            "var m=document.createElement('meta'); m.content='width=device-width'; m.content",
3346            "width=device-width",
3347        ),
3348        (
3349            "var m=document.createElement('meta'); m.charset='utf-8'; m.charset",
3350            "utf-8",
3351        ),
3352        // `blockquote`/`q`/`ins`/`del.cite`(単純な文字列反映)と
3353        // `ins`/`del`/`time.dateTime`(`datetime`属性のcamelCase IDL
3354        // プロパティ版)が丸ごと未対応だった。2026-07-17 発見・実装)。
3355        (
3356            "var b=document.createElement('blockquote'); b.cite='https://example.com'; b.getAttribute('cite')",
3357            "https://example.com",
3358        ),
3359        (
3360            "var t=document.createElement('time'); t.dateTime='2026-07-17'; t.getAttribute('datetime')",
3361            "2026-07-17",
3362        ),
3363        (
3364            "var i=document.createElement('ins'); i.setAttribute('datetime','2026-07-17'); i.dateTime",
3365            "2026-07-17",
3366        ),
3367        // `video.disablePictureInPicture`/`iframe.allowFullscreen`(単純な
3368        // ブール属性反映だが丸ごと未対応だった。2026-07-17 発見・実装)。
3369        (
3370            "document.createElement('video').disablePictureInPicture",
3371            "false",
3372        ),
3373        (
3374            "var v=document.createElement('video'); v.disablePictureInPicture=true; v.getAttribute('disablepictureinpicture')",
3375            "disablepictureinpicture",
3376        ),
3377        (
3378            "var f=document.createElement('iframe'); f.allowFullscreen=true; f.getAttribute('allowfullscreen')",
3379            "allowfullscreen",
3380        ),
3381        // `canvas.width`/`.height`(欠落・不正値時の既定値`300`/`150`)と
3382        // `img`/`video.width`/`.height`(既定値`0`)が丸ごと未対応
3383        // だった。2026-07-17 発見・実装)。
3384        (
3385            "document.createElement('canvas').width+','+document.createElement('canvas').height",
3386            "300,150",
3387        ),
3388        (
3389            "var c=document.createElement('canvas'); c.width=640; c.height=480; c.width+','+c.height",
3390            "640,480",
3391        ),
3392        (
3393            "document.createElement('img').width+','+document.createElement('img').height",
3394            "0,0",
3395        ),
3396        (
3397            "var v=document.createElement('video'); v.width=320; v.width",
3398            "320",
3399        ),
3400        // `embed`/`object`/`iframe.width`/`.height`(`DOMString`型の単純な
3401        // 文字列反映。`canvas`/`img`/`video`の`unsigned long`型とは異なり
3402        // 数値クランプ・既定値フォールバックが無い。丸ごと未対応
3403        // だった。2026-07-17 発見・実装)。
3404        (
3405            "var e=document.createElement('embed'); e.width='50%'; e.width",
3406            "50%",
3407        ),
3408        (
3409            "var o=document.createElement('object'); o.height='300'; o.height",
3410            "300",
3411        ),
3412        ("document.createElement('iframe').width", ""),
3413        (
3414            "var f=document.createElement('iframe'); f.setAttribute('width','640'); f.width",
3415            "640",
3416        ),
3417        // `input`/`textarea.defaultValue`(`value`属性/初期テキストを
3418        // 反映)と`input.defaultChecked`/`option.defaultSelected`(`checked`/
3419        // `selected`属性の有無を反映するブール型)が丸ごと未対応
3420        // だった。2026-07-17 発見・実装)。
3421        (
3422            "var i=document.createElement('input'); i.defaultValue='hi'; i.getAttribute('value')",
3423            "hi",
3424        ),
3425        (
3426            "var i=document.createElement('input'); i.setAttribute('value','preset'); i.defaultValue",
3427            "preset",
3428        ),
3429        (
3430            "var i=document.createElement('input'); i.setAttribute('value','preset'); i.value='changed'; i.defaultValue",
3431            "preset",
3432        ),
3433        (
3434            "var i=document.createElement('input'); i.setAttribute('value','preset'); i.value='changed'; i.value",
3435            "changed",
3436        ),
3437        (
3438            "document.createElement('input').defaultChecked",
3439            "false",
3440        ),
3441        (
3442            "var i=document.createElement('input'); i.defaultChecked=true; i.getAttribute('checked')",
3443            "checked",
3444        ),
3445        (
3446            "var i=document.createElement('input'); i.defaultChecked=true; i.checked=false; i.defaultChecked",
3447            "true",
3448        ),
3449        (
3450            "document.createElement('option').defaultSelected",
3451            "false",
3452        ),
3453        (
3454            "var o=document.createElement('option'); o.defaultSelected=true; o.getAttribute('selected')",
3455            "selected",
3456        ),
3457        // `output.type`(仕様上、属性値に関わらず常に固定文字列
3458        // `"output"`を返す読み取り専用IDLプロパティ。`<output>`要素
3459        // 自体が丸ごと未対応だった。2026-07-17 発見・実装)。
3460        ("document.createElement('output').type", "output"),
3461        (
3462            "var o=document.createElement('output'); o.setAttribute('type','bogus'); o.type",
3463            "output",
3464        ),
3465        // `iframe.srcdoc`(単純な文字列反映だが丸ごと未対応だった。
3466        // 2026-07-17 発見・実装)。
3467        (
3468            "var f=document.createElement('iframe'); f.srcdoc='<p>hi</p>'; f.getAttribute('srcdoc')",
3469            "<p>hi</p>",
3470        ),
3471        (
3472            "var f=document.createElement('iframe'); f.setAttribute('srcdoc','<b>x</b>'); f.srcdoc",
3473            "<b>x</b>",
3474        ),
3475        // `li.value`(順序リストの番号上書き用`long`型IDLプロパティ。
3476        // 欠落・不正値時の既定値は`0`。丸ごと未対応だった。汎用の`value`
3477        // アームだと数値ではなく文字列になってしまう衝突があった。
3478        // 2026-07-17 発見・実装)。
3479        ("document.createElement('li').value", "0"),
3480        (
3481            "var l=document.createElement('li'); l.value=5; l.value",
3482            "5",
3483        ),
3484        (
3485            "var l=document.createElement('li'); l.setAttribute('value','bogus'); l.value",
3486            "0",
3487        ),
3488        // `input.pattern`(`placeholder`と同じ単純な文字列反映だが丸ごと
3489        // 未対応だった。2026-07-17 発見・実装)。
3490        (
3491            "var i=document.createElement('input'); i.pattern='[0-9]+'; i.getAttribute('pattern')",
3492            "[0-9]+",
3493        ),
3494        (
3495            "var i=document.createElement('input'); i.setAttribute('pattern','[a-z]+'); i.pattern",
3496            "[a-z]+",
3497        ),
3498        // `input.capture`(`type="file"`のカメラ/マイク取り込みヒント。
3499        // 単純な文字列反映だが丸ごと未対応だった。2026-07-17 発見・実装)。
3500        (
3501            "var i=document.createElement('input'); i.capture='environment'; i.getAttribute('capture')",
3502            "environment",
3503        ),
3504        (
3505            "var i=document.createElement('input'); i.setAttribute('capture','user'); i.capture",
3506            "user",
3507        ),
3508        // `input.autocomplete`(単純な文字列反映)と`element.autofocus`
3509        // (ブール型。実際のフォーカス自動移動機構は無いため属性値の
3510        // 反映のみ)が丸ごと未対応だった。2026-07-18 発見・実装)。
3511        (
3512            "var i=document.createElement('input'); i.autocomplete='email'; i.getAttribute('autocomplete')",
3513            "email",
3514        ),
3515        (
3516            "var i=document.createElement('input'); i.setAttribute('autocomplete','off'); i.autocomplete",
3517            "off",
3518        ),
3519        (
3520            "document.createElement('input').autofocus",
3521            "false",
3522        ),
3523        (
3524            "var i=document.createElement('input'); i.autofocus=true; i.getAttribute('autofocus')",
3525            "autofocus",
3526        ),
3527        (
3528            "var i=document.createElement('input'); i.min='0'; i.max='10'; i.step='2'; i.min+','+i.max+','+i.step",
3529            "0,10,2",
3530        ),
3531        (
3532            "var i=document.createElement('input'); i.maxLength='5'; i.maxLength",
3533            "5",
3534        ),
3535        // `maxLength`/`minLength`はWebIDL上`long`(数値型)のIDL属性で、無指定時
3536        // の既定値は`-1`だが、以前は`maxLength`が文字列型(属性が無ければ空
3537        // 文字列)で返っており、`minLength`はゲッター自体が丸ごと未対応だった
3538        // (`min`/`max`/`step`は`DOMString`型が正しいIDL定義のため対象外)。
3539        (
3540            "typeof document.createElement('input').maxLength",
3541            "number",
3542        ),
3543        (
3544            "document.createElement('input').maxLength",
3545            "-1",
3546        ),
3547        (
3548            "var i=document.createElement('input'); i.minLength=3; typeof i.minLength + ',' + i.minLength",
3549            "number,3",
3550        ),
3551        (
3552            "document.createElement('textarea').minLength",
3553            "-1",
3554        ),
3555        (
3556            "var a=document.createElement('a'); a.target='_blank'; a.rel='noopener'; a.download='f.txt'; a.target+','+a.rel+','+a.download",
3557            "_blank,noopener,f.txt",
3558        ),
3559        (
3560            "var i=document.createElement('input'); i.placeholder='enter name'; i.placeholder",
3561            "enter name",
3562        ),
3563        (
3564            "var d=document.createElement('details'); d.open",
3565            "false",
3566        ),
3567        (
3568            "var d=document.createElement('details'); d.open=true; d.open",
3569            "true",
3570        ),
3571        (
3572            "var d=document.createElement('details'); d.setAttribute('open',''); d.open",
3573            "true",
3574        ),
3575        (
3576            "var i=document.createElement('input'); i.name='email'; i.name",
3577            "email",
3578        ),
3579        // `select.value`/`select.selectedIndex` が丸ごと未対応だった。`<select>` は
3580        // 自身に `value` 属性を持たず、子の `<option selected>` が選択状態を持つため、
3581        // 汎用の属性直結 getter では常に空文字列になっていた。
3582        (
3583            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.value",
3584            "a",
3585        ),
3586        (
3587            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.value='b'; s.value",
3588            "b",
3589        ),
3590        (
3591            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.value='b'; s.selectedIndex",
3592            "1",
3593        ),
3594        (
3595            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.selectedIndex=1; s.value",
3596            "b",
3597        ),
3598        // `option.index`(`select.selectedIndex`と対になる、`<option>`側の0始まり
3599        // 位置を返すIDL属性)が丸ごと未対応だった。
3600        (
3601            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.options[1].index",
3602            "1",
3603        ),
3604        (
3605            "document.createElement('option').index",
3606            "0",
3607        ),
3608        // `select.length`(`select.options.length`と同じ値を返すIDL属性、および
3609        // 末尾optionを切り捨てる/空optionで埋めるセッター)が丸ごと未対応だった。
3610        (
3611            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.length",
3612            "2",
3613        ),
3614        (
3615            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.length=1; s.options.length + ',' + s.value",
3616            "1,a",
3617        ),
3618        (
3619            "var s=document.createElement('select'); s.length=2; s.options.length",
3620            "2",
3621        ),
3622        // `select.remove(index)`(`HTMLSelectElement`固有のオーバーロード。
3623        // 指定インデックスのoptionを削除する)が、同名の`ChildNode.remove()`
3624        // (引数無しで自身を親から削除する)と混同され、`select.remove(1)`が
3625        // 常にselect自身を親ごと削除してしまうバグだった。
3626        (
3627            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.remove(0); s.options.length + ',' + s.value",
3628            "1,b",
3629        ),
3630        (
3631            // 引数無しの `.remove()` は従来どおり自身を親から削除する
3632            // (ChildNode.remove()と同じ挙動を維持)。
3633            "var d=document.createElement('div'); var s=document.createElement('select'); d.appendChild(s); s.remove(); d.children.length",
3634            "0",
3635        ),
3636        (
3637            // 負のインデックスはno-op(`as usize`の飽和変換で誤って先頭が
3638            // 削除されるバグを防いだ確認)。
3639            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option>\"; s.remove(-1); s.options.length",
3640            "1",
3641        ),
3642        // `textarea.value` も同種のバグだった(`<textarea>` の初期値は本来テキスト
3643        // ノードの内容で表現されるべきだが、`.value` 書込み後の読み出し往復は
3644        // 最低限このとおり動くことを確認する)。
3645        (
3646            "var t=document.createElement('textarea'); t.value='hi'; t.value",
3647            "hi",
3648        ),
3649        ("typeof console.table", "function"),
3650        ("typeof globalThis", "object"),
3651        ("typeof globalThis.document", "object"),
3652        // fetch(data: URL でネット非依存にフルチェーン検証: Promise→Response→text()/json())
3653        ("typeof fetch", "function"),
3654        ("(await fetch('data:,hello')).status", "200"),
3655        ("(await fetch('data:,hello')).ok", "true"),
3656        ("await (await fetch('data:,hello world')).text()", "hello world"),
3657        ("await fetch('data:text/plain,abc').then(r => r.text())", "abc"),
3658        ("(await (await fetch('data:application/json,{\"a\":7,\"b\":[1,2]}')).json()).a", "7"),
3659        ("(await (await fetch('data:application/json,{\"a\":7,\"b\":[1,2]}')).json()).b.length", "2"),
3660        // `Response.prototype.blob()`(丸ごと未対応で `blob is not defined` だった)。
3661        ("(await (await fetch('data:,hello')).blob()).size", "5"),
3662        ("await (await (await fetch('data:,hello')).blob()).text()", "hello"),
3663        // `Response.prototype.arrayBuffer()` も同種の欠落だった。実バイト列付きの
3664        // ArrayBuffer で解決するため、そこから作った DataView で正しく読み戻せる。
3665        (
3666            "var buf = await (await fetch('data:,A')).arrayBuffer(); new DataView(buf).getUint8(0)",
3667            "65",
3668        ),
3669        ("await fetch('ftp://x/y').then(() => 'ok', e => 'err:' + (e.message || e))", "err:fetch: only absolute http(s) and data URLs are supported"),
3670        // `Response.prototype.statusText` が丸ごと未対応で常に `undefined` だった
3671        // (エラーログの定番パターン `console.log(response.status, response.statusText)`
3672        // で参照される)。
3673        ("(await fetch('data:,hi')).statusText", "OK"),
3674        // `Response.prototype.clone()`(`response.clone().json()` のように本文を
3675        // 2回読みたい場合に使う定番パターン)が丸ごと未対応で `clone is not a
3676        // function` だった。
3677        (
3678            "var r = await fetch('data:,hello'); var c = r.clone(); (await c.text()) + ',' + (await r.text())",
3679            "hello,hello",
3680        ),
3681        ("(await fetch('data:,x')).clone().status", "200"),
3682        // `Response.prototype.url`(リダイレクト後の最終 URL 確認等で使われる
3683        // 定番プロパティ)が丸ごと未対応で常に `undefined` だった。
3684        (
3685            "(await fetch('data:,hi')).url",
3686            "data:,hi",
3687        ),
3688        // `Response` コンストラクタ自体が丸ごと未対応だった(`fetch()` 内部からしか
3689        // 生成できず、`new Response(...)` が呼び出すと TypeError になっていた)。
3690        ("new Response('hi').status", "200"),
3691        ("await new Response('hi', {status: 201}).text()", "hi"),
3692        (
3693            "(await new Response(JSON.stringify({a:1}), {headers:{'content-type':'application/json'}}).json()).a",
3694            "1",
3695        ),
3696        // `Response.json(data, init)`(ES2022 static factory)。
3697        ("(await Response.json({a:5}).json()).a", "5"),
3698        ("Response.json({}).headers.get('content-type')", "application/json"),
3699        // `Response.error()`。
3700        ("Response.error().ok", "false"),
3701        ("Response.error().type", "error"),
3702        // `Response.redirect(url, status?)`。
3703        ("Response.redirect('https://e.com/x').status", "302"),
3704        ("Response.redirect('https://e.com/x', 301).headers.get('location')", "https://e.com/x"),
3705        (
3706            "(await fetch('data:,x')).clone().url",
3707            "data:,x",
3708        ),
3709        // `fetch(url, {signal})` が丸ごと未対応で、既に aborted な signal を渡しても
3710        // 常にリクエストを実行してしまっていた。
3711        (
3712            "await fetch('data:,hi', {signal: AbortSignal.abort('why')}).then(() => 'no-throw', e => 'caught:' + e)",
3713            "caught:why",
3714        ),
3715        (
3716            "await fetch('data:,hi', {signal: new AbortController().signal}).then(r => r.status)",
3717            "200",
3718        ),
3719        // `new Request(url, options)` が丸ごと未対応で `Request is not defined`
3720        // だった。`fetch(request)` という定番パターンも合わせて確認する。
3721        ("new Request('data:,hi').url", "data:,hi"),
3722        ("new Request('data:,hi', {method:'POST'}).method", "POST"),
3723        // `request.headers` が仕様上常に本物の `Headers` インスタンスであるべき
3724        // ところ、生の値をそのまま格納するだけで `.get(...)` が壊れていた。
3725        (
3726            "new Request('data:,hi', {headers:{'X-Foo':'bar'}}).headers.get('x-foo')",
3727            "bar",
3728        ),
3729        ("typeof new Request('data:,hi').headers.get", "function"),
3730        // `response.type`/`.redirected` が丸ごと未対応で常に `undefined` だった。
3731        ("(await fetch('data:,hi')).type", "basic"),
3732        ("(await fetch('data:,hi')).redirected", "false"),
3733        ("await fetch(new Request('data:,hello')).then(r => r.text())", "hello"),
3734        (
3735            "await fetch(new Request('data:,x'), {method:'GET'}).then(r => r.status)",
3736            "200",
3737        ),
3738        // `Headers`(Fetch API の get/set/has/append/delete 付き専用クラス)が
3739        // 丸ごと未対応だった。ヘッダ名は大小無視で正規化される。
3740        ("new Headers({'Content-Type':'text/plain'}).get('content-type')", "text/plain"),
3741        // `[[k,v], ...]` 配列形式の init(`make_headers` の `is_array` 分岐。
3742        // 2026-07-14、clippy `single_match` 修正時に既存テストで一度もこの
3743        // 分岐が通っていなかったことに気づき追加)。
3744        ("new Headers([['A','1'],['b','2']]).get('a')", "1"),
3745        ("new Headers().has('x')", "false"),
3746        (
3747            "var h=new Headers(); h.set('X-Foo','1'); h.has('x-foo')",
3748            "true",
3749        ),
3750        (
3751            "var h=new Headers(); h.append('a','1'); h.append('a','2'); h.get('a')",
3752            "1, 2",
3753        ),
3754        (
3755            "var h=new Headers({a:'1'}); h.delete('a'); h.has('a')",
3756            "false",
3757        ),
3758        (
3759            "var h=new Headers({a:'1',b:'2'}); var s=''; h.forEach(function(v,k){s+=k+'='+v+';'}); s",
3760            "a=1;b=2;",
3761        ),
3762        // `entries`/`keys`/`values`/`Symbol.iterator`(`for (const [k,v] of headers)`
3763        // という定番イディオムを含む)が丸ごと未対応だった。
3764        (
3765            "Array.from(new Headers({b:'2',a:'1'}).entries()).map(function(e){return e[0]+'='+e[1]}).join(',')",
3766            "a=1,b=2",
3767        ),
3768        ("Array.from(new Headers({a:'1',b:'2'}).keys()).join(',')", "a,b"),
3769        ("Array.from(new Headers({a:'1',b:'2'}).values()).join(',')", "1,2"),
3770        (
3771            "var s=''; for (var e of new Headers({a:'1',b:'2'})) { s += e[0]+e[1]; } s",
3772            "a1b2",
3773        ),
3774        // `response.headers` が丸ごと欠落しておらず、本物の `Headers`(メソッド持ち)
3775        // が返ること。この処理系はレスポンスヘッダを捕捉していないため中身は
3776        // 常に空になる。
3777        (
3778            "typeof (await fetch('data:,hi')).headers.get",
3779            "function",
3780        ),
3781        // `xhr.getResponseHeader(name)` が丸ごと未対応で「関数ではない」の
3782        // TypeError になっていた。
3783        (
3784            "var x=new XMLHttpRequest(); x.open('GET','data:,hi'); x.send(); x.getResponseHeader('x') === null",
3785            "true",
3786        ),
3787        // XMLHttpRequest(同期。data: でオフライン検証)
3788        ("typeof XMLHttpRequest", "function"),
3789        ("var x=new XMLHttpRequest(); x.open('GET','data:,hi'); x.send(); x.responseText", "hi"),
3790        ("var x=new XMLHttpRequest(); x.open('GET','data:,hi'); x.send(); x.status", "200"),
3791        ("var x=new XMLHttpRequest(); x.open('GET','data:,hi'); x.send(); x.readyState", "4"),
3792        // `xhr.statusText` がコンストラクタで空文字列初期化されたきり `send()` 完了後も
3793        // 一切更新されないバグだった(`Response.prototype.statusText` と同種)。
3794        ("var x=new XMLHttpRequest(); x.open('GET','data:,hi'); x.send(); x.statusText", "OK"),
3795        ("var g=''; var x=new XMLHttpRequest(); x.onload=function(){g=x.responseText}; x.open('GET','data:,YO'); x.send(); g", "YO"),
3796        ("var g=''; var x=new XMLHttpRequest(); x.addEventListener('load',function(){g='L'+x.status}); x.open('GET','data:,q'); x.send(); g", "L200"),
3797        // `xhr.removeEventListener` が丸ごと未対応で、呼び出すと「関数ではない」の
3798        // TypeError になっていた。
3799        (
3800            "var g=''; var x=new XMLHttpRequest(); var fn=function(){g='L'}; x.addEventListener('load',fn); x.removeEventListener('load',fn); x.open('GET','data:,q'); x.send(); g",
3801            "",
3802        ),
3803        (
3804            "typeof new XMLHttpRequest().removeEventListener",
3805            "function",
3806        ),
3807        ("var x=new XMLHttpRequest(); x.open('GET','data:application/json,{\"n\":5}'); x.send(); JSON.parse(x.responseText).n", "5"),
3808        // fetch/XHR の POST オプション配線(data: は method 非依存で本文を返す=経路確認)
3809        ("await (await fetch('data:,hi', {method:'POST', body:'x'})).text()", "hi"),
3810        ("(await fetch('data:,ok', {method:'POST', headers:{'Content-Type':'application/json'}, body:'{}'})).status", "200"),
3811        ("var x=new XMLHttpRequest(); x.open('POST','data:,ok'); x.setRequestHeader('Content-Type','application/json'); x.send('{\"a\":1}'); x.responseText", "ok"),
3812        ("var x=new XMLHttpRequest(); x.open('POST','data:,z'); x.send(); x.status", "200"),
3813        // RegExp
3814        ("/\\d+/.test('abc123')", "true"),
3815        ("/^\\d+$/.test('abc')", "false"),
3816        ("'2026-06-15'.match(/(\\d+)-(\\d+)-(\\d+)/)[2]", "06"),
3817        ("'hello world'.replace(/o/g, '0')", "hell0 w0rld"),
3818        ("'a1b2c3'.replace(/\\d/g, m => '[' + m + ']')", "a[1]b[2]c[3]"),
3819        ("'one,two;three four'.split(/[,; ]/).join('|')", "one|two|three|four"),
3820        ("'foobarbaz'.match(/a/g).length", "2"),
3821        ("new RegExp('[A-Z]+').test('hello WORLD')", "true"),
3822        ("'Hello'.replace(/(\\w)(\\w+)/, '$2$1')", "elloH"),
3823        ("'price: $42'.match(/\\$(\\d+)/)[1]", "42"),
3824        ("/cat/i.test('CAT')", "true"),
3825        ("'a,b,,c'.split(/,/).length", "4"),
3826        // RegExp `y`(sticky)フラグ(ES2015): lastIndex の位置でのみマッチを試みる
3827        // (`g` と違い前方探索しない)。dotAll/sticky ゲッタープロパティも合わせて検証。
3828        ("/x/y.sticky", "true"),
3829        ("/x/g.sticky", "false"),
3830        ("/./s.dotAll", "true"),
3831        ("/./.dotAll", "false"),
3832        (
3833            "var re=/foo/y; re.lastIndex=3; re.test('xxxfoo')",
3834            "true",
3835        ),
3836        (
3837            "var re=/foo/y; re.lastIndex=2; re.test('xxxfoo')",
3838            "false",
3839        ),
3840        (
3841            "var re=/foo/y; var a=re.exec('foofoo').index; var b=re.exec('foofoo').index; a+','+b",
3842            "0,3",
3843        ),
3844        // RegExp `d`(hasIndices)フラグ(ES2022): exec() 結果に各キャプチャの
3845        // [start,end) 文字インデックスを持つ indices 配列を追加する。
3846        ("/x/d.hasIndices", "true"),
3847        ("/x/.hasIndices", "false"),
3848        (
3849            "var m=/(foo)(bar)/d.exec('xxfoobar'); m.indices[0].join(',')",
3850            "2,8",
3851        ),
3852        (
3853            "var m=/(foo)(bar)/d.exec('xxfoobar'); m.indices[1].join(',')+'/'+m.indices[2].join(',')",
3854            "2,5/5,8",
3855        ),
3856        (
3857            "var m=/(foo)(baz)?/d.exec('foo'); typeof m.indices[2]",
3858            "undefined",
3859        ),
3860        ("/x/.exec('x').indices", "undefined"),
3861        // setInterval: 発火確認は同一 eval 内では行えない(コールバックは
3862        // event_loop の次 tick で発火するため、登録直後は n=0 が正しい)。
3863        // 複数回発火の検証は下の「2段階評価テスト」で行う。
3864        // clearInterval: 登録直後にクリアすれば未発火のまま n=0。
3865        ("var n=0; var id=setInterval(function(){n++;}, 10); clearInterval(id); n", "0"),
3866        // window.innerWidth / innerHeight
3867        ("typeof window.innerWidth", "number"),
3868        ("typeof window.innerHeight", "number"),
3869        // XMLHttpRequest 任意メソッド(PUT/DELETE/PATCH)。data: URL は同期・無ネットワークで
3870        // メソッド非依存に 200+ボディを返すため、do_http_request→web_request 経路が
3871        // GET/POST 以外でも例外なく通ることを決定論的に検証できる。
3872        ("var x=new XMLHttpRequest(); x.open('PUT','data:text/plain,HELLO'); x.send('body'); x.status", "200"),
3873        ("var x=new XMLHttpRequest(); x.open('PUT','data:text/plain,HELLO'); x.send('body'); x.responseText", "HELLO"),
3874        ("var x=new XMLHttpRequest(); x.open('DELETE','data:text/plain,GONE'); x.send(); x.status", "200"),
3875        ("var x=new XMLHttpRequest(); x.open('DELETE','data:text/plain,GONE'); x.send(); x.responseText", "GONE"),
3876        ("var x=new XMLHttpRequest(); x.open('PATCH','data:text/plain,Pdata'); x.send('d'); x.responseText", "Pdata"),
3877        // 小文字メソッドも web_request 側で大文字正規化され通ること。
3878        ("var x=new XMLHttpRequest(); x.open('put','data:text/plain,low'); x.send('b'); x.responseText", "low"),
3879        // history.back/forward/go: ナビ要求を history._pending_nav(相対移動量)に積む。
3880        // 実ナビはホスト側 process_pending_nav が回収・実行するため、ここでは積算値を検証。
3881        ("history.back(); history._pending_nav", "-1"),
3882        ("history.forward(); history._pending_nav", "1"),
3883        ("history.go(-2); history._pending_nav", "-2"),
3884        ("history.back(); history.back(); history._pending_nav", "-2"),
3885        ("history.go(0); history._pending_nav || 0", "0"),
3886        // popstate リスナ登録は window の隠し配列へ蓄積される(発火はホスト主導)。
3887        ("window.addEventListener('popstate', function(){}); window._popstate_listeners.length", "1"),
3888        // scroll リスナも window の隠し配列 _scroll_listeners へ蓄積される(発火はホスト主導)。
3889        ("window.addEventListener('scroll', function(){}); window._scroll_listeners.length", "1"),
3890        ("window.addEventListener('scroll', function(){}); window.addEventListener('scroll', function(){}); window._scroll_listeners.length", "2"),
3891        // window.scrollY 初期値は 0(ホストが fire_scroll で更新する)。
3892        ("window.scrollY", "0"),
3893        // `window.postMessage()`(丸ごと未対応だった。フレーム分離が無いため
3894        // 同一 window 上の 'message' リスナへ同期配送する簡略実装)。
3895        (
3896            "var got=null; window.addEventListener('message', function(e){ got=e.data; }); \
3897             window.postMessage({x:1}); got.x",
3898            "1",
3899        ),
3900        (
3901            "var t=null; window.addEventListener('message', function(e){ t=e.type; }); \
3902             window.postMessage('hi'); t",
3903            "message",
3904        ),
3905        // `window.addEventListener(type, fn, {signal})`(丸ごと未対応だった。
3906        // `AbortController` でリスナを一括解除する定番パターン。2026-07-14
3907        // 発見・実装)。abort 後は発火しなくなる。
3908        (
3909            "var c=new AbortController(); var n=0; \
3910             window.addEventListener('message', function(){n++;}, {signal: c.signal}); \
3911             window.postMessage('a'); c.abort(); window.postMessage('b'); n",
3912            "1",
3913        ),
3914        // 登録前に既に abort 済みの signal を渡した場合は登録自体が行われない。
3915        (
3916            "var c=new AbortController(); c.abort(); var n=0; \
3917             window.addEventListener('message', function(){n++;}, {signal: c.signal}); \
3918             window.postMessage('a'); n",
3919            "0",
3920        ),
3921        // ---- Proxy / Reflect ----
3922        // get トラップ: プロパティ読み取りを横取りする。
3923        ("var p=new Proxy({}, {get:function(t,k){return 'G:'+k;}}); p.foo", "G:foo"),
3924        // get トラップ未定義なら target へフォワード。
3925        ("var p=new Proxy({a:42}, {}); p.a", "42"),
3926        // set トラップ: 書き込みを横取りして target に別キーで格納。
3927        ("var log=''; var p=new Proxy({}, {set:function(t,k,v){t['_'+k]=v;return true;}}); p.x=9; p._x", "9"),
3928        // set トラップ未定義なら target に直接書く。
3929        ("var p=new Proxy({}, {}); p.y=7; p.y", "7"),
3930        // has トラップ: in 演算子を横取り。
3931        ("var p=new Proxy({}, {has:function(t,k){return k==='magic';}}); ('magic' in p)", "true"),
3932        ("var p=new Proxy({}, {has:function(t,k){return k==='magic';}}); ('other' in p)", "false"),
3933        // has トラップ未定義なら target の存在判定にフォワード。
3934        ("var p=new Proxy({z:1}, {}); ('z' in p)", "true"),
3935        ("var p=new Proxy({z:1}, {}); ('w' in p)", "false"),
3936        // deleteProperty トラップ: `delete p.prop` を横取り。以前はここが未対応で、
3937        // トラップ呼び出しはおろか target へのフォワードすら行われず常に何も削除せず
3938        // `true` を返すだけの黙殺バグだった。
3939        (
3940            "var log=''; var p=new Proxy({a:1}, {deleteProperty:function(t,k){log+=k; delete t[k]; return true;}}); delete p.a; log + ':' + ('a' in p)",
3941            "a:false",
3942        ),
3943        // deleteProperty トラップ未定義なら target への delete にフォワードする。
3944        ("var p=new Proxy({a:1}, {}); delete p.a; ('a' in p)", "false"),
3945        // `Index`(`delete p['a']`)経由でも同じくフォワードされる。
3946        ("var p=new Proxy({a:1}, {}); delete p['a']; ('a' in p)", "false"),
3947        // `Object.defineProperty`/`Object.getOwnPropertyDescriptor` も Proxy 自身の
3948        // 意味の無い `.props` に書き込む/読み取るだけで target に一切反映・反射されない
3949        // バグだった。target フォワードで対応(trap 呼び出しまでは非対応)。
3950        (
3951            "var t={}; var p=new Proxy(t, {}); Object.defineProperty(p, 'x', {value:5}); t.x",
3952            "5",
3953        ),
3954        (
3955            "var p=new Proxy({x:5}, {}); Object.getOwnPropertyDescriptor(p, 'x').value",
3956            "5",
3957        ),
3958        // apply トラップ: 関数呼び出しを横取り。
3959        ("var f=function(a,b){return a+b;}; var p=new Proxy(f, {apply:function(t,thiz,args){return args[0]*args[1];}}); p(3,4)", "12"),
3960        // apply トラップ未定義なら target を普通に呼ぶ。
3961        ("var f=function(a,b){return a+b;}; var p=new Proxy(f, {}); p(3,4)", "7"),
3962        // construct トラップ: new を横取り。
3963        ("function C(){} var p=new Proxy(C, {construct:function(t,args){return {made:args[0]};}}); (new p(99)).made", "99"),
3964        // construct トラップ未定義なら target を new する。
3965        ("function C(x){this.v=x;} var p=new Proxy(C, {}); (new p(5)).v", "5"),
3966        // Reflect.get / set
3967        ("var o={a:1}; Reflect.set(o,'a',8); Reflect.get(o,'a')", "8"),
3968        // Reflect.has
3969        ("Reflect.has({k:1},'k')", "true"),
3970        ("Reflect.has({k:1},'x')", "false"),
3971        // Reflect.deleteProperty
3972        ("var o={a:1,b:2}; Reflect.deleteProperty(o,'a'); ('a' in o)", "false"),
3973        // Reflect.ownKeys
3974        ("Reflect.ownKeys({a:1,b:2,c:3}).length", "3"),
3975        // Reflect.defineProperty
3976        ("var o={}; Reflect.defineProperty(o,'x',{value:42}); o.x", "42"),
3977        // Reflect.apply
3978        ("Reflect.apply(function(a,b){return a-b;}, null, [10,3])", "7"),
3979        // Reflect.apply/construct の argumentsList は仕様上 array-like 全般
3980        // (実配列に限らない)を受け付けるべきだが、以前は `&mut Interp` を持たない
3981        // `iterable_values` を使っており、素の array-like を渡すと引数が消えていた。
3982        (
3983            "Reflect.apply(function(a,b){return a-b;}, null, {0:10,1:3,length:2})",
3984            "7",
3985        ),
3986        // Reflect.construct
3987        ("function P(x){this.x=x;} Reflect.construct(P,[77]).x", "77"),
3988        (
3989            "function P(x){this.x=x;} Reflect.construct(P,{0:77,length:1}).x",
3990            "77",
3991        ),
3992        // Reflect.get がプロトタイプ連鎖を辿る。
3993        ("var base={greet:'hi'}; var o=Object.create(base); Reflect.get(o,'greet')", "hi"),
3994        // Proxy + Reflect 併用: get をログしつつ target にフォワード。
3995        ("var p=new Proxy({n:5}, {get:function(t,k){return Reflect.get(t,k)*2;}}); p.n", "10"),
3996        // 配列を包む Proxy の get(数値インデックス)。
3997        ("var p=new Proxy([10,20,30], {get:function(t,k){return Reflect.get(t,k);}}); p[1]", "20"),
3998        // ネストした Proxy(二重ラップ)。
3999        ("var inner=new Proxy({v:1},{get:function(t,k){return 100;}}); var outer=new Proxy(inner,{}); outer.v", "100"),
4000        // WeakMap
4001        ("var wm=new WeakMap(); var k={}; wm.set(k,42); wm.get(k)", "42"),
4002        ("var wm=new WeakMap(); var k={}; wm.set(k,1); wm.has(k)", "true"),
4003        ("var wm=new WeakMap(); var k={}; wm.set(k,1); wm.delete(k); wm.has(k)", "false"),
4004        // WeakSet
4005        ("var ws=new WeakSet(); var o={}; ws.add(o); ws.has(o)", "true"),
4006        ("var ws=new WeakSet(); var o={}; ws.add(o); ws.delete(o); ws.has(o)", "false"),
4007        // `WeakMap`/`WeakSet` は仕様上オブジェクト以外のキー/値を拒否し `TypeError` を
4008        // 投げるべきだが、以前は型を一切検証せず静かに成功していたバグ。
4009        (
4010            "try { new WeakMap().set(1,'x'); 'no-throw' } catch(e) { 'caught' }",
4011            "caught",
4012        ),
4013        (
4014            "try { new WeakSet().add('x'); 'no-throw' } catch(e) { 'caught' }",
4015            "caught",
4016        ),
4017        // `new WeakMap(iterable)`/`new WeakSet(iterable)` の初期化引数が `Map` と違い
4018        // 完全に無視されていた(常に空になるバグ)。
4019        ("var k1={}; var wm=new WeakMap([[k1,1]]); wm.get(k1)", "1"),
4020        ("var o1={}; var ws=new WeakSet([o1]); ws.has(o1)", "true"),
4021        // WeakRef
4022        ("var o={v:7}; var wr=new WeakRef(o); wr.deref().v", "7"),
4023        // Symbol 関数(AtmOS では文字列近似)
4024        ("typeof Symbol('x')", "string"),
4025        ("Symbol('a') === Symbol('a')", "false"),
4026        ("Symbol.for('x') === Symbol.for('x')", "true"),
4027        // `Symbol.keyFor`(`Symbol.for` の逆引き)が丸ごと未対応だった。
4028        ("Symbol.keyFor(Symbol.for('mykey'))", "mykey"),
4029        ("typeof Symbol.keyFor(Symbol('local'))", "undefined"),
4030        ("typeof Symbol.iterator", "string"),
4031        // MutationObserver — typeof で存在確認。
4032        ("typeof MutationObserver", "function"),
4033        // MutationObserver — コンストラクタが呼べる。
4034        ("var mo = new MutationObserver(function(){}); typeof mo.observe", "function"),
4035        // MutationObserver — disconnect と takeRecords が存在する。
4036        ("var mo = new MutationObserver(function(){}); typeof mo.disconnect + ',' + typeof mo.takeRecords", "function,function"),
4037        // MutationObserver — takeRecords は初期状態で空配列。
4038        ("var mo = new MutationObserver(function(){}); mo.takeRecords().length", "0"),
4039        // MutationObserver — attributeOldValue が丸ごと未対応で oldValue が常に
4040        // null 固定だったバグ(以前は old_value 自体を捕捉していなかった)。
4041        (
4042            "var el=document.createElement('div'); el.setAttribute('foo','bar'); \
4043             var mo=new MutationObserver(function(){}); \
4044             mo.observe(el,{attributes:true,attributeOldValue:true}); \
4045             el.setAttribute('foo','baz'); var r=mo.takeRecords(); \
4046             r.length+','+r[0].attributeName+','+r[0].oldValue",
4047            "1,foo,bar",
4048        ),
4049        // MutationObserver — attributeFilter が丸ごと未対応で、フィルタ対象外の
4050        // 属性変化まで常に通知されてしまっていたバグ。
4051        (
4052            "var el=document.createElement('div'); \
4053             var mo=new MutationObserver(function(){}); \
4054             mo.observe(el,{attributes:true,attributeFilter:['foo']}); \
4055             el.setAttribute('bar','x'); el.setAttribute('foo','y'); \
4056             var r=mo.takeRecords(); r.length+','+r[0].attributeName",
4057            "1,foo",
4058        ),
4059        // MutationObserver — removeAttribute() が非対称に通知漏れしていたバグ
4060        // (setAttribute() は既に notify_attribute を呼んでいたが removeAttribute()
4061        // 側だけこの呼び出し自体が丸ごと欠けていた)。
4062        (
4063            "var el=document.createElement('div'); el.setAttribute('foo','bar'); \
4064             var mo=new MutationObserver(function(){}); \
4065             mo.observe(el,{attributes:true,attributeOldValue:true}); \
4066             el.removeAttribute('foo'); var r=mo.takeRecords(); \
4067             r.length+','+r[0].attributeName+','+r[0].oldValue",
4068            "1,foo,bar",
4069        ),
4070        // MutationObserver — classList.add()/.remove() が「class」属性を書き換える
4071        // にもかかわらず notify_attribute を一切呼んでおらず通知漏れしていたバグ
4072        // (setAttribute('class',...)/removeAttribute() 経由の変更は通知されるのに
4073        // classList 経由だけ抜けていた非対称ペア)。
4074        (
4075            "var el=document.createElement('div'); el.className='a'; \
4076             var mo=new MutationObserver(function(){}); \
4077             mo.observe(el,{attributes:true,attributeOldValue:true}); \
4078             el.classList.add('b'); var r=mo.takeRecords(); \
4079             r.length+','+r[0].attributeName+','+r[0].oldValue",
4080            "1,class,a",
4081        ),
4082        (
4083            "var el=document.createElement('div'); el.className='a b'; \
4084             var mo=new MutationObserver(function(){}); \
4085             mo.observe(el,{attributes:true,attributeOldValue:true}); \
4086             el.classList.remove('b'); var r=mo.takeRecords(); \
4087             r.length+','+r[0].attributeName+','+r[0].oldValue",
4088            "1,class,a b",
4089        ),
4090        // MutationObserver — style.xxx=/.cssText= が「style」属性を書き換える
4091        // にもかかわらず notify_attribute を一切呼んでいなかったバグ(classList と
4092        // 同型の非対称ペア)。
4093        (
4094            "var el=document.createElement('div'); el.style.color='red'; \
4095             var mo=new MutationObserver(function(){}); \
4096             mo.observe(el,{attributes:true,attributeOldValue:true}); \
4097             el.style.color='blue'; var r=mo.takeRecords(); \
4098             r.length+','+r[0].attributeName+','+r[0].oldValue",
4099            "1,style,color: red",
4100        ),
4101        // MutationObserver — 仕様上`attributeOldValue`/`attributeFilter`が
4102        // 指定され`attributes`自体が省略された場合は`attributes`を暗黙的に
4103        // `true`とみなすべきところ、この暗黙有効化ロジックが丸ごと無く
4104        // `observe(el,{attributeOldValue:true})`という定番の省略記法
4105        // (`attributes:true`を明示せずとも動くのが仕様)が黙って何も
4106        // 監視しないバグだった。2026-07-17 発見・実装。
4107        (
4108            "var el=document.createElement('div'); el.setAttribute('data-x','1'); \
4109             var mo=new MutationObserver(function(){}); \
4110             mo.observe(el,{attributeOldValue:true}); \
4111             el.setAttribute('data-x','2'); var r=mo.takeRecords(); \
4112             r.length+','+r[0].type",
4113            "1,attributes",
4114        ),
4115        // `attributeFilter`のみ指定(`attributes`省略)でも同様に暗黙有効化される。
4116        (
4117            "var el=document.createElement('div'); \
4118             var mo=new MutationObserver(function(){}); \
4119             mo.observe(el,{attributeFilter:['data-x']}); \
4120             el.setAttribute('data-x','1'); var r=mo.takeRecords(); \
4121             r.length",
4122            "1",
4123        ),
4124        // 【2026-10-18】以下の characterData の試験は、要素ではなくテキストノードの textContent を変える
4125        // (要素への代入は子の置き換えで childList になる。Chrome で確認)。
4126        // `characterDataOldValue`のみ指定(`characterData`省略)でも同様。
4127        (
4128            "var el=document.createElement('div'); el.textContent='hi'; \
4129             var mo=new MutationObserver(function(){}); \
4130             mo.observe(el,{characterDataOldValue:true, subtree:true}); \
4131             el.firstChild.textContent='bye'; var r=mo.takeRecords(); \
4132             r.length+','+r[0].type+','+r[0].oldValue",
4133            "1,characterData,hi",
4134        ),
4135        // `attributes:false`を明示している場合は暗黙有効化しない(明示指定を
4136        // 尊重する。`attributes_specified.is_none()`条件の裏側の確認)。
4137        (
4138            "var el=document.createElement('div'); el.setAttribute('data-x','1'); \
4139             var mo=new MutationObserver(function(){}); \
4140             mo.observe(el,{attributes:false, attributeOldValue:true}); \
4141             el.setAttribute('data-x','2'); var r=mo.takeRecords(); \
4142             r.length",
4143            "0",
4144        ),
4145        // MutationObserver — el.innerHTML=... が childList の変化を一切通知して
4146        // いなかったバグ(appendChild/removeChild/insertBefore/replaceChild は
4147        // 既に notify_child_list を呼んでいたのに、最も頻繁に使われる innerHTML
4148        // 代入だけがこの呼び出しを欠いていた)。
4149        (
4150            "var el=document.createElement('div'); \
4151             var mo=new MutationObserver(function(){}); \
4152             mo.observe(el,{childList:true}); \
4153             el.innerHTML='<span>hi</span>'; var r=mo.takeRecords(); \
4154             r.length+','+r[0].type+','+r[0].addedNodes.length",
4155            "1,childList,1",
4156        ),
4157        // 要素の textContent 再代入は子の置き換え(childList 1 件。Chrome と同じ)。
4158        (
4159            "var el=document.createElement('div'); el.textContent='hi'; \
4160             var mo=new MutationObserver(function(){}); \
4161             mo.observe(el,{childList:true}); \
4162             el.textContent='bye'; var r=mo.takeRecords(); \
4163             r.length+','+r[0].type+','+r[0].removedNodes.length+','+r[0].addedNodes.length",
4164            "1,childList,1,1",
4165        ),
4166        // MutationObserver — 子が1件も無い要素への textContent=... 代入
4167        // (新規テキストノードを1件追加する経路)も childList を通知していなかった。
4168        (
4169            "var el=document.createElement('div'); \
4170             var mo=new MutationObserver(function(){}); \
4171             mo.observe(el,{childList:true}); \
4172             el.textContent='hi'; var r=mo.takeRecords(); \
4173             r.length+','+r[0].type+','+r[0].addedNodes.length",
4174            "1,childList,1",
4175        ),
4176        // MutationObserver — `characterData`オプションが丸ごと未対応だった
4177        // (`childList`/`attributes`は既に対応済みで`characterData`だけ抜けて
4178        // いた兄弟ギャップ。テキストノードが既に存在する要素への
4179        // `textContent`再代入は`characterData`変化として通知されるべき
4180        // ところ、対応する通知経路自体が存在せず`observe(el,
4181        // {characterData:true})`を指定してもコールバックが一切呼ばれ
4182        // なかった。2026-07-17 発見・実装)。
4183        (
4184            "var el=document.createElement('div'); el.textContent='hi'; \
4185             var mo=new MutationObserver(function(){}); \
4186             mo.observe(el,{characterData:true, subtree:true}); \
4187             el.firstChild.textContent='bye'; var r=mo.takeRecords(); \
4188             r.length+','+r[0].type",
4189            "1,characterData",
4190        ),
4191        // `characterDataOldValue:true`で変更前のテキストを取得できる。
4192        (
4193            "var el=document.createElement('div'); el.textContent='hi'; \
4194             var mo=new MutationObserver(function(){}); \
4195             mo.observe(el,{characterData:true, characterDataOldValue:true, subtree:true}); \
4196             el.firstChild.textContent='bye'; var r=mo.takeRecords(); \
4197             r[0].oldValue",
4198            "hi",
4199        ),
4200        // 値が実質変化していない代入(同じ文字列への再代入)は通知しない。
4201        (
4202            "var el=document.createElement('div'); el.textContent='hi'; \
4203             var mo=new MutationObserver(function(){}); \
4204             mo.observe(el,{characterData:true, subtree:true}); \
4205             el.firstChild.textContent='hi'; var r=mo.takeRecords(); r.length",
4206            "0",
4207        ),
4208        // `characterData`未指定(既定false)ならテキスト変化を通知しない
4209        // (`childList:true`のみ指定している場合、テキストノードの中身が
4210        // 変わっただけでは子ノードの追加/削除ではないため無関係)。
4211        (
4212            "var el=document.createElement('div'); el.textContent='hi'; \
4213             var mo=new MutationObserver(function(){}); \
4214             mo.observe(el,{childList:true}); \
4215             el.firstChild.textContent='bye'; var r=mo.takeRecords(); r.length",
4216            "0",
4217        ),
4218        // MutationObserver — 同一インスタンスによる複数ターゲット監視。
4219        (
4220            "var e1=document.createElement('div'); var e2=document.createElement('div'); \
4221             var mo=new MutationObserver(function(){}); \
4222             mo.observe(e1,{attributes:true}); mo.observe(e2,{attributes:true}); \
4223             e1.setAttribute('a','1'); e2.setAttribute('b','2'); \
4224             var r=mo.takeRecords(); r.length",
4225            "2",
4226        ),
4227        // MutationObserver — W3Cプロパティ previousSibling/nextSibling/attributeNamespace が null。
4228        (
4229            "var el=document.createElement('div'); \
4230             var mo=new MutationObserver(function(){}); \
4231             mo.observe(el,{attributes:true}); \
4232             el.setAttribute('a','1'); \
4233             var r=mo.takeRecords()[0]; \
4234             (r.previousSibling===null) && (r.nextSibling===null) && (r.attributeNamespace===null)",
4235            "true",
4236        ),
4237        // Object.defineProperty — データ記述子。
4238        ("var o={}; Object.defineProperty(o,'x',{value:42,writable:true}); o.x", "42"),
4239        // Object.defineProperty — getter。
4240        ("var o={_v:7}; Object.defineProperty(o,'v',{get:function(){return this._v*2;}}); o.v", "14"),
4241        // Object.defineProperty — setter + getter。
4242        ("var o={_x:0}; Object.defineProperty(o,'x',{get:function(){return this._x;},set:function(v){this._x=v+1;}}); o.x=9; o.x", "10"),
4243        // Object.defineProperty — setterのみ(getterなし→undefined)。
4244        ("var o={_s:''}; Object.defineProperty(o,'s',{set:function(v){this._s=v;}}); o.s='hi'; o._s", "hi"),
4245        // `__defineGetter__`/`__defineSetter__`/`__lookupGetter__`/
4246        // `__lookupSetter__`(Annex B.3.1)が丸ごと未対応だった。
4247        ("var o={_v:7}; o.__defineGetter__('v', function(){return this._v*2;}); o.v", "14"),
4248        ("var o={_x:0}; o.__defineSetter__('x', function(v){this._x=v+1;}); o.x=9; o._x", "10"),
4249        // getter/setter を別々に定義しても互いを上書きしない。
4250        (
4251            "var o={_x:1}; o.__defineGetter__('x', function(){return this._x;}); \
4252             o.__defineSetter__('x', function(v){this._x=v;}); o.x=5; o.x",
4253            "5",
4254        ),
4255        ("var o={}; o.__defineGetter__('v', function(){return 1;}); typeof o.__lookupGetter__('v')", "function"),
4256        ("var o={}; typeof o.__lookupGetter__('nope')", "undefined"),
4257        // Object.defineProperties — 複数。
4258        ("var o={}; Object.defineProperties(o,{a:{value:1},b:{value:2}}); o.a+o.b", "3"),
4259        // getter で動的値を返す。
4260        ("var o={n:0}; Object.defineProperty(o,'next',{get:function(){return ++this.n;}}); o.next; o.next; o.next", "3"),
4261        // Object.getOwnPropertyDescriptor — データ記述子。
4262        ("var o={x:5}; Object.getOwnPropertyDescriptor(o,'x').value", "5"),
4263        // Object.getOwnPropertyDescriptor — アクセサ記述子。
4264        ("var o={}; Object.defineProperty(o,'v',{get:function(){return 99;}}); typeof Object.getOwnPropertyDescriptor(o,'v').get", "function"),
4265        // Object.getOwnPropertyDescriptor — 存在しないキー→undefined。
4266        ("Object.getOwnPropertyDescriptor({x:1},'y')", "undefined"),
4267        // Object.getOwnPropertyDescriptors — 全記述子。
4268        ("var o={a:1}; Object.defineProperty(o,'b',{get:function(){return 2;}}); Object.getOwnPropertyDescriptors(o).a.value + Object.getOwnPropertyDescriptors(o).b.get()", "3"),
4269        // getter/setter リテラル構文(ES2015。Object.defineProperty 経由でのみ可能だった
4270        // アクセサ定義を、オブジェクトリテラル/クラス本体で直接書けるようにする)。
4271        // オブジェクトリテラルの getter。
4272        ("var o={_v:7, get v(){return this._v*2;}}; o.v", "14"),
4273        // オブジェクトリテラルの getter+setter。
4274        ("var o={_x:0, get x(){return this._x;}, set x(v){this._x=v+1;}}; o.x=9; o.x", "10"),
4275        // "get"/"set" という名前そのもののプロパティ/メソッドは従来通り動作する
4276        // (2トークン先読みでアクセサと誤認しないこと)。
4277        ("var o={get: 5}; o.get", "5"),
4278        ("var o={get(){return 7;}}; o.get()", "7"),
4279        // クラスの getter。
4280        (
4281            "class Circle { constructor(r){this.r=r;} get area(){return Math.round(Math.PI*this.r*this.r);} } new Circle(2).area",
4282            "13",
4283        ),
4284        // クラスの getter+setter(private フィールド風の下線プレフィックス)。
4285        (
4286            "class Box { constructor(){this._w=0;} get w(){return this._w;} set w(v){this._w=v<0?0:v;} } var b=new Box(); b.w=-5; b.w",
4287            "0",
4288        ),
4289        // static getter。
4290        (
4291            "class Config { static get version(){return '1.0';} } Config.version",
4292            "1.0",
4293        ),
4294        // Array.prototype.splice — 要素削除と返値。
4295        ("var a=[1,2,3,4]; var r=a.splice(1,2); r.join(',')+'/'+a.join(',')", "2,3/1,4"),
4296        // splice — 挿入。
4297        ("var a=[1,4]; a.splice(1,0,2,3); a.join(',')", "1,2,3,4"),
4298        // lastIndexOf。
4299        ("[1,2,3,2,1].lastIndexOf(2)", "3"),
4300        // copyWithin。
4301        ("[1,2,3,4,5].copyWithin(0,3).join(',')", "4,5,3,4,5"),
4302        // copyWithin — 負のインデックス(末尾からのオフセット)。
4303        ("[1,2,3,4,5].copyWithin(-2,0).join(',')", "1,2,3,1,2"),
4304        // copyWithin — end 引数で範囲を限定。
4305        ("[1,2,3,4,5].copyWithin(0,3,4).join(',')", "4,2,3,4,5"),
4306        // toSorted — 元配列を変更しない。
4307        ("var a=[3,1,2]; var s=a.toSorted(); a[0]+'/'+s.join(',')", "3/1,2,3"),
4308        // sort — undefined は比較関数に渡さず常に末尾に送る(SortCompare の特別扱い)。
4309        ("[undefined,3,undefined,1,2].sort((a,b)=>a-b).join(',')", "1,2,3,,"),
4310        ("[undefined,3,1,2].sort().join(',')", "1,2,3,"),
4311        ("[undefined,3,1,2].toSorted((a,b)=>a-b).join(',')", "1,2,3,"),
4312        // 配列の数値変換(ToPrimitive→ToNumber)。以前は Object 全般と同じく常に NaN だった。
4313        ("[5]*2", "10"),
4314        ("[]+1", "1"),
4315        ("['5']-2", "3"),
4316        ("[1,2]*1", "NaN"),
4317        // `Value::to_number` にも同種の Proxy 素通しバグがあり、`Array` は
4318        // 特別扱いされているのに `Proxy` で包むと常に `NaN` になっていた
4319        // (`Array.isArray`/`to_js_string` 等は既に透過性修正済みだった)。
4320        ("new Proxy([5],{})*2", "10"),
4321        ("+new Proxy([5],{})", "5"),
4322        // 緩やか等価(==)で Object(配列)と Number/String を比較。以前はどの分岐にも
4323        // 該当せず常に false になっていた。
4324        ("[5]==5", "true"),
4325        ("[5]=='5'", "true"),
4326        ("''==[]", "true"),
4327        ("[1,2]==5", "false"),
4328        // Number("0x..")/("0o..")/("0b..") 整数リテラル変換。以前は NaN 固定だった。
4329        ("Number('0x1F')", "31"),
4330        ("Number('0o17')", "15"),
4331        ("Number('0b101')", "5"),
4332        ("+'0xff'", "255"),
4333        // String.prototype.normalize — 丸ごと欠落していた。Unicode 分解/合成テーブルが無い
4334        // ため恒等変換の簡略実装だが、少なくとも呼び出せて有効な form は通ることを確認。
4335        ("'abc'.normalize()", "abc"),
4336        ("'abc'.normalize('NFD')", "abc"),
4337        ("(function(){ try { 'x'.normalize('bogus'); return 'no-throw'; } catch(e) { return 'threw'; } })()", "threw"),
4338        // 西欧言語の分音符付きラテン文字に限定した部分実装の NFC/NFD 検証
4339        ("'e\\u0301'.normalize('NFC') === '\\u00e9'", "true"),
4340        ("'\\u00e9'.normalize('NFD') === 'e\\u0301'", "true"),
4341        ("'\\u00e9'.normalize('NFC') === '\\u00e9'", "true"),
4342        // オプショナルチェイニング `a?.[b]`(computed member)。以前は Index に optional
4343        // フラグ自体が存在せず、常に通常のプロパティアクセスとして評価され null/undefined
4344        // で TypeError になっていた(`a?.b` の識別子版は動いていたが `[]` 版だけ壊れていた)。
4345        ("var a=null; a?.['x']", "undefined"),
4346        ("var a=undefined; a?.[0]", "undefined"),
4347        ("var a=[1,2,3]; a?.[1]", "2"),
4348        ("var f=null; f?.['x'](1,2)", "undefined"),
4349        // オプショナルチェイン全体の短絡伝播。以前は直近1段しか短絡せず `a?.b.c` は
4350        // `a` が null/undefined のとき後続の `.c` アクセスで TypeError になっていた。
4351        ("var a=null; a?.b.c", "undefined"),
4352        ("var a=undefined; a?.b.c.d", "undefined"),
4353        ("var a=null; a?.b[0].c", "undefined"),
4354        ("var a=null; a?.b()", "undefined"),
4355        ("var o={b:{c:5}}; o?.b.c", "5"),
4356        // for...in がプロトタイプ連鎖上の継承プロパティを列挙しないバグ。以前は自身の
4357        // プロパティのみだった。
4358        ("(function(){ function Base(){} Base.prototype.x=1; function D(){this.y=2;} D.prototype=Object.create(Base.prototype); var d=new D(); var ks=[]; for(var k in d) ks.push(k); return ks.sort().join(','); })()", "constructor,x,y"),
4359        // 自身のプロパティがプロトタイプ側の同名キーを覆い隠す(重複しない)ことも確認。
4360        ("(function(){ function Base(){} Base.prototype.x=1; function D(){this.x=99;} D.prototype=Object.create(Base.prototype); var d=new D(); var ks=[]; for(var k in d) ks.push(k); return ks.sort().join(','); })()", "constructor,x"),
4361        // `in` 演算子・for-in が accessors(getter/setter 限定プロパティ)専用マップを
4362        // 見ておらず「無い」扱いになっていたバグ。
4363        ("'x' in {get x(){return 1;}}", "true"),
4364        ("(function(){ var o={get x(){return 1;}}; var ks=[]; for(var k in o) ks.push(k); return ks.join(','); })()", "x"),
4365        // Object.keys/values/entries/assign も同じ accessors 見落としバグがあった。
4366        ("Object.keys({get x(){return 1;}}).join(',')", "x"),
4367        ("Object.values({get x(){return 42;}}).join(',')", "42"),
4368        ("JSON.stringify(Object.entries({get x(){return 7;}}))", "[[\"x\",7]]"),
4369        ("Object.assign({}, {get x(){return 9;}}).x", "9"),
4370        // JSON.stringify も同じ accessors 見落としバグがあった。
4371        ("JSON.stringify({get x(){return 5;}})", "{\"x\":5}"),
4372        // オブジェクトスプレッド {...src} も同じ accessors 見落としバグがあった。
4373        ("({...{get x(){return 3;}}}).x", "3"),
4374        ("var s={get x(){return 1;}}; var o={...s, y:2}; o.x+','+o.y", "1,2"),
4375        // Number.prototype.toExponential/toPrecision/valueOf — 丸ごと欠落していた。
4376        ("(150).toExponential(2)", "1.50e+2"),
4377        ("(0.0012345).toExponential(3)", "1.235e-3"),
4378        ("(123.456).toPrecision(5)", "123.46"),
4379        ("(0.0001234).toPrecision(2)", "0.00012"),
4380        ("(123456).toPrecision(2)", "1.2e+5"),
4381        ("(5).valueOf()", "5"),
4382        // `Number.prototype.toFixed(fractionDigits)` は仕様上 `fractionDigits` が
4383        // 0〜100 の範囲外なら RangeError を投げる必要があるが、以前は範囲外/非有限値を
4384        // 黙って0にクランプするだけで上限チェックが無く、`(1).toFixed(500)` のような
4385        // 呼び出しが `10^500`(Infinity)経由で壊れた出力になり得た。
4386        ("(1).toFixed(0)", "1"),
4387        ("(1.5).toFixed(100).length", "102"),
4388        (
4389            "try { (1).toFixed(-1); 'no-throw' } catch(e) { 'threw' }",
4390            "threw",
4391        ),
4392        (
4393            "try { (1).toFixed(101); 'no-throw' } catch(e) { 'threw' }",
4394            "threw",
4395        ),
4396        ("(NaN).toFixed(2)", "NaN"),
4397        ("(Infinity).toFixed(2)", "Infinity"),
4398        // Array.prototype.toLocaleString / Number.prototype.toLocaleString — 丸ごと欠落。
4399        ("[1,2,3].toLocaleString()", "1,2,3"),
4400        ("[1,null,undefined,2].toLocaleString()", "1,,,2"),
4401        // Number.prototype.toLocaleString は `toString()` の別名でしかなく、実際のブラウザなら
4402        // `Intl` 非搭載でも入る3桁区切りのカンマが欠落していた(2026-07-09に修正)。
4403        ("(1234.5).toLocaleString()", "1,234.5"),
4404        ("(1234567).toLocaleString()", "1,234,567"),
4405        ("(123).toLocaleString()", "123"),
4406        ("(-1234567.89).toLocaleString()", "-1,234,567.89"),
4407        // `Intl` グローバルが丸ごと未対応だった。ロケール/オプション引数は無視するが、
4408        // `new Intl.NumberFormat().format(n)` という最頻出イディオムだけは救う最小実装。
4409        ("new Intl.NumberFormat().format(1234567)", "1,234,567"),
4410        ("new Intl.NumberFormat('en-US').format(-1234.5)", "-1,234.5"),
4411        ("typeof Intl.NumberFormat().format", "function"),
4412        ("(0).toLocaleString()", "0"),
4413        ("(NaN).toLocaleString()", "NaN"),
4414        ("(Infinity).toLocaleString()", "Infinity"),
4415        // Boolean プリミティブへのプロパティアクセスが一律 undefined になり、メソッド呼出
4416        // 自体が丸ごと欠落していたバグ(プリミティブ中 Boolean だけメソッドが皆無だった)。
4417        ("true.toString()", "true"),
4418        ("false.toString()", "false"),
4419        ("true.valueOf()", "true"),
4420        ("(false).valueOf() === false", "true"),
4421        // 関数の name/length が丸ごと欠落していたバグ。
4422        ("function foo(a,b){} foo.name", "foo"),
4423        ("function foo(a,b){} foo.length", "2"),
4424        ("function foo(a,b=1,...c){} foo.length", "1"),
4425        ("(function(){}).length", "0"),
4426        ("Math.max.length", "0"),
4427        // `Function.prototype.bind()` が返す関数の `.name`/`.length` が丸ごと未対応で
4428        // 常に `undefined` になっていた。
4429        // 【2026-09-26】`Function.prototype` が存在しなかった(OpenStreetMap 埋め込みで発見)。
4430        ("typeof Function.prototype", "object"),
4431        ("typeof Function.prototype.toString", "function"),
4432        ("function foo(){} Function.prototype.toString.call(foo).includes('foo')", "true"),
4433        ("function add(a,b){return a+b} Function.prototype.call.call(add, null, 2, 3)", "5"),
4434        ("var of = 7; of + 1", "8"),
4435        ("0x10000000000000000 === 18446744073709551616", "true"),
4436        ("function foo(a,b,c){} foo.bind(null).name", "bound foo"),
4437        ("function foo(a,b,c){} foo.bind(null).length", "3"),
4438        ("function foo(a,b,c){} foo.bind(null,1).length", "2"),
4439        ("function foo(a,b,c){} foo.bind(null,1,2,3,4).length", "0"),
4440        // 束縛済み関数の文字列化(`String(fn.bind(...))`)も専用ケースが無く
4441        // `[object Object]` に落ちるバグだった。
4442        (
4443            "function foo(){} String(foo.bind(null)).includes('native code')",
4444            "true",
4445        ),
4446        // `Object.prototype.toString.call(x)` が `this` の実際の種別を一切見ず
4447        // 常に `[object Object]` を返すバグだった(`lodash` 等で広く使われる
4448        // 型判定イディオムが配列/Map/Set/Date/RegExp/関数のいずれに対しても
4449        // 機能しなくなる、実用上かなり影響の大きいバグ)。
4450        ("Object.prototype.toString.call([1,2])", "[object Array]"),
4451        ("Object.prototype.toString.call({})", "[object Object]"),
4452        ("Object.prototype.toString.call(new Date())", "[object Date]"),
4453        ("Object.prototype.toString.call(/a/)", "[object RegExp]"),
4454        ("Object.prototype.toString.call(new Map())", "[object Map]"),
4455        ("Object.prototype.toString.call(new Set())", "[object Set]"),
4456        ("Object.prototype.toString.call(function(){})", "[object Function]"),
4457        ("Object.prototype.toString.call(null)", "[object Null]"),
4458        ("Object.prototype.toString.call(undefined)", "[object Undefined]"),
4459        // `Symbol.toStringTag`(クラスでカスタムタグを定義する仕組み)も一切考慮して
4460        // いなかった。データプロパティ/アクセサ(getter)どちらの定義方法も動くこと。
4461        (
4462            "class Foo { get [Symbol.toStringTag](){ return 'Foo'; } } Object.prototype.toString.call(new Foo())",
4463            "[object Foo]",
4464        ),
4465        (
4466            "var o={}; o['Symbol(Symbol.toStringTag)']='Bar'; Object.prototype.toString.call(o)",
4467            "[object Bar]",
4468        ),
4469        // NamedEvaluation(ES2015): `const f = function(){}`/`const f = () => {}` の
4470        // ような無名関数式を単純な識別子へ代入すると、その識別子名を `.name` として
4471        // 継承する仕様が丸ごと未対応で、常に空文字列のままだった。
4472        ("var f = function(){}; f.name", "f"),
4473        ("var g = () => {}; g.name", "g"),
4474        // 既に名前を持つ関数式(`function foo(){}`)は変数名で上書きされない。
4475        ("var h = function foo(){}; h.name", "foo"),
4476        // object literal のプロパティ値としての無名関数式も同様にキー名を継承する。
4477        ("({bar: function(){}}).bar.name", "bar"),
4478        ("({baz: () => {}}).baz.name", "baz"),
4479        // メソッド短縮記法は元々キー名を持つため、この継承の影響を受けない
4480        // (上書きではなく「無名の場合のみ設定」であることの確認)。
4481        ("({qux(){}}).qux.name", "qux"),
4482        // structuredClone(Date/RegExp) — 実データが props に無く plain object 化に落ちて
4483        // 消えてしまうバグ(Map/Set と同種)。
4484        ("var d=new Date(2020,0,1); var c=structuredClone(d); c.getTime()===d.getTime() && c!==d", "true"),
4485        ("var r=/abc/gi; var c=structuredClone(r); c.source+','+c.flags+','+(c!==r)", "abc,gi,true"),
4486        // 分割代入の rest ({...rest}) も同じ accessors 見落としバグがあった。
4487        ("var o={get x(){return 1;}, y:2}; var {y, ...rest} = o; rest.x+','+rest.y", "1,undefined"),
4488        // hasOwnProperty/Object.hasOwn が accessors と配列インデックス/length を
4489        // 見落とすバグ。
4490        ("({get x(){return 1;}}).hasOwnProperty('x')", "true"),
4491        ("[1,2,3].hasOwnProperty(0)", "true"),
4492        ("[1,2,3].hasOwnProperty('length')", "true"),
4493        ("[1,2,3].hasOwnProperty(5)", "false"),
4494        ("Object.hasOwn({get x(){return 1;}}, 'x')", "true"),
4495        ("({}).toLocaleString()", "[object Object]"),
4496        ("[{}, {}].toLocaleString()", "[object Object],[object Object]"),
4497        // Object.getOwnPropertyDescriptor(s) が配列の数値インデックス/length を見落とし
4498        // 常に undefined になるバグ。
4499        ("Object.getOwnPropertyDescriptor([1,2,3], '0').value", "1"),
4500        ("Object.getOwnPropertyDescriptor([1,2,3], 'length').value", "3"),
4501        ("Object.keys(Object.getOwnPropertyDescriptors([1,2])).sort().join(',')", "0,1,length"),
4502        // Object.defineProperty が配列の数値インデックス/length に書き込んでも
4503        // 実データ(ObjKind::Array)側に反映されないバグ。
4504        ("var a=[1,2,3]; Object.defineProperty(a,'0',{value:99}); a[0]", "99"),
4505        ("var a=[1,2]; Object.defineProperty(a,'5',{value:9}); a.length+','+a[5]", "6,9"),
4506        ("var a=[1,2,3]; Object.defineProperty(a,'length',{value:1}); a.join(',')", "1"),
4507        // Array/Map/Set の Symbol.iterator が丸ごと未登録で、明示呼出し
4508        // (`arr[Symbol.iterator]()`)が「関数ではない」になっていたバグ。for...of は
4509        // 内部高速経路のため気づかれなかった。
4510        ("[...[1,2,3][Symbol.iterator]()].join(',')", "1,2,3"),
4511        ("var m=new Map([['a',1]]); [...m[Symbol.iterator]()][0].join(',')", "a,1"),
4512        ("[...new Set([1,2])[Symbol.iterator]()].join(',')", "1,2"),
4513        // entries/keys/values が本物の Iterator(`.next()`/`Symbol.iterator` 持ち)ではなく
4514        // 単なる配列を返しており、明示的なイテレータプロトコル駆動
4515        // (`const it = arr.values(); it.next()`) が「next が存在しない」で壊れていたバグ。
4516        ("var it=[10,20].values(); it.next().value+','+it.next().value+','+it.next().done", "10,20,true"),
4517        ("var it=[1,2].entries(); it.next().value.join(',')", "0,1"),
4518        ("var it=[1,2].keys(); it.next().value+','+it.next().value", "0,1"),
4519        ("var it=new Map([['a',1]]).entries(); it.next().value.join(',')", "a,1"),
4520        ("var it=new Set([5,6]).values(); it.next().value+','+it.next().value", "5,6"),
4521        ("var it=[1].values(); it[Symbol.iterator]()===it", "true"),
4522        // String.prototype[Symbol.iterator] が丸ごと欠落していたバグ。
4523        ("[...'ab'[Symbol.iterator]()].join(',')", "a,b"),
4524        ("var it='xy'[Symbol.iterator](); it.next().value+it.next().value+it.next().done", "xytrue"),
4525        // 上記の Iterator 化に伴う回帰: Array.from/new Map/new Set が新しい軽量イテレータ
4526        // オブジェクト(entries/keys/values の戻り値)を「iterable」として認識できず
4527        // 空になっていたバグ(本イテレータ導入時に発見・即修正)。
4528        ("Array.from([10,20].values()).join(',')", "10,20"),
4529        ("Array.from([1,2].entries()).map(e=>e.join(':')).join(',')", "0:1,1:2"),
4530        ("var m=new Map(new Map([['a',1]]).entries()); m.get('a')", "1"),
4531        ("var s=new Set([1,2,2,3].values()); [...s].join(',')", "1,2,3"),
4532        // イテレータの内部実装詳細(_items/_pos)が Object.keys/JSON.stringify/spread から
4533        // 見えてしまう漏れ(make_iterator 導入時に発見・即修正)。
4534        ("Object.keys([1,2].values()).length", "0"),
4535        ("JSON.stringify([1,2].values())", "{}"),
4536        ("Object.keys({...[1,2].values()}).length", "0"),
4537        // AggregateError(ES2021)が丸ごと欠落していた。Promise.any が全滅した際に
4538        // 最後の reject 理由しか見えず、`.errors` を持つ AggregateError も
4539        // 投げられていなかった。
4540        ("new AggregateError([1,2],'m').errors.join(',')", "1,2"),
4541        ("new AggregateError([],'m') instanceof Error", "true"),
4542        (
4543            "await Promise.any([Promise.reject('a'),Promise.reject('b')]).catch(e => e.name+':'+e.errors.join(','))",
4544            "AggregateError:a,b",
4545        ),
4546        // Date の日時文字列パース(ISO 8601)が丸ごと非対応だった(`new Date("2024-01-15")`
4547        // が Invalid Date になっていた)。
4548        ("new Date('2024-01-15').getFullYear()+'-'+(new Date('2024-01-15').getMonth()+1)+'-'+new Date('2024-01-15').getDate()", "2024-1-15"),
4549        ("Date.parse('2024-01-15T00:00:00Z')", "1705276800000"),
4550        ("Date.parse('2024-01-15T02:00:00+02:00')", "1705276800000"),
4551        // コロン無しタイムゾーンオフセット(`+0900` 形式。ISO 8601/ECMA-262 双方で正当)
4552        ("Date.parse('2024-01-15T09:00:00+0900')", "1705276800000"),
4553        ("Date.parse('2024-01-15T00:00:00+00')", "1705276800000"),
4554        ("new Date('2024-01-15T00:00:00.500Z').getTime()", "1705276800500"),
4555        ("isNaN(Date.parse('not a date'))", "true"),
4556        // RFC 2822 形式(HTTP日付ヘッダ等の定番書式)が丸ごと非対応で、ISO 8601以外の
4557        // 文字列は常に NaN になっていたバグ。曜日名の有無・タイムゾーンオフセット双方を
4558        // 確認する。
4559        ("Date.parse('Mon, 15 Jan 2024 00:00:00 GMT')", "1705276800000"),
4560        ("Date.parse('15 Jan 2024 00:00:00 GMT')", "1705276800000"),
4561        ("Date.parse('Mon, 15 Jan 2024 02:00:00 +0200')", "1705276800000"),
4562        // `Date.now`/`Date.parse` は登録済みだったが `Date.UTC` が丸ごと未対応だった。
4563        ("Date.UTC(2024, 0, 15) === Date.parse('2024-01-15T00:00:00Z')", "true"),
4564        ("new Date(Date.UTC(2024,0,15,10,30,0)).getUTCHours()", "10"),
4565        // 省略引数の既定値(day のみ1、他は0)。
4566        ("new Date(Date.UTC(2024,0)).getDate()", "1"),
4567        ("new Date('Mon, 15 Jan 2024 00:00:00 GMT').getFullYear()", "2024"),
4568        // RegExp 名前付きキャプチャグループ (?<name>...)(ES2018)が丸ごと非対応で
4569        // `(?<`(lookbehind と誤認され `:` フォールバックと同じ経路)を通ると壊れた
4570        // パース結果になり、`match.groups` は常に undefined だった。
4571        ("'2024-01-15'.match(/(?<year>\\d+)-(?<month>\\d+)-(?<day>\\d+)/).groups.year", "2024"),
4572        ("'2024-01-15'.match(/(?<year>\\d+)-(?<month>\\d+)-(?<day>\\d+)/).groups.month", "01"),
4573        ("/(?<a>x)(y)/.exec('xy')[2]", "y"),
4574        ("/(x)/.exec('x').groups", "undefined"),
4575        ("[...'a1 b2'.matchAll(/(?<letter>[a-z])(?<num>\\d)/g)].map(m=>m.groups.letter+m.groups.num).join(',')", "a1,b2"),
4576        // replace() の $<name> 置換パターン(ES2018)が丸ごと非対応だった。合わせて
4577        // $`(一致より前)/$'(一致より後)も未対応だった。
4578        ("'2024-01-15'.replace(/(?<y>\\d+)-(?<m>\\d+)-(?<d>\\d+)/, '$<d>/$<m>/$<y>')", "15/01/2024"),
4579        ("'abc'.replace(/b/, \"[$`|$']\")", "a[a|c]c"),
4580        ("'x'.replace(/(?<a>x)/, (m,a,off,str,groups)=>groups.a+'!')", "x!"),
4581        // RegExp の \uXXXX/\u{X...}/\xXX エスケープが丸ごと未対応で、`A` が文字コード
4582        // 指定ではなく `u0041`(5文字のリテラル)として誤解釈されていたバグ。
4583        ("/\\u0041/.test('A')", "true"),
4584        ("/\\u{1F600}/u.test('\\u{1F600}')", "true"),
4585        ("/\\x41/.test('A')", "true"),
4586        ("'A1'.match(/\\u0041\\d/)[0]", "A1"),
4587        // 文字クラス内の \uXXXX/\xXX(範囲の端点含む)も同じバグがあった。
4588        ("/[\\u0041-\\u005A]/.test('M')", "true"),
4589        ("/[\\x41-\\x5A]/.test('Z')", "true"),
4590        ("/[\\u0041-\\u005A]/.test('m')", "false"),
4591        // String.prototype.split(regexp) がキャプチャグループを結果に含めないバグ
4592        // (仕様上、区切りに使った正規表現のキャプチャは結果配列に挿入される)。
4593        ("'a1b2c'.split(/(\\d)/).join('|')", "a|1|b|2|c"),
4594        ("'axb'.split(/(x)|(y)/).join(',')", "a,x,,b"),
4595        ("'abc'.split(/b/).join(',')", "a,c"),
4596        // クラス/オブジェクトリテラルの async/generator メソッド短縮記法が丸ごと
4597        // 未認識で、`*`/`async` に遭遇するとキー解析がその場で失敗し、メンバ自体が
4598        // 消えるだけでなく後続メンバの解析まで壊れる重大バグだった。
4599        ("class C { *gen(){ yield 1; yield 2; } } [...new C().gen()].join(',')", "1,2"),
4600        ("await (new (class C { async f(){ return 42; } })()).f()", "42"),
4601        ("class C { *gen(){ yield 'a'; } method(){ return 'm'; } } var c=new C(); [...c.gen()].join(',')+c.method()", "am"),
4602        ("[...{ *gen(){ yield 1; yield 2; } }.gen()].join(',')", "1,2"),
4603        ("await { async f(){ return 'ok'; } }.f()", "ok"),
4604        // static + async/generator の組み合わせ、および算出メソッド名との組み合わせ。
4605        ("await (class C { static async f(){ return 7; } }).f()", "7"),
4606        ("class C { static *gen(){ yield 9; } } [...C.gen()].join(',')", "9"),
4607        ("class C { *[Symbol.iterator](){ yield 3; } } [...new C()].join(',')", "3"),
4608        ("({ async *gen(){}, method(){ return 'ok'; } }).method()", "ok"),
4609        // 分割代入の算出プロパティ名 `{[expr]: target}` が丸ごと未認識で、`[` に遭遇すると
4610        // パターン解析全体が壊れる(後続プロパティも巻き添え)バグだった。
4611        ("var k='x'; var {[k]: v} = {x: 42}; v", "42"),
4612        ("var k='a'; var {[k]: v, y} = {a: 1, y: 2}; v+','+y", "1,2"),
4613        ("var k='n'; function f({[k]: val}){ return val; } f({n: 7})", "7"),
4614        // 直前の async/generator 修飾子検出の実装ミス("async"/"*" を常に修飾子として
4615        // 消費してしまい、"async" という名前の通常プロパティ/フィールド自体が壊れる
4616        // 新規回帰)を発見・即修正。
4617        ("({ async: 1, b: 2 }).async + ({ async: 1, b: 2 }).b", "3"),
4618        ("class C { async = 5; b = 6; } var c = new C(); c.async + c.b", "11"),
4619        // "static" の修飾子誤認識も同じバグ族(`static`/`async`/`*` 全てに共通の
4620        // 「次が `(` でなければ無条件で修飾子」という緩すぎる判定)だった。
4621        ("class C { static = 5; b = 6; } var c = new C(); c.static + c.b", "11"),
4622        ("class C { static x = 1; static method(){ return 2; } } C.x + C.method()", "3"),
4623        // get/set の算出プロパティ名 `get [expr](){}` がアクセサとして認識されず、
4624        // "get"/"set" という名前の孤立フィールド+無関係なメソッドに分解される
4625        // バグだった。
4626        ("var k='x'; var o={ get [k](){ return 42; } }; o.x", "42"),
4627        ("var k='y'; var o={y:0, set [k](v){ this._y=v; } }; o.y=5; o._y", "5"),
4628        ("var k='z'; class C { get [k](){ return 9; } } new C().z", "9"),
4629        // for(let i=...) の反復ごとの束縛(per-iteration binding)が丸ごと未対応で、
4630        // ループ内で作ったクロージャが全て同じ最終値を捕捉してしまうバグだった
4631        // (`var` は対象外で仕様どおり単一束縛のまま)。
4632        ("var a=[]; for(let i=0;i<3;i++){ a.push(()=>i); } a.map(f=>f()).join(',')", "0,1,2"),
4633        ("var a=[]; for(var i=0;i<3;i++){ a.push(()=>i); } a.map(f=>f()).join(',')", "3,3,3"),
4634        ("var a=[]; for(let i=0;i<3;i++){ if(i===1) continue; a.push(()=>i); } a.map(f=>f()).join(',')", "0,2"),
4635        // for-of/for-in の let/const も同じ反復ごとの束縛が必要(var は対象外)。
4636        ("var a=[]; for(const x of [1,2,3]){ a.push(()=>x); } a.map(f=>f()).join(',')", "1,2,3"),
4637        ("var a=[]; for(var x of [1,2,3]){ a.push(()=>x); } a.map(f=>f()).join(',')", "3,3,3"),
4638        ("var a=[]; for(let k in {a:1,b:2}){ a.push(()=>k); } a.map(f=>f()).join(',')", "a,b"),
4639        // 配列プロトタイプメソッドのほぼ全てが使う共通ヘルパ this_items/this_objref が
4640        // Proxy を素通しできず、Proxy でラップした配列へメソッド呼出し
4641        // (`.map()`/`.push()` 等)すると常に空扱いになるバグだった(for...of/スプレッドは
4642        // 別経路で既に対応済みだったが、メソッド呼出し経由は漏れていた)。
4643        ("new Proxy([1,2,3],{}).map(x=>x*2).join(',')", "2,4,6"),
4644        ("var p=new Proxy([1,2],{}); p.push(3); p.join(',')", "1,2,3"),
4645        ("new Proxy([5,6,7],{}).indexOf(6)", "1"),
4646        // `String(proxy)`/文字列連結/テンプレートリテラル補間が使う
4647        // `Value::to_js_string` にも同種の Proxy 素通しバグがあり、`ObjKind::Proxy`
4648        // 専用ケースが無く汎用の "[object Object]" に落ちていた。
4649        ("String(new Proxy([1,2,3], {}))", "1,2,3"),
4650        ("'' + new Proxy([7,8], {})", "7,8"),
4651        // Map/Set の全メソッドが使う with_map/with_set にも同じ Proxy 素通しバグがあった。
4652        ("var p=new Proxy(new Map([['a',1]]),{}); p.get('a')", "1"),
4653        ("var p=new Proxy(new Set([1,2]),{}); p.add(3); [...p].join(',')", "1,2,3"),
4654        // WeakSet / Promise / Generator の共通ヘルパにも同じ Proxy 素通しバグがあった。
4655        ("var o={}; var p=new Proxy(new WeakSet([o]),{}); p.has(o)", "true"),
4656        ("await new Proxy(Promise.resolve(5), {}).then(x=>x*2)", "10"),
4657        ("function* g(){ yield 1; yield 2; } var p=new Proxy(g(),{}); [...p].join(',')", "1,2"),
4658        // String.fromCharCode/fromCodePoint が同じ実装を共有しており、fromCharCode の
4659        // サロゲートペア結合(絵文字1文字を表す ES1 以来の定番イディオム)が壊れていた。
4660        ("String.fromCharCode(72,105)", "Hi"),
4661        ("String.fromCharCode(0xD83D,0xDE00)", "\u{1F600}"),
4662        ("String.fromCodePoint(0x1F600)", "\u{1F600}"),
4663        ("String.fromCodePoint(72,105)", "Hi"),
4664        // Object.create(proto, propertiesObject) の第2引数が丸ごと無視されていたバグ。
4665        ("Object.create(null, {x:{value:5}}).x", "5"),
4666        ("var o=Object.create(null, {y:{value:9}, z:{value:1}}); o.y + o.z", "10"),
4667        // JSON.stringify の toJSON() フックが Date 専用の特別扱いのみで、任意のオブジェクトに
4668        // 定義できる汎用フックとして機能していなかった。
4669        ("JSON.stringify({toJSON(){ return 42; }})", "42"),
4670        ("JSON.stringify({a: {toJSON(){ return 'x'; }}})", "{\"a\":\"x\"}"),
4671        ("JSON.stringify([{toJSON(){ return 1; }}, 2])", "[1,2]"),
4672        // 数値セパレータ `_`(ES2021)が丸ごと未対応で、`1_000` の `_` の時点で数値
4673        // リテラルが打ち切られ、残りが別トークン(識別子)として構文解析を静かに
4674        // 壊すバグだった。
4675        ("1_000_000", "1000000"),
4676        ("0x1_000", "4096"),
4677        ("1_000.5_5", "1000.55"),
4678        // `0o`(8進)/`0b`(2進)リテラルがソースコード上で丸ごと未対応で、`0` だけ
4679        // 数値トークンとして打ち切られ `o17`/`b101` が別の識別子トークンになり
4680        // 構文解析が静かに壊れるバグだった(16進 `0x` は既存で対応済みだったのと非対称)。
4681        ("0o17", "15"),
4682        ("0b101", "5"),
4683        ("0o17 + 0b101", "20"),
4684        ("0b101n + 2n", "7"),
4685        // 文字列リテラル中の行継続(`\` の直後の改行)が仕様上「何も追加しない」はずが、
4686        // 改行文字そのものを値に混入させていたバグ。
4687        ("'abc\\\ndef'", "abcdef"),
4688        ("'abc\\\r\ndef'.length", "6"),
4689        // toReversed — 元配列を変更しない。
4690        ("var a=[1,2,3]; var r=a.toReversed(); a[0]+'/'+r.join(',')", "1/3,2,1"),
4691        // toSpliced — 元配列を変更しない。
4692        ("var a=[1,2,3]; var s=a.toSpliced(1,1,9); a.join(',')+'/'+s.join(',')", "1,2,3/1,9,3"),
4693        // toSpliced — deleteCount 省略時は末尾まで削除するが、明示的な `undefined` は
4694        // 仕様上 `ToIntegerOrInfinity(undefined)` = 0(省略とは異なる)扱いになる。
4695        ("[1,2,3].toSpliced(1).join(',')", "1"),
4696        ("[1,2,3].toSpliced(1, undefined).join(',')", "1,2,3"),
4697        // Array.prototype.concat — `Symbol.isConcatSpreadable` が丸ごと未対応で、
4698        // 常に `ObjKind::Array` かどうかだけで展開の有無を決めていたバグ。
4699        // 配列に `[Symbol.isConcatSpreadable]=false` を明示すると展開されず単一要素になる。
4700        (
4701            "var a=[1,2]; a[Symbol.isConcatSpreadable]=false; [0].concat(a).length",
4702            "2",
4703        ),
4704        // 非配列の array-like に `Symbol.isConcatSpreadable=true` を明示すると
4705        // `length`+添字プロパティから展開される。
4706        (
4707            "var o={0:'x',1:'y',length:2}; o[Symbol.isConcatSpreadable]=true; [0].concat(o).join(',')",
4708            "0,x,y",
4709        ),
4710        // フラグ未指定時は従来どおり配列のみ自動展開、非配列は単一要素のまま。
4711        ("[1].concat([2,3],4).join(',')", "1,2,3,4"),
4712        // `Array.prototype.join` は仕様上ジェネリックメソッド(`this` の `length` +
4713        // 添字プロパティだけを見る)だが、以前は `ObjKind::Array` 以外を無条件で空扱いし
4714        // 配列インスタンスから取り出した `join` を array-like へ `.call()` しても
4715        // 常に `""` になっていた(この処理系はメソッド解決を `ObjKind::Array` の
4716        // プロパティ取得時にのみ行う簡略実装のため、実際に呼び出し可能な関数値を
4717        // 得るには `Array.prototype.join` ではなく実配列インスタンスから取り出す)。
4718        (
4719            "var f=[].join; f.call({0:'a',1:'b',length:2}, '-')",
4720            "a-b",
4721        ),
4722        // `push`/`pop`/`shift`/`unshift` は仕様上ミューテートするメソッドの中でも
4723        // 特にジェネリック(`length` を介して任意の array-like を書き換える)
4724        // ことが要求される定番のイディオム(`Array.prototype.push.call(arguments, x)` 等)。
4725        (
4726            "var o={length:0}; var f=[].push; f.call(o,'a','b')+','+o.length+','+o[0]+','+o[1]",
4727            "2,2,a,b",
4728        ),
4729        (
4730            "var o={0:'a',1:'b',length:2}; var f=[].pop; f.call(o)+','+o.length",
4731            "b,1",
4732        ),
4733        (
4734            "var o={0:'a',1:'b',length:2}; var f=[].shift; f.call(o)+','+o.length+','+o[0]",
4735            "a,1,b",
4736        ),
4737        (
4738            "var o={0:'a',length:1}; var f=[].unshift; f.call(o,'x','y')+','+o.length+','+o[0]+','+o[1]+','+o[2]",
4739            "3,3,x,y,a",
4740        ),
4741        // 通常の配列に対する挙動には回帰なし。
4742        ("[1,2,3].join('-')", "1-2-3"),
4743        // `arguments.callee`(Annex B。無名関数の自己再帰呼び出しイディオム)が
4744        // 丸ごと未対応だった。
4745        (
4746            "var fact = function(n){ return n <= 1 ? 1 : n * arguments.callee(n - 1); }; fact(5)",
4747            "120",
4748        ),
4749        ("var g = function(){ return arguments.callee === g; }; g()", "true"),
4750        // 名前付き関数式の自己参照束縛(`(function f(){ ...f... })()` で `f` を
4751        // 本体内から参照できる仕様どおりの挙動)が丸ごと未対応だった。
4752        ("(function f(n){ return n<=1?1:n*f(n-1); })(5)", "120"),
4753        ("(function f(){ return typeof f; })()", "function"),
4754        // 外側スコープからは関数式自身の名前が見えない(仕様どおり)。
4755        ("(function f(){})(); typeof f", "undefined"),
4756        // `Array.prototype.slice` も同じジェネリックメソッド未対応バグがあった
4757        // (`join`/`concat` と同型。`this_items` が `ObjKind::Array` 以外を無条件で
4758        // 空 `Vec` 扱いしていたため array-like への適用が常に `[]` になっていた)。
4759        (
4760            "var s=[].slice; s.call({0:'a',1:'b',length:2}, 0, 1).join(',')",
4761            "a",
4762        ),
4763        (
4764            "var s=[].slice; s.call({0:'a',1:'b',2:'c',length:3}, 1).join(',')",
4765            "b,c",
4766        ),
4767        // 通常の配列に対する挙動には回帰なし。
4768        ("[1,2,3,4].slice(1,3).join(',')", "2,3"),
4769        // `forEach`/`map`/`filter`/`indexOf`/`includes` にも同型のジェネリックメソッド
4770        // 未対応バグがあった(`join`/`slice`/`concat` で修正済みの続き)。array-like
4771        // (`arguments`/`NodeList` 等への `.call()` の定番イディオム)への適用が
4772        // 「何も反復しない/常に見つからない」結果になっていた。
4773        (
4774            "var out=''; var fe=[].forEach; fe.call({0:'a',1:'b',length:2}, function(v){ out+=v }); out",
4775            "ab",
4776        ),
4777        (
4778            "var m=[].map; m.call({0:1,1:2,length:2}, function(v){ return v*2 }).join(',')",
4779            "2,4",
4780        ),
4781        (
4782            "var fl=[].filter; fl.call({0:1,1:2,2:3,length:3}, function(v){ return v>1 }).join(',')",
4783            "2,3",
4784        ),
4785        (
4786            "var io=[].indexOf; io.call({0:'a',1:'b',length:2}, 'b')",
4787            "1",
4788        ),
4789        (
4790            "var inc=[].includes; inc.call({0:'a',1:'b',length:2}, 'b')",
4791            "true",
4792        ),
4793        // `reduce`/`find`/`findIndex`/`some`/`every` にも同型のジェネリックメソッド
4794        // 未対応バグがあった(`forEach`/`map`/`filter`/`indexOf`/`includes` の続き)。
4795        (
4796            "var r=[].reduce; r.call({0:1,1:2,2:3,length:3}, function(a,v){ return a+v }, 0)",
4797            "6",
4798        ),
4799        (
4800            "var fd=[].find; fd.call({0:1,1:2,2:3,length:3}, function(v){ return v>1 })",
4801            "2",
4802        ),
4803        (
4804            "var fi=[].findIndex; fi.call({0:1,1:2,2:3,length:3}, function(v){ return v>1 })",
4805            "1",
4806        ),
4807        (
4808            "var sm=[].some; sm.call({0:1,1:2,length:2}, function(v){ return v>1 })",
4809            "true",
4810        ),
4811        (
4812            "var ev=[].every; ev.call({0:1,1:2,length:2}, function(v){ return v>0 })",
4813            "true",
4814        ),
4815        (
4816            "var at=[].at; at.call({0:1,1:2,length:2}, -1)",
4817            "2",
4818        ),
4819        (
4820            "var fl=[].findLast; fl.call({0:1,1:2,length:2}, function(v){ return v<2 })",
4821            "1",
4822        ),
4823        (
4824            "var fli=[].findLastIndex; fli.call({0:1,1:2,length:2}, function(v){ return v<2 })",
4825            "0",
4826        ),
4827        (
4828            "var fm=[].flatMap; fm.call({0:1,1:2,length:2}, function(v){ return v*2 }).join(',')",
4829            "2,4",
4830        ),
4831        (
4832            "var rr=[].reduceRight; rr.call({0:1,1:2,length:2}, function(a,v){ return a+v })",
4833            "3",
4834        ),
4835        (
4836            "var lio=[].lastIndexOf; lio.call({0:1,1:1,length:2}, 1)",
4837            "1",
4838        ),
4839        (
4840            "var fla=[].flat; fla.call({0:1,1:[2,3],length:2}).join(',')",
4841            "1,2,3",
4842        ),
4843        (
4844            "var en=[].entries; var itr=en.call({0:1,1:2,length:2}); itr.next().value.join(',')",
4845            "0,1",
4846        ),
4847        (
4848            "var ky=[].keys; var itr=ky.call({0:1,1:2,length:2}); itr.next().value+','+itr.next().value",
4849            "0,1",
4850        ),
4851        (
4852            "var vl=[].values; var itr=vl.call({0:1,1:2,length:2}); itr.next().value+','+itr.next().value",
4853            "1,2",
4854        ),
4855        (
4856            "var tls=[].toLocaleString; tls.call({0:1,1:2,length:2})",
4857            "1,2",
4858        ),
4859        (
4860            "var o={0:1,1:2,length:2}; o[Symbol.isConcatSpreadable]=true; [].concat.call(o, 3).join(',')",
4861            "1,2,3",
4862        ),
4863        (
4864            "var o={0:1,1:2,length:2}; [].concat.call(o, 3).length",
4865            "2",
4866        ),
4867        (
4868            "var ts=[].toSorted; ts.call({0:3,1:1,length:2}).join(',')",
4869            "1,3",
4870        ),
4871        (
4872            "var tr=[].toReversed; tr.call({0:1,1:2,length:2}).join(',')",
4873            "2,1",
4874        ),
4875        (
4876            "var tsp=[].toSpliced; tsp.call({0:1,1:2,length:2}, 1, 0, 9).join(',')",
4877            "1,9,2",
4878        ),
4879        (
4880            "var w=[].with; w.call({0:1,1:2,length:2}, 0, 9).join(',')",
4881            "9,2",
4882        ),
4883        // 通常の配列に対する挙動には回帰なし。
4884        ("[1,2,3].map(function(v){return v*2}).join(',')", "2,4,6"),
4885        // with — 元配列を変更しない。
4886        ("var a=[1,2,3]; var b=a.with(1,9); a[1]+'/'+b[1]", "2/9"),
4887        // TypeError コンストラクタ。
4888        ("(function(){ try { throw new TypeError('bad'); } catch(e) { return e.name+':'+e.message; } })()", "TypeError:bad"),
4889        // RangeError コンストラクタ。
4890        ("(function(){ try { throw new RangeError('oor'); } catch(e) { return e.name; } })()", "RangeError"),
4891        // FinalizationRegistry — register は no-op (object が取れる)。
4892        ("typeof new FinalizationRegistry(function(){}).register", "function"),
4893        // requestIdleCallback — コールバックが呼ばれ、didTimeout が false。
4894        ("var ok=false; requestIdleCallback(function(d){ ok = !d.didTimeout && d.timeRemaining()>0; }); ok", "true"),
4895        // Object.hasOwn — あり/なし。
4896        ("Object.hasOwn({x:1},'x')+','+Object.hasOwn({x:1},'y')", "true,false"),
4897        // Object.groupBy — 偶奇グループ。
4898        ("var g=Object.groupBy([1,2,3,4],function(n){return n%2===0?'e':'o'}); g.e.join(',')+'/'+g.o.join(',')", "2,4/1,3"),
4899        // Map.groupBy(ES2024)— Object.groupBy と違いキーを文字列化せず任意の値のまま使える。
4900        ("var m=Map.groupBy([1,2,3,4],function(n){return n%2===0?'e':'o'}); m.get('e').join(',')+'/'+m.get('o').join(',')", "2,4/1,3"),
4901        (
4902            "var kEven={}; var kOdd={}; var m=Map.groupBy([1,2,3],function(n){return n%2===0?kEven:kOdd}); m.get(kEven).join(',')+'/'+m.get(kOdd).join(',')",
4903            "2/1,3",
4904        ),
4905        ("Map.groupBy([1,2,3],function(n){return n}).size", "3"),
4906        // Object.groupBy/Map.groupBy は仕様上任意の iterable を受け付ける(配列限定ではない)。
4907        // 以前は実配列以外は無条件で空扱いになり Set/Map/ジェネレータが常に空グループになるバグだった。
4908        (
4909            "var g=Object.groupBy(new Set([1,2,3,4]),function(n){return n%2===0?'e':'o'}); g.e.join(',')+'/'+g.o.join(',')",
4910            "2,4/1,3",
4911        ),
4912        (
4913            "function* gen(){yield 1;yield 2;yield 3;} var g=Object.groupBy(gen(),function(n){return n%2===0?'e':'o'}); g.e.join(',')+'/'+g.o.join(',')",
4914            "2/1,3",
4915        ),
4916        (
4917            "var m=Map.groupBy(new Set([1,2,3,4]),function(n){return n%2===0?'e':'o'}); m.get('e').join(',')+'/'+m.get('o').join(',')",
4918            "2,4/1,3",
4919        ),
4920        // Map/Set/WeakMap/WeakSet コンストラクタ・Promise.all 系・Object.fromEntries も
4921        // 同じく `Interp` 不要の純関数 `iterable_values` を使っており、Generator を渡すと
4922        // 常に空になる同型のバグがあった(`it.iter_to_vec` に統一して解消)。
4923        (
4924            "function* gen(){yield [1,'a'];yield [2,'b'];} var m=new Map(gen()); m.get(1)+','+m.get(2)",
4925            "a,b",
4926        ),
4927        (
4928            "function* gen(){yield 1;yield 2;yield 2;} [...new Set(gen())].join(',')",
4929            "1,2",
4930        ),
4931        (
4932            "function* gen(){yield ['a',1];} Object.fromEntries(gen()).a",
4933            "1",
4934        ),
4935        (
4936            "function* gen(){yield Promise.resolve(1);yield Promise.resolve(2);} await Promise.all(gen()).then(a => a.join(','))",
4937            "1,2",
4938        ),
4939        // Uint8Array — 長さ指定・要素代入・length。
4940        ("var a=new Uint8Array(3); a[0]=10; a[1]=20; a.length+','+a[0]+','+a[1]", "3,10,20"),
4941        // Uint8Array — 配列/イテラブルから構築。
4942        ("Array.from(new Uint8Array([1,2,3])).join(',')", "1,2,3"),
4943        // Uint8Array.from — 静的メソッド。
4944        ("Uint8Array.from([4,5,6]).join(',')", "4,5,6"),
4945        // Uint8Array コンストラクタは Generator も受け付けるべき(仕様上どの iterable でも可)。
4946        (
4947            "function* gen(){yield 1;yield 2;yield 3;} new Uint8Array(gen()).join(',')",
4948            "1,2,3",
4949        ),
4950        // AggregateError も同様に Generator を errors として受け付ける。
4951        (
4952            "function* gen(){yield 'a';yield 'b';} new AggregateError(gen(), 'msg').errors.join(',')",
4953            "a,b",
4954        ),
4955        // Uint8Array — 値のラップ(256 は mod 256 で 0、-1 は 255、300 は 44)。
4956        ("var a=new Uint8Array([256,-1,300]); a.join(',')", "0,255,44"),
4957        // TypedArray コンストラクタに負の length を渡すと仕様上 RangeError(`ToIndex` が
4958        // `integer >= 0` を要求)だが、以前は `.max(0.0)` で黙って0にクランプしていた。
4959        (
4960            "try { new Uint8Array(-1); 'no-throw' } catch(e) { 'threw' }",
4961            "threw",
4962        ),
4963        (
4964            "try { new Float64Array(-3); 'no-throw' } catch(e) { 'threw' }",
4965            "threw",
4966        ),
4967        // `typedArray.set(array, offset)`は仕様上`offset + array.length`が
4968        // 対象の長さを超えるとRangeErrorだが、以前は範囲外の書き込みを
4969        // 黙って無視するだけで、`ta.set([1,2,3], hugeOffset)`が例外にならず
4970        // 無害なno-opとして成立してしまっていた(TypedArrayコンストラクタの
4971        // 負lengthバグと同じ「範囲外を黙って受け流す」パターン)。
4972        (
4973            "try { new Uint8Array(3).set([1,2,3], 5); 'no-throw' } catch(e) { 'threw' }",
4974            "threw",
4975        ),
4976        (
4977            "try { new Uint8Array(3).set([1,2], 2); 'no-throw' } catch(e) { 'threw' }",
4978            "threw",
4979        ),
4980        (
4981            // 境界ちょうど(offset + length === target.length)は範囲内なので例外にならない。
4982            "var a=new Uint8Array(3); a.set([9,9], 1); a.join(',')",
4983            "0,9,9",
4984        ),
4985        // Uint8Array.set — offset 付き上書き。
4986        ("var a=new Uint8Array(4); a.set([9,9],1); a.join(',')", "0,9,9,0"),
4987        // Uint8Array.set — 仕様上 array-like(非配列プレーンオブジェクト)も受け付けるべき。
4988        (
4989            "var a=new Uint8Array(3); a.set({0:5,1:6,length:2}); a.join(',')",
4990            "5,6,0",
4991        ),
4992        // Uint8Array.slice — 独立コピーを返す。
4993        ("var a=new Uint8Array([1,2,3,4,5]); var s=a.slice(1,3); s.join(',')+'/'+a.length", "2,3/5"),
4994        // 【2026-10-17】グローバルの var と window のプロパティは同じ束縛(YouTube の
4995        // `var ytcfg = …; window.ytcfg.set(…)`)。代入も双方向に効く。組み込みも window から見える。
4996        // スクリプトのトップレベルの `this` は window(Closure Compiler 形式の `(function(w){…})(this)`)。
4997        ("this === window", "true"),
4998        // DataView はバッファを共有(書いた値が同じバッファの TypedArray から見える)。
4999        ("var zab=new ArrayBuffer(8); var zdv=new DataView(zab, 2, 4); zdv.setUint16(0, 0x0102); var zua=new Uint8Array(zab); var zdv2=new DataView(zab); [zua[2], zua[3], zdv2.getUint8(3), zdv.byteLength, new Uint8Array(zdv.buffer, zdv.byteOffset, 2)[1]].join()", "1,2,2,4,2"),
5000        // subarray・バッファ上のビューへの書き込みが元の配列へ届く(protobuf の書き手)。
5001        ("var zo=new Uint8Array(6); var zs=zo.subarray(2,5); zs[0]=7; zs.set([8,9],1); var zw=new Uint8Array(zo.buffer, 1, 2); zw[0]=5; var zss=zs.subarray(2); zss[0]=4; [Array.from(zo).join(''), zs.byteOffset, zss.byteOffset].join(' ')", "057840 2 4"),
5002        // TypedArray と ArrayBuffer の共有(後から書いた値がバッファ経由の新しいビューで見える)。
5003        ("var zu8=new Uint8Array(4); zu8[0]=7; zu8[3]=9; var zv=new Uint8Array(zu8.buffer, 1, 3); var zd=new DataView(zu8.buffer); [zv[0], zv[2], zd.getUint8(0), new Uint8Array(zu8.buffer)[3]].join()", "0,9,7,9"),
5004        // Closure の goog.Uri の分解用正規表現(遅延量指定子・先読み・文字クラス内の \\ と /)。
5005        ("var zre=/^(?:([^:/?#.]+):)?(?:\\/\\/(?:([^\\\\/?#]*)@)?([^\\\\/?#]*?)(?::([0-9]+))?(?=[\\\\/?#]|$))?([^?#]+)?(?:\\?([^#]*))?(?:#([\\s\\S]*))?$/; var zm='https://a.com/videoplayback?x=1'.match(zre); [zm[1],zm[3],zm[5],zm[6]].join('|')", "https|a.com|/videoplayback|x=1"),
5006        // URL のパス(ホストとパスの間の "/")。
5007        ("var zu=new URL('https://a.com'); zu.pathname='videoplayback'; var zu2=new URL('https://a.com/'); zu2.pathname='/x'; [zu.href, zu2.href, new URL('videoplayback?x=1','https://a.com').href, new URL('https://a.com?q=1').href, new URL('https://a.com').pathname].join(' ')", "https://a.com/videoplayback https://a.com/x https://a.com/videoplayback?x=1 https://a.com/?q=1 /"),
5008        // try/finally の復元(return・throw・break・continue・入れ子)。
5009        ("var zst=[0]; function zf1(){ var g=zst[0]; zst[0]=1; try { return 5; } finally { zst[0]=g; } } function zf2(){ var g=zst[0]; zst[0]=2; try { throw new Error('e'); } finally { zst[0]=g; } } var zr1=zf1(); try { zf2(); } catch(e) {} var zc=0; for (var i=0;i<3;i++){ try { if(i==1) continue; if(i==2) break; } finally { zc++; } } [zr1, zst[0], zc].join()", "5,0,3"),
5010        ("var zst=[0]; function zf3(){ try { try { return 'a'; } finally { zst[0]=7; } } finally { zst[0]+=1; } } [zf3(), zst[0]].join()", "a,8"),
5011        ("function zf4(){ try { return 1; } finally { return 2; } } zf4()", "2"),
5012        // undefined 値のプロパティも自身のプロパティ(Wiz のコンテキスト `_.kp`)。
5013        ("var zka={defaultValue:void 0}; [Object.hasOwn(zka,'defaultValue'), !zka||!Object.hasOwn(zka,'defaultValue'), Object.keys(Object.assign({}, zka)).length].join()", "true,false,1"),
5014        // 同じ説明の Symbol は別物(switch・Map・オブジェクトのキー)。
5015        ("var zs1=Symbol('x'), zs2=Symbol('x'); var zr; switch(zs2){case zs1: zr='a'; break; case zs2: zr='b';} var zm=new Map(); zm.set(zs1,1); zm.set(zs2,2); var zo={}; zo[zs1]=3; zo[zs2]=4; [zr, zm.get(zs1), zm.get(zs2), zo[zs1], zo[zs2], zs1===zs2].join()", "b,1,2,3,4,false"),
5016        // プリミティブも Object.prototype のメソッドとポリフィルを引ける。
5017        ("[false.hasOwnProperty('x'), (1).hasOwnProperty('x'), 'ab'.hasOwnProperty('length'), typeof true.isPrototypeOf].join()", "false,false,true,function"),
5018        ("String.prototype.zq = function(){ return 'z' + this; }; Boolean.prototype.zb = function(){ return 1; }; ['a'.zq(), true.zb()].join()", "za,1"),
5019        // 組込みの instanceof(遅延コンポーネントの `m instanceof Promise`)。
5020        ("[Promise.resolve(1) instanceof Promise, new Map() instanceof Map, new Date() instanceof Date, /a/ instanceof RegExp, (function(){}) instanceof Function, [] instanceof Object, Promise.resolve() instanceof Object, ({}) instanceof Promise, Object.create(null) instanceof Object].join()", "true,true,true,true,true,true,true,false,false"),
5021        // 要素の独自プロパティ(expando)は別の経路で取った同じ要素からも見える。
5022        ("var zp=document.createElement('div'); var ze=document.createElement('span'); zp.appendChild(ze); ze.getApi=function(){return 7;}; ze.k=3; var zf=zp.children[0]; [zf.getApi(), zf.k, 'getApi' in zf, typeof document.createElement('div').getApi].join()", "7,3,true,undefined"),
5023        ("var zo=document.createElement('div'); zo.appendChild=function(){return 'patched';}; zo.appendChild(1)", "patched"),
5024        // DOM ノードの動的プロパティも `in` で見える(Closure のノード判定 `"nodeType" in a`)。
5025        ("var zd=document.createElement('div'); ['nodeType' in zd, 'tagName' in zd, 'appendChild' in zd, 'zzz' in zd, 'nodeType' in document.createTextNode('x')].join()", "true,true,true,false,true"),
5026        // 組込みコンストラクタの prototype(ポリフィル判定 `typeof X.prototype.m`)。
5027        ("[typeof AbortSignal.prototype, typeof AbortSignal.prototype.throwIfAborted, typeof AbortSignal.timeout, AbortSignal.prototype.constructor === AbortSignal].join()", "object,undefined,function,true"),
5028        ("typeof Map.prototype === 'object' && typeof Promise.prototype === 'object'", "true"),
5029        ("[Error().message, new TypeError().message, typeof Error().stack].join('|')", "||string"),
5030        // 未実装の WebGL は null(機能判定が誤認しない)。
5031        ("document.createElement('canvas').getContext('webgl')", "null"),
5032        // {} のプロトタイプは Object.prototype、その先は null(Redux の isPlainObject)。
5033        ("Object.getPrototypeOf({}) === Object.prototype", "true"),
5034        ("Object.getPrototypeOf(Object.prototype)", "null"),
5035        ("Object.getPrototypeOf([]) === Array.prototype", "true"),
5036        ("Object.getPrototypeOf(Object.create(null))", "null"),
5037        ("function zip(x){ if(typeof x!=='object'||x===null) return false; var p=x; while(Object.getPrototypeOf(p)!==null) p=Object.getPrototypeOf(p); return Object.getPrototypeOf(x)===p; } [zip({type:'a'}), zip([]), zip(new (function F(){})()), zip(Object.create(null))].join()", "true,false,false,false"),
5038        // 非 strict の関数の this は window(YouTube player の `(function(g){var window=this;…})(x)`)。
5039        ("(function(){ return this === window; })()", "true"),
5040        ("var zsl={}; (function(g){ var window=this; g.d = typeof window.document; })(zsl); zsl.d", "object"),
5041        // strict の関数(指令・外側の指令・クラス)は undefined のまま。
5042        ("(function(){ 'use strict'; return this; })() === undefined", "true"),
5043        ("(function(){ 'use strict'; return (function(){ return this; })(); })() === undefined", "true"),
5044        ("class ZS { m(){ return (function(){ return this; })(); } } new ZS().m() === undefined", "true"),
5045        ("(function(){ 'use strict'; var big = function(){ var a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8; return [a,b,c,d,e,f,g,h,this].length && this; }; return big(); })() === undefined", "true"),
5046        // 呼び出しが深すぎるときは捕捉可能な RangeError(従来はスクリプト全体を打ち切っていた)。
5047        ("function zrec(){ return zrec(); } var zr; try { zrec(); zr='no'; } catch(e) { zr = (e instanceof RangeError) + ':' + e.message; } zr", "true:Maximum call stack size exceeded"),
5048        // 深さ 1000 の再帰は正常に動く(スタック使用量のガードが早すぎない)。
5049        ("function zdeep(n){ return n ? 1 + zdeep(n - 1) : 0; } zdeep(1000)", "1000"),
5050        ("var zdd=0; function zdm(){ zdd++; zdm(); } try { zdm(); } catch(e) {} zdd > 1500", "true"),
5051        // String.prototype.lastIndexOf(未実装だった)と indexOf の開始位置(無視していた)。
5052        ("'abcabc'.lastIndexOf('b')+','+'abcabc'.lastIndexOf('b',3)+','+'abc'.lastIndexOf('z')+','+'SID=1'.lastIndexOf('SID=',0)", "4,1,-1,0"),
5053        ("'abcabc'.indexOf('b',2)+','+'abc'.indexOf('a',-5)+','+'abc'.indexOf('',9)", "4,0,3"),
5054        ("(function(w){ return typeof w.document; })(this)", "object"),
5055        // 遅延解析した関数本体(24 トークン以上)も入れ子のクロージャごと正しく動く。
5056        ("function zlazy(a){ var t=0; for(var i=0;i<a;i++){ t+=i; } var g=function(x){ var y=x*2; return y+t+1+2+3+4+5; }; return g(a); } zlazy(4)", "29"),
5057        ("var zwin1=5; window.zwin1", "5"),
5058        ("var zwin2=1; window.zwin2=7; zwin2", "7"),
5059        ("typeof window.Element+','+typeof window.HTMLElement", "function,function"),
5060        // `new Image(w,h)` / `new Audio(src)`(HTML の名前付きコンストラクタ)。
5061        ("var im=new Image(3,4); im.tagName+','+im.getAttribute('width')+','+im.getAttribute('height')", "IMG,3,4"),
5062        ("new Audio('a.mp3').getAttribute('src')", "a.mp3"),
5063        // `document.createEvent` + `initEvent`(DOM Level 2)。
5064        ("var ce=document.createEvent('Event'); ce.initEvent('x',true,false); ce.type+','+ce.bubbles+','+ce.cancelable", "x,true,false"),
5065        // Uint8Array.fill — 破壊的に埋めて this を返す。
5066        ("var a=new Uint8Array(4); a.fill(7,1,3); a.join(',')", "0,7,7,0"),
5067        // Uint8Array — byteLength / BYTES_PER_ELEMENT / buffer.byteLength。
5068        ("var a=new Uint8Array(5); a.byteLength+','+a.BYTES_PER_ELEMENT+','+a.buffer.byteLength", "5,1,5"),
5069        // ArrayBuffer — byteLength と、そこから Uint8Array を構築。
5070        ("var b=new ArrayBuffer(8); new Uint8Array(b).length+','+b.byteLength", "8,8"),
5071        // ArrayBuffer.prototype.resize/transfer/resizable/maxByteLength(ES2024)。
5072        // 以前は丸ごと未対応で、非 resizable なバッファしか作れなかった。
5073        (
5074            "var b=new ArrayBuffer(4,{maxByteLength:16}); b.resizable+','+b.maxByteLength+','+b.byteLength",
5075            "true,16,4",
5076        ),
5077        ("var b=new ArrayBuffer(4); b.resizable+','+b.maxByteLength", "false,4"),
5078        // resize は byteLength をその場で伸縮させる(新規領域はゼロ埋め)。
5079        (
5080            "var b=new ArrayBuffer(4,{maxByteLength:16}); b.resize(8); var u=new Uint8Array(b); b.byteLength+','+u.length",
5081            "8,8",
5082        ),
5083        // maxByteLength を超える resize は RangeError 相当の例外。
5084        (
5085            "var b=new ArrayBuffer(4,{maxByteLength:8}); try { b.resize(9); 'no throw'; } catch(e) { 'threw'; }",
5086            "threw",
5087        ),
5088        // 非 resizable なバッファへの resize は TypeError 相当の例外。
5089        (
5090            "var b=new ArrayBuffer(4); try { b.resize(2); 'no throw'; } catch(e) { 'threw'; }",
5091            "threw",
5092        ),
5093        // transfer は同じ byteLength の新バッファを返し、元のバッファを detach する(byteLength→0)。
5094        // 注: この処理系の ArrayBuffer/TypedArray は実メモリを共有しない独立コピー簡易実装
5095        // (DataView 等と同じ既存の割り切り)のため、TypedArray 経由で書いた値の引き継ぎまでは検証しない。
5096        (
5097            "var b=new ArrayBuffer(4); var b2=b.transfer(); b.byteLength+','+b2.byteLength",
5098            "0,4",
5099        ),
5100        // detach 後の再 transfer/resize は例外。
5101        (
5102            "var b=new ArrayBuffer(4); b.transfer(); try { b.transfer(); 'no throw'; } catch(e) { 'threw'; }",
5103            "threw",
5104        ),
5105        // transferToFixedLength は常に非 resizable な結果を返す。
5106        (
5107            "var b=new ArrayBuffer(4,{maxByteLength:8}); var b2=b.transferToFixedLength(); b2.resizable",
5108            "false",
5109        ),
5110        // `structuredClone(resizableArrayBuffer)`が`resizable`/`maxByteLength`
5111        // を引き継がず非resizableな複製に化けていたバグ(丸ごと未対応
5112        // だった。2026-07-17 発見・実装)。
5113        (
5114            "var b=new ArrayBuffer(4,{maxByteLength:16}); var c=structuredClone(b); \
5115             c.resizable+','+c.maxByteLength+','+c.byteLength",
5116            "true,16,4",
5117        ),
5118        (
5119            "var b=new ArrayBuffer(4,{maxByteLength:16}); var c=structuredClone(b); \
5120             c.resize(8); c.byteLength",
5121            "8",
5122        ),
5123        // Uint8Array.prototype.toBase64/toHex + 静的 fromBase64/fromHex(ES2024/2025)。
5124        ("new Uint8Array([72,101,108,108,111]).toBase64()", "SGVsbG8="),
5125        ("Array.from(Uint8Array.fromBase64('SGVsbG8=')).join(',')", "72,101,108,108,111"),
5126        ("new Uint8Array([222,173,190,239]).toHex()", "deadbeef"),
5127        ("Array.from(Uint8Array.fromHex('deadbeef')).join(',')", "222,173,190,239"),
5128        // 往復(バイト列 -> base64/hex -> バイト列)が元に戻ることを確認。
5129        (
5130            "var a=new Uint8Array([1,2,3,255,0]); Array.from(Uint8Array.fromBase64(a.toBase64())).join(',')",
5131            "1,2,3,255,0",
5132        ),
5133        (
5134            "var a=new Uint8Array([1,2,3,255,0]); Array.from(Uint8Array.fromHex(a.toHex())).join(',')",
5135            "1,2,3,255,0",
5136        ),
5137        // fromHex: 不正な16進文字列(奇数長)は例外を投げる。
5138        (
5139            "try { Uint8Array.fromHex('abc'); 'no-throw' } catch(e) { 'caught:' + e.name }",
5140            "caught:SyntaxError",
5141        ),
5142        // setFromBase64/setFromHex(ES2024/2025)— 既存バッファへインプレース書き込み。
5143        (
5144            "var a=new Uint8Array(5); var r=a.setFromBase64('SGVsbG8='); Array.from(a).join(',')+'|'+r.read+'|'+r.written",
5145            "72,101,108,108,111|8|5",
5146        ),
5147        (
5148            "var a=new Uint8Array(4); var r=a.setFromHex('deadbeef'); Array.from(a).join(',')+'|'+r.read+'|'+r.written",
5149            "222,173,190,239|8|4",
5150        ),
5151        (
5152            "try { new Uint8Array(1).setFromHex('zz'); 'no-throw' } catch(e) { 'caught:' + e.name }",
5153            "caught:SyntaxError",
5154        ),
5155        // TextEncoder — Uint8Array を返し、UTF-8 バイト値が正しい。
5156        ("var e=new TextEncoder().encode('AB'); e.length+','+e[0]+','+e[1]", "2,65,66"),
5157        // TextEncoder→TextDecoder 往復。
5158        ("new TextDecoder().decode(new TextEncoder().encode('hello'))", "hello"),
5159        // `TextEncoder.prototype.encodeInto(source, destination)` が丸ごと
5160        // 未対応だった(`encode()` は既存だが、この性能向けバリアントが欠けていた)。
5161        (
5162            "var buf=new Uint8Array(5); var r=new TextEncoder().encodeInto('Hi', buf); \
5163             r.read+','+r.written+','+buf[0]+','+buf[1]",
5164            "2,2,72,105",
5165        ),
5166        // 宛先バッファが足りない場合はコードポイント境界で打ち切る(マルチバイト
5167        // 文字を途中で分割しない)。
5168        (
5169            "var buf=new Uint8Array(1); var r=new TextEncoder().encodeInto('AB', buf); \
5170             r.read+','+r.written",
5171            "1,1",
5172        ),
5173        // Int8Array — 符号付きラップ(200 は -56、-100 はそのまま)。
5174        ("var a=new Int8Array([200,-100]); a.join(',')", "-56,-100"),
5175        // Uint8ClampedArray — 飽和(クランプ、ラップではない)。
5176        ("var a=new Uint8ClampedArray([300,-50,128.6]); a.join(',')", "255,0,129"),
5177        // ToUint8Clamp は同点を偶数丸め(banker's rounding)する仕様
5178        // (Math.round の四捨五入とは異なる)。
5179        ("var a=new Uint8ClampedArray([0.5,1.5,2.5,3.5]); a.join(',')", "0,2,2,4"),
5180        // Int16Array / Uint16Array — 幅16bitのラップ。
5181        ("var a=new Int16Array([40000]); a[0]", "-25536"),
5182        // 索引代入 `ta[i]=v` はコンストラクタ/`set`/`fill` と違って型変換
5183        // (ラップ/クランプ)が一切効かず生の数値がそのまま入るバグだった。
5184        ("var a=new Int8Array(1); a[0]=200; a[0]", "-56"),
5185        ("var a=new Uint8ClampedArray(1); a[0]=300; a[0]", "255"),
5186        ("var a=new Uint8ClampedArray(1); a[0]=-50; a[0]", "0"),
5187        ("var a=new Int16Array(1); a[0]=40000; a[0]", "-25536"),
5188        ("var a=new Uint16Array([70000]); a[0]", "4464"),
5189        // Int32Array / Uint32Array — 幅32bitのラップ。
5190        ("var a=new Int32Array([4294967295]); a[0]", "-1"),
5191        ("var a=new Uint32Array([-1]); a[0]", "4294967295"),
5192        // Float32Array — 単精度への丸め込み(倍精度のままではない)。
5193        ("var a=new Float32Array([0.1]); a[0] !== 0.1", "true"),
5194        // Float16Array / DataView getFloat16 / setFloat16 (ES2025)。
5195        ("var a=new Float16Array([1.5]); a[0] === 1.5", "true"),
5196        ("var dv=new DataView(new ArrayBuffer(2)); dv.setFloat16(0, 2.5); dv.getFloat16(0)", "2.5"),
5197        // Float64Array — 倍精度はそのまま保持。
5198        ("var a=new Float64Array([0.1]); a[0] === 0.1", "true"),
5199        // 各 TypedArray の BYTES_PER_ELEMENT。
5200        ("new Int16Array(1).BYTES_PER_ELEMENT+','+new Float64Array(1).BYTES_PER_ELEMENT", "2,8"),
5201        // TypedArray.from(静的)と set/slice/fill の型別クロスチェック。
5202        ("Int32Array.from([1,2,3]).join(',')", "1,2,3"),
5203        ("var a=new Uint16Array(3); a.set([1,2],1); a.join(',')", "0,1,2"),
5204        ("var a=new Int8Array([1,2,3,4]); a.slice(1,3).join(',')", "2,3"),
5205        ("var a=new Uint32Array(3); a.fill(5); a.join(',')", "5,5,5"),
5206        // `toSorted`/`toReversed`/`toSpliced`/`with`(ES2023)が TypedArray に対しては
5207        // 常に普通の配列を返すバグだった(`structuredClone(TypedArray)` と同種)。
5208        // 型変換(ラップ)も維持されることを確認する。
5209        (
5210            "var a=new Int8Array([200,3,1]); var b=a.toSorted(); b.join(',')+','+b.BYTES_PER_ELEMENT",
5211            "-56,1,3,1",
5212        ),
5213        (
5214            "var a=new Int8Array([1,2,200]); var b=a.toReversed(); b.join(',')+','+b.BYTES_PER_ELEMENT",
5215            "-56,2,1,1",
5216        ),
5217        (
5218            "var a=new Int8Array([1,2,3]); var b=a.with(1,200); b.join(',')+','+b.BYTES_PER_ELEMENT",
5219            "1,-56,3,1",
5220        ),
5221        // `map`/`filter` も同じ理由で TypedArray に対しては常に普通の配列を返す
5222        // バグだった。callback の戻り値が有効域外でも型ごとの変換(ラップ)が働く。
5223        (
5224            "var a=new Int8Array([1,2,3]); var b=a.map(x => x + 200); b.join(',')+','+b.BYTES_PER_ELEMENT",
5225            "-55,-54,-53,1",
5226        ),
5227        (
5228            "var a=new Int8Array([1,2,3,4]); var b=a.filter(x => x % 2 === 0); b.join(',')+','+b.BYTES_PER_ELEMENT",
5229            "2,4,1",
5230        ),
5231        // DataView(ES2015)が丸ごと未対応で `new DataView(buf)` が
5232        // `DataView is not defined` になっていた。
5233        (
5234            "var dv=new DataView(new ArrayBuffer(4)); dv.setUint8(0,255); dv.getUint8(0)",
5235            "255",
5236        ),
5237        // デフォルトはビッグエンディアン。
5238        (
5239            "var dv=new DataView(new ArrayBuffer(4)); dv.setUint32(0,0x01020304); dv.getUint8(0)+','+dv.getUint8(3)",
5240            "1,4",
5241        ),
5242        // littleEndian=true を渡すとバイト順が反転する。
5243        (
5244            "var dv=new DataView(new ArrayBuffer(4)); dv.setUint32(0,0x01020304,true); dv.getUint8(0)+','+dv.getUint8(3)",
5245            "4,1",
5246        ),
5247        // 符号付き/符号無しの往復(16bit)。
5248        ("var dv=new DataView(new ArrayBuffer(2)); dv.setInt16(0,-1); dv.getUint16(0)", "65535"),
5249        // Float64 の往復(丸めなし)。
5250        ("var dv=new DataView(new ArrayBuffer(8)); dv.setFloat64(0,3.5); dv.getFloat64(0)", "3.5"),
5251        // 範囲外アクセスは例外。
5252        (
5253            "try { new DataView(new ArrayBuffer(1)).getUint32(0); 'no throw'; } catch(e) { 'threw'; }",
5254            "threw",
5255        ),
5256        // Blob(File API の基礎コンテナ)が丸ごと未対応で `new Blob([...])` が
5257        // `Blob is not defined` になっていた。
5258        ("new Blob(['hello', ' ', 'world']).size", "11"),
5259        ("new Blob(['abc'], {type:'text/plain'}).type", "text/plain"),
5260        ("await new Blob(['hello world']).text()", "hello world"),
5261        ("await new Blob(['hello world']).slice(0,5).text()", "hello"),
5262        // `Blob.arrayBuffer()` が返す ArrayBuffer は実バイト列を持ち、そこから作った
5263        // DataView で正しく読み戻せる(`Blob`→`ArrayBuffer`→`DataView` の実用連携)。
5264        (
5265            "var buf = await new Blob(['A']).arrayBuffer(); new DataView(buf).getUint8(0)",
5266            "65",
5267        ),
5268        // `Blob.prototype.bytes()`(ES2024。丸ごと未対応だった。`await blob.
5269        // arrayBuffer()`→`new Uint8Array(...)` の2手間を1メソッドで済ませる。
5270        // 2026-07-15 発見・実装)。
5271        // 注: `instanceof Uint8Array` では検証しない — 調査の結果、この処理系の
5272        // TypedArray コンストラクタ群は `.prototype` を一切持たず `instanceof` が
5273        // 常に `false` を返す既存の別バグと判明(`Array`/`Map` 等 TypedArray 以外の
5274        // 組み込み型も含め instanceof 自体の自己テストがコードベースに1件も無く、
5275        // 広範な既知の未検証領域)。`BYTES_PER_ELEMENT`(Uint8Array 固有の値)で
5276        // 型を検証し、この既存バグを新規追跡項目として TODO.md へ記録した。
5277        (
5278            "var u = await new Blob(['AB']).bytes(); u.BYTES_PER_ELEMENT",
5279            "1",
5280        ),
5281        ("var u = await new Blob(['AB']).bytes(); u[0]+','+u[1]", "65,66"),
5282        // `File`(`Blob` を継承し `name`/`lastModified` を追加する File API の
5283        // 基礎コンストラクタ)が丸ごと未対応で `File is not defined` になっていた。
5284        ("new File(['hi'], 'a.txt').name", "a.txt"),
5285        ("new File(['hi'], 'a.txt', {type:'text/plain'}).type", "text/plain"),
5286        ("new File(['hello'], 'a.txt').size", "5"),
5287        ("await new File(['hello'], 'a.txt').text()", "hello"),
5288        // `Blob` 由来のメソッド(`slice`)もそのまま継承する。
5289        ("await new File(['hello world'], 'a.txt').slice(0,5).text()", "hello"),
5290        // `lastModified` は明示指定を尊重する。
5291        ("new File(['x'], 'a.txt', {lastModified: 12345}).lastModified", "12345"),
5292        // `File.webkitRelativePath`(丸ごと未対応だった。実際のディレクトリ
5293        // 選択UIが無いため常に空文字列の誠実な簡略実装。2026-07-17 発見・
5294        // 実装)。
5295        ("new File(['x'], 'a.txt').webkitRelativePath", ""),
5296        // `structuredClone(File)`が`Blob`分岐に落ちて`name`/`lastModified`
5297        // が失われ、素の`Blob`へ格下げされていたバグ(丸ごと未対応
5298        // だった。2026-07-17 発見・実装)。
5299        (
5300            "var c=structuredClone(new File(['hi'], 'a.txt', {lastModified: 12345})); \
5301             c.name + ',' + c.lastModified",
5302            "a.txt,12345",
5303        ),
5304        (
5305            "await structuredClone(new File(['hello'], 'a.txt')).text()",
5306            "hello",
5307        ),
5308        // 素の`Blob`(`name`無し)は引き続き`name`を持たない`Blob`のまま
5309        // 複製される(回帰確認)。
5310        ("structuredClone(new Blob(['x'])).name", "undefined"),
5311        // `FileReader` が丸ごと未対応だった(`blob.text()` 等の Promise 版は
5312        // 既に対応済みだったが、古典的なイベントベースの `readAsText`+`onload`
5313        // という書き方自体が丸ごと存在しなかった)。
5314        (
5315            "var r=new FileReader(); var out; r.onload=function(e){ out=e.target.result; }; \
5316             r.readAsText(new Blob(['hello'])); out",
5317            "hello",
5318        ),
5319        (
5320            "var r=new FileReader(); r.readAsText(new Blob(['x'])); r.readyState",
5321            "2",
5322        ),
5323        (
5324            "var r=new FileReader(); var buf; r.onload=function(e){ buf=e.target.result; }; \
5325             r.readAsArrayBuffer(new Blob(['A'])); new DataView(buf).getUint8(0)",
5326            "65",
5327        ),
5328        (
5329            "var r=new FileReader(); var url; r.onload=function(e){ url=e.target.result; }; \
5330             r.readAsDataURL(new Blob(['hi'], {type:'text/plain'})); url",
5331            "data:text/plain;base64,aGk=",
5332        ),
5333        // `addEventListener('load', ...)` 経由でも `onload` と同じく発火する
5334        // (`on<type>` 単一リスナ方式を共有しているため)。
5335        (
5336            "var r=new FileReader(); var fired=false; r.addEventListener('load', ()=>fired=true); \
5337             r.readAsText(new Blob(['x'])); fired",
5338            "true",
5339        ),
5340        // `Notification`(丸ごと未対応だった。通知 UI 自体が無いこの OS では
5341        // 実際の表示は行わず JS 側の契約のみ満たす簡略実装)。
5342        ("Notification.permission", "granted"),
5343        ("await Notification.requestPermission()", "granted"),
5344        (
5345            "var n = new Notification('hi', {body: 'world'}); n.title + ':' + n.body",
5346            "hi:world",
5347        ),
5348        ("typeof new Notification('x').close", "function"),
5349        // `data`/`requireInteraction`/`silent`/`dir`/`lang`(丸ごと未対応
5350        // だった。`title`/`body`/`icon`/`tag`は既に対応済みだったが、同じ
5351        // コンストラクタoptionsの残りが漏れていた。2026-07-17 発見・実装)。
5352        (
5353            "var n = new Notification('x', {data: {id: 5}, requireInteraction: true, silent: true}); \
5354             n.data.id + ',' + n.requireInteraction + ',' + n.silent",
5355            "5,true,true",
5356        ),
5357        ("new Notification('x').dir", "auto"),
5358        ("new Notification('x').data", "null"),
5359        ("typeof new Notification('x').removeEventListener", "function"),
5360        // Promise.withResolvers(ES2024)
5361        (
5362            "var {promise, resolve} = Promise.withResolvers(); resolve(5); await promise",
5363            "5",
5364        ),
5365        (
5366            "var {promise, reject} = Promise.withResolvers(); reject('e'); await promise.catch(e => 'caught:' + e)",
5367            "caught:e",
5368        ),
5369        // Promise.try(ES2025): 同期の戻り値/例外/Promise 戻り値をすべて統一的に扱う
5370        ("await Promise.try(() => 42)", "42"),
5371        (
5372            "await Promise.try(() => { throw 'boom' }).catch(e => 'caught:' + e)",
5373            "caught:boom",
5374        ),
5375        ("await Promise.try(() => Promise.resolve('ok'))", "ok"),
5376        ("await Promise.try((a, b) => a + b, 1, 2)", "3"),
5377        // String.prototype.isWellFormed / toWellFormed(ES2024)
5378        ("'abc'.isWellFormed()", "true"),
5379        ("'abc'.toWellFormed()", "abc"),
5380        // using 宣言(Explicit Resource Management、ES2023→ES2026ベースライン)
5381        (
5382            "var log=''; { using r = {[Symbol.dispose]: () => log+='d'}; log+='a' } log+='b'; log",
5383            "adb",
5384        ),
5385        // 複数 using は宣言の逆順で dispose される(LIFO)。
5386        (
5387            "var log=''; { using a = {[Symbol.dispose]: () => log+='A'}; using b = {[Symbol.dispose]: () => log+='B'} } log",
5388            "BA",
5389        ),
5390        // return で早期脱出しても dispose は呼ばれる(戻り値確定後に外側の log へ副作用)。
5391        (
5392            "var log=''; function f(){ { using r = {[Symbol.dispose]: () => log+='d'}; return 'a' } } var res=f(); log+res",
5393            "da",
5394        ),
5395        // await using は Symbol.asyncDispose の戻り値を await する。
5396        (
5397            "async function f(){ var log=''; { await using r = {[Symbol.asyncDispose]: async () => { log+='d' }}; log+='a' } return log } await f()",
5398            "ad",
5399        ),
5400        // Array.fromAsync(ES2024/2025): 同期イテラブル + 各要素の Promise を await。
5401        (
5402            "(await Array.fromAsync([Promise.resolve(1), 2, Promise.resolve(3)])).join(',')",
5403            "1,2,3",
5404        ),
5405        // mapFn 付き。
5406        ("(await Array.fromAsync([1,2,3], x => x * 10)).join(',')", "10,20,30"),
5407        // 複数 using の dispose がいずれも例外を投げると SuppressedError へ集約される
5408        // (LIFO: 後から宣言した b が先に dispose される → error=A(最後に投げた), suppressed=B)。
5409        (
5410            "var caught=null; try { { using a = {[Symbol.dispose]: () => { throw 'A' }}; using b = {[Symbol.dispose]: () => { throw 'B' }} } } catch(e) { caught = e } caught.name + ':' + caught.error + ':' + caught.suppressed",
5411            "SuppressedError:A:B",
5412        ),
5413        // for await...of: 同期イテラブル + 各要素の Promise を await するフォールバック。
5414        (
5415            "async function f(){ var out=''; for await (const x of [Promise.resolve(1), 2, Promise.resolve(3)]) { out += x } return out } await f()",
5416            "123",
5417        ),
5418        // for await...of: Symbol.asyncIterator を実装した独自オブジェクトを手動駆動。
5419        (
5420            "async function f(){ var obj={}; obj[Symbol.asyncIterator]=function(){ var i=0; return {next:function(){ i++; if (i<=3) { return Promise.resolve({value:i,done:false}); } return Promise.resolve({value:undefined,done:true}); }}; }; var out=''; for await (const x of obj){ out += x } return out } await f()",
5421            "123",
5422        ),
5423        // async generator インスタンスは Symbol.asyncIterator を公開すべき
5424        // (Generator.prototype[Symbol.asyncIterator] は自分自身を返す仕様)。
5425        // 以前は generator_method に asyncIterator/iterator の登録が無く、for await が
5426        // 常に eager-drain フォールバックへ落ちていた(値は一致するが例外伝播が違った)。
5427        (
5428            "async function* g(){ yield 1; yield 2; yield 3; } typeof g()[Symbol.asyncIterator]",
5429            "function",
5430        ),
5431        (
5432            "async function* g(){ yield 1; yield 2; yield 3; } var it=g(); it[Symbol.asyncIterator]() === it",
5433            "true",
5434        ),
5435        (
5436            "async function* g(){ yield 1; yield 2; yield 3; } async function f(){ var out=''; for await (const x of g()) { out += x } return out } await f()",
5437            "123",
5438        ),
5439        // 通常の(非 async)generator は Symbol.asyncIterator を公開してはいけない
5440        // (for await のフォールバックが正しく同期経路を通ることの確認)。
5441        (
5442            "function* g(){ yield 1; } typeof g()[Symbol.asyncIterator]",
5443            "undefined",
5444        ),
5445        // async generator が途中で例外を投げた場合、for await はそれを正しく伝播する
5446        // (eager-drain フォールバックはこれを黙って握り潰していた)。
5447        (
5448            "async function* g(){ yield 1; throw 'boom'; } async function f(){ var out=''; try { for await (const x of g()) { out += x } } catch(e) { out += ':' + e } return out } await f()",
5449            "1:boom",
5450        ),
5451        // 非宣言形式の for-of(x は既存変数): 配列を反復。
5452        ("var x; var out=''; for (x of [1,2,3]) { out += x } out", "123"),
5453        // 非宣言形式は既存の外側変数へ代入する(ループ後もその値が残る)。
5454        ("var x=0; for (x of [1,2,3]) {} x", "3"),
5455        // 非宣言形式の for-in(k は既存変数)。
5456        ("var k; var out=''; for (k in {a:1,b:2}) { out += k } out", "ab"),
5457        // 非宣言形式 + 配列分割代入 for-of。
5458        (
5459            "var a,b; var out=''; for ([a,b] of [[1,2],[3,4]]) { out += a+','+b+';' } out",
5460            "1,2;3,4;",
5461        ),
5462        // 非宣言形式 + オブジェクト分割代入 for-of。
5463        (
5464            "var a,b; var out=''; for ({a,b} of [{a:1,b:2},{a:3,b:4}]) { out += a+','+b+';' } out",
5465            "1,2;3,4;",
5466        ),
5467        // 非宣言形式 + メンバー式 for-of(obj.prop)。
5468        ("var obj={p:0}; for (obj.p of [1,2,3]) {} obj.p", "3"),
5469        // 非宣言形式 + 添字式 for-of(arr[0])。
5470        ("var arr=[0]; for (arr[0] of [7,8,9]) {} arr[0]", "9"),
5471        // New Set Methods(ES2024/2025)。仕様上第一引数は `Set` に限らず任意の
5472        // set-like(iterable)を受け付けるべきだが、以前は `&mut Interp` を持たない
5473        // `iterable_values` にフォールバックしており Generator を渡すと常に空扱いに
5474        // なるバグだった(`iterable_values`/`this_items` 系の横展開監査で見落とし)。
5475        (
5476            "function* g(){yield 3;yield 4;} [...new Set([1,2,3]).union(g())].sort().join(',')",
5477            "1,2,3,4",
5478        ),
5479        (
5480            "function* g(){yield 2;yield 3;} [...new Set([1,2,3]).intersection(g())].sort().join(',')",
5481            "2,3",
5482        ),
5483        ("[...new Set([1,2,3]).union(new Set([3,4,5]))].sort().join(',')", "1,2,3,4,5"),
5484        ("[...new Set([1,2,3]).intersection(new Set([2,3,4]))].sort().join(',')", "2,3"),
5485        ("[...new Set([1,2,3]).difference(new Set([2,3]))].join(',')", "1"),
5486        (
5487            "[...new Set([1,2,3]).symmetricDifference(new Set([2,3,4]))].sort().join(',')",
5488            "1,4",
5489        ),
5490        ("new Set([1,2]).isSubsetOf(new Set([1,2,3]))", "true"),
5491        ("new Set([1,2,3]).isSupersetOf(new Set([1,2]))", "true"),
5492        ("new Set([1,2]).isDisjointFrom(new Set([3,4]))", "true"),
5493        ("new Set([1,2]).isDisjointFrom(new Set([2,3]))", "false"),
5494        // Iterator helpers(ES2025): generator に対する map/filter/take/drop/flatMap/toArray/
5495        // forEach/some/every/find/reduce。無限イテレータには非対応の簡略実装(要素を全展開してから処理)。
5496        ("function* g(){ yield 1; yield 2; yield 3; } g().map(x=>x*2).join(',')", "2,4,6"),
5497        (
5498            "function* g(){ yield 1; yield 2; yield 3; yield 4; } g().filter(x=>x%2===0).join(',')",
5499            "2,4",
5500        ),
5501        (
5502            "function* g(){ yield 1; yield 2; yield 3; yield 4; yield 5; } g().take(2).join(',')",
5503            "1,2",
5504        ),
5505        (
5506            "function* g(){ yield 1; yield 2; yield 3; yield 4; yield 5; } g().drop(3).join(',')",
5507            "4,5",
5508        ),
5509        (
5510            "function* g(){ yield 1; yield 2; } g().flatMap(x=>[x,x*10]).join(',')",
5511            "1,10,2,20",
5512        ),
5513        ("function* g(){ yield 1; yield 2; } g().toArray().join(',')", "1,2"),
5514        (
5515            "function* g(){ yield 1; yield 2; yield 3; } var s=0; g().forEach(x=>s+=x); s",
5516            "6",
5517        ),
5518        ("function* g(){ yield 1; yield 2; yield 3; } g().some(x=>x>2)", "true"),
5519        ("function* g(){ yield 1; yield 2; yield 3; } g().every(x=>x>0)", "true"),
5520        ("function* g(){ yield 1; yield 2; yield 3; } g().find(x=>x>1)", "2"),
5521        (
5522            "function* g(){ yield 1; yield 2; yield 3; } g().reduce((a,x)=>a+x,0)",
5523            "6",
5524        ),
5525        // Iterator.from(ES2025): 任意のイテラブル(配列/Set/Generator)を Iterator helpers
5526        // 一式にアクセスできる形でラップする。map/filter 等は Array.prototype 経由、
5527        // take/drop/toArray は Iterator helpers 経由(Array に無いメソッドのため)。
5528        ("Iterator.from([1,2,3]).map(x=>x*2).join(',')", "2,4,6"),
5529        ("Iterator.from([1,2,3,4,5]).take(2).join(',')", "1,2"),
5530        ("Iterator.from([1,2,3,4,5]).drop(3).join(',')", "4,5"),
5531        ("Iterator.from(new Set([1,2,3])).toArray().join(',')", "1,2,3"),
5532        (
5533            "function* g(){ yield 1; yield 2; yield 3; } Iterator.from(g()).take(2).join(',')",
5534            "1,2",
5535        ),
5536        // Iterator.concat(ES2025 Standard): 複数のイテラブル(配列、Set、Generator等)をシームレスに結合する。
5537        ("Iterator.concat([1,2], new Set([3,4]), [5]).toArray().join(',')", "1,2,3,4,5"),
5538        ("Iterator.concat(['a','b'], ['c']).take(2).toArray().join(',')", "a,b"),
5539        // `instanceof`(ES1 以来の基礎演算子)が `BinaryOp::InstanceOf => Value::Bool(false)`
5540        // という未実装のプレースホルダのまま放置され、常に false を返す重大バグだった。
5541        // OrdinaryHasInstance 相当(`r.prototype` を起点に `l` のプロトタイプ連鎖を辿る)を実装。
5542        ("function Foo(){} new Foo() instanceof Foo", "true"),
5543        ("function Foo(){} function Bar(){} new Foo() instanceof Bar", "false"),
5544        ("class Animal{} class Dog extends Animal{} new Dog() instanceof Animal", "true"),
5545        ("class Animal{} class Dog extends Animal{} new Dog() instanceof Dog", "true"),
5546        ("1 instanceof Object", "false"),
5547        // 組み込みの `Object`/`Array` コンストラクタは(他のビルトインメソッド解決と同様に
5548        // ObjKind による特殊扱いで実装されており)実際の `.prototype` オブジェクトを
5549        // 持たないため、リテラルとの `instanceof` は常に false になる簡略実装の既知の限界。
5550        // ユーザー定義の function/class コンストラクタ(実際に `.prototype` を持つ)が
5551        // 主要なユースケースであり、そちらは上のテストの通り正しく動作する。
5552        ("({}) instanceof Object", "true"),
5553        ("[] instanceof Array", "true"),
5554        // グローバルな `Error` コンストラクタ自体が長期間欠落しており(TypeError/RangeError/
5555        // SyntaxError というサブタイプだけが存在し基底の `Error` が無い状態だった)、
5556        // `new Error("msg")` は「Error is not defined」で ReferenceError になっていた。
5557        // 合わせて各サブタイプの `.prototype` を `Error.prototype` を proto に持つ形へ
5558        // 再構成し、`instanceof` の実装と対で `new TypeError() instanceof Error` 等が
5559        // 正しく true になるようにした。
5560        ("new Error('boom').message", "boom"),
5561        ("new Error('boom').name", "Error"),
5562        ("new Error('boom') instanceof Error", "true"),
5563        // `Error.prototype.stack`(丸ごと未対応だった)。実際のコールフレーム
5564        // 一覧は再現できないため、V8 の1行目と同じ `"name: message"` 形式の
5565        // 簡略実装(詳細は `builtins::build_error_stack` 参照)。
5566        ("new Error('boom').stack", "Error: boom"),
5567        ("new TypeError('x').stack", "TypeError: x"),
5568        ("typeof new Error().stack === 'string' && new Error().stack.length > 0", "true"),
5569        (
5570            "(function(){ try { null.foo; } catch(e) { return typeof e.stack; } })()",
5571            "string",
5572        ),
5573        ("new TypeError('x') instanceof Error", "true"),
5574        ("new TypeError('x') instanceof TypeError", "true"),
5575        ("new RangeError('x') instanceof TypeError", "false"),
5576        ("new Uint8Array(1) instanceof Uint8Array", "true"),
5577        ("new Int8Array(1) instanceof Int8Array", "true"),
5578        ("new Uint8Array(1) instanceof Int8Array", "false"),
5579        ("Object.getPrototypeOf(new Uint8Array(1)) === Uint8Array.prototype", "true"),
5580        ("Uint8Array.prototype.constructor === Uint8Array", "true"),
5581        (
5582            "try { null.x } catch(e) { e instanceof Error }",
5583            "true",
5584        ),
5585        // ユーザー定義のカスタムエラークラス(`class MyError extends Error`)は、`super(msg)`
5586        // が到達するネイティブ `Error` コンストラクタが `this`(サブクラスのインスタンス。
5587        // proto は MyError.prototype → Error.prototype と連鎖)へ直接 message を書き込む
5588        // ことに依存しており、今回の一連の修正で自然に動くようになった実用パターン。
5589        (
5590            "class MyError extends Error { constructor(m){ super(m); this.name='MyError'; } } var e = new MyError('bad'); e.message + '/' + e.name",
5591            "bad/MyError",
5592        ),
5593        (
5594            "class MyError extends Error { constructor(m){ super(m); } } new MyError('x') instanceof Error",
5595            "true",
5596        ),
5597        (
5598            "class MyError extends Error { constructor(m){ super(m); } } new MyError('x') instanceof MyError",
5599            "true",
5600        ),
5601        // **重要**: `Symbol.hasInstance`(ES2015。`class Foo { static
5602        // [Symbol.hasInstance](x){...} }` によるカスタム `instanceof` 判定)が丸ごと
5603        // 未対応で、常にプロトタイプ連鎖の照合に決め打ちされていた。
5604        (
5605            "class Even { static [Symbol.hasInstance](n){ return typeof n === 'number' && n % 2 === 0; } } (4 instanceof Even) + ',' + (3 instanceof Even)",
5606            "true,false",
5607        ),
5608        (
5609            "class AlwaysTrue { static [Symbol.hasInstance](x){ return true; } } ({} instanceof AlwaysTrue)",
5610            "true",
5611        ),
5612        // `Error.isError(value)`(ES2025)が丸ごと欠落していた。`instanceof Error` と違い
5613        // `Symbol.hasInstance`/`Error.prototype` の上書きに影響されず組込み proto を直接見る。
5614        ("Error.isError(new Error('x'))", "true"),
5615        ("Error.isError(new TypeError('x'))", "true"),
5616        (
5617            "class MyError extends Error { constructor(m){ super(m); } } Error.isError(new MyError('x'))",
5618            "true",
5619        ),
5620        ("Error.isError({message:'x'})", "false"),
5621        ("Error.isError(42)", "false"),
5622        ("Error.isError(undefined)", "false"),
5623        // `new Error(msg, {cause})`(ES2022)が丸ごと未対応で、`err.cause` が常に
5624        // `undefined` になっていた(例外を包んで再送出する定番イディオムで使われる)。
5625        ("new Error('wrap', {cause: 'orig'}).cause", "orig"),
5626        ("typeof new Error('x').cause", "undefined"),
5627        ("new TypeError('t', {cause: 42}).cause", "42"),
5628        (
5629            "try { throw new Error('inner') } catch(e) { new Error('outer', {cause: e}).cause.message }",
5630            "inner",
5631        ),
5632        // `RegExp.escape(str)`(ES2025)が丸ごと未対応だった。ユーザー入力を
5633        // `new RegExp(...)` へそのまま渡す前に構文文字を無害化する定番イディオム。
5634        ("RegExp.escape('a.b*c')", "a\\.b\\*c"),
5635        ("new RegExp(RegExp.escape('1+1=2')).test('1+1=2')", "true"),
5636        (
5637            "new RegExp('^' + RegExp.escape('a.b') + '$').test('aXb')",
5638            "false",
5639        ),
5640        // `RegExp.prototype.compile()`(レガシーだが仕様に残るメソッド。丸ごと
5641        // 未対応だった)。`this` を新しいオブジェクトを作らずその場で差し替える。
5642        ("var r=/a/; r.compile('b'); r.test('b')", "true"),
5643        ("var r=/a/i; r.compile('a'); r.flags", ""),
5644        ("var r=/a/; r.compile('a','gi'); r.flags", "gi"),
5645        ("var r=/a/g; r.lastIndex=3; r.compile('b'); r.lastIndex", "0"),
5646        ("var r=/x/; var r2=r.compile('y'); r===r2", "true"),
5647        // `CSS.escape(str)`(CSSOM。丸ごと未対応だった。`CSS` 名前空間自体が
5648        // 存在しなかった)。
5649        ("CSS.escape('a.b')", "a\\.b"),
5650        ("CSS.escape('.foo#bar')", "\\.foo\\#bar"),
5651        ("CSS.escape('123')", "\\31 23"),
5652        ("CSS.escape('-1')", "-\\31 "),
5653        ("CSS.escape('-')", "\\-"),
5654        ("CSS.escape('abc')", "abc"),
5655        // `CSS.supports(conditionText)`/`CSS.supports(property, value)`
5656        // (CSSOM。丸ごと未対応だった。`@supports` の条件式評価をそのまま
5657        // 再利用するため、`not`/`and`/`or` の論理結合も正しく評価される)。
5658        ("CSS.supports('(display: grid)')", "true"),
5659        ("CSS.supports('display', 'grid')", "true"),
5660        ("CSS.supports('not (display: grid)')", "false"),
5661        (
5662            "CSS.supports('(display: grid) and (gap: 1rem)')",
5663            "true",
5664        ),
5665        (
5666            "CSS.supports('not (display: grid) or (gap: 1rem)')",
5667            "true",
5668        ),
5669        // `<audio>`/`<video>` の `.play()`/`.pause()`/`.load()`/`.canPlayType()`
5670        // (HTMLMediaElement。丸ごと未対応だった)。
5671        (
5672            "typeof document.createElement('audio').play().then",
5673            "function",
5674        ),
5675        ("typeof document.createElement('video').pause()", "undefined"),
5676        ("typeof document.createElement('audio').load()", "undefined"),
5677        ("document.createElement('video').canPlayType('video/mp4')", "maybe"),
5678        ("document.createElement('video').canPlayType('video/mp4; codecs=\"avc1.42E01E\"')", "probably"),
5679        ("document.createElement('video').canPlayType('video/ogg')", ""),
5680        // ARIA 反映 IDL 属性(`.role`/`.ariaLabel`/`.ariaChecked` 等)が丸ごと
5681        // 未対応だった。
5682        ("document.createElement('div').role", "null"),
5683        (
5684            "var e=document.createElement('div'); e.role='button'; e.getAttribute('role')",
5685            "button",
5686        ),
5687        (
5688            "var e=document.createElement('div'); e.setAttribute('aria-label','x'); e.ariaLabel",
5689            "x",
5690        ),
5691        (
5692            "var e=document.createElement('div'); e.ariaExpanded='true'; \
5693             e.getAttribute('aria-expanded')",
5694            "true",
5695        ),
5696        (
5697            "var e=document.createElement('div'); e.ariaLabel='x'; e.ariaLabel=null; \
5698             e.hasAttribute('aria-label')",
5699            "false",
5700        ),
5701        // 表・リスト系の残りの ARIA 反映 IDL 属性(丸ごと未対応だった。
5702        // 2026-07-15 発見・実装)。
5703        (
5704            "var e=document.createElement('div'); e.ariaColCount='3'; \
5705             e.getAttribute('aria-colcount')",
5706            "3",
5707        ),
5708        (
5709            "var e=document.createElement('div'); e.setAttribute('aria-rowindex','2'); \
5710             e.ariaRowIndex",
5711            "2",
5712        ),
5713        (
5714            "var e=document.createElement('div'); e.ariaSetSize='5'; e.ariaPosInSet='2'; \
5715             e.ariaSetSize + ',' + e.ariaPosInSet",
5716            "5,2",
5717        ),
5718        (
5719            "var e=document.createElement('div'); e.ariaLevel='1'; e.getAttribute('aria-level')",
5720            "1",
5721        ),
5722        // `String.prototype.repeat(count)` が仕様上必須の「負値/+Infinity で RangeError」
5723        // を投げず黙って空文字列に丸めていたバグ。
5724        ("'ab'.repeat(3)", "ababab"),
5725        ("'x'.repeat(0)", ""),
5726        (
5727            "try { 'x'.repeat(-1); 'no-throw' } catch(e) { 'threw' }",
5728            "threw",
5729        ),
5730        (
5731            "try { 'x'.repeat(Infinity); 'no-throw' } catch(e) { 'threw' }",
5732            "threw",
5733        ),
5734        // `eval`/`Function`(ES1 の最基礎機能の2つ)が丸ごと欠落していた
5735        // (`eval is not a function`/`Function is not defined`)。
5736        ("eval('1 + 2 * 3')", "7"),
5737        ("eval(42)", "42"),
5738        ("var x = 10; eval('x = x + 5'); x", "15"),
5739        ("new Function('a', 'b', 'return a + b')(3, 4)", "7"),
5740        ("new Function('return 1 + 1')()", "2"),
5741        (
5742            "typeof new Function('a', 'return a * 2')",
5743            "function",
5744        ),
5745        // Iterator Helpers(ES2025)が丸ごと未対応だった。`arr.values()` 等が返す軽量
5746        // イテレータに `map`/`filter`/`take`/`drop`/`toArray`/`forEach`/`reduce` を追加。
5747        ("[1,2,3].values().map(x => x * 2).toArray().join(',')", "2,4,6"),
5748        ("[1,2,3,4].values().filter(x => x % 2 === 0).toArray().join(',')", "2,4"),
5749        ("[1,2,3,4,5].values().take(2).toArray().join(',')", "1,2"),
5750        ("[1,2,3,4,5].values().drop(3).toArray().join(',')", "4,5"),
5751        (
5752            "[1,2,3].values().map(x => x + 1).filter(x => x > 2).toArray().join(',')",
5753            "3,4",
5754        ),
5755        (
5756            "var s=''; [1,2,3].values().forEach(x => s += x); s",
5757            "123",
5758        ),
5759        ("[1,2,3,4].values().reduce((a,b) => a + b)", "10"),
5760        ("[1,2,3].values().reduce((a,b) => a + b, 10)", "16"),
5761        // `some`/`every`/`find`(同じ ES2025 Iterator Helpers 提案に含まれる残り3つ
5762        // が丸ごと未対応だった。`map`/`filter`/`take`/`drop`/`toArray`/`forEach`/
5763        // `reduce` は既に対応済み)。
5764        ("[1,2,3].values().some(x => x > 2)", "true"),
5765        ("[1,2,3].values().some(x => x > 5)", "false"),
5766        ("[1,2,3].values().every(x => x > 0)", "true"),
5767        ("[1,2,3].values().every(x => x > 1)", "false"),
5768        ("[1,2,3,4].values().find(x => x % 2 === 0)", "2"),
5769        ("typeof [1,2,3].values().find(x => x > 5)", "undefined"),
5770        // `flatMap`(ES2025 Iterator Helpers 提案の最後の1つが丸ごと未対応だった)。
5771        (
5772            "[1,2,3].values().flatMap(x => [x, x * 10]).toArray().join(',')",
5773            "1,10,2,20,3,30",
5774        ),
5775        // `Iterator.prototype.flatMap` のマッパー戻り値が配列以外の iterable(Generator 等)
5776        // でも展開されるべき(以前は実配列以外を無条件で空扱いする同型のバグがあった)。
5777        (
5778            "function* dup(x){ yield x; yield x*10; } [1,2].values().flatMap(x => dup(x)).toArray().join(',')",
5779            "1,10,2,20",
5780        ),
5781        // `Object.getPrototypeOf`/`Object.setPrototypeOf`(ES5/ES6。`Reflect` 版は既に
5782        // あったが、実用上はるかに一般的なこちらの静的メソッドが欠落していた)。
5783        (
5784            "function Foo(){} Object.getPrototypeOf(new Foo()) === Foo.prototype",
5785            "true",
5786        ),
5787        (
5788            "var a={}; var b={x:1}; Object.setPrototypeOf(a,b); a.x",
5789            "1",
5790        ),
5791        // `Object.setPrototypeOf` は循環参照を検証しないため、循環したプロトタイプ
5792        // 連鎖を持つオブジェクトへのプロパティ読み書き/`in`/`for...in` が無限ループ
5793        // (QEMU 起動ハング)になり得たバグ。
5794        (
5795            "var a={}; Object.setPrototypeOf(a,a); a.nope",
5796            "undefined",
5797        ),
5798        (
5799            "var a={}; var b={}; Object.setPrototypeOf(a,b); Object.setPrototypeOf(b,a); a.nope",
5800            "undefined",
5801        ),
5802        (
5803            "var a={}; Object.setPrototypeOf(a,a); ('nope' in a)",
5804            "false",
5805        ),
5806        (
5807            "var a={}; Object.setPrototypeOf(a,a); a.x=1; a.x",
5808            "1",
5809        ),
5810        (
5811            "var a={}; Object.setPrototypeOf(a,a); var ks=[]; for (var k in a) { ks.push(k); } ks.length",
5812            "0",
5813        ),
5814        // `obj.__proto__`(Annex B のレガシーアクセサ。`Object.getPrototypeOf`/
5815        // `setPrototypeOf` は既に対応済みだったが、実務コードで極めて広く使われる
5816        // こちらの糖衣構文自体が丸ごと未対応だった)。
5817        ("var a={}; var b={x:1}; a.__proto__ = b; a.x", "1"),
5818        ("var b={x:1}; var a=Object.create(b); a.__proto__ === b", "true"),
5819        ("({}).__proto__ === null", "true"),
5820        ("var a={}; a.__proto__ = null; a.__proto__", "null"),
5821        // オブジェクトリテラル内の `__proto__: value`(算出キーでない場合)は
5822        // 通常のプロパティではなく [[Prototype]] を設定する特別構文(Annex B.3.1)。
5823        ("var p={x:5}; var o={__proto__: p}; o.x", "5"),
5824        ("var p={x:5}; var o={__proto__: p}; Object.keys(o).includes('__proto__')", "false"),
5825        // 算出キー `{['__proto__']: v}` は通常の own プロパティのまま(特別扱いしない)。
5826        ("var o={['__proto__']: 5}; o.__proto__", "5"),
5827        // `Object.prototype.isPrototypeOf`/`propertyIsEnumerable`。
5828        ("function Foo(){} Foo.prototype.isPrototypeOf(new Foo())", "true"),
5829        ("({}).isPrototypeOf({})", "false"),
5830        ("({x:1}).propertyIsEnumerable('x')", "true"),
5831        ("({x:1}).propertyIsEnumerable('y')", "false"),
5832        // `Function.prototype.call`/`apply`/`bind`(ES3/ES5)が丸ごと欠落していた
5833        // (`Reflect.apply` はあったが、はるかに一般的なこちらのインスタンスメソッドが無かった)。
5834        ("function f(a,b){ return this.x+a+b; } f.call({x:10}, 1, 2)", "13"),
5835        ("function f(a,b){ return this.x+a+b; } f.apply({x:10}, [1,2])", "13"),
5836        // `Function.prototype.apply` の第2引数も `Reflect.apply` と同じく array-like
5837        // 全般(非配列)を受け付けるべきだが、以前は `iterable_values` を使っており
5838        // 素の array-like を渡すと引数が消える同型のバグだった。
5839        (
5840            "function f(a,b){ return this.x+a+b; } f.apply({x:10}, {0:1,1:2,length:2})",
5841            "13",
5842        ),
5843        (
5844            "function f(a,b){ return this.x+a+b; } var g = f.bind({x:10}, 1); g(2)",
5845            "13",
5846        ),
5847        (
5848            "function f(){ return this.x; } var g = f.bind({x:5}); g.call({x:99})",
5849            "5",
5850        ),
5851        // `new.target`(ES6 MetaProperty)が未実装だった。`new` 経由の呼び出しでは
5852        // コンストラクタ自身、通常呼び出しでは undefined になる。
5853        // `new Foo()` はコンストラクタが(オブジェクトではなく)真偽値を return しても
5854        // 仕様どおり無視され `this`(構築済みインスタンス)を返すため、`new.target` の値は
5855        // 外側の変数に代入して確認する。
5856        (
5857            "var r; function Foo(){ r = new.target === Foo; } new Foo(); r",
5858            "true",
5859        ),
5860        ("function Foo(){ return new.target === Foo; } Foo()", "false"),
5861        (
5862            "function Foo(){ return typeof new.target; } Foo()",
5863            "undefined",
5864        ),
5865        (
5866            "class Foo { constructor(){ this.isNew = new.target === Foo; } } new Foo().isNew",
5867            "true",
5868        ),
5869        // アロー関数は `this` と同様に `new.target` もレキシカルに周囲から継承する
5870        // (アロー自身は `new` できず独自のフレームを持たないため)。
5871        (
5872            "var r; function Foo(){ var f = () => { r = new.target === Foo; }; f(); } new Foo(); r",
5873            "true",
5874        ),
5875        // `catch` 節の分割代入パターン(ES2015)。以前は `catch_param` が単なる識別子
5876        // 文字列で、パターン(`{message}` 等)を受け付けられなかった。
5877        (
5878            "try { throw {message:'boom', code:42}; } catch({message, code}) { message + '/' + code; }",
5879            "boom/42",
5880        ),
5881        (
5882            "try { throw [1,2,3]; } catch([a,,c]) { a + '/' + c; }",
5883            "1/3",
5884        ),
5885        ("try { throw 'x'; } catch(e) { e; }", "x"),
5886        // クラスの算出メソッド名 `[expr](){}` が未対応だった(パーサがキーとして
5887        // 識別子/文字列/数値/`[`を含む未知トークンを読み飛ばすだけで、`[Symbol.iterator]`
5888        // のようなよくあるパターンが正しくパースできなかった)。
5889        (
5890            "class C { ['foo' + 'bar'](){ return 42; } } new C().foobar()",
5891            "42",
5892        ),
5893        // 上の算出メソッド名対応と組み合わせ、`for...of` がユーザー定義の
5894        // `[Symbol.iterator]()` を実装したカスタムイテラブル(class)を正しく駆動できる
5895        // ようにした(従来は Array/Set/Map/Generator/文字列以外は「オブジェクト自身の
5896        // プロパティ値」を無条件に列挙するだけで、Symbol.iterator 自体を一切見ていなかった)。
5897        (
5898            "class Range { constructor(n){ this.n = n; } [Symbol.iterator](){ var i=0, n=this.n; return { next(){ return i<n ? {value:i++, done:false} : {value:undefined, done:true}; } }; } } var s=''; for (const x of new Range(4)) { s += x; } s",
5899            "0123",
5900        ),
5901        (
5902            "class Range { constructor(n){ this.n = n; } [Symbol.iterator](){ var i=0, n=this.n; return { next(){ return i<n ? {value:i++, done:false} : {value:undefined, done:true}; } }; } } [...new Range(3)].join(',')",
5903            "0,1,2",
5904        ),
5905        // `[Symbol.iterator]` 等の偽装 Symbol キーが `Object.keys`/`values`/`entries`/
5906        // `for...in`/`JSON.stringify` に漏れて列挙されてしまうバグの検証
5907        // (`Object.assign`/スプレッド構文は仕様どおり対象のままなので混同しないこと)。
5908        (
5909            "var o={a:1}; o[Symbol.iterator]=function(){}; Object.keys(o).join(',')",
5910            "a",
5911        ),
5912        (
5913            "var o={a:1}; o[Symbol.iterator]=function(){}; Object.values(o).join(',')",
5914            "1",
5915        ),
5916        (
5917            "var o={a:1}; o[Symbol.iterator]=function(){}; JSON.stringify(o)",
5918            "{\"a\":1}",
5919        ),
5920        (
5921            "var o={a:1}; o[Symbol.iterator]=function(){}; var k=''; for (const x in o) { k+=x; } k",
5922            "a",
5923        ),
5924        // `Date` が丸ごと未実装だった(`performance.now()` はあったが実時刻を持たない
5925        // 単純カウンタで、`Date` 自体はグローバルに存在しなかった)。実時刻は NTP 同期済みの
5926        // カーネル壁時計(`kernel::timer::get_unix_time()`)を利用する。
5927        (
5928            "new Date(2024, 0, 15, 10, 30, 45, 500).toISOString()",
5929            "2024-01-15T10:30:45.500Z",
5930        ),
5931        ("new Date(0).toISOString()", "1970-01-01T00:00:00.000Z"),
5932        // 束縛関数の new(ES5 の `new (Function.prototype.bind.apply(C, [null].concat(args)))`)。
5933        ("function ZB(a,b){this.s=a+b;} ZB.prototype.get=function(){return this.s;}; var zb=new (Function.prototype.bind.apply(ZB,[null].concat([2,3]))); [zb instanceof ZB, zb.get()].join()", "true,5"),
5934        ("function ZC(a,b){this.v=a*10+b;} var ZCB=ZC.bind({},4); new ZCB(2).v", "42"),
5935        // toUTCString/toGMTString(cookie の expires= に使われる。未実装だった)。
5936        ("new Date(0).toUTCString()", "Thu, 01 Jan 1970 00:00:00 GMT"),
5937        ("new Date(2024, 1, 29, 7, 5, 9).toGMTString()", "Thu, 29 Feb 2024 07:05:09 GMT"),
5938        ("new Date(NaN).toUTCString()", "Invalid Date"),
5939        // `toDateString`/`toTimeString`/`toLocaleDateString`/`toLocaleTimeString` は
5940        // 以前すべて `toString`(フルISO日時文字列)にエイリアスされており、日付のみ/
5941        // 時刻のみを返すべきところに他方の情報が混入していた。
5942        (
5943            "new Date(2024, 0, 15, 10, 30, 45, 500).toDateString()",
5944            "2024-01-15",
5945        ),
5946        (
5947            "new Date(2024, 0, 15, 10, 30, 45, 500).toTimeString()",
5948            "10:30:45",
5949        ),
5950        (
5951            "new Date(2024, 0, 15).toLocaleDateString()",
5952            "2024-01-15",
5953        ),
5954        ("new Date(2024, 0, 15).toLocaleTimeString()", "00:00:00"),
5955        ("new Date(2024, 0, 1).getFullYear()", "2024"),
5956        ("new Date(2024, 5, 15).getMonth()", "5"),
5957        ("new Date(2024, 0, 1).getDay()", "1"), // 2024-01-01 は月曜日
5958        // getTimezoneOffset()(丸ごと未対応だった)。UTC を常にローカルとみなす簡略実装のため常に0。
5959        ("typeof new Date().getTimezoneOffset()", "number"),
5960        ("new Date().getTimezoneOffset()", "0"),
5961        // `Date.prototype.set*` が `setTime` も含めて1つも実装されておらず、getter
5962        // しか無い丸ごと未対応バグだった(`setFullYear`/`setDate` 等の定番書き換え
5963        // イディオムが軒並み `undefined is not a function` になっていた)。
5964        (
5965            "var d=new Date(2024,0,15); d.setFullYear(2025); d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate()",
5966            "2025-1-15",
5967        ),
5968        // `getYear`/`setYear`(Annex B.2.4/B.2.5。2桁年時代の遺産)が丸ごと未対応だった。
5969        ("new Date(2024,0,1).getYear()", "124"),
5970        // `setYear(y)` は `0<=y<=99` なら `1900+y` を、それ以外は `y` をそのまま
5971        // フルイヤーとして使う仕様どおりの奇妙な後方互換ロジック。
5972        ("var d=new Date(2024,0,1); d.setYear(5); d.getFullYear()", "1905"),
5973        ("var d=new Date(2024,0,1); d.setYear(2030); d.getFullYear()", "2030"),
5974        (
5975            "var d=new Date(2024,0,15); d.setMonth(5); d.getMonth()",
5976            "5",
5977        ),
5978        (
5979            "var d=new Date(2024,0,15); d.setDate(d.getDate()+1); d.getDate()",
5980            "16",
5981        ),
5982        // setDate は月境界を仕様どおり繰り上げる。
5983        (
5984            "var d=new Date(2024,0,31); d.setDate(d.getDate()+1); (d.getMonth()+1)+'-'+d.getDate()",
5985            "2-1",
5986        ),
5987        (
5988            "var d=new Date(2024,0,15,10,20,30); d.setHours(23); d.getHours()+':'+d.getMinutes()+':'+d.getSeconds()",
5989            "23:20:30",
5990        ),
5991        (
5992            "var d=new Date(2024,0,15,10,20,30); d.setMinutes(5); d.getMinutes()",
5993            "5",
5994        ),
5995        (
5996            "var d=new Date(2024,0,15,10,20,30); d.setSeconds(59); d.getSeconds()",
5997            "59",
5998        ),
5999        (
6000            "var d=new Date(2024,0,15); d.setMilliseconds(123); d.getMilliseconds()",
6001            "123",
6002        ),
6003        (
6004            "var d=new Date(0); d.setTime(5000); d.getTime()",
6005            "5000",
6006        ),
6007        // set* の戻り値は仕様どおり新しい getTime() と同じ。
6008        (
6009            "var d=new Date(0); var r=d.setFullYear(2000); r === d.getTime()",
6010            "true",
6011        ),
6012        // `Date` は `to_number()` に `ObjKind::DateObj` 特殊扱いを追加したことで、
6013        // `.getTime()` を明示せずとも算術/比較演算がそのまま動く
6014        // (この処理系はオブジェクト全般の `valueOf`/`Symbol.toPrimitive` は汎用サポートしない
6015        // ため、これは `Date` だけの特殊扱い)。
6016        ("new Date(1000) - new Date(0)", "1000"),
6017        ("new Date(0) < new Date(1000)", "true"),
6018        ("+new Date(500)", "500"),
6019        ("typeof Date.now()", "number"),
6020        // `delete`(ES1 以来の基礎演算子)が AST/パーサ/インタプリタのどこにも存在せず、
6021        // 丸ごと未実装だった(`delete` トークン自体はレキサーが認識するのに、パーサが
6022        // どの式としても解釈しない状態)。
6023        ("var o = {x:1}; delete o.x; 'x' in o", "false"),
6024        ("var o = {x:1, y:2}; delete o.x; o.y", "2"),
6025        ("var k='x'; var o = {x:1}; delete o[k]; 'x' in o", "false"),
6026        ("var a=[1,2,3]; delete a[1]; a[1]", "undefined"),
6027        ("var a=[1,2,3]; delete a[1]; a.length", "3"),
6028        ("delete 5", "true"),
6029        // カンマ演算子 — 以前はパーサが先行する式を単に上書きして AST から消してしまい、
6030        // その式の副作用(関数呼び出し・代入等)が一切実行されない重大なバグだった。
6031        ("var s=''; (s+='a', s+='b', s+='c'); s", "abc"),
6032        ("var x=(1,2,3); x", "3"),
6033        (
6034            "var s=''; for (var i=0,j=10; i<3; i++,j--) { s += i+':'+j+' '; } s",
6035            "0:10 1:9 2:8 ",
6036        ),
6037        ("var a; var b; (a=1, b=a+1); a+','+b", "1,2"),
6038        // `generator.throw(err)`(ES2015)が丸ごと未実装だった(`next`/`return` はあったが
6039        // `throw` だけが欠落)。中断中の yield 位置に例外を注入する。
6040        (
6041            "function* g(){ try { yield 1; yield 2; } catch(e) { yield 'caught:'+e; } } var it=g(); it.next(); it.throw('boom').value",
6042            "caught:boom",
6043        ),
6044        (
6045            "function* g(){ yield 1; yield 2; } var it=g(); it.next(); var r; try { it.throw('x'); } catch(e) { r = 'propagated:'+e; } r",
6046            "propagated:x",
6047        ),
6048        // `String.prototype.codePointAt`(ES2015)/`localeCompare`(ES3)が丸ごと未実装だった。
6049        ("'A'.codePointAt(0)", "65"),
6050        ("'ABC'.codePointAt(5)", "undefined"),
6051        ("'a'.localeCompare('b')", "-1"),
6052        ("'b'.localeCompare('a')", "1"),
6053        ("'a'.localeCompare('a')", "0"),
6054        // `String.prototype.substr`(ES3 由来の Annex B 非推奨メソッド)が `slice`/
6055        // `substring` はあったのに欠落していた。古いコードで依然として広く使われる。
6056        ("'Hello World'.substr(6)", "World"),
6057        ("'Hello World'.substr(0, 5)", "Hello"),
6058        ("'Hello World'.substr(-5)", "World"),
6059        ("'Hello World'.substr(-5, 3)", "Wor"),
6060        // `static { ... }`(ES2022 静的初期化ブロック)が未実装だった。パーサはメソッド名
6061        // として識別子/文字列/数値/`[computed]` しか認識せず、`{` が来ると読み飛ばして
6062        // 続行する未実装フォールバックに落ちていた。
6063        (
6064            "class C { static x; static { C.x = 1 + 2; } } C.x",
6065            "3",
6066        ),
6067        (
6068            "class C { static x = 10; static { C.x += 5; } } C.x",
6069            "15",
6070        ),
6071        // 複数の static ブロックは宣言順に実行される。
6072        (
6073            "class C { static log=''; static { C.log+='a'; } static { C.log+='b'; } } C.log",
6074            "ab",
6075        ),
6076        // `Array.prototype.flat(depth)` が `depth` 引数を完全に無視し、常に1段しか
6077        // 展開しないバグだった。`.flat(Infinity)`(深いネスト解除の定番イディオム)も
6078        // 動作していなかった。
6079        ("[1,[2,3]].flat().join(',')", "1,2,3"),
6080        ("[1,[2,[3,[4]]]].flat(2).join(',')", "1,2,3,4"),
6081        ("[1,[2,[3,[4,[5]]]]].flat(Infinity).join(',')", "1,2,3,4,5"),
6082        // `.flat(Infinity)` は循環参照する配列(`a.push(a)`)に対しては祖先追跡
6083        // ガードが無いと無限再帰になり、この no_std 環境ではスタック
6084        // オーバーフロー(クラッシュ/ハング)に直結し得るバグだった(2026-07-13。
6085        // `Value::to_js_string` で修正済みの循環参照系と同種)。クラッシュ/
6086        // ハングせず完走することを確認する(既に祖先に現れた配列はそれ以上
6087        // 展開されずそのまま結果に含まれるため、要素数は打ち切り位置に依存)。
6088        ("var a=[1]; a.push(a); a.flat(Infinity); 'ok'", "ok"),
6089        // depth=0 は展開しない(トップレベルの要素数がそのまま length に残ることで確認)。
6090        ("[1,[2,[3]]].flat(0).length", "2"),
6091        // `Object.freeze()` が明示的な no-op(同一オブジェクトを返すだけ)だった。
6092        // `Object.isFrozen()` 自体も丸ごと欠落していた。
6093        ("var o={x:1}; Object.freeze(o); o.x=2; o.x", "1"),
6094        ("var o={x:1}; Object.freeze(o); o.y=2; 'y' in o", "false"),
6095        ("var o={x:1}; Object.freeze(o); delete o.x; o.x", "1"),
6096        ("var o={x:1}; Object.isFrozen(o)", "false"),
6097        ("var o={x:1}; Object.freeze(o); Object.isFrozen(o)", "true"),
6098        ("Object.isFrozen(5)", "true"),
6099        // `Object.seal`/`isSealed`/`preventExtensions`/`isExtensible` も丸ごと欠落していた。
6100        ("var o={x:1}; Object.seal(o); o.x=2; o.x", "2"),
6101        ("var o={x:1}; Object.seal(o); o.y=2; 'y' in o", "false"),
6102        ("var o={x:1}; Object.seal(o); delete o.x; 'x' in o", "true"),
6103        ("var o={x:1}; Object.isSealed(o)", "false"),
6104        ("var o={x:1}; Object.seal(o); Object.isSealed(o)", "true"),
6105        (
6106            "var o={x:1}; Object.preventExtensions(o); o.y=2; 'y' in o",
6107            "false",
6108        ),
6109        (
6110            "var o={x:1}; Object.preventExtensions(o); o.x=5; delete o.x; 'x' in o",
6111            "false",
6112        ),
6113        ("var o={x:1}; Object.isExtensible(o)", "true"),
6114        (
6115            "var o={x:1}; Object.preventExtensions(o); Object.isExtensible(o)",
6116            "false",
6117        ),
6118        // `Array.isArray` は仕様上 Proxy を透過して target を見る必要があるが、
6119        // 以前は Proxy でラップした配列が false 判定になっていた。
6120        ("Array.isArray(new Proxy([1,2], {}))", "true"),
6121        ("Array.isArray(new Proxy({}, {}))", "false"),
6122        // TypedArray も内部表現は ObjKind::Array を流用するため、`_ta_kind` タグを
6123        // 見ずに判定すると `Array.isArray(new Int8Array(...))` が仕様に反して
6124        // `true` になるバグだった(TypedArray は Array exotic object ではない)。
6125        ("Array.isArray(new Int8Array([1,2]))", "false"),
6126        ("Array.isArray([1,2])", "true"),
6127        // `Object.keys`/`values`/`entries` は Proxy を素通しできず(Proxy 自身は props を
6128        // 持たないため)常に空を返すバグだった。target フォワードのみの Proxy では
6129        // 正しく動くようにした(ownKeys トラップの呼び出しまでは非対応)。
6130        ("Object.keys(new Proxy({a:1,b:2}, {})).sort().join(',')", "a,b"),
6131        ("Object.values(new Proxy({a:1,b:2}, {})).sort().join(',')", "1,2"),
6132        (
6133            "Object.entries(new Proxy({a:1}, {})).map(e=>e[0]+':'+e[1]).join(',')",
6134            "a:1",
6135        ),
6136        // `JSON.stringify` も同じ Proxy 透過漏れで、Proxy を渡すと常に `{}` になっていた。
6137        ("JSON.stringify(new Proxy({a:1,b:2}, {}))", "{\"a\":1,\"b\":2}"),
6138        // `for...of`/スプレッドも同じ Proxy 透過漏れで、target フォワードのみの Proxy が
6139        // 配列/Set/Map を包んでいても何も反復しないバグだった(`iterate_values()` が自身の
6140        // `ObjKind` だけを見て Proxy を Array 等として認識できていなかった)。
6141        (
6142            "var s=''; for (const x of new Proxy([1,2,3], {})) { s += x; } s",
6143            "123",
6144        ),
6145        ("[...new Proxy([1,2,3], {})].join(',')", "1,2,3"),
6146        // `Object.assign` も同じ系統のバグを2つ抱えていた: (1) ソースが Proxy だと
6147        // 何もコピーされない、(2) ソースが配列だと要素が `ObjKind::Array` 側にあり
6148        // `props` には無いため、これも何もコピーされない。
6149        (
6150            "var t = Object.assign({}, new Proxy({a:1,b:2}, {})); t.a+','+t.b",
6151            "1,2",
6152        ),
6153        (
6154            "var t = Object.assign({}, [10,20,30]); t[0]+','+t[1]+','+t[2]",
6155            "10,20,30",
6156        ),
6157        // `Reflect.deleteProperty` は `delete` 演算子と同じ凍結/封印済みオブジェクト保護を
6158        // 適用すべきだが、以前は一切チェックせず常に削除・成功させていた(非対称だった)。
6159        (
6160            "var o={x:1}; Object.freeze(o); Reflect.deleteProperty(o,'x')",
6161            "false",
6162        ),
6163        ("var o={x:1}; Reflect.deleteProperty(o,'x')", "true"),
6164        // `Reflect.set` も同様に、以前は常に `true` を返しており、凍結済みオブジェクトへの
6165        // 書込みが黙殺されていても呼び出し元からは成功したように見えていた。
6166        (
6167            "var o={x:1}; Object.freeze(o); Reflect.set(o,'x',2)",
6168            "false",
6169        ),
6170        ("var o={x:1}; Reflect.set(o,'x',2); o.x", "2"),
6171        // `Array.prototype.includes` は SameValueZero で比較すべきところ `===` を使っており、
6172        // `[NaN].includes(NaN)` が false になるバグだった(`indexOf` は `===` のままで正しい)。
6173        ("[NaN].includes(NaN)", "true"),
6174        ("[NaN].indexOf(NaN)", "-1"),
6175        ("[1,2,NaN].includes(NaN)", "true"),
6176        ("[1,2,3].includes(2)", "true"),
6177        ("[1,2,3].includes(5)", "false"),
6178        // 高階配列メソッドの第2引数 `thisArg` が `map`/`filter`/`forEach`/`find` 系
6179        // すべてで無視されていた(コールバックの `this` を常に `undefined` 決め打ちしていた)。
6180        (
6181            "var ctx={mul:10}; [1,2,3].map(function(x){return x*this.mul;}, ctx).join(',')",
6182            "10,20,30",
6183        ),
6184        (
6185            "var ctx={min:2}; [1,2,3].filter(function(x){return x>this.min;}, ctx).join(',')",
6186            "3",
6187        ),
6188        (
6189            "var ctx={sum:0}; [1,2,3].forEach(function(x){this.sum+=x;}, ctx); ctx.sum",
6190            "6",
6191        ),
6192        (
6193            "var ctx={target:2}; [1,2,3].find(function(x){return x===this.target;}, ctx)",
6194            "2",
6195        ),
6196        (
6197            "var ctx={target:5}; [1,2,3].some(function(x){return x===this.target;}, ctx)",
6198            "false",
6199        ),
6200        (
6201            "var ctx={min:0}; [1,2,3].every(function(x){return x>this.min;}, ctx)",
6202            "true",
6203        ),
6204        // 同じ `thisArg` 無視バグが `Map.prototype.forEach`/`Set.prototype.forEach`/
6205        // `Array.from` の第3引数にもあった。
6206        (
6207            "var ctx={sum:0}; var m=new Map([['a',1],['b',2]]); m.forEach(function(v){this.sum+=v;}, ctx); ctx.sum",
6208            "3",
6209        ),
6210        (
6211            "var ctx={sum:0}; var s=new Set([1,2,3]); s.forEach(function(v){this.sum+=v;}, ctx); ctx.sum",
6212            "6",
6213        ),
6214        (
6215            "var ctx={mul:2}; Array.from([1,2,3], function(x){return x*this.mul;}, ctx).join(',')",
6216            "2,4,6",
6217        ),
6218        // `JSON.stringify` の第3引数 `space`(インデント整形。デバッグ出力の定番
6219        // イディオム `JSON.stringify(obj, null, 2)`)が完全に無視されていた。
6220        (
6221            "JSON.stringify({a:1,b:2}, null, 2)",
6222            "{\n  \"a\": 1,\n  \"b\": 2\n}",
6223        ),
6224        ("JSON.stringify([1,2], null, 2)", "[\n  1,\n  2\n]"),
6225        ("JSON.stringify({}, null, 2)", "{}"),
6226        ("JSON.stringify([], null, 2)", "[]"),
6227        ("JSON.stringify({a:1}, null, '\\t')", "{\n\t\"a\": 1\n}"),
6228        // space 省略時は従来どおりコンパクト出力のまま。
6229        ("JSON.stringify({a:1,b:2})", "{\"a\":1,\"b\":2}"),
6230        // `JSON.stringify` は BigInt を直列化できず本来 TypeError を投げるべきだが、
6231        // 以前は静かに省略していた(`undefined`/関数と同じ扱いになっていたバグ)。
6232        (
6233            "try { JSON.stringify(10n); 'no-throw' } catch(e) { 'caught:' + e.name }",
6234            "caught:Error",
6235        ),
6236        (
6237            "try { JSON.stringify({a:10n}); 'no-throw' } catch(e) { 'caught' }",
6238            "caught",
6239        ),
6240        // `replacer` 引数(キーの絞り込み用配列 / 値差し替え関数)も同様に丸ごと未対応だった。
6241        ("JSON.stringify({a:1,b:2,c:3}, ['a','c'])", "{\"a\":1,\"c\":3}"),
6242        (
6243            "JSON.stringify({a:1,b:2}, function(k,v){ return typeof v === 'number' ? v*10 : v; })",
6244            "{\"a\":10,\"b\":20}",
6245        ),
6246        // replacer 配列はオブジェクトのキー絞り込みのみに適用され、配列要素自体は
6247        // 絞り込まれない(仕様どおり)。
6248        ("JSON.stringify([1,2,3], ['a'])", "[1,2,3]"),
6249        // `JSON.parse` の第2引数 `reviver`(キーをボトムアップで巡る変換関数)も
6250        // 同様に丸ごと未対応だった。
6251        (
6252            "JSON.parse('{\"a\":1,\"b\":2}', function(k,v){ return typeof v === 'number' ? v*10 : v; }).a",
6253            "10",
6254        ),
6255        (
6256            "var r = JSON.parse('{\"a\":1,\"b\":2}', function(k,v){ return k==='b' ? undefined : v; }); 'b' in r",
6257            "false",
6258        ),
6259        (
6260            "JSON.parse('[1,2,3]', function(k,v){ return typeof v === 'number' ? v+1 : v; }).join(',')",
6261            "2,3,4",
6262        ),
6263        ("JSON.parse('{\"a\":1}').a", "1"),
6264        // `String.prototype.replaceAll` が RegExp パターン(`g` フラグ必須)・関数
6265        // リプレーサのどちらも未対応だった(RegExp を渡すと文字列表現 "/pat/flags" として
6266        // 扱われ無変換になっていた)。
6267        ("'aaa'.replaceAll(/a/g, 'b')", "bbb"),
6268        (
6269            "try { 'aaa'.replaceAll(/a/, 'b'); 'no-throw' } catch(e) { 'threw' }",
6270            "threw",
6271        ),
6272        (
6273            "'a1b2'.replaceAll(/[0-9]/g, function(m){ return '['+m+']'; })",
6274            "a[1]b[2]",
6275        ),
6276        ("'aXaXa'.replaceAll('X', function(){ return '-'; })", "a-a-a"),
6277        ("'abc'.replaceAll('x', 'y')", "abc"),
6278        // 同一の `var` 文の中で、前の宣言子を後の初期化子が参照できるか。
6279        // jQuery 1.8.2 の先頭が
6280        // `var ...,h=[],p="1.8.2",d=h.concat,...,w=p.trim,...` という形で、
6281        // ここが順に評価されないと `p` が undefined になり
6282        // 「Cannot read properties of undefined (reading 'trim')」で落ちる。
6283        ("var a1 = 1, b1 = a1 + 1; b1", "2"),
6284        ("var s1 = 'xy', t1 = s1.length; t1", "2"),
6285        ("var h1 = [], p1 = '1.8.2', w1 = p1.trim; typeof w1", "function"),
6286        ("var q1 = [], r1 = q1.push; typeof r1", "function"),
6287        // `String.prototype` が丸ごと無く、プロトタイプから直接読む形が
6288        // undefined になっていた。jQuery 1.8.2 の `o=String.prototype.trim`
6289        // で本体が実行時に落ちていた。
6290        ("typeof String.prototype", "object"),
6291        ("typeof String.prototype.trim", "function"),
6292        ("typeof String.prototype.slice", "function"),
6293        ("String.prototype.trim.call('  x  ')", "x"),
6294        ("String.prototype.toUpperCase.call('ab')", "AB"),
6295        // 実体からの解決は従来どおり。
6296        ("'  y '.trim()", "y"),
6297        // 素のオブジェクト/配列/関数から、プロトタイプ経由で
6298        // メソッドを**値として**読めるか。jQuery は
6299        // `core_toString = class2type.toString`(`class2type = {}`)のように
6300        // 読んでから `.call(...)` する。読めないと
6301        // 「Cannot read properties of undefined (reading 'call')」になる。
6302        ("typeof ({}).toString", "function"),
6303        ("typeof ({}).hasOwnProperty", "function"),
6304        ("typeof [].slice", "function"),
6305        ("typeof [].concat", "function"),
6306        ("typeof (function(){}).call", "function"),
6307        ("typeof (function(){}).apply", "function"),
6308        ("({}).toString.call([])", "[object Array]"),
6309        // `Array.prototype` はオブジェクトとしては在ったがメソッドが空だった。
6310        // jQuery 1.8.2 の
6311        // `j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf`
6312        // で undefined になり、続く `k.call(...)` で落ちていた。
6313        ("typeof Array.prototype.push", "function"),
6314        ("typeof Array.prototype.slice", "function"),
6315        ("typeof Array.prototype.indexOf", "function"),
6316        ("Array.prototype.slice.call([1,2,3], 1).join(',')", "2,3"),
6317        ("Array.prototype.indexOf.call([4,5,6], 5)", "1"),
6318        // `Object.prototype` 側も jQuery が読む 2 つを確かめる。
6319        ("typeof Object.prototype.toString", "function"),
6320        ("typeof Object.prototype.hasOwnProperty", "function"),
6321        // jQuery 1.8.2 の機能検出は、**切り離された**要素へ innerHTML を入れ、
6322        // そこから要素を取り出して `style` を触る。この一連が通らないと
6323        // 「Cannot read properties of undefined (reading 'style')」になる。
6324        ("var d = document.createElement('div'); typeof d", "object"),
6325        ("var d2 = document.createElement('div'); typeof d2.style", "object"),
6326        (
6327            "var d3 = document.createElement('div'); d3.innerHTML = \"<a href='/a'>a</a>\";              d3.getElementsByTagName('a').length",
6328            "1",
6329        ),
6330        (
6331            "var d4 = document.createElement('div'); d4.innerHTML = \"<a href='/a'>a</a>\";              typeof d4.getElementsByTagName('a')[0]",
6332            "object",
6333        ),
6334        (
6335            "var d5 = document.createElement('div'); d5.innerHTML = \"<a href='/a'>a</a>\";              typeof d5.getElementsByTagName('a')[0].style",
6336            "object",
6337        ),
6338        (
6339            "var d6 = document.createElement('div');              d6.innerHTML = '<table></table><a href=\"/a\">a</a>';              d6.getElementsByTagName('*').length > 0",
6340            "true",
6341        ),
6342        (
6343            "var d7 = document.createElement('div'); d7.style.cssText = 'top:1px';              typeof d7.style.cssText",
6344            "string",
6345        ),
6346        (
6347            "var d8 = document.createElement('div'); d8.setAttribute('className','t');              d8.getAttribute('className')",
6348            "t",
6349        ),
6350        // jQuery 1.8.2 の機能検出が実際に入れる HTML。先頭の空白 2 つ・
6351        // 自己閉じの `<link/>`・`<table></table>` が `<a>` の前に来る。
6352        // ここで `<a>` が取れないと `d.style` で落ちる。
6353        // どの要素が原因かを分けて確かめる。
6354        (
6355            "var e1 = document.createElement('div'); e1.innerHTML = '<link/><a href=\"/a\">a</a>';              e1.getElementsByTagName('a').length",
6356            "1",
6357        ),
6358        (
6359            "var e2 = document.createElement('div');              e2.innerHTML = '<table></table><a href=\"/a\">a</a>';              e2.getElementsByTagName('a').length",
6360            "1",
6361        ),
6362        (
6363            "var e3 = document.createElement('div'); e3.innerHTML = '  <a href=\"/a\">a</a>';              e3.getElementsByTagName('a').length",
6364            "1",
6365        ),
6366        (
6367            "var e4 = document.createElement('div');              e4.innerHTML = '<input type=\"checkbox\"/><a href=\"/a\">a</a>';              e4.getElementsByTagName('a').length",
6368            "1",
6369        ),
6370        // jQuery が入れる文字列そのもの。
6371        (
6372            "var e5 = document.createElement('div');              e5.innerHTML = '  <link/><table></table><a href=\"/a\">a</a><input type=\"checkbox\"/>';              e5.getElementsByTagName('a').length",
6373            "1",
6374        ),
6375        (
6376            "var e6 = document.createElement('div');              e6.innerHTML = '  <link/><table></table><a href=\"/a\">a</a><input type=\"checkbox\"/>';              e6.getElementsByTagName('*').length > 0",
6377            "true",
6378        ),
6379        // jQuery は IIFE の中で `e = a.document`(`a = window`)として
6380        // **`window.document` 経由**で触る。グローバルの `document` を
6381        // 直接使う経路とは別なので、そちらも確かめる。
6382        ("typeof window.document", "object"),
6383        // グローバルにあるのに `window.` 経由で読めない漏れ。
6384        // jQuery 1.8.2 は `a.navigator.userAgent`(`a` は window)と書く。
6385        ("typeof window.navigator", "object"),
6386        ("typeof window.navigator.userAgent", "string"),
6387        ("window.navigator === navigator", "true"),
6388        ("typeof window.location", "object"),
6389        ("typeof window.JSON", "object"),
6390        ("typeof window.Math", "object"),
6391        ("typeof window.XMLHttpRequest", "function"),
6392        // `window.x = ...` の後に素の `x` で読めること。
6393        // jQuery 1.8.2 は最後に `a.jQuery = a.$ = p`(`a` は window)で
6394        // 自分を公開するので、これが無いと **jQuery が完走しても
6395        // `$ is not defined`** になる。
6396        ("window.myGlobal1 = 42; myGlobal1", "42"),
6397        ("window.myFn1 = function(){ return 'z'; }; myFn1()", "z"),
6398        ("window.mySelf1 = window; typeof mySelf1.document", "object"),
6399        // スコープの変数が優先される(window 側で影を作らない)。
6400        ("window.shadow1 = 'w'; var shadow1 = 'v'; shadow1", "v"),
6401        // `Object.prototype.toString` はプリミティブの種別を返す。
6402        // 全部 `[object Object]` だと、jQuery の `jQuery.extend(true, ...)` が
6403        // 文字列を「素のオブジェクト」と誤判定して `{}` に置き換える
6404        // (実際に `s.type`("GET")が壊れ `$.getJSON` が落ちていた)。
6405        ("Object.prototype.toString.call('x')", "[object String]"),
6406        ("Object.prototype.toString.call(1)", "[object Number]"),
6407        ("Object.prototype.toString.call(true)", "[object Boolean]"),
6408        ("Object.prototype.toString.call(null)", "[object Null]"),
6409        ("Object.prototype.toString.call(undefined)", "[object Undefined]"),
6410        ("Object.prototype.toString.call({})", "[object Object]"),
6411        ("Object.prototype.toString.call([])", "[object Array]"),
6412        ("Object.prototype.toString.call(function(){})", "[object Function]"),
6413        ("window.document === document", "true"),
6414        ("typeof window.document.createElement", "function"),
6415        (
6416            "var w1 = window.document.createElement('div'); typeof w1.style",
6417            "object",
6418        ),
6419        (
6420            "var w2 = window.document.createElement('div');              w2.innerHTML = '  <link/><table></table><a href=\"/a\">a</a><input type=\"checkbox\"/>';              typeof w2.getElementsByTagName('a')[0]",
6421            "object",
6422        ),
6423        // 関数の引数として渡した window から辿る形(jQuery の IIFE と同じ)。
6424        (
6425            "(function(a){ var e = a.document, n = e.createElement('div');              n.setAttribute('className','t');              n.innerHTML = '  <link/><table></table><a href=\"/a\">a</a><input type=\"checkbox\"/>';              var d = n.getElementsByTagName('a')[0]; return typeof d; })(window)",
6426            "object",
6427        ),
6428        // `replaceAll`/`matchAll` が独自の `[Symbol.replace]`/`[Symbol.matchAll]` へ
6429        // 委譲していなかった(`replace`/`match`/`search`/`split` は委譲済みだった)。
6430        // 【順序】`g` フラグの検査が**先**、委譲はその後。逆にすると
6431        // 非 g の RegExp が委譲側へ流れて TypeError が出なくなる。
6432        (
6433            "var o = {}; o[Symbol.replace] = function(s, r){ return 'X' + s + r; };              'ab'.replaceAll(o, 'Z')",
6434            "XabZ",
6435        ),
6436        (
6437            "var o = {}; o[Symbol.matchAll] = function(s){ return ['m:' + s]; };              'ab'.matchAll(o)[0]",
6438            "m:ab",
6439        ),
6440        // 委譲を足しても、非 g の RegExp は従来どおり TypeError のまま。
6441        (
6442            "try { 'aaa'.matchAll(/a/); 'no-throw' } catch(e) { 'threw' }",
6443            "threw",
6444        ),
6445        // `String.prototype.split` の第2引数 `limit` が完全に無視されていた。
6446        ("'a,b,c,d'.split(',', 2).join('|')", "a|b"),
6447        ("'a,b,c'.split(',', 0).length", "0"),
6448        ("'a1b2c3'.split(/[0-9]/, 2).join('|')", "a|b"),
6449        ("'a,b,c'.split(',').join('|')", "a|b|c"),
6450        // `matchAll` は `g` フラグ必須(無ければ TypeError)だが、以前は無条件に
6451        // 全件マッチを返していた。
6452        (
6453            "try { 'aa'.matchAll(/a/); 'no-throw' } catch(e) { 'threw' }",
6454            "threw",
6455        ),
6456        (
6457            "[...'a1b2'.matchAll(/[0-9]/g)].map(m=>m[0]).join(',')",
6458            "1,2",
6459        ),
6460        // `structuredClone` は循環参照(自己参照)を扱えず無限再帰(この no_std 環境では
6461        // クラッシュ/ハングになり得る)に陥るバグだった。
6462        (
6463            "var a={x:1}; a.self=a; var c=structuredClone(a); c.self===c",
6464            "true",
6465        ),
6466        ("var a={x:1}; a.self=a; var c=structuredClone(a); c.x", "1"),
6467        // 同一オブジェクトへの複数参照は複製後も共有構造として保たれる(仕様どおり)。
6468        (
6469            "var shared={v:1}; var o={a:shared,b:shared}; var c=structuredClone(o); c.a===c.b",
6470            "true",
6471        ),
6472        // `JSON.stringify` にも同じ「循環参照で無限再帰」バグがあった。仕様どおり
6473        // TypeError を投げるようにした(同一オブジェクトへの非循環な複数参照=
6474        // 構造共有は問題なく直列化できることも確認)。
6475        (
6476            "try { var a={}; a.self=a; JSON.stringify(a); 'no-throw' } catch(e) { 'threw' }",
6477            "threw",
6478        ),
6479        // 深いネスト(30段超)でもスタックオーバーフローせずTypeErrorをスローするかの保護テスト
6480        (
6481            "var cur={}; for(var i=0;i<40;i++){ cur={child:cur}; } try { JSON.stringify(cur); 'no-throw' } catch(e) { 'depth-exceeded' }",
6482            "depth-exceeded",
6483        ),
6484        (
6485            "var shared={v:1}; JSON.stringify({a:shared,b:shared})",
6486            "{\"a\":{\"v\":1},\"b\":{\"v\":1}}",
6487        ),
6488        // `console.log` の表示整形(`display_value`)にも同じ「循環参照で無限再帰」
6489        // バグがあった。`console.log(a)`(`a.self=a`)は特に日常的にありうる操作のため、
6490        // 一番踏みやすいケースだった(クラッシュ/ハングせず完走することを確認)。
6491        (
6492            "var a={}; a.self=a; console.log(a); 'ok'",
6493            "ok",
6494        ),
6495        (
6496            "var a=[1,2]; a.push(a); console.log(a); 'ok'",
6497            "ok",
6498        ),
6499        // `console.log` の表示整形(`display_value_inner`)が `Proxy` を
6500        // `Array`/`Plain` 以外への素通しフォールバックに落としており、
6501        // `[ 1, 2, 3 ]` のような角括弧付き表示にならず素の `"1,2,3"` に
6502        // なっていた(`Value::to_js_string` の Proxy 素通しバグ修正と同時に
6503        // 発見。target へ委譲して同じ表示ロジックへ再帰するよう修正)。
6504        (
6505            "console.log(new Proxy([1,2,3], {})); 'ok'",
6506            "ok",
6507        ),
6508        // `Value::to_js_string`(`Array.prototype.join`/`toString`・テンプレート
6509        // リテラル補間・暗黙の文字列連結が共通で使う変換)にも同じ「循環参照で
6510        // 無限再帰」バグが残っていた(`structuredClone`/`JSON.stringify`/
6511        // `console.log` は既に修正済みだったが、この経路だけ未点検だった)。
6512        // `a.push(a); a.join()` はクラッシュ/ハングせず完走し、循環要素は
6513        // 仕様どおり空文字列として扱われることを確認する。
6514        ("var a=[1]; a.push(a); a.join()", "1,1,"),
6515        ("var a=[1]; a.push(a); String(a); 'ok'", "ok"),
6516        ("var a=[1]; a.push(a); `${a}`; 'ok'", "ok"),
6517        // `Number.prototype.toString(radix)` が `radix` 引数を完全に無視し常に10進表示
6518        // だった(`n.toString(16)` 等の定番イディオムが動かなかった)。
6519        ("(255).toString(16)", "ff"),
6520        ("(8).toString(2)", "1000"),
6521        ("(255).toString()", "255"),
6522        ("(-255).toString(16)", "-ff"),
6523        ("(0).toString(2)", "0"),
6524        (
6525            "try { (1).toString(1); 'no-throw' } catch(e) { 'threw' }",
6526            "threw",
6527        ),
6528        // `parseInt` が `radix` 未指定時に `0x` 接頭辞を自動検出せず、明示的に
6529        // `radix=16` を渡しても `0x` を読み飛ばさないバグだった。
6530        ("parseInt('0xFF')", "255"),
6531        ("parseInt('0xFF', 16)", "255"),
6532        ("parseInt('FF', 16)", "255"),
6533        ("parseInt('10')", "10"),
6534        ("parseInt('-0x10')", "-16"),
6535        ("parseInt('10', 2)", "2"),
6536        // `Number.parseFloat`/`Number.parseInt`(ES2015)は仕様上グローバル
6537        // `parseFloat`/`parseInt` と同一の関数オブジェクトでなければならないが、
6538        // 以前は別々に構築されており `===` が `false` になっていた。
6539        ("Number.parseFloat === parseFloat", "true"),
6540        ("Number.parseInt === parseInt", "true"),
6541        ("Number.parseFloat.name", "parseFloat"),
6542        ("Number.parseInt.name", "parseInt"),
6543        // `JSON.stringify` は `Date` の `toJSON`(ISO 文字列)相当を特殊扱いする。
6544        // 以前は Date インスタンスが(`toJSON` を汎用的にチェックしないため)
6545        // プロパティなしの `{}` として直列化されてしまっていた。
6546        (
6547            "JSON.stringify(new Date(0))",
6548            "\"1970-01-01T00:00:00.000Z\"",
6549        ),
6550        (
6551            "JSON.stringify({d: new Date(0)})",
6552            "{\"d\":\"1970-01-01T00:00:00.000Z\"}",
6553        ),
6554        // 文字列化(テンプレートリテラル補間・暗黙の文字列連結)でも `[object Object]` ではなく
6555        // ISO 形式が出るようにした(`Value::to_js_string()` に `ObjKind::DateObj` 分岐を追加)。
6556        ("'' + new Date(0)", "1970-01-01T00:00:00.000Z"),
6557        ("`date=${new Date(0)}`", "date=1970-01-01T00:00:00.000Z"),
6558        // Object.defineProperty / Property attributes (writable, configurable, enumerable)
6559        (
6560            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); o.x = 2; o.x",
6561            "1",
6562        ),
6563        (
6564            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); delete o.x",
6565            "false",
6566        ),
6567        (
6568            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); Object.keys(o).length",
6569            "0",
6570        ),
6571        (
6572            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); var keys=[]; for(var k in o){ keys.push(k); } keys.length",
6573            "0",
6574        ),
6575        (
6576            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); JSON.stringify(o)",
6577            "{}",
6578        ),
6579        (
6580            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); ({...o}).x",
6581            "undefined",
6582        ),
6583        (
6584            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); Object.assign({}, o).x",
6585            "undefined",
6586        ),
6587        (
6588            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); Reflect.deleteProperty(o, 'x')",
6589            "false",
6590        ),
6591        // `DataView.prototype.getBigInt64`/`.getBigUint64`/`.setBigInt64`/
6592        // `.setBigUint64`(ES2020)が丸ごと未対応だった。64bit整数はNumberの
6593        // 安全整数範囲(2^53)を超えうるためBigIntで往復する必要がある。
6594        // 2026-07-18 発見・実装)。
6595        (
6596            "var b=new ArrayBuffer(8); var v=new DataView(b); v.setBigInt64(0, 123456789012345n); v.getBigInt64(0).toString()",
6597            "123456789012345",
6598        ),
6599        (
6600            "var b=new ArrayBuffer(8); var v=new DataView(b); v.setBigInt64(0, -1n); v.getBigInt64(0).toString()",
6601            "-1",
6602        ),
6603        (
6604            "var b=new ArrayBuffer(8); var v=new DataView(b); v.setBigUint64(0, -1n); v.getBigUint64(0).toString()",
6605            "18446744073709551615",
6606        ),
6607        (
6608            "var b=new ArrayBuffer(8); var v=new DataView(b); v.setBigInt64(0, 1n, true); v.getBigInt64(0, true).toString()",
6609            "1",
6610        ),
6611        // `JSON.rawJSON`/`JSON.isRawJSON`(ES2025)が丸ごと未対応だった。
6612        // Numberの精度で表現できない巨大な整数をJSON文字列へ精度損失無しで
6613        // 埋め込むための機能。2026-07-18 発見・実装)。
6614        (
6615            "JSON.stringify({n: JSON.rawJSON('123456789012345678901234567890')})",
6616            "{\"n\":123456789012345678901234567890}",
6617        ),
6618        ("JSON.isRawJSON(JSON.rawJSON('42'))", "true"),
6619        ("JSON.isRawJSON({rawJSON: '42'})", "false"),
6620        ("JSON.isRawJSON(42)", "false"),
6621        (
6622            "(function(){ try { JSON.rawJSON(' 1'); return 'no-throw'; } catch(e) { return e instanceof Error; } })()",
6623            "true",
6624        ),
6625        (
6626            "(function(){ try { JSON.rawJSON('not json'); return 'no-throw'; } catch(e) { return e instanceof Error; } })()",
6627            "true",
6628        ),
6629        // `button.command`/`.commandForElement`(Invoker Commands API)が
6630        // 丸ごと未対応だった。`popoverTargetAction`/`.popoverTargetElement`と
6631        // 全く同じ反映・idref解決パターンで実装(クリック時の実際の
6632        // `CommandEvent`発火/組込み動作は本サイクルでは未対応。
6633        // 2026-07-18 発見・実装)。
6634        (
6635            "var b=document.createElement('button'); b.command='show-modal'; b.command",
6636            "show-modal",
6637        ),
6638        (
6639            "document.createElement('button').commandForElement",
6640            "null",
6641        ),
6642        // `commandForElement`セッターの往復(`b.commandForElement = 要素`→
6643        // `commandfor`属性へidを反映)。この方向はDOMツリーへの接続を
6644        // 要さない(`popoverTargetElement`と同じ、対象要素のidを直接
6645        // 読むだけの実装)ため、`document.getElementById`のアタッチ要件
6646        // (共有テストフィクスチャ`wrap`が本テストスイート内の別テストで
6647        // 未接続化され得る既知の不安定要素)に依存しない自己完結した形に
6648        // している。逆方向(属性→要素解決)は`popoverTargetElement`と
6649        // 全く同じ`get_element_by_id`経路を再利用しているため別途検証済み。
6650        (
6651            "var t=document.createElement('div'); t.setAttribute('id','cfe-target'); var b=document.createElement('button'); b.commandForElement=t; b.getAttribute('commandfor')",
6652            "cfe-target",
6653        ),
6654        // プレーンオブジェクトのプロパティ列挙順序(ECMA-262
6655        // `OrdinaryOwnPropertyKeys`)が丸ごと未対応だった(`Obj.props`が
6656        // `BTreeMap`のため常に文字列の辞書式順序になっていた、
6657        // architecture-level の既知ギャップ)。`BTreeMap`→`IndexMap`移行
6658        // (挿入順保持。2026-07-18完了)に続けて、`Object.keys`/`.values`/
6659        // `.entries`へ「正整数キー→数値昇順」「残り→挿入順」の2段階
6660        // 並べ替え(`spec_key_order`)を配線し、仕様どおりの列挙順序を
6661        // 完成させた。2026-07-21 に `for...in`/`JSON.stringify`/スプレッド構文/
6662        // `Object.assign` 等すべての列挙箇所への同ロジック横展開を完了。
6663        (
6664            "Object.keys({y:1,x:2,a:3}).join(',')",
6665            "y,x,a",
6666        ),
6667        (
6668            "var o={}; delete o.x; o.b=1; o.a=2; o.c=3; delete o.a; o.a=4; Object.keys(o).join(',')",
6669            "b,c,a",
6670        ),
6671        (
6672            "Object.keys({2:'b',1:'a',10:'c'}).join(',')",
6673            "1,2,10",
6674        ),
6675        (
6676            "Object.keys({10:'c',y:1,2:'b',x:2}).join(',')",
6677            "2,10,y,x",
6678        ),
6679        (
6680            "Object.values({10:'c',y:1,2:'b',x:2}).join(',')",
6681            "b,c,1,2",
6682        ),
6683        (
6684            "var res=[]; for(var k in {10:'c',y:1,2:'b',x:2}){ res.push(k); } res.join(',')",
6685            "2,10,y,x",
6686        ),
6687        (
6688            "JSON.stringify({10:'c',y:1,2:'b',x:2})",
6689            "{\"2\":\"b\",\"10\":\"c\",\"y\":1,\"x\":2}",
6690        ),
6691        (
6692            "Object.keys({...{10:'c',y:1,2:'b',x:2}}).join(',')",
6693            "2,10,y,x",
6694        ),
6695        (
6696            "Object.keys(Object.assign({}, {10:'c',y:1,2:'b',x:2})).join(',')",
6697            "2,10,y,x",
6698        ),
6699        (
6700            "Object.entries({2:'b',y:1,1:'a'}).map(e=>e.join(':')).join(',')",
6701            "1:a,2:b,y:1",
6702        ),
6703        // `01`(先頭ゼロ)は仕様上「配列インデックス」ではなく通常の文字列
6704        // キー扱いのため数値昇順の対象外(挿入順のまま)。
6705        (
6706            "Object.keys({'01':'x', 1:'y'}).join(',')",
6707            "1,01",
6708        ),
6709        // ECMAScript Internationalization API (Intl)
6710        (
6711            "typeof Intl",
6712            "object",
6713        ),
6714        (
6715            "Intl.getCanonicalLocales('ja-JP').join(',')",
6716            "ja-JP",
6717        ),
6718        // `ja-JP`も`en-US`と同じ3桁カンマ区切り書式を使う言語のため`"123,456"`が
6719        // 正しい期待値(この処理系はロケール別の書式データベースを持たず全て
6720        // 同じ簡略書式を使うため、ロケール文字列自体は解析結果に影響しない)。
6721        // 元は書式化ロジック実装前の無加工出力`"123456"`をそのまま期待値に
6722        // していた誤り(2026-07-18 発見・修正)。
6723        (
6724            "new Intl.NumberFormat('ja-JP').format(123456)",
6725            "123,456",
6726        ),
6727        (
6728            "new Intl.DateTimeFormat('ja-JP').resolvedOptions().locale",
6729            "ja-JP",
6730        ),
6731        (
6732            "new Intl.Collator().compare('a', 'b')",
6733            "-1",
6734        ),
6735        (
6736            "new Intl.PluralRules().select(1)",
6737            "one",
6738        ),
6739        (
6740            "new Intl.RelativeTimeFormat().format(-1, 'day')",
6741            "1 days ago",
6742        ),
6743        (
6744            "new Intl.ListFormat().format(['a', 'b'])",
6745            "a, b",
6746        ),
6747        (
6748            "new Intl.DisplayNames(['en'], {type: 'language'}).of('ja')",
6749            "ja",
6750        ),
6751        // RegExp u/v flags & Unicode property escapes
6752        (
6753            "new RegExp('a', 'u').unicode",
6754            "true",
6755        ),
6756        (
6757            "new RegExp('a', 'v').unicodeSets",
6758            "true",
6759        ),
6760        (
6761            "/\\p{Letter}/u.test('A')",
6762            "true",
6763        ),
6764        (
6765            "/\\p{Number}/u.test('9')",
6766            "true",
6767        ),
6768        // Generator.prototype.return(v) & try...finally
6769        (
6770            "var finRan = false; function* g() { try { yield 1; } finally { finRan = true; } } var it = g(); it.next(); it.return(42); finRan",
6771            "true",
6772        ),
6773        // `Object.create(null)`/`Object.groupBy()`/`Object.setPrototypeOf(obj,null)`が
6774        // 実際には`Object.prototype`のメソッド(`hasOwnProperty`/`toString`等)を
6775        // 引き続き持ってしまうバグ(`proto: None`だけでは「明示的にnull
6776        // プロトタイプ」なのか「単に通常オブジェクトでチェーンが尽きた」のか
6777        // 区別できず、`get_property`の共通メソッドフォールバックが無条件に
6778        // 適用されていた)。`null_proto`フラグを新設して修正(2026-07-18
6779        // 発見・実装)。
6780        (
6781            "typeof Object.create(null).hasOwnProperty",
6782            "undefined",
6783        ),
6784        (
6785            "typeof Object.create(null).toString",
6786            "undefined",
6787        ),
6788        (
6789            "Object.getPrototypeOf(Object.create(null))",
6790            "null",
6791        ),
6792        // 通常のオブジェクトリテラルは引き続き`Object.prototype`のメソッドを
6793        // 継承する(`null_proto`の導入で既存の通常オブジェクトの挙動を
6794        // 壊していないことの回帰確認)。
6795        (
6796            "typeof {}.hasOwnProperty",
6797            "function",
6798        ),
6799        (
6800            "typeof Object.groupBy([1,2,3], x=>x%2).hasOwnProperty",
6801            "undefined",
6802        ),
6803        (
6804            "var o={}; Object.setPrototypeOf(o, null); typeof o.toString",
6805            "undefined",
6806        ),
6807        // `setPrototypeOf`で実オブジェクトへ戻せば`null_proto`が解除され、
6808        // 継承したメソッドが再び見えることの確認。
6809        (
6810            "var o={}; Object.setPrototypeOf(o, null); Object.setPrototypeOf(o, Object.prototype); typeof o.hasOwnProperty",
6811            "function",
6812        ),
6813        // `crypto.subtle.digest('SHA-256', data)`が丸ごと未対応だった
6814        // (`Float16Array`/`oklch()`と同格の「1ビットの誤りが静かに間違った
6815        // 結果を生み続けるリスク」と判断し従来は着手見送りだったが、SHA-256は
6816        // NIST/RFC公開の既知の正解値で完全一致検証できる純粋なビット演算
6817        // アルゴリズムのため浮動小数点近似とは性質が異なり、リスク評価を
6818        // 見直し実装した。以下はFIPS 180-4/一般に広く知られる既知の正解値
6819        // (Known Answer Test)。2026-07-18 発見・実装)。
6820        (
6821            "await crypto.subtle.digest('SHA-256', new TextEncoder().encode('')).then(buf => Array.from(new Uint8Array(buf)).map(b=>b.toString(16).padStart(2,'0')).join(''))",
6822            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
6823        ),
6824        (
6825            "await crypto.subtle.digest('SHA-256', new TextEncoder().encode('abc')).then(buf => Array.from(new Uint8Array(buf)).map(b=>b.toString(16).padStart(2,'0')).join(''))",
6826            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
6827        ),
6828        (
6829            "await crypto.subtle.digest('SHA-256', new TextEncoder().encode('The quick brown fox jumps over the lazy dog')).then(buf => Array.from(new Uint8Array(buf)).map(b=>b.toString(16).padStart(2,'0')).join(''))",
6830            "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
6831        ),
6832        // `algorithm`引数を`{name: "SHA-256"}`形式(実ブラウザでも有効な
6833        // もう一方の呼び出し形式)で渡しても同じ結果になることの確認。
6834        (
6835            "await crypto.subtle.digest({name: 'SHA-256'}, new TextEncoder().encode('abc')).then(buf => Array.from(new Uint8Array(buf)).map(b=>b.toString(16).padStart(2,'0')).join(''))",
6836            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
6837        ),
6838        // 未対応アルゴリズム名は仕様どおりPromiseのreject(例外)で通知される。
6839        (
6840            "await crypto.subtle.digest('SHA-1', new TextEncoder().encode('x')).then(() => 'resolved', () => 'rejected')",
6841            "rejected",
6842        ),
6843        // `TextDecoder.decode(arrayBuffer)`(TypedArrayビューではなく生の
6844        // `ArrayBuffer`を直接渡す、仕様上有効な呼び出し形式)が常に空文字列を
6845        // 返すバグだった(`crypto.subtle.digest`実装の検証中に発見した
6846        // `new Uint8Array(arrayBuffer)`のゼロ埋めバグと同系統の「生の
6847        // ArrayBufferから実データを読む経路」の欠落)。2026-07-18 発見・実装。
6848        (
6849            "new TextDecoder().decode(new TextEncoder().encode('hello').buffer)",
6850            "hello",
6851        ),
6852        (
6853            "new TextDecoder().decode(new Uint8Array([65,116,109,79,83]).buffer)",
6854            "AtmOS",
6855        ),
6856        // `DataView` を `TextDecoder.decode` や `crypto.subtle.digest` や
6857        // `new Uint8Array(dataView)` に渡した際、`_dv_bytes` が参照されず
6858        // 全ゼロ埋めになるバグを修正(2026-07-21 発見・実装)。
6859        (
6860            "new TextDecoder().decode(new DataView(new TextEncoder().encode('AtmOS').buffer))",
6861            "AtmOS",
6862        ),
6863        (
6864            "await crypto.subtle.digest('SHA-256', new DataView(new TextEncoder().encode('abc').buffer)).then(buf => Array.from(new Uint8Array(buf)).map(b=>b.toString(16).padStart(2,'0')).join(''))",
6865            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
6866        ),
6867        (
6868            "new Uint8Array(new DataView(new Uint8Array([65,66,67]).buffer)).join('')",
6869            "656667",
6870        ),
6871        // `String.prototype.normalize()` 日本語ひらがな・カタカナの濁点・半濁点の分解(NFD)・合成(NFC)
6872        (
6873            "'が'.normalize('NFD') === 'か\\u3099'",
6874            "true",
6875        ),
6876        (
6877            "'か\\u3099'.normalize('NFC')",
6878            "が",
6879        ),
6880        (
6881            "'パ'.normalize('NFD') === 'ハ\\u309A'",
6882            "true",
6883        ),
6884        (
6885            "'ハ\\u309A'.normalize('NFC')",
6886            "パ",
6887        ),
6888        // `structuredClone` 極端な深いネストの保護(DataCloneError 例外の返却)。
6889        // 元は閾値1000・ネスト1100段でテストしていたが、これ自体が
6890        // ブート時点の64KB初期コアスタックを枯渇させ、QEMU起動が丸ごと
6891        // 無応答になる重大な回帰を引き起こしていた(実測で深さ80段でも
6892        // 無応答、50段は安全と確認)。`deep_clone_value`の閾値を30へ
6893        // 引き下げ、ネスト段数も安全に確認できた範囲内の40へ縮小する
6894        // (2026-07-18 発見・修正)。
6895        (
6896            "var a = []; var cur = a; for (var i = 0; i < 40; i++) { var n = []; cur.push(n); cur = n; } try { structuredClone(a); 'ok'; } catch (e) { e.name; }",
6897            "DataCloneError",
6898        ),
6899        // `structuredClone` フラットな大配列(要素数>30のオブジェクト配列)が
6900        // 累積オブジェクト数(seen.len())ではなく再帰深さ(depth)で判定され
6901        // 正しく複製されることの検証(seen.len() ガード時代の過剰エラーバグ防止)。
6902        (
6903            "var a = []; for (var i = 0; i < 50; i++) { a.push({v: i}); } var c = structuredClone(a); c.length === 50 && c[49].v === 49",
6904            "true",
6905        ),
6906        // `JSON.parse`(`os_lib::json::parse`のRust側相互再帰パーサ)にも
6907        // `structuredClone`と同種のRustネイティブ再帰スタックオーバーフロー
6908        // リスクがあったため、同じ閾値30の深さガードを新設(2026-07-21)。
6909        // 20階層のネストは閾値内で安全にパースできる。
6910        (
6911            "(function(){ var s = '['.repeat(20) + '0' + ']'.repeat(20); try { JSON.parse(s); return 'ok'; } catch (e) { return 'error'; } })()",
6912            "ok",
6913        ),
6914        // GlobalEventHandlers (onchange / onclick 等) の属性・プロパティ統合テスト
6915        (
6916            "var d=document.createElement('div'); var r='none'; d.onchange=function(){r='ok';}; d.dispatchEvent(new Event('change')); r",
6917            "ok",
6918        ),
6919        // outerHTML セッターのテスト
6920        (
6921            "var p=document.createElement('div'); p.innerHTML='<span id=x>old</span>'; var s=p.children[0]; s.outerHTML='<b id=y>new</b>'; p.children[0].tagName+','+p.children[0].id",
6922            "B,y",
6923        ),
6924    ];
6925    let mut passed = 0;
6926    let mut total = cases.len();
6927    for (src, expect) in cases {
6928        let mut rt = JsRuntime::new();
6929        match rt.eval(src) {
6930            Ok(v) if &v.to_js_string() == expect => passed += 1,
6931            Ok(v) => crate::println!(
6932                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
6933                src,
6934                v.to_js_string(),
6935                expect
6936            ),
6937            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
6938        }
6939    }
6940
6941    // 【2026-10-20】文書の読み込み状態: loading の間に登録したリスナは段階に達したときに呼ばれる。
6942    {
6943        total += 1;
6944        let mut rt = JsRuntime::new();
6945        rt.set_document_ready_state("loading");
6946        let _ = rt.eval(
6947            "var log = [document.readyState]; document.addEventListener('readystatechange', function(){ log.push('rsc:' + document.readyState); }); \
6948             document.addEventListener('DOMContentLoaded', function(){ log.push('dcl'); }); window.addEventListener('load', function(){ log.push('load'); });",
6949        );
6950        rt.set_document_ready_state("interactive");
6951        rt.set_document_ready_state("complete");
6952        match rt.eval("window.addEventListener('load', function(){ log.push('late'); }); log.join(',')") {
6953            Ok(v) if v.to_js_string() == "loading,rsc:interactive,dcl,rsc:complete,load,late" => passed += 1,
6954            Ok(v) => crate::println!("JS_SELFTEST FAIL: `readyState lifecycle` => `{}`", v.to_js_string()),
6955            Err(e) => crate::println!("JS_SELFTEST ERR:  `readyState lifecycle` => {}", e),
6956        }
6957    }
6958
6959    // 【2026-10-19】循環ゴミの回収: 外から届く値は残し、届かない循環だけを解放する。
6960    {
6961        total += 1;
6962        let mut rt = JsRuntime::new();
6963        let _ = rt.eval(
6964            "var keep = (function(){ var o = {v: 42}; o.self = o; return function(){ return o.v + (o.self === o ? 0 : 1000); }; })(); \
6965             var arr = []; arr.push(arr); var m = new Map(); m.set('k', m); \
6966             for (var i = 0; i < 2000; i++) { (function(){ var a = {}; a.me = a; function g(){ return a; } a.g = g; })(); }",
6967        );
6968        // 途中で自動の回収が走っても結果が同じになるよう、回収後に生き残った数で判定する
6969        // (回収しなければ 2000 回分の a・g・スコープ=6000 個以上が残る)。
6970        let st = super::cycle_gc::collect(true);
6971        let live_objs = st.objs - st.garbage_objs;
6972        let live_scopes = st.scopes - st.garbage_scopes;
6973        let ok_garbage = live_objs < 2000 && live_scopes < 100;
6974        match rt.eval("keep() + ',' + (arr[0] === arr) + ',' + (m.get('k') === m) + ',' + (function(){ var q = {}; q.q = q; return typeof q.q; })()") {
6975            Ok(v) if v.to_js_string() == "42,true,true,object" && ok_garbage => passed += 1,
6976            Ok(v) => crate::println!(
6977                "JS_SELFTEST FAIL: `cycle_gc` => `{}` {:?} (want 42,true,true,object and live < 2000 objs / < 100 scopes)",
6978                v.to_js_string(),
6979                st
6980            ),
6981            Err(e) => crate::println!("JS_SELFTEST ERR:  `cycle_gc` => {}", e),
6982        }
6983        // 2 回目はほとんど取らず、値も壊れない。
6984        total += 1;
6985        let st2 = super::cycle_gc::collect(true);
6986        match rt.eval("keep() + ',' + arr.length") {
6987            // 直前の確認の式(q.q = q)が作った少しのゴミだけ。
6988            Ok(v) if v.to_js_string() == "42,1" && st2.garbage_objs < 10 => passed += 1,
6989            Ok(v) => crate::println!("JS_SELFTEST FAIL: `cycle_gc 2nd` => `{}` {:?}", v.to_js_string(), st2),
6990            Err(e) => crate::println!("JS_SELFTEST ERR:  `cycle_gc 2nd` => {}", e),
6991        }
6992    }
6993
6994    // MutationObserver コールバックの2段階評価テスト(コールバック第2引数として observer 自身を受け取る)
6995    {
6996        total += 1;
6997        let mut rt = JsRuntime::new();
6998        let _ = rt.eval(
6999            "var moEl=document.createElement('div'); var recObs=null; \
7000             var moInst=new MutationObserver(function(recs, obs){ recObs = obs; }); \
7001             moInst.observe(moEl,{attributes:true}); \
7002             moEl.setAttribute('a','1');",
7003        );
7004        match rt.eval("recObs === moInst") {
7005            Ok(v) if v.to_js_string() == "true" => passed += 1,
7006            Ok(v) => crate::println!(
7007                "JS_SELFTEST FAIL: `MutationObserver cb obs param` => `{}` (want true)",
7008                v.to_js_string()
7009            ),
7010            Err(e) => crate::println!("JS_SELFTEST ERR:  `MutationObserver cb obs param` => {}", e),
7011        }
7012    }
7013
7014    // setInterval / clearInterval の2段階評価テスト
7015    {
7016        total += 1;
7017        let mut rt = JsRuntime::new();
7018        let _ = rt.eval("var n=0; var id=setInterval(function(){n++;}, 10);");
7019        // 【2026-10-17】タイマーは実時間で発火する。試験は仮想時刻を早送りして流す。
7020        rt.fast_forward_timers(50);
7021        match rt.eval("n > 0") {
7022            Ok(v) if v.to_js_string() == "true" => passed += 1,
7023            Ok(v) => crate::println!(
7024                "JS_SELFTEST FAIL: `setInterval` => n > 0 is `{}` (want true)",
7025                v.to_js_string()
7026            ),
7027            Err(e) => crate::println!("JS_SELFTEST ERR:  `setInterval` => {}", e),
7028        }
7029    }
7030    {
7031        total += 1;
7032        let mut rt = JsRuntime::new();
7033        let _ = rt.eval("var n=0; var id=setInterval(function(){n++;}, 10); clearInterval(id);");
7034        match rt.eval("n") {
7035            Ok(v) if v.to_js_string() == "0" => passed += 1,
7036            Ok(v) => crate::println!(
7037                "JS_SELFTEST FAIL: `clearInterval` => n is `{}` (want 0)",
7038                v.to_js_string()
7039            ),
7040            Err(e) => crate::println!("JS_SELFTEST ERR:  `clearInterval` => {}", e),
7041        }
7042    }
7043    // `setInterval`の発火回数上限(以前は8回。実サイト www.sugi-lab.net の
7044    // 統計カウンターアニメーション`increment=target/100`が最終値の8%程度で
7045    // 頭打ちになり正しい数値へ到達できないバグだった。2026-07-22発見・修正で
7046    // 200回へ引き上げ)の回帰防止テスト。target=50, increment=0.5として、
7047    // 100回の発火で正確にtargetへ到達し`clearInterval`されることを検証する
7048    // (8回上限のままなら`n`は50に到達せず4のまま止まる)。
7049    {
7050        total += 1;
7051        let mut rt = JsRuntime::new();
7052        let _ = rt.eval(
7053            "var n=0; var target=50; var inc=target/100; \
7054             var id=setInterval(function(){ \
7055                 n+=inc; \
7056                 if (n>=target) { n=target; clearInterval(id); } \
7057             }, 1);",
7058        );
7059        rt.fast_forward_timers(1000);
7060        match rt.eval("n") {
7061            Ok(v) if v.to_js_string() == "50" => passed += 1,
7062            Ok(v) => crate::println!(
7063                "JS_SELFTEST FAIL: setInterval 統計カウンター模擬 => n is `{}` (want 50)",
7064                v.to_js_string()
7065            ),
7066            Err(e) => crate::println!("JS_SELFTEST ERR:  setInterval 統計カウンター模擬 => {}", e),
7067        }
7068    }
7069    // `AbortSignal.timeout(ms)`(ES2022)の2段階評価テスト(setTimeout 経由で
7070    // マクロタスクキューが解ける必要があるため setInterval と同じ2段階構成)。
7071    {
7072        total += 1;
7073        let mut rt = JsRuntime::new();
7074        let _ = rt.eval(
7075            "var fired=false; var s=AbortSignal.timeout(0); s.addEventListener('abort', function(){fired=true;});",
7076        );
7077        // `s.reason.name`(丸ごと未対応だった。以前は既定の `AbortError` の
7078        // ままで仕様上の `TimeoutError`ではなかったバグ。2026-07-16 発見・
7079        // 実装)も合わせて検証する。
7080        match rt.eval("fired + ',' + s.reason.name") {
7081            Ok(v) if v.to_js_string() == "true,TimeoutError" => passed += 1,
7082            Ok(v) => crate::println!(
7083                "JS_SELFTEST FAIL: `AbortSignal.timeout` => `{}` (want `true,TimeoutError`)",
7084                v.to_js_string()
7085            ),
7086            Err(e) => crate::println!("JS_SELFTEST ERR:  `AbortSignal.timeout` => {}", e),
7087        }
7088    }
7089
7090    // DOM 連携(Phase 3)。小さな DOM を構築してから評価する。
7091    let dom_html = "<div id='out'>0</div><p class='msg'>hi</p><button id='b' class='x'>go</button>";
7092    let dom_cases: &[(&str, &str)] = &[
7093        // 【2026-10-20】element.attributes の getNamedItem/item/名前参照(NamedNodeMap)
7094        ("var d = document.createElement('div'); d.setAttribute('foo', 'bar'); var a = d.attributes; a.getNamedItem('foo').value + ',' + a.getNamedItem('nope') + ',' + a.item(0).name + ',' + a.item(5) + ',' + a.foo.value", "bar,null,foo,null,bar"),
7095        // 【2026-10-20】DOM ノードの __proto__ 代入(ShadyDOM の ShadowRoot 化)
7096        ("function SR(){} SR.prototype.hello = function(){ return 'hi:' + this.nodeType; }; var f = document.createDocumentFragment(); f.__proto__ = SR.prototype; f.hello() + ',' + (Object.getPrototypeOf(f) === SR.prototype) + ',' + (f.__proto__ === SR.prototype)", "hi:11,true,true"),
7097        // 【2026-10-20】ToPrimitive(利用者定義の toString/valueOf/Symbol.toPrimitive。Node で確認)
7098        ("function U(s){ this.s = s; } U.prototype.toString = function(){ return 'u:' + this.s; }; var u = new U('a'); var o = { valueOf: function(){ return 40; } }; var p = { [Symbol.toPrimitive]: function(h){ return h; } }; String(u) + ',' + (u + '!') + ',' + `${u}` + ',' + (o + 2) + ',' + (p + '') + ',' + String(p) + ',' + String({}) + ',' + ([1,2] + '')", "u:a,u:a!,u:a,42,default,string,[object Object],1,2"),
7099        // 【2026-10-20】DOM の原型の JS による上書き(Node.prototype.appendChild の包み)が組込みより先
7100        ("var orig = Node.prototype.appendChild; var n = 0; Node.prototype.appendChild = function(c){ n++; return orig.call(this, c); }; var d = document.createElement('div'); d.appendChild(document.createElement('span')); Node.prototype.appendChild = orig; d.appendChild(document.createElement('b')); n + ',' + d.children.length", "1,2"),
7101        // 【2026-10-20】ノード種別ごとの原型(DocumentFragment・Text・video)
7102        ("var f = document.createDocumentFragment(); f.appendChild(document.createElement('i')); var t = document.createTextNode('x'); (f instanceof DocumentFragment) + ',' + (f instanceof Element) + ',' + (t instanceof Text) + ',' + (t instanceof Node) + ',' + DocumentFragment.prototype.querySelectorAll.call(f, '*').length + ',' + (document.createElement('video') instanceof HTMLVideoElement)", "true,false,true,true,1,true"),
7103        // 【2026-10-20】document の nodeType/nodeName/ownerDocument
7104        ("document.nodeType + ',' + document.nodeName + ',' + document.ownerDocument + ',' + (document.createElement('div').ownerDocument === document)", "9,#document,null,true"),
7105        // 【2026-10-20】組込みの constructor(Node で確認)
7106        ("({}).constructor === Object && [].constructor === Array && (function(){}).constructor === Function && (new Map()).constructor === Map && Object.create(null).constructor === undefined", "true"),
7107        // 【2026-10-20】`in` は組込みの prototype のメンバも見る(Node で確認)
7108        ("('constructor' in {}) + ',' + ('toString' in {}) + ',' + ('push' in []) + ',' + ('call' in function(){}) + ',' + ('length' in function(){}) + ',' + ('x' in {}) + ',' + ('constructor' in Object.create(null))", "true,true,true,true,true,false,false"),
7109        // 【2026-10-20】Node/Element.prototype の DOM アクセサの記述子(ShadyDOM が写す)
7110        ("var d = document.createElement('div'); var ic = Object.getOwnPropertyDescriptor(Node.prototype, 'isConnected'); var tc = Object.getOwnPropertyDescriptor(Node.prototype, 'textContent'); var b1 = ic.get.call(d); document.getElementById('out').appendChild(d); tc.set.call(d, 'hey'); b1 + ',' + ic.get.call(d) + ',' + d.textContent + ',' + Object.getOwnPropertyDescriptor(Element.prototype, 'children').get.call(document.getElementById('out')).length", "false,true,hey,1"),
7111        // 【2026-10-20】カスタム要素のクラスの上書きが組込みより先・Element/Node.prototype の転送
7112        ("var log = []; class XI extends HTMLElement { removeAttribute(n){ log.push('rm:' + n); super.removeAttribute(n); } } customElements.define('x-i', XI); var e = document.createElement('x-i'); e.setAttribute('q', '1'); e.removeAttribute('q'); log.join(',') + '|' + e.hasAttribute('q') + ',' + (typeof Node.prototype.appendChild)", "rm:q|false,function"),
7113        ("var d = document.createElement('div'); Element.prototype.setAttribute.call(d, 'a', '1'); Node.prototype.appendChild.call(document.getElementById('out'), d); d.getAttribute('a') + ',' + (d.parentNode.id)", "1,out"),
7114        // 【2026-10-20】attributeChangedCallback(静的 getter の observedAttributes、昇格時の既存属性)
7115        ("var log = []; class XG extends HTMLElement { static get observedAttributes(){ return ['foo']; } attributeChangedCallback(n, o, v){ log.push(n + ':' + o + '>' + v); } } customElements.define('x-g', XG); var e = document.createElement('x-g'); e.setAttribute('foo', '1'); e.setAttribute('bar', '2'); e.setAttribute('foo', '3'); e.removeAttribute('foo'); log.join(',')", "foo:null>1,foo:1>3,foo:3>null"),
7116        ("var d = document.getElementById('out'); d.innerHTML = '<x-h foo=\"a\"></x-h>'; var log = []; class XH extends HTMLElement { static get observedAttributes(){ return ['foo']; } attributeChangedCallback(n, o, v){ log.push(n + ':' + o + '>' + v); } } customElements.define('x-h', XH); d.firstChild.removeAttribute('foo'); log.join(',')", "foo:null>a,foo:a>null"),
7117        // 【2026-10-20】Reflect.construct の newTarget と、ES5 版のカスタム要素(custom-elements-es5-adapter の形)
7118        ("function A(){ this.a = 1; } function B(){} B.prototype.hi = function(){ return 'hi'; }; var o = Reflect.construct(A, [], B); o.a + ',' + o.hi() + ',' + (o instanceof B)", "1,hi,true"),
7119        ("var BuiltIn = HTMLElement; HTMLElement = function(){ return Reflect.construct(BuiltIn, [], this.constructor); }; HTMLElement.prototype = BuiltIn.prototype; HTMLElement.prototype.constructor = HTMLElement; Object.setPrototypeOf(HTMLElement, BuiltIn); function XF(){ var t = HTMLElement.call(this) || this; t.z = 3; return t; } XF.prototype = Object.create(HTMLElement.prototype); XF.prototype.constructor = XF; customElements.define('x-f', XF); var e = document.createElement('x-f'); var d = document.createElement('div'); d.innerHTML = '<x-f></x-f>'; var c = d.cloneNode(true); e.tagName + ',' + e.z + ',' + (e instanceof XF) + ',' + c.firstChild.z", "X-F,3,true,3"),
7120        // 【2026-10-20】カスタム要素: 構築・昇格(define 時・innerHTML・importNode)・ライフサイクル・prototype の setter
7121        ("var log = []; class XA extends HTMLElement { constructor(){ super(); log.push('ctor'); this.v = 5; } connectedCallback(){ log.push('conn'); } disconnectedCallback(){ log.push('disc'); } get dbl(){ return this.v * 2; } } customElements.define('x-a', XA); var e = document.createElement('x-a'); log.push('created'); document.getElementById('out').appendChild(e); e.remove(); log.join(',') + '|' + (e instanceof XA) + ',' + e.dbl + ',' + (Object.getPrototypeOf(e) === XA.prototype) + ',' + e.tagName", "ctor,created,conn,disc|true,10,true,X-A"),
7122        ("var d = document.createElement('div'); document.getElementById('out').appendChild(d); d.innerHTML = '<x-b id=q></x-b>'; var log = []; class XB extends HTMLElement { constructor(){ super(); log.push('ctor:' + this.id); } connectedCallback(){ log.push('conn'); } } customElements.define('x-b', XB); log.join(',') + '|' + (document.getElementById('q') instanceof XB)", "ctor:q,conn|true"),
7123        ("class XC extends HTMLElement { constructor(){ super(); this.k = 1; } } customElements.define('x-c', XC); var d = document.createElement('div'); document.getElementById('out').appendChild(d); d.innerHTML = '<x-c></x-c>'; (d.firstChild instanceof XC) + ',' + d.firstChild.k", "true,1"),
7124        ("class XD extends HTMLElement { set foo(v){ this._f = v * 3; } get foo(){ return this._f; } } customElements.define('x-d', XD); var x = new XD(); x.foo = 2; x.tagName + ',' + x.foo + ',' + x._f", "X-D,6,6"),
7125        ("class XE extends HTMLElement { constructor(){ super(); this.up = 'yes'; } } customElements.define('x-e', XE); var t = document.createElement('template'); t.innerHTML = '<div><x-e></x-e></div>'; var c = document.importNode(t.content, true); c.querySelector('x-e').up", "yes"),
7126        ("document.getElementById('out').textContent", "0"),
7127        ("var e=document.getElementById('out'); e.textContent='42'; e.textContent", "42"),
7128        ("document.getElementById('out').tagName", "DIV"),
7129        ("document.querySelector('.msg').textContent", "hi"),
7130        ("document.querySelectorAll('div').length", "1"),
7131        ("document.getElementById('b').classList.contains('x')", "true"),
7132        ("var e=document.getElementById('b'); e.classList.add('y'); e.classList.contains('y')", "true"),
7133        ("var e=document.getElementById('b'); e.classList.toggle('x'); e.classList.contains('x')", "false"),
7134        ("var e=document.getElementById('out'); e.style.color='red'; e.style.color", "red"),
7135        ("document.getElementById('missing') === null", "true"),
7136        ("document.getElementById('b').getAttribute('id')", "b"),
7137        // `document.children`/`.childElementCount`(`ParentNode`ミックスイン
7138        // の`Document`側配線漏れ。丸ごと未対応だった。bare `cases`配列では
7139        // `<html>`未構築のため、実際にHTMLフィクスチャがあるこちらで検証。
7140        // 2026-07-17 発見・実装)。
7141        ("document.children.length", "1"),
7142        ("document.children[0] === document.documentElement", "true"),
7143        ("document.childElementCount", "1"),
7144        // addEventListener + クリック発火(ブリッジ経由でディスパッチ)
7145        ("var n=0; document.getElementById('b').addEventListener('click', () => n++); n", "0"),
7146        // 動的ノード(createElement / appendChild / innerHTML / remove)
7147        ("document.createElement('DIV').tagName", "DIV"),
7148        ("var d=document.createElement('div'); d.textContent='hi'; d.textContent", "hi"),
7149        // textContent/innerHTML は WebIDL 上 nullable (DOMString?) につき null/undefined 代入は空文字列になる
7150        ("var d=document.createElement('div'); d.textContent='hi'; d.textContent=null; d.textContent===''", "true"),
7151        ("var d=document.createElement('div'); d.innerHTML='<b>x</b>'; d.innerHTML=undefined; d.innerHTML===''", "true"),
7152        ("var c=document.getElementById('out'); var s=document.createElement('span'); s.textContent='X'; c.appendChild(s); c.textContent", "0X"),
7153        ("var c=document.getElementById('out'); c.innerHTML='<b>Y</b>'; c.textContent", "Y"),
7154        ("var c=document.getElementById('out'); var i=document.createElement('i'); c.appendChild(i); document.querySelector('i') === null", "false"),
7155        ("var c=document.getElementById('out'); c.innerHTML='<span class=z>1</span><span class=z>2</span>'; document.querySelectorAll('.z').length", "2"),
7156        ("var c=document.getElementById('out'); var t=document.createTextNode('TT'); c.appendChild(t); c.textContent", "0TT"),
7157        ("var c=document.getElementById('out'); c.appendChild(document.createElement('em')); document.querySelector('em').remove(); document.querySelector('em') === null", "true"),
7158        // `ariaActiveDescendantElement`(ARIA Element Reflection。
7159        // `aria-activedescendant`のidref文字列ではなく実際のElement参照を
7160        // 返す/設定する。`popoverTargetElement`と同じidref解決パターン。
7161        // 丸ごと未対応だった。既にDOMツリーに接続されたフィクスチャ要素
7162        // (`get_element_by_id`は接続済みノードしか見ない)が必要なため、
7163        // bare `cases`配列ではなくこちらのfixture付き配列で検証。
7164        // 2026-07-18 発見・実装)。
7165        (
7166            "var a=document.createElement('div'); var b=document.getElementById('out'); a.ariaActiveDescendantElement=b; a.getAttribute('aria-activedescendant')+'|'+(a.ariaActiveDescendantElement===b)",
7167            "out|true",
7168        ),
7169        // `ariaLabelledByElements`/`ariaDescribedByElements`等(ARIA Element
7170        // Reflection、複数形)が丸ごと未対応だった。空白区切りidref属性を
7171        // 実際のElement参照の配列として読み書きする。単数形の
7172        // `ariaActiveDescendantElement`と同じ理由で接続済みfixture要素が
7173        // 必要。2026-07-18 発見・実装)。
7174        (
7175            "var a=document.createElement('div'); var b1=document.getElementById('out'); var b2=document.getElementById('b'); a.ariaLabelledByElements=[b1,b2]; a.getAttribute('aria-labelledby')+'|'+a.ariaLabelledByElements.length+'|'+(a.ariaLabelledByElements[0]===b1)+'|'+(a.ariaLabelledByElements[1]===b2)",
7176            "out b|2|true|true",
7177        ),
7178        (
7179            "document.createElement('div').ariaDescribedByElements.length",
7180            "0",
7181        ),
7182        // `parent.moveBefore(movedNode, referenceNode)`(丸ごと未対応
7183        // だった。この処理系には保持すべき「生きた状態」自体が無いため、
7184        // `insertBefore`と同じdetach+挿入の簡略実装で仕様どおり動作する。
7185        // 2026-07-18 発見・実装)。
7186        (
7187            "var p1=document.createElement('div'); var p2=document.createElement('div'); var m=document.createElement('span'); p1.appendChild(m); var r=document.createElement('b'); p2.appendChild(r); p2.moveBefore(m, r); m.parentNode===p2 && p2.firstChild===m && p1.children.length",
7188            "0",
7189        ),
7190        (
7191            "var p=document.createElement('div'); var a=document.createElement('i'); var b=document.createElement('b'); p.appendChild(a); p.appendChild(b); p.moveBefore(b, a); p.firstChild===b",
7192            "true",
7193        ),
7194        (
7195            "var p1=document.createElement('div'); var p2=document.createElement('div'); var m=document.createElement('span'); var other=document.createElement('b'); p1.appendChild(m); p2.appendChild(other); try { p1.moveBefore(m, other); 'no-throw'; } catch(e) { e.name; }",
7196            "NotFoundError",
7197        ),
7198    ];
7199    total += dom_cases.len();
7200    for (src, expect) in dom_cases {
7201        let mut rt = JsRuntime::new();
7202        rt.dom
7203            .borrow_mut()
7204            .build_from(&crate::os_lib::dom::parse_html(dom_html));
7205        match rt.eval(src) {
7206            Ok(v) if &v.to_js_string() == expect => passed += 1,
7207            Ok(v) => crate::println!(
7208                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
7209                src,
7210                v.to_js_string(),
7211                expect
7212            ),
7213            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
7214        }
7215    }
7216
7217    // 要素 DOM API(hasAttribute/removeAttribute/matches/closest/element querySelector/ナビ)。
7218    let dom2_html = "<div id='wrap' class='container'><p id='t' class='a b' data-k='v'>x</p><span id='s'>y</span></div>";
7219    let dom2_cases: &[(&str, &str)] = &[
7220        ("document.getElementById('t').hasAttribute('class')", "true"),
7221        ("document.getElementById('t').hasAttribute('nope')", "false"),
7222        ("document.getElementById('t').getAttribute('data-k')", "v"),
7223        ("var e=document.getElementById('t'); e.removeAttribute('class'); e.hasAttribute('class')", "false"),
7224        ("document.getElementById('t').matches('.a')", "true"),
7225        ("document.getElementById('t').matches('.z')", "false"),
7226        ("document.getElementById('t').matches('p')", "true"),
7227        ("document.getElementById('t').closest('.container').id", "wrap"),
7228        ("document.getElementById('t').closest('#wrap').id", "wrap"),
7229        ("document.getElementById('t').closest('p').id", "t"),
7230        ("document.getElementById('t').closest('.nope') === null", "true"),
7231        ("document.getElementById('wrap').querySelector('.a').id", "t"),
7232        ("document.getElementById('wrap').querySelector('span').id", "s"),
7233        ("document.getElementById('wrap').querySelectorAll('*').length", "2"),
7234        // コンパウンドセレクタ(タグ+クラス、以前は文字列全体をタグ名として比較しており
7235        // 常に false になっていた)。
7236        ("document.getElementById('t').matches('p.a')", "true"),
7237        ("document.getElementById('t').matches('p.z')", "false"),
7238        ("document.getElementById('t').matches('p.a.b')", "true"),
7239        // 属性セレクタ(以前は丸ごと未対応で常に false になっていた)。
7240        ("document.getElementById('t').matches('[data-k]')", "true"),
7241        ("document.getElementById('t').matches('[data-k=v]')", "true"),
7242        ("document.getElementById('t').matches('[data-k=x]')", "false"),
7243        ("document.getElementById('t').matches('[data-k^=v]')", "true"),
7244        // 結合子(子孫/子、以前は丸ごと未対応で常に false になっていた)。
7245        ("document.getElementById('t').matches('#wrap p')", "true"),
7246        ("document.getElementById('t').matches('#wrap > p')", "true"),
7247        ("document.getElementById('t').matches('#wrap > span')", "false"),
7248        // 一般兄弟結合子(`p ~ span`)。
7249        ("document.getElementById('s').matches('p ~ span')", "true"),
7250        ("document.getElementById('s').matches('span ~ span')", "false"),
7251        // querySelector(All) でも同じコンパウンド/属性セレクタが機能する。
7252        ("document.getElementById('wrap').querySelector('p.a').id", "t"),
7253        ("document.getElementById('wrap').querySelectorAll('[data-k]').length", "1"),
7254        // `:not()` 疑似クラス。
7255        ("document.getElementById('t').matches(':not(span)')", "true"),
7256        ("document.getElementById('t').matches(':not(p)')", "false"),
7257        ("document.getElementById('t').matches('p:not(.z)')", "true"),
7258        ("document.getElementById('t').matches('p:not(.a)')", "false"),
7259        ("document.getElementById('wrap').querySelectorAll(':not(span)').length", "1"),
7260        // `:scope` 疑似クラス(Selectors Level 4。丸ごと未対応だった)。
7261        // `element.querySelector(All)`/`matches`/`closest` における `:scope` は
7262        // 呼び出し元の要素自身を指す。
7263        ("document.getElementById('wrap').querySelectorAll(':scope > p').length", "1"),
7264        ("document.getElementById('wrap').querySelectorAll(':scope > p')[0].id", "t"),
7265        ("document.getElementById('wrap').querySelector(':scope > span').id", "s"),
7266        ("document.getElementById('wrap').querySelectorAll(':scope > .nope').length", "0"),
7267        ("document.getElementById('t').matches(':scope')", "true"),
7268        ("document.getElementById('t').closest(':scope').id", "t"),
7269        // 位置系疑似クラス(`:first-child`/`:last-child`/`:only-child`/`:nth-child()`)。
7270        ("document.getElementById('t').matches(':first-child')", "true"),
7271        ("document.getElementById('t').matches(':last-child')", "false"),
7272        ("document.getElementById('s').matches(':last-child')", "true"),
7273        ("document.getElementById('t').matches(':only-child')", "false"),
7274        ("document.getElementById('t').matches(':nth-child(1)')", "true"),
7275        ("document.getElementById('s').matches(':nth-child(2)')", "true"),
7276        ("document.getElementById('t').matches(':nth-child(odd)')", "true"),
7277        ("document.getElementById('s').matches(':nth-child(odd)')", "false"),
7278        ("document.getElementById('s').matches(':nth-child(2n)')", "true"),
7279        // `:nth-last-child()`(末尾から数える版。丸ごと未対応だった)。
7280        // #t/#s は2要素中1番目/2番目 → 末尾からは2番目/1番目。
7281        ("document.getElementById('s').matches(':nth-last-child(1)')", "true"),
7282        ("document.getElementById('t').matches(':nth-last-child(1)')", "false"),
7283        ("document.getElementById('t').matches(':nth-last-child(2)')", "true"),
7284        ("document.getElementById('s').matches(':nth-last-child(odd)')", "true"),
7285        // `:first-of-type`/`:last-of-type`/`:only-of-type`/`:nth-of-type()`(タグ名が
7286        // 同じ兄弟のみを数える点以外は `:nth-child()` 系と同じ規則)。各テストケースは
7287        // `document.getElementById` の「同一 id が複数あれば最初のものを返す」挙動と
7288        // 衝突しないよう、テストごとに固有の id を使う(以前は全ケースで同じ
7289        // p1/s1/p2/p3 を使い回しており、2回目以降は常に最初に追加した要素を
7290        // 参照してしまっていた)。
7291        (
7292            "var w=document.createElement('div'); w.innerHTML=\"<p id='ot1a'>a</p><span id='ot1b'>b</span><p id='ot1c'>c</p><p id='ot1d'>d</p>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ot1a').matches(':first-of-type')",
7293            "true",
7294        ),
7295        (
7296            "var w=document.createElement('div'); w.innerHTML=\"<p id='ot2a'>a</p><span id='ot2b'>b</span><p id='ot2c'>c</p><p id='ot2d'>d</p>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ot2d').matches(':last-of-type')",
7297            "true",
7298        ),
7299        (
7300            "var w=document.createElement('div'); w.innerHTML=\"<p id='ot3a'>a</p><span id='ot3b'>b</span><p id='ot3c'>c</p><p id='ot3d'>d</p>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ot3c').matches(':last-of-type')",
7301            "false",
7302        ),
7303        (
7304            "var w=document.createElement('div'); w.innerHTML=\"<p id='ot4a'>a</p><span id='ot4b'>b</span><p id='ot4c'>c</p><p id='ot4d'>d</p>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ot4b').matches(':only-of-type')",
7305            "true",
7306        ),
7307        (
7308            "var w=document.createElement('div'); w.innerHTML=\"<p id='ot5a'>a</p><span id='ot5b'>b</span><p id='ot5c'>c</p><p id='ot5d'>d</p>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ot5a').matches(':only-of-type')",
7309            "false",
7310        ),
7311        (
7312            "var w=document.createElement('div'); w.innerHTML=\"<p id='ot6a'>a</p><span id='ot6b'>b</span><p id='ot6c'>c</p><p id='ot6d'>d</p>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ot6c').matches(':nth-of-type(2)')",
7313            "true",
7314        ),
7315        (
7316            "var w=document.createElement('div'); w.innerHTML=\"<p id='ot7a'>a</p><span id='ot7b'>b</span><p id='ot7c'>c</p><p id='ot7d'>d</p>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ot7a').matches(':nth-of-type(2)')",
7317            "false",
7318        ),
7319        // `:nth-last-of-type()`(末尾から数える版。丸ごと未対応だった)。
7320        // p 要素は a/c/d の3つ → 末尾からは d=1番目, c=2番目, a=3番目。
7321        (
7322            "var w=document.createElement('div'); w.innerHTML=\"<p id='ot8a'>a</p><span id='ot8b'>b</span><p id='ot8c'>c</p><p id='ot8d'>d</p>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ot8d').matches(':nth-last-of-type(1)')",
7323            "true",
7324        ),
7325        (
7326            "var w=document.createElement('div'); w.innerHTML=\"<p id='ot9a'>a</p><span id='ot9b'>b</span><p id='ot9c'>c</p><p id='ot9d'>d</p>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ot9a').matches(':nth-last-of-type(3)')",
7327            "true",
7328        ),
7329        // `:is()`/`:where()`(コンパウンドセレクタのリストのみ対応。結合子を含む
7330        // 複雑なセレクタは非対応)。
7331        ("document.getElementById('t').matches(':is(.z, .a)')", "true"),
7332        ("document.getElementById('t').matches(':is(.z, .y)')", "false"),
7333        ("document.getElementById('t').matches(':where(span, p.a)')", "true"),
7334        ("document.getElementById('s').matches(':is(span)')", "true"),
7335        // `:empty` / `:checked` / `:disabled` / `:enabled` 疑似クラス(Selectors Level 3/4)。
7336        (
7337            "var w=document.createElement('div'); w.innerHTML=\"<div id='em1'></div><div id='em2'>text</div><input id='ck1' type='checkbox' checked><input id='ck2' type='checkbox'><button id='ds1' disabled>x</button><button id='ds2'>y</button>\"; document.getElementById('wrap').appendChild(w); document.getElementById('em1').matches(':empty')",
7338            "true",
7339        ),
7340        (
7341            "var w=document.createElement('div'); w.innerHTML=\"<div id='em1'></div><div id='em2'>text</div><input id='ck1' type='checkbox' checked><input id='ck2' type='checkbox'><button id='ds1' disabled>x</button><button id='ds2'>y</button>\"; document.getElementById('wrap').appendChild(w); document.getElementById('em2').matches(':empty')",
7342            "false",
7343        ),
7344        (
7345            "var w=document.createElement('div'); w.innerHTML=\"<div id='em1'></div><div id='em2'>text</div><input id='ck1' type='checkbox' checked><input id='ck2' type='checkbox'><button id='ds1' disabled>x</button><button id='ds2'>y</button>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ck1').matches(':checked')",
7346            "true",
7347        ),
7348        (
7349            "var w=document.createElement('div'); w.innerHTML=\"<div id='em1'></div><div id='em2'>text</div><input id='ck1' type='checkbox' checked><input id='ck2' type='checkbox'><button id='ds1' disabled>x</button><button id='ds2'>y</button>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ck2').matches(':checked')",
7350            "false",
7351        ),
7352        (
7353            "var w=document.createElement('div'); w.innerHTML=\"<div id='em1'></div><div id='em2'>text</div><input id='ck1' type='checkbox' checked><input id='ck2' type='checkbox'><button id='ds1' disabled>x</button><button id='ds2'>y</button>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ds1').matches(':disabled')",
7354            "true",
7355        ),
7356        (
7357            "var w=document.createElement('div'); w.innerHTML=\"<div id='em1'></div><div id='em2'>text</div><input id='ck1' type='checkbox' checked><input id='ck2' type='checkbox'><button id='ds1' disabled>x</button><button id='ds2'>y</button>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ds2').matches(':disabled')",
7358            "false",
7359        ),
7360        (
7361            "var w=document.createElement('div'); w.innerHTML=\"<div id='em1'></div><div id='em2'>text</div><input id='ck1' type='checkbox' checked><input id='ck2' type='checkbox'><button id='ds1' disabled>x</button><button id='ds2'>y</button>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ds1').matches(':enabled')",
7362            "false",
7363        ),
7364        (
7365            "var w=document.createElement('div'); w.innerHTML=\"<div id='em1'></div><div id='em2'>text</div><input id='ck1' type='checkbox' checked><input id='ck2' type='checkbox'><button id='ds1' disabled>x</button><button id='ds2'>y</button>\"; document.getElementById('wrap').appendChild(w); document.getElementById('ds2').matches(':enabled')",
7366            "true",
7367        ),
7368        (
7369            "var w=document.createElement('div'); w.innerHTML=\"<div id='em1'></div><div id='em2'>text</div><input id='ck1' type='checkbox' checked><input id='ck2' type='checkbox'><button id='ds1' disabled>x</button><button id='ds2'>y</button>\"; document.getElementById('wrap').appendChild(w); document.getElementById('em1').closest('.container').id",
7370            "wrap",
7371        ),
7372        ("document.getElementById('t').parentElement.id", "wrap"),
7373        ("document.getElementById('wrap').children.length", "2"),
7374        ("document.getElementById('wrap').childElementCount", "2"),
7375        // `node.hasChildNodes()`(丸ごと未対応だった。2026-07-15 発見・実装)。
7376        ("document.getElementById('wrap').hasChildNodes()", "true"),
7377        ("document.createElement('div').hasChildNodes()", "false"),
7378        // `document.createElementNS(ns, tag)`(丸ごと未対応だった。SVG 要素を
7379        // 動的生成する標準的な定番パターン。2026-07-15 発見・実装)。
7380        // `*AttributeNS` と同じく名前空間自体はモデル化せず非 NS 版へ委譲する
7381        // 簡略実装のため、生成される要素は通常の `createElement` と同じ。
7382        (
7383            "document.createElementNS('http://www.w3.org/2000/svg', 'circle').tagName",
7384            "CIRCLE",
7385        ),
7386        (
7387            "var e=document.createElementNS('http://www.w3.org/2000/svg', 'rect'); \
7388             e.setAttribute('width', '10'); e.getAttribute('width')",
7389            "10",
7390        ),
7391        ("document.getElementById('wrap').firstElementChild.id", "t"),
7392        ("document.getElementById('wrap').lastElementChild.id", "s"),
7393        ("document.getElementById('t').nextElementSibling.id", "s"),
7394        ("document.getElementById('s').previousElementSibling.id", "t"),
7395        ("document.getElementById('wrap').getAttributeNames().join(',')", "id,class"),
7396        // `element.attributes`(DOM 標準の `NamedNodeMap`。丸ごと未対応だった。
7397        // `{name,value}` オブジェクトの配列という簡略表現)。
7398        ("document.getElementById('wrap').attributes.length", "2"),
7399        ("document.getElementById('wrap').attributes[0].name", "id"),
7400        ("document.getElementById('wrap').attributes[0].value", "wrap"),
7401        // `getAttributeNode`/`setAttributeNode`/`removeAttributeNode`
7402        // (丸ごと未対応だった。2026-07-14 発見・実装)。
7403        ("document.getElementById('wrap').getAttributeNode('id').value", "wrap"),
7404        ("document.getElementById('wrap').getAttributeNode('missing')", "null"),
7405        (
7406            "var e=document.getElementById('wrap'); \
7407             e.setAttributeNode({name:'data-x', value:'1'}); e.getAttribute('data-x')",
7408            "1",
7409        ),
7410        (
7411            "var e=document.getElementById('wrap'); var a=e.getAttributeNode('id'); \
7412             e.removeAttributeNode(a); e.hasAttribute('id')",
7413            "false",
7414        ),
7415        // `document.createAttribute(name)`(丸ごと未対応だった。`Attr` ノードを
7416        // 新規に作ってから `setAttributeNode()` で取り付ける定番パターン。
7417        // 2026-07-15 発見・実装)。仕様どおり名前は小文字化される。
7418        ("document.createAttribute('DATA-X').name", "data-x"),
7419        ("document.createAttribute('data-x').value", ""),
7420        ("document.createAttribute('data-x').ownerElement", "null"),
7421        (
7422            "var e=document.getElementById('wrap'); var a=document.createAttribute('data-y'); \
7423             a.value='42'; e.setAttributeNode(a); e.getAttribute('data-y')",
7424            "42",
7425        ),
7426        // Fullscreen API(`element.requestFullscreen()`/`document.exitFullscreen()`/
7427        // `document.fullscreenElement`。丸ごと未対応だった。実際の画面占有切替は
7428        // 行わず状態のみ追跡する簡略実装)。
7429        ("document.fullscreenElement", "null"),
7430        ("document.fullscreenEnabled", "true"),
7431        (
7432            "var e=document.getElementById('wrap'); e.requestFullscreen(); document.fullscreenElement===e",
7433            "true",
7434        ),
7435        (
7436            "var e=document.getElementById('wrap'); e.requestFullscreen(); document.exitFullscreen(); document.fullscreenElement",
7437            "null",
7438        ),
7439        // Pointer Lock API(`element.requestPointerLock()`/`document.exitPointerLock()`/
7440        // `document.pointerLockElement`。丸ごと未対応だった。Fullscreen API と
7441        // 同じ「状態のみ追跡する」簡略実装)。
7442        ("document.pointerLockElement", "null"),
7443        (
7444            "var e=document.getElementById('wrap'); e.requestPointerLock(); document.pointerLockElement===e",
7445            "true",
7446        ),
7447        (
7448            "var e=document.getElementById('wrap'); e.requestPointerLock(); document.exitPointerLock(); document.pointerLockElement",
7449            "null",
7450        ),
7451        // Picture-in-Picture API(`element.requestPictureInPicture()`/
7452        // `document.exitPictureInPicture()`/`document.pictureInPictureElement`。
7453        // 丸ごと未対応だった。Fullscreen/Pointer Lock API と同じ
7454        // 「状態のみ追跡する」簡略実装)。
7455        ("document.pictureInPictureElement", "null"),
7456        ("document.pictureInPictureEnabled", "true"),
7457        (
7458            "var e=document.getElementById('wrap'); e.requestPictureInPicture(); \
7459             document.pictureInPictureElement===e",
7460            "true",
7461        ),
7462        (
7463            "var e=document.getElementById('wrap'); e.requestPictureInPicture(); \
7464             document.exitPictureInPicture(); document.pictureInPictureElement",
7465            "null",
7466        ),
7467        // `document.createTreeWalker(root)`(丸ごと未対応だった。DOM 部分木を
7468        // 走査する定番パターン。`wrap` の子は `#t`(p)/`#s`(span) の2要素)。
7469        (
7470            "document.createTreeWalker(document.getElementById('wrap')).currentNode.id",
7471            "wrap",
7472        ),
7473        (
7474            "var tw=document.createTreeWalker(document.getElementById('wrap')); \
7475             tw.firstChild(); tw.currentNode.id",
7476            "t",
7477        ),
7478        (
7479            "var tw=document.createTreeWalker(document.getElementById('wrap')); \
7480             tw.firstChild(); tw.nextSibling(); tw.currentNode.id",
7481            "s",
7482        ),
7483        (
7484            "var tw=document.createTreeWalker(document.getElementById('wrap')); \
7485             tw.firstChild(); tw.nextSibling(); tw.parentNode(); tw.currentNode.id",
7486            "wrap",
7487        ),
7488        (
7489            "var tw=document.createTreeWalker(document.getElementById('wrap')); \
7490             tw.nextNode(); tw.currentNode.id",
7491            "t",
7492        ),
7493        (
7494            "var tw=document.createTreeWalker(document.getElementById('wrap')); \
7495             var r=tw.parentNode(); r === null && tw.currentNode.id === 'wrap'",
7496            "true",
7497        ),
7498        // `document.createNodeIterator(root)`(丸ごと未対応だった。`TreeWalker` とは
7499        // 異なる「参照ノード+前後どちら側を指しているか」のポインタモデル)。
7500        (
7501            "document.createNodeIterator(document.getElementById('wrap')).nextNode().id",
7502            "wrap",
7503        ),
7504        (
7505            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7506             ni.nextNode(); ni.nextNode().id",
7507            "t",
7508        ),
7509        // `#t` の中身はテキストノード "x" のため pre-order 3件目はそのテキスト
7510        // ノード(`nodeValue` で判定。`#s` に到達するのは5件目)。
7511        (
7512            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7513             ni.nextNode(); ni.nextNode(); ni.nextNode().nodeValue",
7514            "x",
7515        ),
7516        (
7517            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7518             ni.nextNode(); ni.nextNode(); ni.nextNode(); ni.nextNode().id",
7519            "s",
7520        ),
7521        (
7522            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7523             ni.nextNode(); ni.nextNode(); ni.nextNode(); ni.nextNode(); ni.nextNode(); \
7524             ni.nextNode() === null",
7525            "true",
7526        ),
7527        (
7528            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7529             ni.nextNode(); ni.nextNode(); ni.previousNode().id",
7530            "t",
7531        ),
7532        (
7533            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7534             ni.nextNode(); ni.nextNode(); ni.previousNode(); ni.previousNode().id",
7535            "wrap",
7536        ),
7537        // `element.contentEditable`/`.isContentEditable`/`document.designMode`
7538        // (丸ごと未対応だった。属性値の反映のみを行う簡略実装)。
7539        ("document.getElementById('wrap').contentEditable", "inherit"),
7540        ("document.getElementById('wrap').isContentEditable", "false"),
7541        (
7542            "var e=document.getElementById('wrap'); e.contentEditable='true'; \
7543             e.contentEditable + ',' + e.isContentEditable",
7544            "true,true",
7545        ),
7546        (
7547            "var e=document.getElementById('wrap'); e.contentEditable='plaintext-only'; \
7548             e.contentEditable + ',' + e.isContentEditable",
7549            "plaintext-only,true",
7550        ),
7551        (
7552            "var e=document.getElementById('wrap'); e.contentEditable='bogus'; e.contentEditable",
7553            "inherit",
7554        ),
7555        ("document.designMode", "off"),
7556        // `element.spellcheck`/`.inputMode`/`.enterKeyHint`(`spellcheck`/
7557        // `inputmode`/`enterkeyhint` グローバル属性の JS プロパティ版。丸ごと
7558        // 未対応だった。属性値の反映のみを行う簡略実装。2026-07-15 発見・実装)。
7559        ("document.getElementById('wrap').spellcheck", "true"),
7560        (
7561            "var e=document.getElementById('wrap'); e.spellcheck=false; e.spellcheck",
7562            "false",
7563        ),
7564        ("document.getElementById('wrap').inputMode", ""),
7565        (
7566            "var e=document.getElementById('wrap'); e.inputMode='numeric'; e.inputMode",
7567            "numeric",
7568        ),
7569        (
7570            "var e=document.getElementById('wrap'); e.enterKeyHint='search'; e.enterKeyHint",
7571            "search",
7572        ),
7573        // `element.autocapitalize`/`.popover`(`autocapitalize`/`popover`
7574        // グローバル属性の JS プロパティ版。丸ごと未対応だった。2026-07-15
7575        // 発見・実装。`popover` は `showPopover()`等のメソッド自体は前サイクル
7576        // 実装済みだが、属性読み書き用の IDL プロパティが欠けていた)。
7577        (
7578            "var e=document.getElementById('wrap'); e.autocapitalize='words'; e.autocapitalize",
7579            "words",
7580        ),
7581        ("document.createElement('div').popover", "null"),
7582        (
7583            "var e=document.createElement('div'); e.popover='manual'; e.popover",
7584            "manual",
7585        ),
7586        (
7587            "var e=document.createElement('div'); e.popover='auto'; e.popover=null; e.popover",
7588            "null",
7589        ),
7590        // `button.popoverTargetElement`/`.popoverTargetAction`(丸ごと未対応
7591        // だった。`popovertarget` idref 属性を実際の要素へ解決する IDL 属性。
7592        // 2026-07-15 発見・実装)。
7593        (
7594            "var b=document.createElement('button'); b.setAttribute('popovertarget','wrap'); \
7595             b.popoverTargetElement === document.getElementById('wrap')",
7596            "true",
7597        ),
7598        ("document.createElement('button').popoverTargetElement", "null"),
7599        (
7600            "var b=document.createElement('button'); b.popoverTargetElement = document.getElementById('wrap'); \
7601             b.getAttribute('popovertarget')",
7602            "wrap",
7603        ),
7604        (
7605            "var b=document.createElement('button'); b.popoverTargetAction='hide'; b.popoverTargetAction",
7606            "hide",
7607        ),
7608        // `input.list`(`list` idref 属性から実際の `<datalist>` 要素を解決する
7609        // IDL 属性。丸ごと未対応だった。2026-07-15 発見・実装)。
7610        ("document.createElement('input').list", "null"),
7611        (
7612            "var i=document.createElement('input'); i.setAttribute('list','missing'); i.list",
7613            "null",
7614        ),
7615        (
7616            "var dl=document.createElement('datalist'); dl.id='opts'; \
7617             document.getElementById('wrap').appendChild(dl); \
7618             var i=document.createElement('input'); i.setAttribute('list','opts'); \
7619             i.list === dl",
7620            "true",
7621        ),
7622        (
7623            "var d=document.createElement('div'); d.id='notdl'; \
7624             document.getElementById('wrap').appendChild(d); \
7625             var i=document.createElement('input'); i.setAttribute('list','notdl'); i.list",
7626            "null",
7627        ),
7628        // `a`/`area`/`link.relList`(`rel` 属性のトークン配列版。丸ごと未対応
7629        // だった。読み取り専用の簡略実装。2026-07-15 発見・実装)。
7630        ("document.createElement('a').relList.length", "0"),
7631        (
7632            "var a=document.createElement('a'); a.rel='noopener noreferrer'; \
7633             a.relList.join(',')",
7634            "noopener,noreferrer",
7635        ),
7636        (
7637            "var a=document.createElement('a'); a.rel='noopener noreferrer'; \
7638             a.relList.includes('noreferrer')",
7639            "true",
7640        ),
7641        // `.referrerPolicy`/`.loading`/`.decoding`/`.allow`/`.sandbox`(丸ごと
7642        // 未対応だった。属性値の反映のみを行う簡略実装。2026-07-15 発見・実装)。
7643        (
7644            "var a=document.createElement('a'); a.referrerPolicy='no-referrer'; \
7645             a.referrerPolicy",
7646            "no-referrer",
7647        ),
7648        (
7649            "var i=document.createElement('img'); i.loading='lazy'; i.loading",
7650            "lazy",
7651        ),
7652        (
7653            "var i=document.createElement('img'); i.decoding='async'; i.decoding",
7654            "async",
7655        ),
7656        (
7657            "var f=document.createElement('iframe'); f.allow='camera'; f.allow",
7658            "camera",
7659        ),
7660        ("document.createElement('iframe').sandbox.length", "0"),
7661        (
7662            "var f=document.createElement('iframe'); \
7663             f.setAttribute('sandbox','allow-scripts allow-forms'); \
7664             f.sandbox.join(',')",
7665            "allow-scripts,allow-forms",
7666        ),
7667        // `.crossOrigin`/`.fetchPriority`/`.integrity`/`script.async`/`.defer`/
7668        // `.noModule`(丸ごと未対応だった。属性値の反映のみを行う簡略実装。
7669        // 2026-07-15 発見・実装)。
7670        ("document.createElement('img').crossOrigin", "null"),
7671        (
7672            "var i=document.createElement('img'); i.crossOrigin='anonymous'; i.crossOrigin",
7673            "anonymous",
7674        ),
7675        ("document.createElement('img').fetchPriority", "auto"),
7676        (
7677            "var i=document.createElement('img'); i.fetchPriority='high'; i.fetchPriority",
7678            "high",
7679        ),
7680        (
7681            "var s=document.createElement('script'); s.integrity='sha256-x'; s.integrity",
7682            "sha256-x",
7683        ),
7684        (
7685            "var s=document.createElement('script'); s.async=true; s.async + ',' + \
7686             s.hasAttribute('async')",
7687            "true,true",
7688        ),
7689        (
7690            "var s=document.createElement('script'); s.defer=true; s.defer=false; s.defer",
7691            "false",
7692        ),
7693        (
7694            "var s=document.createElement('script'); s.noModule=true; s.noModule",
7695            "true",
7696        ),
7697        // `.srcset`/`.sizes`/`.media`/`img.useMap`/`.isMap`/`.currentSrc`
7698        // (丸ごと未対応だった。属性値の反映のみを行う簡略実装。2026-07-15
7699        // 発見・実装)。
7700        (
7701            "var i=document.createElement('img'); i.srcset='a.jpg 1x, b.jpg 2x'; i.srcset",
7702            "a.jpg 1x, b.jpg 2x",
7703        ),
7704        (
7705            "var i=document.createElement('img'); i.sizes='(max-width:600px) 480px'; i.sizes",
7706            "(max-width:600px) 480px",
7707        ),
7708        (
7709            "var l=document.createElement('link'); l.media='print'; l.media",
7710            "print",
7711        ),
7712        (
7713            "var i=document.createElement('img'); i.useMap='#m'; i.useMap",
7714            "#m",
7715        ),
7716        ("document.createElement('img').isMap", "false"),
7717        (
7718            "var i=document.createElement('img'); i.isMap=true; i.hasAttribute('ismap')",
7719            "true",
7720        ),
7721        (
7722            "var i=document.createElement('img'); i.src='a.jpg'; i.currentSrc",
7723            "a.jpg",
7724        ),
7725        // `img.complete`/`.naturalWidth`/`.naturalHeight`/`.decode()`が丸ごと
7726        // 未対応だった(2026-07-17 発見・実装。実デコード寸法を追跡する
7727        // 経路が無いため`naturalWidth`/`.naturalHeight`は常に`0`を返す
7728        // 誠実な簡略実装。`complete`は`src`の有無のみで判定)。
7729        ("document.createElement('img').complete", "false"),
7730        (
7731            "var i=document.createElement('img'); i.src='a.jpg'; i.complete",
7732            "true",
7733        ),
7734        (
7735            "var i=document.createElement('img'); i.src='a.jpg'; i.naturalWidth + ',' + i.naturalHeight",
7736            "0,0",
7737        ),
7738        (
7739            "var i=document.createElement('img'); i.src='a.jpg'; \
7740             await i.decode().then(() => 'ok')",
7741            "ok",
7742        ),
7743        (
7744            "var i=document.createElement('img'); \
7745             await i.decode().catch(e => e.name)",
7746            "EncodingError",
7747        ),
7748        // `audio`/`video.autoplay`/`.controls`/`.loop`/`.muted`/`.playsInline`/
7749        // `.preload`/`.poster`/`a.ping`/`.hreflang`(丸ごと未対応だった。属性値の
7750        // 反映のみを行う簡略実装。2026-07-15 発見・実装)。
7751        ("document.createElement('video').autoplay", "false"),
7752        (
7753            "var v=document.createElement('video'); v.autoplay=true; v.autoplay + \
7754             ',' + v.hasAttribute('autoplay')",
7755            "true,true",
7756        ),
7757        (
7758            "var v=document.createElement('video'); v.controls=true; v.controls=false; \
7759             v.controls",
7760            "false",
7761        ),
7762        (
7763            "var v=document.createElement('video'); v.loop=true; v.loop",
7764            "true",
7765        ),
7766        (
7767            "var v=document.createElement('video'); v.muted=true; v.muted",
7768            "true",
7769        ),
7770        (
7771            "var v=document.createElement('video'); v.playsInline=true; \
7772             v.hasAttribute('playsinline')",
7773            "true",
7774        ),
7775        (
7776            "var v=document.createElement('video'); v.preload='auto'; v.preload",
7777            "auto",
7778        ),
7779        (
7780            "var v=document.createElement('video'); v.poster='p.jpg'; v.poster",
7781            "p.jpg",
7782        ),
7783        (
7784            "var a=document.createElement('a'); a.ping='https://x'; a.ping",
7785            "https://x",
7786        ),
7787        (
7788            "var a=document.createElement('a'); a.hreflang='en'; a.hreflang",
7789            "en",
7790        ),
7791        // `audio`/`video.paused`/`.currentTime`/`.duration`/`.volume`/
7792        // `.playbackRate`(丸ごと未対応だった。`.play()`/`.pause()`と同じ
7793        // 状態追跡のみの簡略実装。2026-07-15 発見・実装)。
7794        ("document.createElement('video').paused", "true"),
7795        (
7796            "var v=document.createElement('video'); await v.play(); v.paused",
7797            "false",
7798        ),
7799        (
7800            "var v=document.createElement('video'); await v.play(); v.pause(); v.paused",
7801            "true",
7802        ),
7803        (
7804            "var v=document.createElement('video'); v.currentTime=12.5; v.currentTime",
7805            "12.5",
7806        ),
7807        (
7808            "var v=document.createElement('video'); Number.isNaN(v.duration)",
7809            "true",
7810        ),
7811        ("document.createElement('video').volume", "1"),
7812        (
7813            "var v=document.createElement('video'); v.volume=0.5; v.volume",
7814            "0.5",
7815        ),
7816        (
7817            "var v=document.createElement('video'); v.volume=5; v.volume",
7818            "1",
7819        ),
7820        (
7821            "var v=document.createElement('video'); v.playbackRate=2; v.playbackRate",
7822            "2",
7823        ),
7824        // `.play()`/`.pause()` の `play`/`playing`/`pause` イベント発火(丸ごと
7825        // 未対応だった。既に同じ状態なら発火しない冪等性も含む。2026-07-15
7826        // 発見・実装)。
7827        (
7828            "var v=document.createElement('video'); var log=[]; \
7829             v.addEventListener('play', ()=>log.push('play')); \
7830             v.addEventListener('playing', ()=>log.push('playing')); \
7831             await v.play(); log.join(',')",
7832            "play,playing",
7833        ),
7834        (
7835            "var v=document.createElement('video'); var n=0; \
7836             v.addEventListener('pause', ()=>n++); \
7837             await v.play(); v.pause(); v.pause(); n",
7838            "1",
7839        ),
7840        (
7841            "var v=document.createElement('video'); var n=0; \
7842             v.addEventListener('play', ()=>n++); \
7843             await v.play(); await v.play(); n",
7844            "1",
7845        ),
7846        // `.currentTime`/`.volume`/`.muted`/`.playbackRate` 代入時の
7847        // `seeked`/`volumechange`/`ratechange` イベント発火(丸ごと未対応
7848        // だった。2026-07-15 発見・実装)。
7849        (
7850            "var v=document.createElement('video'); var f=false; \
7851             v.addEventListener('seeked', ()=>f=true); v.currentTime=5; f",
7852            "true",
7853        ),
7854        (
7855            "var v=document.createElement('video'); var f=false; \
7856             v.addEventListener('volumechange', ()=>f=true); v.volume=0.3; f",
7857            "true",
7858        ),
7859        (
7860            "var v=document.createElement('video'); var f=false; \
7861             v.addEventListener('volumechange', ()=>f=true); v.muted=true; f",
7862            "true",
7863        ),
7864        (
7865            "var v=document.createElement('video'); var f=false; \
7866             v.addEventListener('ratechange', ()=>f=true); v.playbackRate=1.5; f",
7867            "true",
7868        ),
7869        // `.readyState`/`.networkState`/`.error`(丸ごと未対応だった。実際の
7870        // ネットワーク層/デコーダが無いため誠実に「何もロードしていない」
7871        // 状態を返す簡略実装。2026-07-15 発見・実装)。
7872        ("document.createElement('video').readyState", "0"),
7873        ("document.createElement('video').networkState", "0"),
7874        (
7875            "var v=document.createElement('video'); v.src='a.mp4'; v.networkState",
7876            "3",
7877        ),
7878        ("document.createElement('video').error", "null"),
7879        // `.buffered`/`.seekable`(`TimeRanges`。丸ごと未対応だった。実際には
7880        // メディアを一切ロードしないため常に空。範囲外アクセスは仕様どおり
7881        // 例外を投げる誠実な簡略実装)。`.defaultPlaybackRate` は単純な数値
7882        // 状態の保持のみ(`.playbackRate`とは独立)。2026-07-15 発見・実装。
7883        ("document.createElement('video').buffered.length", "0"),
7884        ("document.createElement('video').seekable.length", "0"),
7885        (
7886            "try { document.createElement('video').buffered.start(0); 'no-throw' } \
7887             catch(e) { 'threw' }",
7888            "threw",
7889        ),
7890        ("document.createElement('video').defaultPlaybackRate", "1"),
7891        (
7892            "var v=document.createElement('video'); v.defaultPlaybackRate=1.5; \
7893             v.defaultPlaybackRate + ',' + v.playbackRate",
7894            "1.5,1",
7895        ),
7896        // `element.getAnimations()`/`document.getAnimations()`(丸ごと未対応
7897        // だった。`.animate()` の戻り値を一切保持していないため常に空配列を
7898        // 返す誠実な簡略実装。2026-07-15 発見・実装)。
7899        (
7900            "document.createElement('div').getAnimations().length",
7901            "0",
7902        ),
7903        (
7904            "var e=document.createElement('div'); e.animate([], 100); \
7905             e.getAnimations().length",
7906            "0",
7907        ),
7908        ("document.getAnimations().length", "0"),
7909        // `Animation.reverse()`/`.finish()`/`.playbackRate`(丸ごと未対応
7910        // だった。`play`/`pause`/`cancel`と同じno-opの簡略方針。
7911        // 2026-07-17 発見・実装)。
7912        (
7913            "var a=document.createElement('div').animate([],100); \
7914             typeof a.reverse + ',' + typeof a.finish + ',' + a.playbackRate",
7915            "function,function,1",
7916        ),
7917        (
7918            "var a=document.createElement('div').animate([],100); a.reverse(); a.finish(); a.playbackRate=2; a.playbackRate",
7919            "2",
7920        ),
7921        // `script`/`style.nonce`(丸ごと未対応だった。属性値の反映のみを行う
7922        // 簡略実装。2026-07-15 発見・実装)。
7923        (
7924            "var s=document.createElement('script'); s.nonce='abc123'; s.nonce",
7925            "abc123",
7926        ),
7927        // `Selection`(`document.getSelection()`)の残りのメソッド群/
7928        // プロパティが丸ごと未対応だった。テキスト選択機構自体が無いため
7929        // 「常に何も選択していない」誠実な簡略実装。2026-07-15 発見・実装。
7930        (
7931            "var s=document.getSelection(); s.collapse(document.body, 0); \
7932             s.anchorNode + ',' + s.type",
7933            "null,None",
7934        ),
7935        (
7936            "var s=document.getSelection(); s.containsNode(document.body)",
7937            "false",
7938        ),
7939        // `Selection.removeRange(range)`が`addRange`/`removeAllRanges`の隣で
7940        // 漏れていた兄弟ギャップ(丸ごと未対応だった。2026-07-17 発見・
7941        // 実装)。
7942        (
7943            "typeof document.getSelection().removeRange",
7944            "function",
7945        ),
7946        (
7947            "document.getSelection().removeRange(document.createRange())",
7948            "undefined",
7949        ),
7950        (
7951            "try { document.getSelection().getRangeAt(0); 'no-throw' } \
7952             catch(e) { 'threw' }",
7953            "threw",
7954        ),
7955        // `Range`(`document.createRange()`)の残りのメソッド群/プロパティが
7956        // 丸ごと未対応だった。`Selection` と同方針の誠実な簡略実装。
7957        // 2026-07-15 発見・実装。
7958        (
7959            "var r=document.createRange(); r.setStart(document.body, 0); \
7960             r.collapsed + ',' + r.toString()",
7961            "true,",
7962        ),
7963        (
7964            "var r=document.createRange(); var r2=r.cloneRange(); \
7965             r2.collapsed",
7966            "true",
7967        ),
7968        (
7969            "document.createRange().isPointInRange(document.body, 0)",
7970            "false",
7971        ),
7972        ("typeof Range", "function"),
7973        ("Range.START_TO_START + ',' + Range.END_TO_START", "0,3"),
7974        (
7975            "var b=document.getElementById('wrap'); var r=new Range(); r.setStart(b, 1); r.setEnd(b, 5); \
7976             r.collapsed + ',' + r.startOffset + ',' + r.endOffset",
7977            "false,1,5",
7978        ),
7979        (
7980            "var b=document.getElementById('wrap'); var r=new Range(); r.setStart(b, 1); r.setEnd(b, 5); \
7981             var r2=r.cloneRange(); r2.collapsed + ',' + r2.startOffset + ',' + r2.endOffset",
7982            "false,1,5",
7983        ),
7984        (
7985            "var b=document.getElementById('wrap'); var r=new Range(); r.setStart(b, 1); r.setEnd(b, 5); \
7986             r.collapse(true); r.collapsed + ',' + r.startOffset + ',' + r.endOffset",
7987            "true,1,1",
7988        ),
7989        (
7990            "var b=document.getElementById('wrap'); var r=new Range(); r.setStart(b, 1); r.setEnd(b, 5); \
7991             r.isPointInRange(b, 3)",
7992            "true",
7993        ),
7994        (
7995            "var b=document.getElementById('wrap'); var r=new Range(); r.setStart(b, 1); r.setEnd(b, 5); \
7996             r.isPointInRange(b, 10)",
7997            "false",
7998        ),
7999        (
8000            "var b=document.getElementById('wrap'); var s=window.getSelection(); var r=new Range(); r.setStart(b, 2); \
8001             r.setEnd(b, 8); s.addRange(r); \
8002             s.rangeCount + ',' + s.isCollapsed + ',' + s.type",
8003            "1,false,Range",
8004        ),
8005        (
8006            "var b=document.getElementById('wrap'); var s=window.getSelection(); var r=new Range(); r.setStart(b, 2); \
8007             r.setEnd(b, 8); s.addRange(r); \
8008             window.getSelection().getRangeAt(0).startOffset",
8009            "2",
8010        ),
8011        (
8012            "var s=window.getSelection(); s.removeAllRanges(); s.rangeCount + ',' + s.type",
8013            "0,None",
8014        ),
8015        (
8016            "document.designMode='on'; document.designMode",
8017            "on",
8018        ),
8019        // `element.hasAttributes()`(丸ごと未対応だった。単一属性の有無を見る
8020        // `hasAttribute(name)` は既に対応済みだったが、こちらが漏れていた)。
8021        ("document.getElementById('wrap').hasAttributes()", "true"),
8022        ("document.createElement('div').hasAttributes()", "false"),
8023        // `node.isSameNode(other)`/`node.isEqualNode(other)` が丸ごと未対応だった。
8024        ("var e=document.getElementById('wrap'); e.isSameNode(e)", "true"),
8025        (
8026            "document.getElementById('wrap').isSameNode(document.getElementById('t'))",
8027            "false",
8028        ),
8029        (
8030            "var a=document.createElement('div'); a.id='x'; var b=document.createElement('div'); b.id='x'; a.isEqualNode(b)",
8031            "true",
8032        ),
8033        (
8034            "var a=document.createElement('div'); a.id='x'; var b=document.createElement('div'); b.id='y'; a.isEqualNode(b)",
8035            "false",
8036        ),
8037        // `node.normalize()` が丸ごと未対応だった。連続する隣接テキストノードを
8038        // 1つに連結し、空のテキストノードを取り除く。
8039        (
8040            "var d=document.createElement('div'); d.appendChild(document.createTextNode('a')); d.appendChild(document.createTextNode('b')); d.normalize(); d.childNodes.length",
8041            "1",
8042        ),
8043        (
8044            "var d=document.createElement('div'); d.appendChild(document.createTextNode('a')); d.appendChild(document.createTextNode('b')); d.normalize(); d.textContent",
8045            "ab",
8046        ),
8047        (
8048            "var d=document.createElement('div'); d.appendChild(document.createTextNode('')); d.normalize(); d.childNodes.length",
8049            "0",
8050        ),
8051        // `document.createDocumentFragment()` が丸ごと未対応だった。挿入すると
8052        // フラグメント自身ではなく中身の子要素群がそのまま移動する(実 DOM の挙動)。
8053        ("typeof document.createDocumentFragment", "function"),
8054        (
8055            "var f=document.createDocumentFragment(); f.appendChild(document.createElement('span')); f.appendChild(document.createElement('em')); var d=document.createElement('div'); d.appendChild(f); d.children.length+','+d.children[0].tagName+','+d.children[1].tagName",
8056            "2,SPAN,EM",
8057        ),
8058        // フラグメント自身は挿入後に空になる(中身だけが移動したことの確認)。
8059        (
8060            "var f=document.createDocumentFragment(); f.appendChild(document.createElement('span')); var d=document.createElement('div'); d.appendChild(f); f.childNodes.length",
8061            "0",
8062        ),
8063        // getElementsByClassName / getElementsByTagName
8064        ("document.getElementsByTagName('p').length", "1"),
8065        ("document.getElementsByClassName('a')[0].id", "t"),
8066        ("document.getElementsByTagName('span')[0].id", "s"),
8067        // 要素インスタンス版の getElementsByClassName/getElementsByTagName
8068        // (`document` 版は対応済みだったがこちらが丸ごと未対応だった)。
8069        (
8070            "document.getElementById('wrap').getElementsByClassName('a')[0].id",
8071            "t",
8072        ),
8073        (
8074            "document.getElementById('wrap').getElementsByTagName('span')[0].id",
8075            "s",
8076        ),
8077        (
8078            "document.getElementById('s').getElementsByClassName('a').length",
8079            "0",
8080        ),
8081        // getElementsByTagNameNS / getAttributeNodeNS(丸ごと未対応だった)。
8082        ("document.getElementsByTagNameNS('*', 'span')[0].id", "s"),
8083        (
8084            "document.getElementById('wrap').getElementsByTagNameNS(null, 'span')[0].id",
8085            "s",
8086        ),
8087        (
8088            "document.getElementById('wrap').getAttributeNodeNS(null, 'id').value",
8089            "wrap",
8090        ),
8091        (
8092            "document.getElementById('wrap').getAttributeNodeNS('http://www.w3.org/1999/xhtml', 'missing') === null",
8093            "true",
8094        ),
8095        // `document.getElementsByName(name)`(丸ごと未対応だった。ラジオボタン
8096        // グループ等、同じ `name` 属性を持つ要素群をまとめて取得する定番
8097        // パターン。2026-07-15 発見・実装)。
8098        (
8099            "var a=document.createElement('input'); a.setAttribute('name','q'); \
8100             var b=document.createElement('input'); b.setAttribute('name','q'); \
8101             var wrap=document.getElementById('wrap'); \
8102             wrap.appendChild(a); wrap.appendChild(b); \
8103             document.getElementsByName('q').length",
8104            "2",
8105        ),
8106        ("document.getElementsByName('no-such-name').length", "0"),
8107        // append / prepend / before / after / replaceWith
8108        ("var w=document.getElementById('wrap'); var n=document.createElement('b'); n.id='ap'; w.append(n); w.lastElementChild.id", "ap"),
8109        ("var w=document.getElementById('wrap'); var n=document.createElement('b'); n.id='pp'; w.prepend(n); w.firstElementChild.id", "pp"),
8110        ("var n=document.createElement('b'); n.id='bf'; document.getElementById('t').before(n); document.getElementById('t').previousElementSibling.id", "bf"),
8111        ("var n=document.createElement('b'); n.id='af'; document.getElementById('t').after(n); document.getElementById('t').nextElementSibling.id", "af"),
8112        ("var n=document.createElement('b'); n.id='rw'; document.getElementById('s').replaceWith(n); document.getElementById('wrap').lastElementChild.id", "rw"),
8113        ("var w=document.getElementById('wrap'); w.append('TXT'); w.textContent.indexOf('TXT') >= 0", "true"),
8114        // `element.replaceChildren(...nodes)`(`innerHTML=''; append(...)` の1行版)
8115        // が丸ごと未対応だった。既存の子は全て消え、渡した内容だけが残る。
8116        (
8117            "var w=document.getElementById('wrap'); var n=document.createElement('b'); n.id='rc'; w.replaceChildren(n); w.children.length + ',' + w.firstElementChild.id",
8118            "1,rc",
8119        ),
8120        (
8121            "var w=document.getElementById('wrap'); w.replaceChildren(); w.children.length",
8122            "0",
8123        ),
8124        // `element.scrollTo(x,y)`/`.scrollBy(x,y)`(オブジェクト引数 `{top}` 形式も
8125        // 含む)が丸ごと未対応だった。既存の `element.scrollTop` setter へ委譲する。
8126        (
8127            "var w=document.getElementById('wrap'); w.scrollTo(0, 50); w.scrollTop",
8128            "50",
8129        ),
8130        (
8131            "var w=document.getElementById('wrap'); w.scrollTo({top: 30}); w.scrollTop",
8132            "30",
8133        ),
8134        (
8135            "var w=document.getElementById('wrap'); w.scrollTop=10; w.scrollBy(0, 5); w.scrollTop",
8136            "15",
8137        ),
8138        // `element.scrollLeft` および `scrollTo`/`scrollBy`/`scroll`(水平スクロール対応)
8139        (
8140            "var w=document.getElementById('wrap'); w.scrollLeft=40; w.scrollLeft",
8141            "40",
8142        ),
8143        (
8144            "var w=document.getElementById('wrap'); w.scrollTo(80, 20); w.scrollLeft + ',' + w.scrollTop",
8145            "80,20",
8146        ),
8147        (
8148            "var w=document.getElementById('wrap'); w.scrollTo({left: 60, top: 10}); w.scrollLeft + ',' + w.scrollTop",
8149            "60,10",
8150        ),
8151        (
8152            "var w=document.getElementById('wrap'); w.scroll({left: 25, top: 35}); w.scrollLeft + ',' + w.scrollTop",
8153            "25,35",
8154        ),
8155        (
8156            "var w=document.getElementById('wrap'); w.scroll({left: 25, top: 35}); w.scrollBy(10, -5); w.scrollLeft + ',' + w.scrollTop",
8157            "35,30",
8158        ),
8159        (
8160            "var w=document.getElementById('wrap'); typeof w.scrollWidth",
8161            "number",
8162        ),
8163        // classList 追加(length / value / item / toString)
8164        ("document.getElementById('t').classList.length", "2"),
8165        ("document.getElementById('t').classList.value", "a b"),
8166        ("document.getElementById('t').classList.item(0)", "a"),
8167        ("document.getElementById('t').classList.toString()", "a b"),
8168        // insertAdjacentHTML / insertAdjacentElement / toggleAttribute
8169        ("var w=document.getElementById('wrap'); w.insertAdjacentHTML('beforeend','<i id=iah>z</i>'); document.getElementById('iah').tagName", "I"),
8170        // position 引数は仕様上 ASCII 大文字小文字を無視すべきだが、以前は完全一致
8171        // のみで `"beforeEnd"` 等を渡すと静かに no-op になっていた。
8172        ("var w=document.getElementById('wrap'); w.insertAdjacentHTML('beforeEnd','<b id=iahc>z</b>'); document.getElementById('iahc').tagName", "B"),
8173        ("var t=document.getElementById('t'); t.insertAdjacentHTML('afterend','<u id=iae>q</u>'); t.nextElementSibling.id", "iae"),
8174        ("var t=document.getElementById('t'); var n=document.createElement('b'); n.id='iel'; t.insertAdjacentElement('beforebegin', n); t.previousElementSibling.id", "iel"),
8175        ("var t=document.getElementById('t'); t.toggleAttribute('hidden'); t.hasAttribute('hidden')", "true"),
8176        ("var t=document.getElementById('t'); t.toggleAttribute('hidden'); t.toggleAttribute('hidden'); t.hasAttribute('hidden')", "false"),
8177        ("var t=document.getElementById('t'); t.toggleAttribute('hidden', true); t.hasAttribute('hidden')", "true"),
8178        ("var t=document.getElementById('t'); t.toggleAttribute('hidden', true); t.toggleAttribute('hidden', false); t.hasAttribute('hidden')", "false"),
8179        // <dialog> の show()/showModal()/close()(以前は丸ごと未対応だった)。
8180        ("var d=document.createElement('dialog'); d.open", "false"),
8181        ("var d=document.createElement('dialog'); d.show(); d.open", "true"),
8182        ("var d=document.createElement('dialog'); d.showModal(); d.open", "true"),
8183        // `.show()`と`.showModal()`が同一実装を共有しておりCSS `:modal`用の
8184        // 区別が一切無かった(2026-07-18 発見・実装)。`_modal`は
8185        // `showModal()`のみが書き込む内部専用属性で、`.show()`では
8186        // 付かず、`.close()`/`.requestClose()`でどちらも除去される。
8187        ("var d=document.createElement('dialog'); d.show(); d.hasAttribute('_modal')", "false"),
8188        ("var d=document.createElement('dialog'); d.showModal(); d.hasAttribute('_modal')", "true"),
8189        ("var d=document.createElement('dialog'); d.showModal(); d.close(); d.hasAttribute('_modal')", "false"),
8190        ("var d=document.createElement('dialog'); d.show(); d.close(); d.open", "false"),
8191        ("var d=document.createElement('dialog'); d.show(); d.close('ok'); d.returnValue", "ok"),
8192        (
8193            "var d=document.createElement('dialog'); var closed=false; d.addEventListener('close', function(){ closed=true; }); d.show(); d.close(); closed",
8194            "true",
8195        ),
8196        ("var d=document.createElement('dialog'); d.open=true; d.hasAttribute('open')", "true"),
8197        // Popover API `showPopover()`/`hidePopover()`/`togglePopover(force?)`
8198        // (丸ごと未対応だった。`<dialog>` の `open` 属性と同じ内部専用属性の
8199        // 付け外しのみの簡略実装。2026-07-15 発見・実装)。
8200        ("var e=document.createElement('div'); e.togglePopover()", "true"),
8201        (
8202            "var e=document.createElement('div'); e.togglePopover(); e.togglePopover()",
8203            "false",
8204        ),
8205        (
8206            "var e=document.createElement('div'); e.togglePopover(true); e.togglePopover(true)",
8207            "true",
8208        ),
8209        (
8210            "var e=document.createElement('div'); e.showPopover(); e.togglePopover()",
8211            "false",
8212        ),
8213        // Popover API が状態変化を`toggle`イベントとして発火していな
8214        // かった(`<dialog>.close()`は既に`"close"`イベントを発火して
8215        // いたのに同じ役目のPopover側だけ漏れていた。丸ごと未対応
8216        // だった。2026-07-17 発見・実装)。
8217        (
8218            "var e=document.createElement('div'); var n=0; \
8219             e.addEventListener('toggle', () => n++); e.showPopover(); e.hidePopover(); n",
8220            "2",
8221        ),
8222        (
8223            "var e=document.createElement('div'); var n=0; \
8224             e.addEventListener('toggle', () => n++); e.togglePopover(); n",
8225            "1",
8226        ),
8227        // `toggle`イベントの`oldState`/`newState`(仕様上の`ToggleEvent`の
8228        // 主要プロパティ。丸ごと未対応だった。2026-07-17 発見・実装)。
8229        (
8230            "var e=document.createElement('div'); var s=''; \
8231             e.addEventListener('toggle', ev => s=ev.oldState+'>'+ev.newState); \
8232             e.showPopover(); s",
8233            "closed>open",
8234        ),
8235        (
8236            "var e=document.createElement('div'); var s=''; \
8237             e.addEventListener('toggle', ev => s=ev.oldState+'>'+ev.newState); \
8238             e.showPopover(); e.hidePopover(); s",
8239            "open>closed",
8240        ),
8241        // `beforetoggle`(状態変更「前」に発火する取消可能なイベント。
8242        // `preventDefault()`で開閉を中止できる。丸ごと未対応だった。
8243        // 2026-07-17 発見・実装)。
8244        (
8245            "var e=document.createElement('div'); var n=0; \
8246             e.addEventListener('beforetoggle', () => n++); e.showPopover(); n",
8247            "1",
8248        ),
8249        (
8250            "var e=document.createElement('div'); var toggled=false; \
8251             e.addEventListener('beforetoggle', ev => ev.preventDefault()); \
8252             e.addEventListener('toggle', () => toggled=true); \
8253             e.showPopover(); toggled",
8254            "false",
8255        ),
8256        (
8257            "var e=document.createElement('div'); \
8258             e.addEventListener('beforetoggle', ev => ev.preventDefault()); \
8259             e.showPopover(); e.togglePopover()",
8260            "false",
8261        ),
8262        // `inert` グローバル真偽属性(丸ごと未対応だった)。
8263        ("var t=document.createElement('div'); t.inert", "false"),
8264        ("var t=document.createElement('div'); t.setAttribute('inert',''); t.inert", "true"),
8265        ("var t=document.createElement('div'); t.inert=true; t.hasAttribute('inert')", "true"),
8266        ("var t=document.createElement('div'); t.inert=true; t.inert=false; t.hasAttribute('inert')", "false"),
8267        // <template>.content(丸ごと未対応だった)。
8268        (
8269            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; tpl.content.children.length",
8270            "1",
8271        ),
8272        (
8273            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; tpl.content.firstChild.textContent",
8274            "hi",
8275        ),
8276        // 初回アクセスで子がフラグメント側へ移り、template 自身は空になる。
8277        (
8278            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; var f=tpl.content; tpl.children.length",
8279            "0",
8280        ),
8281        // 2回目以降のアクセスでも同じフラグメントを返す(子を再度奪わない)。
8282        (
8283            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; var f1=tpl.content; var f2=tpl.content; f1===f2",
8284            "true",
8285        ),
8286        // DocumentFragment を appendChild すると中身だけが移動する(実 DOM と同じ挙動)。
8287        (
8288            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; var host=document.createElement('div'); host.appendChild(tpl.content); host.children.length",
8289            "1",
8290        ),
8291        // `document.importNode(externalNode, deep)`/`document.adoptNode(node)`
8292        // (丸ごと未対応だった。`<template>` の中身を取り込む定番パターン。
8293        // 2026-07-15 発見・実装)。`importNode` はコピーなので元の `.content`
8294        // には影響しない。
8295        (
8296            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; \
8297             var imported=document.importNode(tpl.content, true); \
8298             var host=document.createElement('div'); host.appendChild(imported); \
8299             host.children.length + ',' + tpl.content.children.length",
8300            "1,1",
8301        ),
8302        (
8303            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; \
8304             document.importNode(tpl.content, true).firstChild.textContent",
8305            "hi",
8306        ),
8307        (
8308            "var host1=document.createElement('div'); var host2=document.createElement('div'); \
8309             var child=document.createElement('span'); host1.appendChild(child); \
8310             document.adoptNode(child); host2.appendChild(child); \
8311             host1.children.length + ',' + host2.children.length",
8312            "0,1",
8313        ),
8314        // cloneNode(): .content に一度アクセス済みの <template> を複製すると、
8315        // クローン先の .content が元の(同じ)フラグメントを指したまま共有されて
8316        // しまうバグだった(複製後にクローン側の中身を変更しても元に影響しない、
8317        // という cloneNode の基本契約が破られていた)。
8318        (
8319            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; var f=tpl.content; \
8320             var clone=tpl.cloneNode(true); clone.content===tpl.content",
8321            "false",
8322        ),
8323        (
8324            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; var f=tpl.content; \
8325             var clone=tpl.cloneNode(true); clone.content.children.length+','+tpl.content.children.length",
8326            "1,1",
8327        ),
8328        // outerHTML/getAttributeNames() が `_content_frag`(`.content` アクセス済み
8329        // <template> が内部で持つ housekeeping 属性)等の `_` 始まり内部専用属性を
8330        // そのまま漏らしていたバグ。
8331        (
8332            "var tpl=document.createElement('template'); tpl.innerHTML='hi'; var f=tpl.content; tpl.outerHTML",
8333            "<template></template>",
8334        ),
8335        (
8336            "var tpl=document.createElement('template'); tpl.innerHTML='hi'; var f=tpl.content; \
8337             tpl.getAttributeNames().join(',')",
8338            "",
8339        ),
8340        // <progress>/<meter> の .value/.max/.min(丸ごと未対応だった。既定値は
8341        // 仕様どおり value=0/max=1/meter.min=0)。
8342        ("document.createElement('progress').value", "0"),
8343        ("document.createElement('progress').max", "1"),
8344        ("var p=document.createElement('progress'); p.setAttribute('value','30'); p.setAttribute('max','50'); p.value+'/'+p.max", "30/50"),
8345        ("var p=document.createElement('progress'); p.value=40; p.getAttribute('value')", "40"),
8346        ("document.createElement('meter').min", "0"),
8347        ("var m=document.createElement('meter'); m.setAttribute('value','5'); typeof m.value", "number"),
8348        // `input.valueAsNumber`(HTML5。丸ごと未対応だった)。
8349        ("var i=document.createElement('input'); i.type='number'; i.value='42'; i.valueAsNumber", "42"),
8350        ("var i=document.createElement('input'); i.type='text'; i.value='42'; isNaN(i.valueAsNumber)", "true"),
8351        ("var i=document.createElement('input'); i.type='number'; i.valueAsNumber=7; i.value", "7"),
8352        ("var i=document.createElement('input'); i.type='number'; i.value='5'; i.valueAsNumber=NaN; i.value", ""),
8353        // `input.valueAsDate`(HTML5。`type="date"`/`"month"` の丸ごと未対応だった。
8354        // `week`/その他の type は仕様どおり `null`)。
8355        (
8356            "var i=document.createElement('input'); i.type='date'; i.value='2024-03-15'; \
8357             i.valueAsDate.getUTCFullYear()+'-'+(i.valueAsDate.getUTCMonth()+1)+'-'+i.valueAsDate.getUTCDate()",
8358            "2024-3-15",
8359        ),
8360        ("var i=document.createElement('input'); i.type='text'; i.value='2024-03-15'; i.valueAsDate", "null"),
8361        (
8362            "var i=document.createElement('input'); i.type='date'; i.valueAsDate=new Date(Date.UTC(2024,2,15)); i.value",
8363            "2024-03-15",
8364        ),
8365        (
8366            "var i=document.createElement('input'); i.type='month'; i.valueAsDate=new Date(Date.UTC(2024,2,15)); i.value",
8367            "2024-03",
8368        ),
8369        (
8370            "var i=document.createElement('input'); i.type='date'; i.value='x'; i.valueAsDate=null; i.value",
8371            "",
8372        ),
8373        // `input[type=date/month/datetime-local].value`は仕様上、属性値が
8374        // 正しい形式でなければ空文字列を返すべきだが、`color`/`range`と同じ
8375        // バグパターンで不正な生文字列がそのまま漏れていた。
8376        (
8377            "var i=document.createElement('input'); i.type='date'; \
8378             i.setAttribute('value','not-a-date'); i.value",
8379            "",
8380        ),
8381        (
8382            "var i=document.createElement('input'); i.type='date'; \
8383             i.setAttribute('value','2026-07-18'); i.value",
8384            "2026-07-18",
8385        ),
8386        (
8387            "var i=document.createElement('input'); i.type='month'; \
8388             i.setAttribute('value','garbage'); i.value",
8389            "",
8390        ),
8391        (
8392            "var i=document.createElement('input'); i.type='datetime-local'; \
8393             i.setAttribute('value','2026-07-18T14:30'); i.value",
8394            "2026-07-18T14:30",
8395        ),
8396        // getAttributeNS/setAttributeNS/hasAttributeNS/removeAttributeNS(丸ごと
8397        // 未対応だった。名前空間は無視し非NS版と同じ属性ストアへ委譲する簡略実装)。
8398        ("var e=document.createElement('div'); e.setAttributeNS(null,'data-k','v'); e.getAttribute('data-k')", "v"),
8399        ("var e=document.createElement('div'); e.setAttribute('data-k','v'); e.getAttributeNS(null,'data-k')", "v"),
8400        ("var e=document.createElement('div'); e.setAttributeNS(null,'data-k','v'); e.hasAttributeNS(null,'data-k')", "true"),
8401        (
8402            "var e=document.createElement('div'); e.setAttributeNS(null,'data-k','v'); e.removeAttributeNS(null,'data-k'); e.hasAttribute('data-k')",
8403            "false",
8404        ),
8405        // node.getRootNode()(丸ごと未対応だった)。
8406        (
8407            "var p=document.createElement('div'); var c=document.createElement('span'); p.appendChild(c); c.getRootNode()===p",
8408            "true",
8409        ),
8410        // 接続済みツリーでは異なる要素同士でも同じルートを返す。
8411        (
8412            "document.getElementById('t').getRootNode()===document.getElementById('wrap').getRootNode()",
8413            "true",
8414        ),
8415        // 親の無い単独ノードは自分自身を返す。
8416        ("var e=document.createElement('div'); e.getRootNode()===e", "true"),
8417        // lookupNamespaceURI/lookupPrefix/isDefaultNamespace(丸ごと未対応
8418        // だった。2026-07-16 発見・実装。この処理系は XML 名前空間を一切
8419        // モデル化していないため常に「名前空間なし」を返す簡略実装)。
8420        ("var e=document.createElement('div'); e.lookupNamespaceURI('svg')", "null"),
8421        ("var e=document.createElement('div'); e.lookupPrefix('http://www.w3.org/2000/svg')", "null"),
8422        ("var e=document.createElement('div'); e.isDefaultNamespace(null)", "true"),
8423        ("var e=document.createElement('div'); e.isDefaultNamespace('http://www.w3.org/2000/svg')", "false"),
8424        // ノード読みプロパティ(nodeType / nodeName / hidden)
8425        ("document.getElementById('t').nodeType", "1"),
8426        ("document.getElementById('t').nodeName", "P"),
8427        ("document.getElementById('t').hidden", "false"),
8428        ("var e=document.getElementById('t'); e.toggleAttribute('hidden'); e.hidden", "true"),
8429        // dataset(data-* ↔ camelCase)  ※ getBoundingClientRect は別ブロックで rect を注入して検証
8430
8431        ("document.getElementById('t').dataset.k", "v"),
8432        ("var e=document.getElementById('t'); e.dataset.userId='42'; e.getAttribute('data-user-id')", "42"),
8433        ("var e=document.getElementById('t'); e.setAttribute('data-foo-bar','9'); e.dataset.fooBar", "9"),
8434        ("document.getElementById('t').dataset.missing === undefined", "true"),
8435        // `delete element.dataset.foo` は `dataset:` プロキシが `.props` を持たず実データが
8436        // DOM ノード属性側にあるため、何も起きない黙殺バグだった。
8437        (
8438            "var e=document.getElementById('t'); e.dataset.temp='x'; delete e.dataset.temp; e.dataset.temp === undefined",
8439            "true",
8440        ),
8441        (
8442            "var e=document.getElementById('t'); e.dataset.userId='42'; delete e.dataset.userId; e.hasAttribute('data-user-id')",
8443            "false",
8444        ),
8445        // `delete element.style.color` も同じ種類の黙殺バグだった。
8446        (
8447            "var e=document.getElementById('t'); e.style.color='red'; delete e.style.color; e.style.color",
8448            "",
8449        ),
8450        // insertBefore: 新ノードを s の前へ → wrap の子順が t, new, s
8451        ("var w=document.getElementById('wrap'); var n=document.createElement('b'); n.id='n'; w.insertBefore(n, document.getElementById('s')); w.children[1].id", "n"),
8452        ("var w=document.getElementById('wrap'); var n=document.createElement('b'); w.insertBefore(n, null); w.lastElementChild.tagName", "B"),
8453        // replaceChild: s を新ノードに置換
8454        ("var w=document.getElementById('wrap'); var n=document.createElement('i'); n.id='ni'; w.replaceChild(n, document.getElementById('s')); w.lastElementChild.id", "ni"),
8455        ("var w=document.getElementById('wrap'); var n=document.createElement('i'); var old=w.replaceChild(n, document.getElementById('s')); old.id", "s"),
8456        // `insertBefore`/`replaceChild`/`removeChild`は仕様上、参照ノードが
8457        // `this`の子でない場合`NotFoundError`を投げるべきだが、以前は検証が
8458        // 無く黙って末尾へ追加/何もせず成功したかのように振る舞っていた
8459        // (Table DOM/CharacterData/TypedArrayと同じ「不正な参照・範囲の
8460        // 黙殺」バグ族)。
8461        (
8462            "var w=document.getElementById('wrap'); var outsider=document.createElement('div'); \
8463             var n=document.createElement('b'); \
8464             try { w.insertBefore(n, outsider); 'no-throw' } catch(e) { e.name }",
8465            "NotFoundError",
8466        ),
8467        (
8468            "var w=document.getElementById('wrap'); var outsider=document.createElement('div'); \
8469             var n=document.createElement('b'); \
8470             try { w.replaceChild(n, outsider); 'no-throw' } catch(e) { e.name }",
8471            "NotFoundError",
8472        ),
8473        (
8474            "var w=document.getElementById('wrap'); var outsider=document.createElement('div'); \
8475             try { w.removeChild(outsider); 'no-throw' } catch(e) { e.name }",
8476            "NotFoundError",
8477        ),
8478        // cloneNode(deep): テキストごと複製
8479        ("document.getElementById('t').cloneNode(true).textContent", "x"),
8480        ("var c=document.getElementById('t').cloneNode(false); c.tagName + ':' + c.children.length", "P:0"),
8481        ("document.getElementById('t').cloneNode(true).parentElement === null", "true"),
8482        // classList.replace
8483        ("var e=document.getElementById('t'); e.classList.replace('a','z'); e.classList.contains('z') + ',' + e.classList.contains('a')", "true,false"),
8484        ("document.getElementById('t').classList.replace('nope','z')", "false"),
8485        // classList.toggle(name, force) — 第2引数 force が以前は完全に無視されていた。
8486        (
8487            "var e=document.getElementById('t'); e.classList.toggle('force-on', true); e.classList.contains('force-on')",
8488            "true",
8489        ),
8490        (
8491            "var e=document.getElementById('t'); e.classList.add('force-off'); e.classList.toggle('force-off', false); e.classList.contains('force-off')",
8492            "false",
8493        ),
8494        // classList.add/remove の複数引数(可変長引数)対応、classList.supports、webkitMatchesSelector、execCommand
8495        (
8496            "var e=document.createElement('div'); e.classList.add('c1', 'c2', 'c3'); e.className",
8497            "c1 c2 c3",
8498        ),
8499        (
8500            "var e=document.createElement('div'); e.classList.add('c1', 'c2', 'c3'); e.classList.remove('c1', 'c3'); e.className",
8501            "c2",
8502        ),
8503        (
8504            "var e=document.createElement('div'); e.classList.supports('foo')",
8505            "false",
8506        ),
8507        // 元は`document.body.appendChild`/`.remove()`を経由していたが、この
8508        // 自己テスト実行環境(`JsRuntime::new()`直後)には既定でパース済みの
8509        // `<html>`/`<body>`が一切存在しない(`document.body`は常に`null`)
8510        // ため、その`null`への`.appendChild`呼び出しが例外を投げていた
8511        // (2026-07-18 発見・修正)。`#id`セレクタの`matches()`判定は
8512        // ツリーへの接続を要さない(自身の`id`属性のみ見る)ため、
8513        // 接続操作自体が不要と判明し削除。
8514        (
8515            "var e=document.createElement('div'); e.id='wm'; e.webkitMatchesSelector('#wm')",
8516            "true",
8517        ),
8518        (
8519            "document.execCommand('copy')",
8520            "true",
8521        ),
8522        // `classList` の `Symbol.iterator`/`forEach` が丸ごと未対応で、`for...of`/
8523        // スプレッド/`forEach` のいずれも常に何も反復しない黙殺バグだった。
8524        (
8525            "var e=document.createElement('div'); e.className='a b c'; var out=[]; for (const c of e.classList) out.push(c); out.join(',')",
8526            "a,b,c",
8527        ),
8528        (
8529            "var e=document.createElement('div'); e.className='x y'; [...e.classList].join(',')",
8530            "x,y",
8531        ),
8532        (
8533            "var e=document.createElement('div'); e.className='p q'; var out=''; e.classList.forEach(function(c,i){out+=i+':'+c+' '}); out.trim()",
8534            "0:p 1:q",
8535        ),
8536        // `classList.entries()`/`.keys()`/`.values()`(丸ごと未対応だった。`forEach`
8537        // と for-of だけが配線され、明示イテレータ取得メソッド3種は未配線だった。
8538        // 2026-07-15 発見・実装)。
8539        (
8540            "var e=document.createElement('div'); e.className='r s'; var out=[]; for (const [i,c] of e.classList.entries()) out.push(i+':'+c); out.join(',')",
8541            "0:r,1:s",
8542        ),
8543        (
8544            "var e=document.createElement('div'); e.className='r s'; [...e.classList.keys()].join(',')",
8545            "0,1",
8546        ),
8547        (
8548            "var e=document.createElement('div'); e.className='r s'; [...e.classList.values()].join(',')",
8549            "r,s",
8550        ),
8551        // `Array.prototype.with(index, value)`(ES2023)— 範囲外 index は仕様上 RangeError
8552        // だが、以前は黙って無視して元と同じ配列を返していた。
8553        ("[1,2,3].with(1, 'x').join(',')", "1,x,3"),
8554        ("[1,2,3].with(-1, 'x').join(',')", "1,2,x"),
8555        (
8556            "try { [1,2,3].with(5, 'x'); 'no-throw' } catch(e) { 'threw' }",
8557            "threw",
8558        ),
8559        // `element.style.setProperty`/`getPropertyValue`/`removeProperty` が丸ごと
8560        // 欠落していた(直接のプロパティ代入 `el.style.color='red'` はあったが、
8561        // メソッド経由のインラインスタイル読み書きが無く、特に CSS カスタムプロパティ
8562        // `--foo` はこの経路でしか設定できないため影響が大きかった)。
8563        (
8564            "var e=document.getElementById('t'); e.style.setProperty('color','red'); e.style.color",
8565            "red",
8566        ),
8567        (
8568            "var e=document.getElementById('t'); e.style.setProperty('--my-var','10px'); e.style.getPropertyValue('--my-var')",
8569            "10px",
8570        ),
8571        (
8572            "var e=document.getElementById('t'); e.style.setProperty('color','blue'); e.style.removeProperty('color'); e.style.color",
8573            "",
8574        ),
8575        // `style.getPropertyPriority(name)`/`setProperty(name, value, priority)` の
8576        // 第3引数(丸ごと未対応・無視されていた。2026-07-16 発見・実装)。
8577        (
8578            "var e=document.getElementById('t'); e.style.setProperty('color','red','important'); e.style.getPropertyPriority('color')",
8579            "important",
8580        ),
8581        // `!important` 付きで設定した値を `getPropertyValue`/直接プロパティ双方から
8582        // 読んでも、優先度マーカーを含まない値本体のみが返るべき(仕様どおり)。
8583        (
8584            "var e=document.getElementById('t'); e.style.setProperty('color','red','important'); e.style.color",
8585            "red",
8586        ),
8587        (
8588            "var e=document.getElementById('t'); e.style.setProperty('color','red'); e.style.getPropertyPriority('color')",
8589            "",
8590        ),
8591        // `style.length`/`.item(index)`(丸ごと未対応だった。反復イディオム
8592        // `for (let i=0;i<style.length;i++) style.item(i)`。2026-07-17
8593        // 発見・実装)。
8594        (
8595            "var e=document.getElementById('t'); e.style.cssText='color: red; font-size: 12px'; e.style.length",
8596            "2",
8597        ),
8598        (
8599            "var e=document.getElementById('t'); e.style.cssText='color: red; font-size: 12px'; \
8600             e.style.item(0)+','+e.style.item(1)",
8601            "color,font-size",
8602        ),
8603        ("document.getElementById('t').style.length", "0"),
8604        // `style.cssFloat`(`float`プロパティのレガシー別名。丸ごと未対応
8605        // だった。2026-07-17 発見・実装)。
8606        (
8607            "var e=document.getElementById('t'); e.style.cssFloat='left'; e.style.float",
8608            "left",
8609        ),
8610        (
8611            "var e=document.getElementById('t'); e.style.float='right'; e.style.cssFloat",
8612            "right",
8613        ),
8614        // `element.style.cssText`(全プロパティを1つの文字列で読み書きする定番パターン)が
8615        // 丸ごと未対応で、通常のプロパティ名として扱われ常に空文字列になっていた。
8616        (
8617            "var e=document.getElementById('t'); e.style.cssText='color: red; font-size: 12px'; e.style.color+','+e.style.fontSize",
8618            "red,12px",
8619        ),
8620        (
8621            "var e=document.getElementById('t'); e.style.color='blue'; e.style.fontSize='10px'; e.style.cssText",
8622            "color: blue; font-size: 10px",
8623        ),
8624        // innerHTML/outerHTML が丸ごと未対応(innerHTML は textContent と同じ実装を
8625        // 共有しておりマークアップ自体が失われていた)だったバグ。
8626        (
8627            "var e=document.getElementById('t'); e.innerHTML='<b>bold</b>text'; e.innerHTML",
8628            "<b>bold</b>text",
8629        ),
8630        (
8631            "var e=document.getElementById('t'); e.innerHTML='hi'; e.outerHTML",
8632            "<p id=\"t\" class=\"a b\" data-k=\"v\">hi</p>",
8633        ),
8634        // nodeType/nodeName がテキストノードでも常に ELEMENT_NODE(1)/空文字列固定
8635        // だったバグ。childNodes/firstChild/lastChild/nextSibling/previousSibling
8636        // (テキストノードも含む Node レベル走査 API)も丸ごと未対応だった。
8637        (
8638            "var e=document.getElementById('t'); e.innerHTML='a<b>x</b>'; e.childNodes.length+','+e.firstChild.nodeType+','+e.lastChild.nodeType",
8639            "2,3,1",
8640        ),
8641        (
8642            "var e=document.getElementById('t'); e.innerHTML='a<b>x</b>'; e.firstChild.nextSibling.nodeName",
8643            "B",
8644        ),
8645        // `node.nodeValue`(テキストノードは自身のテキスト、要素ノードは仕様どおり
8646        // `null`)が丸ごと未対応だった。代入は要素ノードでは no-op。
8647        (
8648            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.nodeValue",
8649            "hello",
8650        ),
8651        (
8652            "var e=document.getElementById('t'); e.innerHTML='hello'; e.nodeValue === null",
8653            "true",
8654        ),
8655        (
8656            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.nodeValue='bye'; e.textContent",
8657            "bye",
8658        ),
8659        // `CharacterData.data`/`.length`/`.appendData`/`.deleteData`/
8660        // `.insertData`/`.replaceData`/`.substringData`(丸ごと未対応
8661        // だった。`nodeValue`は対応済みだったが単体アクセサ`data`と
8662        // 編集用メソッド群は漏れていた。2026-07-16 発見・実装)。
8663        (
8664            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.data",
8665            "hello",
8666        ),
8667        (
8668            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.length",
8669            "5",
8670        ),
8671        (
8672            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.data='world'; e.textContent",
8673            "world",
8674        ),
8675        (
8676            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.appendData(' world'); e.textContent",
8677            "hello world",
8678        ),
8679        (
8680            "var e=document.getElementById('t'); e.innerHTML='hello world'; e.firstChild.deleteData(5,6); e.textContent",
8681            "hello",
8682        ),
8683        (
8684            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.insertData(5,' world'); e.textContent",
8685            "hello world",
8686        ),
8687        (
8688            "var e=document.getElementById('t'); e.innerHTML='hello world'; e.firstChild.replaceData(6,5,'there'); e.textContent",
8689            "hello there",
8690        ),
8691        (
8692            "var e=document.getElementById('t'); e.innerHTML='hello world'; e.firstChild.substringData(6,5)",
8693            "world",
8694        ),
8695        // `deleteData`/`insertData`/`replaceData`/`substringData`/`splitText`は
8696        // 仕様上`offset`が`length`を超えると`IndexSizeError`を投げるべきだが、
8697        // 以前は`offset`を黙って`length`へクランプしてしまい、範囲外呼び出しが
8698        // 常に無害なno-op/末尾追記として成立していた(Table DOMの
8699        // `insertRow`等と同じ「範囲外インデックスの黙殺」バグ族)。
8700        (
8701            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8702             try { e.firstChild.deleteData(99,1); 'no-throw' } catch(e) { e.name }",
8703            "IndexSizeError",
8704        ),
8705        (
8706            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8707             try { e.firstChild.insertData(99,'x'); 'no-throw' } catch(e) { e.name }",
8708            "IndexSizeError",
8709        ),
8710        (
8711            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8712             try { e.firstChild.replaceData(99,1,'x'); 'no-throw' } catch(e) { e.name }",
8713            "IndexSizeError",
8714        ),
8715        (
8716            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8717             try { e.firstChild.substringData(99,1); 'no-throw' } catch(e) { e.name }",
8718            "IndexSizeError",
8719        ),
8720        (
8721            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8722             try { e.firstChild.splitText(99); 'no-throw' } catch(e) { e.name }",
8723            "IndexSizeError",
8724        ),
8725        (
8726            // offset===lengthはちょうど境界で範囲内(末尾扱い)なので例外にならない。
8727            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8728             e.firstChild.insertData(2,'!'); e.textContent",
8729            "hi!",
8730        ),
8731        // `Text.splitText(offset)`(丸ごと未対応だった。2026-07-16 発見・
8732        // 実装。テキストノードを分割し、後半を新しい兄弟ノードとして
8733        // 挿入する)。
8734        (
8735            "var e=document.getElementById('t'); e.innerHTML='hello world'; \
8736             var t1=e.firstChild; var t2=t1.splitText(5); t1.data+'|'+t2.data",
8737            "hello| world",
8738        ),
8739        (
8740            "var e=document.getElementById('t'); e.innerHTML='hello world'; \
8741             var t1=e.firstChild; t1.splitText(5); e.childNodes.length",
8742            "2",
8743        ),
8744        (
8745            "var e=document.getElementById('t'); e.innerHTML='hello world'; \
8746             var t1=e.firstChild; var t2=t1.splitText(5); t2.nextSibling===null && t2.previousSibling===t1",
8747            "true",
8748        ),
8749        // `Text.wholeText`(丸ごと未対応だった。`splitText`と対をなす読み
8750        // 取りプロパティ。2026-07-16 発見・実装。連続するテキストノード
8751        // 兄弟の`data`を全て連結して返す)。
8752        (
8753            "var e=document.getElementById('t'); e.innerHTML='hello world'; \
8754             var t1=e.firstChild; var t2=t1.splitText(5); t1.wholeText+'|'+t2.wholeText",
8755            "hello world|hello world",
8756        ),
8757        (
8758            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.wholeText",
8759            "hello",
8760        ),
8761        // HTML5 Table DOM(`table.insertRow`/`.deleteRow`/`.rows`、
8762        // `tr.insertCell`/`.deleteCell`/`.cells`、`.rowIndex`/`.cellIndex`)が
8763        // 丸ごと未対応だった(2026-07-17 発見・実装)。
8764        (
8765            "var t=document.createElement('table'); \
8766             t.insertRow(); t.insertRow(); t.rows.length",
8767            "2",
8768        ),
8769        (
8770            "var t=document.createElement('table'); var r=t.insertRow(); r.insertCell(); r.insertCell(); \
8771             r.cells.length",
8772            "2",
8773        ),
8774        (
8775            "var t=document.createElement('table'); \
8776             var r0=t.insertRow(); var r1=t.insertRow(); var rmid=t.insertRow(1); \
8777             rmid===t.rows[1]",
8778            "true",
8779        ),
8780        (
8781            "var t=document.createElement('table'); t.insertRow(); t.insertRow(); t.deleteRow(0); \
8782             t.rows.length",
8783            "1",
8784        ),
8785        (
8786            "var t=document.createElement('table'); var r=t.insertRow(); \
8787             r.insertCell(); var mid=r.insertCell(); r.insertCell(); \
8788             mid===r.cells[1]",
8789            "true",
8790        ),
8791        (
8792            "var t=document.createElement('table'); var r=t.insertRow(); r.insertCell(); r.insertCell(); \
8793             r.deleteCell(0); r.cells.length",
8794            "1",
8795        ),
8796        (
8797            "var t=document.createElement('table'); t.insertRow(); var r=t.insertRow(); r.rowIndex",
8798            "1",
8799        ),
8800        (
8801            "var t=document.createElement('table'); var r=t.insertRow(); r.insertCell(); var c=r.insertCell(); c.cellIndex",
8802            "1",
8803        ),
8804        // `insertRow`/`deleteRow`/`insertCell`/`deleteCell`は仕様上、範囲外の
8805        // インデックス(`-1`未満、または`insertRow`/`insertCell`なら
8806        // `length`超過、`deleteRow`/`deleteCell`なら`length`以上)で
8807        // `IndexSizeError`を投げるべきだが、以前は範囲外の値をすべて
8808        // 「末尾へ追加」/「末尾を削除」として黙って受け入れてしまっていた。
8809        (
8810            "var t=document.createElement('table'); t.insertRow(); \
8811             try { t.insertRow(5); 'no-throw' } catch(e) { e.name }",
8812            "IndexSizeError",
8813        ),
8814        (
8815            "var t=document.createElement('table'); t.insertRow(); \
8816             try { t.deleteRow(5); 'no-throw' } catch(e) { e.name }",
8817            "IndexSizeError",
8818        ),
8819        (
8820            "var t=document.createElement('table'); var r=t.insertRow(); r.insertCell(); \
8821             try { r.insertCell(5); 'no-throw' } catch(e) { e.name }",
8822            "IndexSizeError",
8823        ),
8824        (
8825            "var t=document.createElement('table'); var r=t.insertRow(); r.insertCell(); \
8826             try { r.deleteCell(5); 'no-throw' } catch(e) { e.name }",
8827            "IndexSizeError",
8828        ),
8829        (
8830            // `insertRow(length)`(末尾追加)と`insertRow(-1)`(同じく末尾追加)は
8831            // 範囲内なので従来どおり例外にならない。
8832            "var t=document.createElement('table'); t.insertRow(); \
8833             t.insertRow(1); t.insertRow(-1); t.rows.length",
8834            "3",
8835        ),
8836        // `tr.sectionRowIndex`(丸ごと未対応だった。`rowIndex`は実装済み
8837        // なのに対になるこちらだけ抜けていた兄弟ギャップ。2026-07-17
8838        // 発見・実装)。`thead`2行+`tbody`3行の表で、`tbody`側2行目
8839        // (テーブル全体では4番目=`rowIndex:3`)の`sectionRowIndex`は
8840        // `tbody`内での位置`1`(0始まり)になる。
8841        (
8842            "var t=document.createElement('table'); \
8843             var h=t.createTHead(); h.insertRow(); h.insertRow(); \
8844             var b=document.createElement('tbody'); t.appendChild(b); \
8845             b.insertRow(); var r=b.insertRow(); b.insertRow(); \
8846             r.rowIndex + ',' + r.sectionRowIndex",
8847            "3,1",
8848        ),
8849        // `HTMLTableSectionElement.insertRow`/`.rows`(丸ごと未対応だった。
8850        // `table.insertRow`は実装済みなのに対になるこちらだけ抜けていた
8851        // 兄弟ギャップ。`tbody`自身が持つ`insertRow`/`.rows`はそのセクション
8852        // 内に限定される。2026-07-17 発見・実装)。
8853        (
8854            "var t=document.createElement('table'); var b=document.createElement('tbody'); \
8855             t.appendChild(b); b.insertRow(); b.insertRow(); b.rows.length",
8856            "2",
8857        ),
8858        // `thead`/`tbody`ラッパー無しで直接`table`に`insertRow`した場合は
8859        // `rowIndex`と`sectionRowIndex`が一致する。
8860        (
8861            "var t=document.createElement('table'); t.insertRow(); var r=t.insertRow(); \
8862             r.rowIndex + ',' + r.sectionRowIndex",
8863            "1,1",
8864        ),
8865        // `table.tHead`/`.tFoot`/`.caption`/`.tBodies`/`.createTHead()`/
8866        // `.createTFoot()`/`.createCaption()`/`.delete*()`(丸ごと未対応
8867        // だった。2026-07-17 発見・実装。「既存要素があれば返す、無ければ
8868        // 新規作成」の冪等な動作)。
8869        ("document.createElement('table').tHead", "null"),
8870        (
8871            "var t=document.createElement('table'); var h=t.createTHead(); h===t.tHead && h.tagName",
8872            "THEAD",
8873        ),
8874        (
8875            "var t=document.createElement('table'); var h1=t.createTHead(); var h2=t.createTHead(); h1===h2",
8876            "true",
8877        ),
8878        (
8879            "var t=document.createElement('table'); t.createTHead(); t.deleteTHead(); t.tHead",
8880            "null",
8881        ),
8882        (
8883            "var t=document.createElement('table'); var f=t.createTFoot(); f===t.tFoot && f.tagName",
8884            "TFOOT",
8885        ),
8886        (
8887            "var t=document.createElement('table'); var c=t.createCaption(); c===t.caption && c.tagName",
8888            "CAPTION",
8889        ),
8890        (
8891            "var t=document.createElement('table'); t.appendChild(document.createElement('tbody')); \
8892             t.appendChild(document.createElement('tbody')); t.tBodies.length",
8893            "2",
8894        ),
8895        // `node.isConnected`(click-outside 判定等で使われる)が丸ごと未対応だった。
8896        (
8897            "document.createElement('div').isConnected",
8898            "false",
8899        ),
8900        (
8901            "document.getElementById('t').isConnected",
8902            "true",
8903        ),
8904        (
8905            "var e=document.getElementById('t'); e.remove(); e.isConnected",
8906            "false",
8907        ),
8908        // `Node.ELEMENT_NODE`/`Node.TEXT_NODE`(`if (node.nodeType === Node.
8909        // ELEMENT_NODE)` という定番パターンで使われる標準定数)が丸ごと未対応
8910        // だった(グローバル `Node` 自体が存在しなかった)。
8911        (
8912            "document.createElement('div').nodeType === Node.ELEMENT_NODE",
8913            "true",
8914        ),
8915        ("Node.TEXT_NODE", "3"),
8916        ("Node.COMMENT_NODE", "8"),
8917        ("var c=document.createComment('test'); c.nodeType", "8"),
8918        ("var c=document.createComment('test'); c.nodeName", "#comment"),
8919        ("var c=document.createComment('test'); c.nodeValue", "test"),
8920        ("var c=document.createComment('test'); c.data", "test"),
8921        ("var c=document.createComment('test'); c.length", "4"),
8922        ("var c=document.createComment('test'); c.nodeValue='new'; c.nodeValue", "new"),
8923        ("var c=document.createComment('test'); c.data='new'; c.data", "new"),
8924        ("var c=document.createComment('test'); c.textContent", "test"),
8925        ("var c=document.createComment('test'); c.textContent='new'; c.textContent", "new"),
8926        (
8927            "var d=document.createElement('div'); var c=document.createComment('foo'); d.appendChild(c); d.innerHTML",
8928            "<!--foo-->",
8929        ),
8930        (
8931            "var t=document.getElementById('t'); var s=document.createElement('style'); s.textContent='div { color: red; }'; t.appendChild(s); var sheet=document.styleSheets[0]; sheet.cssRules.length",
8932            "1",
8933        ),
8934        (
8935            "var t=document.getElementById('t'); var s=document.createElement('style'); s.textContent='div { color: red; }'; t.appendChild(s); var sheet=document.styleSheets[0]; sheet.cssRules[0].cssText",
8936            "div { color: red; }",
8937        ),
8938        (
8939            "var t=document.getElementById('t'); var s=document.createElement('style'); s.textContent='div { color: red; }'; t.appendChild(s); var sheet=document.styleSheets[0]; sheet.cssRules[0].selectorText",
8940            "div",
8941        ),
8942        (
8943            "var t=document.getElementById('t'); var s=document.createElement('style'); s.textContent='div { color: red; }'; t.appendChild(s); var sheet=document.styleSheets[0]; sheet.insertRule('span { color: blue; }', 1); sheet.cssRules.length",
8944            "2",
8945        ),
8946        (
8947            "var t=document.getElementById('t'); var s=document.createElement('style'); s.textContent='div { color: red; }'; t.appendChild(s); var sheet=document.styleSheets[0]; sheet.insertRule('span { color: blue; }', 1); sheet.cssRules[1].cssText",
8948            "span { color: blue; }",
8949        ),
8950        (
8951            "var t=document.getElementById('t'); var s=document.createElement('style'); s.textContent='div { color: red; }'; t.appendChild(s); var sheet=document.styleSheets[0]; sheet.insertRule('span { color: blue; }', 1); sheet.deleteRule(0); sheet.cssRules.length",
8952            "1",
8953        ),
8954        (
8955            "var t=document.getElementById('t'); var s=document.createElement('style'); s.textContent='div { color: red; }'; t.appendChild(s); var sheet=document.styleSheets[0]; sheet.insertRule('span { color: blue; }', 1); sheet.deleteRule(0); sheet.cssRules[0].cssText",
8956            "span { color: blue; }",
8957        ),
8958        // `document.title` が常に install 時の空文字列固定で `<title>` 要素の中身/
8959        // 代入結果と一切連動していなかったバグ。ここでは `<head>` 自体が無い状態
8960        // (フレッシュな JsRuntime)での内部プロパティへのフォールバック保存/読み出し
8961        // を確認する(`<head>`/`<title>` 実要素との連動は別ブロックで確認)。
8962        ("document.title = 'Hello'; document.title", "Hello"),
8963        // `element.lang`/`.dir`(グローバル属性の JS プロパティ版)が他の属性直結
8964        // プロパティ群の点検から漏れて丸ごと未対応だった。
8965        (
8966            "var e=document.createElement('p'); e.lang='ja'; e.lang",
8967            "ja",
8968        ),
8969        (
8970            "var e=document.createElement('p'); e.dir='rtl'; e.dir",
8971            "rtl",
8972        ),
8973        (
8974            "var e=document.createElement('p'); e.setAttribute('lang','fr'); e.lang",
8975            "fr",
8976        ),
8977        // `element.title`/`.accessKey`/`.translate`(同じ理由で丸ごと未対応
8978        // だった。未マッチキーは巨大 match の末尾 `_ => Value::Undefined`
8979        // に落ちるだけで汎用フォールバックが無いため常に `undefined` に
8980        // なっていた。2026-07-16 発見・実装)。
8981        (
8982            "var e=document.createElement('p'); e.title='tip'; e.title",
8983            "tip",
8984        ),
8985        (
8986            "var e=document.createElement('p'); e.setAttribute('title','x'); e.title",
8987            "x",
8988        ),
8989        (
8990            "var e=document.createElement('p'); e.accessKey='k'; e.getAttribute('accesskey')",
8991            "k",
8992        ),
8993        // `translate` は既定 `true`、`translate=\"no\"` の時のみ `false`。
8994        ("document.createElement('p').translate", "true"),
8995        (
8996            "var e=document.createElement('p'); e.setAttribute('translate','no'); e.translate",
8997            "false",
8998        ),
8999        (
9000            "var e=document.createElement('p'); e.translate=false; e.getAttribute('translate')",
9001            "no",
9002        ),
9003        // `element.draggable`(丸ごと未対応だった。2026-07-16 発見・実装。
9004        // 明示指定が無ければ既定 `false` の簡略実装)。
9005        ("document.createElement('div').draggable", "false"),
9006        (
9007            "var e=document.createElement('div'); e.draggable=true; e.getAttribute('draggable')",
9008            "true",
9009        ),
9010        (
9011            "var e=document.createElement('div'); e.setAttribute('draggable','true'); e.draggable",
9012            "true",
9013        ),
9014        // `element.slot`(named slot 割り当て名。丸ごと未対応だった。
9015        // 2026-07-16 発見・実装)。
9016        (
9017            "var e=document.createElement('div'); e.slot='header'; e.getAttribute('slot')",
9018            "header",
9019        ),
9020        // DOM コンストラクタ階層 (Node -> Element -> HTMLElement -> HTMLXxxElement)
9021        // および `instanceof` / `.constructor` 連携の自己テスト
9022        (
9023            "document.createElement('div') instanceof HTMLDivElement",
9024            "true",
9025        ),
9026        (
9027            "document.createElement('div') instanceof HTMLElement",
9028            "true",
9029        ),
9030        (
9031            "document.createElement('div') instanceof Element",
9032            "true",
9033        ),
9034        (
9035            "document.createElement('div') instanceof Node",
9036            "true",
9037        ),
9038        (
9039            "document.createElement('div') instanceof HTMLInputElement",
9040            "false",
9041        ),
9042        (
9043            "document.createElement('input') instanceof HTMLInputElement",
9044            "true",
9045        ),
9046        (
9047            "document.createElement('input') instanceof HTMLElement",
9048            "true",
9049        ),
9050        (
9051            "document.createElement('form') instanceof HTMLFormElement",
9052            "true",
9053        ),
9054        (
9055            "document.createElement('div').constructor === HTMLDivElement",
9056            "true",
9057        ),
9058        (
9059            "try { new Node(); false; } catch(e) { e instanceof TypeError }",
9060            "true",
9061        ),
9062        (
9063            "try { new HTMLElement(); false; } catch(e) { e instanceof TypeError }",
9064            "true",
9065        ),
9066        (
9067            "var e=document.createElement('div'); e.setAttribute('slot','footer'); e.slot",
9068            "footer",
9069        ),
9070        // `element.tabIndex`(`tabindex` 属性の数値プロパティ版)も同じ理由で
9071        // 丸ごと未対応だった。未指定時の既定値は `-1`。
9072        (
9073            "document.createElement('div').tabIndex",
9074            "-1",
9075        ),
9076        (
9077            "var e=document.createElement('div'); e.tabIndex=3; e.tabIndex",
9078            "3",
9079        ),
9080        (
9081            "var e=document.createElement('div'); e.tabIndex=2; e.getAttribute('tabindex')",
9082            "2",
9083        ),
9084        // `element.hidden = true/false`(要素の表示/非表示切替の定番イディオム)の
9085        // setter が丸ごと未対応だった(getter は既に対応済み)。
9086        (
9087            "var e=document.createElement('div'); e.hidden=true; e.hidden",
9088            "true",
9089        ),
9090        (
9091            "var e=document.createElement('div'); e.hidden=true; e.hasAttribute('hidden')",
9092            "true",
9093        ),
9094        (
9095            "var e=document.createElement('div'); e.hidden=true; e.hidden=false; e.hidden",
9096            "false",
9097        ),
9098        // `form.noValidate`(`novalidate` 属性の camelCase プロパティ版)が丸ごと
9099        // 未対応だった。
9100        (
9101            "var f=document.createElement('form'); f.noValidate=true; f.noValidate",
9102            "true",
9103        ),
9104        (
9105            "var f=document.createElement('form'); f.noValidate=true; f.hasAttribute('novalidate')",
9106            "true",
9107        ),
9108        (
9109            "document.createElement('form').noValidate",
9110            "false",
9111        ),
9112        // `form.elements`(全フォームコントロールを走査する定番イディオム)が丸ごと
9113        // 未対応だった。
9114        (
9115            "var f=document.createElement('form'); var i=document.createElement('input'); f.appendChild(i); f.elements.length",
9116            "1",
9117        ),
9118        (
9119            "var f=document.createElement('form'); var i=document.createElement('input'); i.name='x'; f.appendChild(i); f.elements[0].name",
9120            "x",
9121        ),
9122        // `form.length`(`form.elements.length`と同じ値を返すIDL属性の数値
9123        // ショートハンド。`select.length`の対になる`<form>`側だが丸ごと
9124        // 未対応だった)。
9125        (
9126            "var f=document.createElement('form'); var i=document.createElement('input'); \
9127             var s=document.createElement('select'); f.appendChild(i); f.appendChild(s); \
9128             f.length + ',' + f.elements.length",
9129            "2,2",
9130        ),
9131        (
9132            "document.createElement('form').length",
9133            "0",
9134        ),
9135        // `fieldset.elements`(`HTMLFieldSetElement`のIDLプロパティ。丸ごと
9136        // 未対応だった。`form.elements`と異なり純粋なDOM子孫のみが対象。
9137        // 2026-07-17 発見・実装)。
9138        (
9139            "var fs=document.createElement('fieldset'); var i=document.createElement('input'); \
9140             fs.appendChild(i); fs.elements.length",
9141            "1",
9142        ),
9143        (
9144            "document.createElement('fieldset').elements.length",
9145            "0",
9146        ),
9147        // `fieldset.type`(仕様上、属性値に関わらず常に固定文字列
9148        // `"fieldset"`を返す読み取り専用IDLプロパティ。丸ごと未対応
9149        // だった。2026-07-17 発見・実装)。
9150        ("document.createElement('fieldset').type", "fieldset"),
9151        (
9152            "var fs=document.createElement('fieldset'); fs.setAttribute('type','x'); fs.type",
9153            "fieldset",
9154        ),
9155        // `select.type`(仕様上、`multiple`属性の有無で`"select-multiple"`/
9156        // `"select-one"`のいずれか固定文字列を返す読み取り専用IDL
9157        // プロパティ。`fieldset.type`と同じ理由で丸ごと未対応だった。
9158        // 2026-07-17 発見・実装)。
9159        ("document.createElement('select').type", "select-one"),
9160        (
9161            "var s=document.createElement('select'); s.multiple=true; s.type",
9162            "select-multiple",
9163        ),
9164        // `button.type`(`type`属性が無いか不正な値なら既定`"submit"`に
9165        // フォールバックする。`fieldset.type`/`select.type`と同じ理由で
9166        // 丸ごと未対応だった。`if (btn.type === 'submit')`という定番
9167        // チェックが常に偽になる実害があった。2026-07-17 発見・実装)。
9168        ("document.createElement('button').type", "submit"),
9169        // `textarea.type`(仕様上、属性値に関わらず常に固定文字列
9170        // `"textarea"`を返す読み取り専用IDLプロパティ。`fieldset.type`と
9171        // 同じ理由で丸ごと未対応だった。2026-07-17 発見・実装)。
9172        ("document.createElement('textarea').type", "textarea"),
9173        (
9174            "var b=document.createElement('button'); b.setAttribute('type','reset'); b.type",
9175            "reset",
9176        ),
9177        (
9178            "var b=document.createElement('button'); b.setAttribute('type','button'); b.type",
9179            "button",
9180        ),
9181        (
9182            "var b=document.createElement('button'); b.setAttribute('type','bogus'); b.type",
9183            "submit",
9184        ),
9185        // `input.type`(`type`属性が無いか既知の列挙値以外なら既定
9186        // `"text"`にフォールバックする。`fieldset.type`/`select.type`/
9187        // `button.type`と同じ理由で丸ごと未対応だった。全入力系IDL
9188        // プロパティの中で最も広く参照される`.type`が常に空文字列に
9189        // なっていた実害の大きいバグ。2026-07-17 発見・実装)。
9190        ("document.createElement('input').type", "text"),
9191        (
9192            "var i=document.createElement('input'); i.setAttribute('type','checkbox'); i.type",
9193            "checkbox",
9194        ),
9195        (
9196            "var i=document.createElement('input'); i.setAttribute('type','DATE'); i.type",
9197            "date",
9198        ),
9199        (
9200            "var i=document.createElement('input'); i.setAttribute('type','bogus'); i.type",
9201            "text",
9202        ),
9203        // `form.elements`が`form="formId"`属性によるフォーム外関連付け
9204        // (HTML5仕様の「form owner」)を反映していなかった兄弟ギャップの
9205        // 自己テストは、この配列が`(&str, &str)`(HTML fixtureを持たない)
9206        // 型のため3要素タプルでは追加できない。別途、独自のHTML fixtureを
9207        // 持つ`fd2_cases`(本ファイル内。`FormData`のform owner対応テストと
9208        // 同じfixtureを再利用)に追加してある。
9209        // `input.form`(自身が属する `<form>` への逆参照)が丸ごと未対応だった。
9210        (
9211            "var f=document.createElement('form'); f.id='ff'; var i=document.createElement('input'); f.appendChild(i); i.form.id",
9212            "ff",
9213        ),
9214        (
9215            "document.createElement('input').form === null",
9216            "true",
9217        ),
9218        // `input.labels`(`for` 属性の明示的な関連付けと、`<label>` 子孫の暗黙の
9219        // 関連付けの両方)が丸ごと未対応だった。
9220        (
9221            "var i=document.createElement('input'); i.id='u'; var l=document.createElement('label'); l.setAttribute('for','u'); i.labels.length",
9222            "1",
9223        ),
9224        (
9225            "var l=document.createElement('label'); var i=document.createElement('input'); l.appendChild(i); i.labels.length",
9226            "1",
9227        ),
9228        (
9229            "document.createElement('input').labels.length",
9230            "0",
9231        ),
9232        // `label.control`(`labels` の逆方向)が丸ごと未対応だった。
9233        (
9234            "var i=document.createElement('input'); i.id='v'; var l=document.createElement('label'); l.setAttribute('for','v'); l.control.id",
9235            "v",
9236        ),
9237        (
9238            "var l=document.createElement('label'); var i=document.createElement('input'); i.name='y'; l.appendChild(i); l.control.name",
9239            "y",
9240        ),
9241        (
9242            "document.createElement('label').control === null",
9243            "true",
9244        ),
9245        // `node.contains(other)`(click-outside 判定等で広く使われる)が丸ごと
9246        // 未対応だった。
9247        (
9248            "var w=document.getElementById('wrap'); var t=document.getElementById('t'); w.contains(t)+','+t.contains(w)+','+t.contains(t)",
9249            "true,false,true",
9250        ),
9251        // `Node.compareDocumentPosition(other)` が丸ごと未対応だった(`contains()` はあったが
9252        // 兄弟同士の前後関係や祖先/子孫の判定を1回のビットマスクで得る標準APIが欠落)。
9253        (
9254            "var t=document.getElementById('t'); t.compareDocumentPosition(t)",
9255            "0",
9256        ),
9257        (
9258            "var w=document.getElementById('wrap'); var t=document.getElementById('t'); t.compareDocumentPosition(w)",
9259            "10",
9260        ),
9261        (
9262            "var w=document.getElementById('wrap'); var t=document.getElementById('t'); w.compareDocumentPosition(t)",
9263            "20",
9264        ),
9265        (
9266            "var t=document.getElementById('t'); var s=document.getElementById('s'); t.compareDocumentPosition(s)",
9267            "4",
9268        ),
9269        (
9270            "var t=document.getElementById('t'); var s=document.getElementById('s'); s.compareDocumentPosition(t)",
9271            "2",
9272        ),
9273        // `element.click()` が単なる no-op で、実際のクリック(登録済みリスナの発火)を
9274        // 一切引き起こさなかったバグ(プログラム的クリックはテストコードで広く使われる
9275        // 定番パターン)。
9276        (
9277            "var e=document.getElementById('t'); var clicked=false; e.addEventListener('click', function(){ clicked=true; }); e.click(); clicked",
9278            "true",
9279        ),
9280        // `document.scrollingElement`(HTML5。丸ごと未対応だった。標準準拠
9281        // モードでは `documentElement`(`<html>`)と同じ要素を返す仕様)。
9282        ("document.scrollingElement.tagName", "HTML"),
9283        ("document.scrollingElement === document.documentElement", "true"),
9284        // `document.activeElement`/`element.focus()`/`.blur()`(HTML5。丸ごと
9285        // 未対応だった。以前は `activeElement` が常に `null` 固定、
9286        // `.focus()`/`.blur()` 自体が丸ごと存在しなかった)。
9287        (
9288            "document.getElementById('t').focus(); document.activeElement.id",
9289            "t",
9290        ),
9291        (
9292            "document.getElementById('t').focus(); document.getElementById('t') === document.activeElement",
9293            "true",
9294        ),
9295        (
9296            "document.getElementById('t').focus(); document.getElementById('s').focus(); document.activeElement.id",
9297            "s",
9298        ),
9299        (
9300            "document.getElementById('t').focus(); document.getElementById('t').blur(); \
9301             document.getElementById('t') === document.activeElement",
9302            "false",
9303        ),
9304        (
9305            "var log=''; document.getElementById('t').addEventListener('focus', ()=>log+='f'); \
9306             document.getElementById('t').addEventListener('blur', ()=>log+='b'); \
9307             document.getElementById('t').focus(); document.getElementById('s').focus(); log",
9308            "fb",
9309        ),
9310        (
9311            "var n=0; document.getElementById('t').addEventListener('focus', ()=>n++); \
9312             document.getElementById('t').focus(); document.getElementById('t').focus(); n",
9313            "1",
9314        ),
9315    ];
9316    total += dom2_cases.len();
9317    for (src, expect) in dom2_cases {
9318        let mut rt = JsRuntime::new();
9319        rt.dom
9320            .borrow_mut()
9321            .build_from(&crate::os_lib::dom::parse_html(dom2_html));
9322        match rt.eval(src) {
9323            Ok(v) if &v.to_js_string() == expect => passed += 1,
9324            Ok(v) => crate::println!(
9325                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9326                src,
9327                v.to_js_string(),
9328                expect
9329            ),
9330            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9331        }
9332    }
9333    // ===== DOM イベントシステム(Phase 3+)=====
9334    // addEventListener / removeEventListener / dispatchEvent と Event オブジェクトの
9335    // 伝播(capture→target→bubble)、preventDefault / stopPropagation /
9336    // stopImmediatePropagation / once / target・currentTarget を JS 層から検証する。
9337    // dispatchEvent はホストの dispatch_key/dispatch_mouse と同じ dispatch_event_in_interp を
9338    // 通るため、ここで伝播ロジック全体を決定論的にカバーできる。
9339    let ev_html = "<div id='outer'><div id='mid'><button id='btn'>go</button></div></div>";
9340    let event_cases: &[(&str, &str)] = &[
9341        // 基本: addEventListener したリスナが dispatchEvent で発火する。
9342        ("var n=0; var b=document.getElementById('btn'); b.addEventListener('click', function(){ n++; }); b.dispatchEvent({type:'click'}); n", "1"),
9343        // 同一要素に複数リスナ → 登録順に全て発火。
9344        ("var s=''; var b=document.getElementById('btn'); b.addEventListener('click', ()=>s+='a'); b.addEventListener('click', ()=>s+='b'); b.dispatchEvent({type:'click'}); s", "ab"),
9345        // removeEventListener で解除したリスナは発火しない。
9346        ("var n=0; var f=()=>n++; var b=document.getElementById('btn'); b.addEventListener('click', f); b.removeEventListener('click', f); b.dispatchEvent({type:'click'}); n", "0"),
9347        // バブリング: button で発火 → 祖先 mid / outer の click リスナも発火。
9348        ("var s=''; document.getElementById('outer').addEventListener('click', ()=>s+='O'); document.getElementById('mid').addEventListener('click', ()=>s+='M'); document.getElementById('btn').addEventListener('click', ()=>s+='B'); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); s", "BMO"),
9349        // event.target は発火元、currentTarget は処理中の要素(バブリング中は祖先)。
9350        ("var t=''; document.getElementById('outer').addEventListener('click', function(e){ t = e.target.id + '/' + e.currentTarget.id; }); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); t", "btn/outer"),
9351        // stopPropagation: button のリスナで止めると祖先には伝わらない。
9352        ("var s=''; document.getElementById('outer').addEventListener('click', ()=>s+='O'); document.getElementById('btn').addEventListener('click', function(e){ s+='B'; e.stopPropagation(); }); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); s", "B"),
9353        // stopImmediatePropagation: 同一要素の後続リスナも止める。
9354        ("var s=''; var b=document.getElementById('btn'); b.addEventListener('click', function(e){ s+='1'; e.stopImmediatePropagation(); }); b.addEventListener('click', ()=>s+='2'); b.dispatchEvent({type:'click'}); s", "1"),
9355        // preventDefault → dispatchEvent は false を返す(defaultPrevented)。
9356        ("var b=document.getElementById('btn'); b.addEventListener('click', e=>e.preventDefault()); b.dispatchEvent({type:'click'})", "false"),
9357        // preventDefault しなければ dispatchEvent は true を返す。
9358        ("var b=document.getElementById('btn'); b.addEventListener('click', ()=>{}); b.dispatchEvent({type:'click'})", "true"),
9359        // e.defaultPrevented フラグが preventDefault 後に true。
9360        ("var d=false; var b=document.getElementById('btn'); b.addEventListener('click', function(e){ e.preventDefault(); d=e.defaultPrevented; }); b.dispatchEvent({type:'click'}); d", "true"),
9361        // once: true のリスナは 1 回だけ発火し、2 回目は無視される。
9362        ("var n=0; var b=document.getElementById('btn'); b.addEventListener('click', ()=>n++, {once:true}); b.dispatchEvent({type:'click'}); b.dispatchEvent({type:'click'}); n", "1"),
9363        // capture フェーズ: 祖先の capture リスナは target より先に発火。
9364        ("var s=''; document.getElementById('outer').addEventListener('click', ()=>s+='C', true); document.getElementById('btn').addEventListener('click', ()=>s+='T'); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); s", "CT"),
9365        // event.type が正しく渡る。
9366        ("var ty=''; var b=document.getElementById('btn'); b.addEventListener('custom', function(e){ ty=e.type; }); b.dispatchEvent({type:'custom'}); ty", "custom"),
9367        // カスタムプロパティ(detail 等)がリスナ側へ転送される。
9368        ("var d=0; var b=document.getElementById('btn'); b.addEventListener('ping', function(e){ d=e.detail; }); b.dispatchEvent({type:'ping', detail:7}); d", "7"),
9369        // 異なるイベント型のリスナは発火しない。
9370        ("var n=0; var b=document.getElementById('btn'); b.addEventListener('mouseover', ()=>n++); b.dispatchEvent({type:'click'}); n", "0"),
9371        // バブリングフラグ未指定でも、本エンジンは委譲(イベント委譲)を成立させるため
9372        // 祖先のリスナも発火する設計(blur/focus を親で受ける UI を許容)。
9373        ("var s=''; document.getElementById('outer').addEventListener('foo', ()=>s+='O'); document.getElementById('btn').addEventListener('foo', ()=>s+='B'); document.getElementById('btn').dispatchEvent({type:'foo'}); s", "BO"),
9374        // `document.addEventListener('click', fn)` によるイベント委譲パターンが
9375        // 丸ごと未対応だった(`document` は `self.dom.nodes` の一員ではないため
9376        // 祖先チェーンに一切含まれず、`click`等の汎用種別は登録すら黙って
9377        // 捨てられていた。2026-07-16 発見・実装)。要素からバブリングした
9378        // クリックが `document` レベルのリスナまで届くことを確認。
9379        ("var s=''; document.addEventListener('click', ()=>s+='D'); document.getElementById('btn').addEventListener('click', ()=>s+='B'); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); s", "BD"),
9380        // `stopPropagation()` すれば document レベルへは届かない。
9381        ("var s=''; document.addEventListener('click', ()=>s+='D'); document.getElementById('btn').addEventListener('click', function(e){ s+='B'; e.stopPropagation(); }); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); s", "B"),
9382        // document.removeEventListener('click', fn) で解除できる。
9383        ("var n=0; var f=()=>n++; document.addEventListener('click', f); document.removeEventListener('click', f); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); n", "0"),
9384        // document 側でも e.target は発火元要素、currentTarget は document 自身。
9385        ("var r=''; document.addEventListener('click', function(e){ r = e.target.id + '/' + (e.currentTarget === document); }); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); r", "btn/true"),
9386        // `document.dispatchEvent(new Event('click'))` で document 自身から直接
9387        // 合成発火することもできる(実DOM要素を経由しない経路)。
9388        ("var n=0; document.addEventListener('click', ()=>n++); document.dispatchEvent(new Event('click')); n", "1"),
9389        // `document.addEventListener(type, fn, {capture:true})` がキャプチャ
9390        // フェーズを一切扱えないバグ(丸ごと未対応だった。`document` の汎用
9391        // リスナは `event_target_add_event_listener` を再利用しており、
9392        // `capture` オプションは元々「DOM ツリーを持たないクラスでは無意味」
9393        // として即座に破棄されていた。`document` に対して再利用したことで
9394        // 意味が変わり、キャプチャ登録が黙ってバブル扱いになっていた。
9395        // 2026-07-16 発見・実装)。document の capture リスナは祖先の
9396        // capture リスナより先(最も外側)に発火する。
9397        ("var s=''; document.addEventListener('click', ()=>s+='D', true); document.getElementById('outer').addEventListener('click', ()=>s+='O', true); document.getElementById('btn').addEventListener('click', ()=>s+='B'); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); s", "DOB"),
9398        // capture 登録は bubble フェーズでは発火しない(両方登録すれば両方発火)。
9399        ("var s=''; document.addEventListener('click', ()=>s+='C', true); document.addEventListener('click', ()=>s+='B'); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); s", "CB"),
9400    ];
9401    total += event_cases.len();
9402    for (src, expect) in event_cases {
9403        let mut rt = JsRuntime::new();
9404        rt.dom
9405            .borrow_mut()
9406            .build_from(&crate::os_lib::dom::parse_html(ev_html));
9407        match rt.eval(src) {
9408            Ok(v) if &v.to_js_string() == expect => passed += 1,
9409            Ok(v) => crate::println!(
9410                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9411                src,
9412                v.to_js_string(),
9413                expect
9414            ),
9415            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9416        }
9417    }
9418
9419    // FormData(form): <form> 配下のコントロールから値を収集(DOM 必須)。
9420    {
9421        let fd_html = "<form id='f'>\
9422            <input name='user' value='alice'>\
9423            <input name='nope'>\
9424            <input type='checkbox' name='agree' value='yes' checked>\
9425            <input type='checkbox' name='news' value='1'>\
9426            <input type='radio' name='plan' value='free'>\
9427            <input type='radio' name='plan' value='pro' checked>\
9428            <input type='submit' name='btn' value='go'>\
9429            <select name='color' value='green'><option value='red'>R</option><option value='green'>G</option></select>\
9430            <textarea name='bio'>hello</textarea>\
9431        </form>";
9432        let fd_cases: &[(&str, &str)] = &[
9433            // user=alice, agree=yes(checked), plan=pro(checked radio), color=green, bio=hello。
9434            // nope は name はあるが value 空 → 空値で含まれる。news/free は未チェックで除外。submit は除外。
9435            (
9436                "new FormData(document.getElementById('f')).get('user')",
9437                "alice",
9438            ),
9439            (
9440                "new FormData(document.getElementById('f')).get('agree')",
9441                "yes",
9442            ),
9443            (
9444                "new FormData(document.getElementById('f')).get('plan')",
9445                "pro",
9446            ),
9447            (
9448                "var f=new FormData(document.getElementById('f')); f.has('news')+','+f.has('btn')",
9449                "false,false",
9450            ),
9451        ];
9452        total += fd_cases.len();
9453        for (src, expect) in fd_cases {
9454            let mut rt = JsRuntime::new();
9455            rt.dom
9456                .borrow_mut()
9457                .build_from(&crate::os_lib::dom::parse_html(fd_html));
9458            match rt.eval(src) {
9459                Ok(v) if &v.to_js_string() == expect => passed += 1,
9460                Ok(v) => crate::println!(
9461                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9462                    src,
9463                    v.to_js_string(),
9464                    expect
9465                ),
9466                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9467            }
9468        }
9469    }
9470    {
9471        // `collect_form_data`(FormData収集の実体)が`form="formId"`属性による
9472        // フォーム外関連付け(HTML5仕様の「form owner」。`element.form`/
9473        // `checkValidity()`は既に対応済み)を反映していなかった兄弟ギャップ。
9474        // 併せて`<fieldset disabled>`祖先による暗黙の無効化(`is_disabled`)も
9475        // 送信データ収集側では未反映だったため同時に修正した。
9476        let fd2_html = "<form id='f6'>\
9477            <input name='inside' value='a'>\
9478            <fieldset disabled><input name='hidden_by_fieldset' value='b'></fieldset>\
9479        </form>\
9480        <input name='outside' form='f6' value='c'>\
9481        <input name='not_associated' value='d'>";
9482        let fd2_cases: &[(&str, &str)] = &[
9483            (
9484                "new FormData(document.getElementById('f6')).get('inside')",
9485                "a",
9486            ),
9487            (
9488                "new FormData(document.getElementById('f6')).get('outside')",
9489                "c",
9490            ),
9491            (
9492                "var f=new FormData(document.getElementById('f6')); f.has('hidden_by_fieldset')",
9493                "false",
9494            ),
9495            (
9496                "var f=new FormData(document.getElementById('f6')); f.has('not_associated')",
9497                "false",
9498            ),
9499            // `form.elements`も同じ`form_associated_controls`を再利用するため、
9500            // フォーム外の`outside`(`form='f6'`)を含む3件(inside/
9501            // hidden_by_fieldset/outside。`not_associated`は含まれない)になる。
9502            (
9503                "document.getElementById('f6').elements.length",
9504                "3",
9505            ),
9506        ];
9507        total += fd2_cases.len();
9508        for (src, expect) in fd2_cases {
9509            let mut rt = JsRuntime::new();
9510            rt.dom
9511                .borrow_mut()
9512                .build_from(&crate::os_lib::dom::parse_html(fd2_html));
9513            match rt.eval(src) {
9514                Ok(v) if &v.to_js_string() == expect => passed += 1,
9515                Ok(v) => crate::println!(
9516                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9517                    src,
9518                    v.to_js_string(),
9519                    expect
9520                ),
9521                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9522            }
9523        }
9524    }
9525    // `<select multiple>` — 選択された option 1つにつき1エントリを送信する仕様が
9526    // FormData 収集で丸ごと未対応だったバグ(複数選択リストボックスという定番 UI
9527    // パターン)。`selectedOptions`(丸ごと未対応だった)も合わせて確認する。
9528    {
9529        let sm_html = "<form id='f2'>\
9530            <select name='tags' multiple>\
9531                <option value='a' selected>A</option>\
9532                <option value='b'>B</option>\
9533                <option value='c' selected>C</option>\
9534            </select>\
9535        </form>";
9536        let sm_cases: &[(&str, &str)] = &[
9537            (
9538                "new FormData(document.getElementById('f2')).getAll('tags').join(',')",
9539                "a,c",
9540            ),
9541            (
9542                "document.querySelector('select').selectedOptions.length",
9543                "2",
9544            ),
9545            (
9546                "Array.from(document.querySelector('select').selectedOptions).map(o => o.value).join(',')",
9547                "a,c",
9548            ),
9549            // `select.options`(動的にドロップダウンを構築する定番イディオム)が
9550            // 丸ごと未対応だった。
9551            (
9552                "document.querySelector('select').options.length",
9553                "3",
9554            ),
9555            (
9556                "Array.from(document.querySelector('select').options).map(o => o.value).join(',')",
9557                "a,b,c",
9558            ),
9559            // `select.options.add`/`.remove`/`.namedItem`(`HTMLOptionsCollection`。
9560            // 丸ごと未対応だった)。
9561            (
9562                "var s=document.querySelector('select'); \
9563                 var o=new Option('D','d'); o.id='optd'; s.options.add(o); \
9564                 s.options.length + ':' + s.options[3].value",
9565                "4:d",
9566            ),
9567            (
9568                "var s=document.querySelector('select'); s.options.remove(1); \
9569                 Array.from(s.options).map(o => o.value).join(',')",
9570                "a,c",
9571            ),
9572            (
9573                "document.querySelector('select').options.namedItem('nope')",
9574                "null",
9575            ),
9576            (
9577                "var s=document.querySelector('select'); s.options.item(0).value",
9578                "a",
9579            ),
9580            (
9581                "var s=document.querySelector('select'); s.options.selectedIndex=1; s.selectedIndex",
9582                "1",
9583            ),
9584            (
9585                "var s=document.querySelector('select'); s.selectedIndex=2; s.options.selectedIndex",
9586                "2",
9587            ),
9588        ];
9589        total += sm_cases.len();
9590        for (src, expect) in sm_cases {
9591            let mut rt = JsRuntime::new();
9592            rt.dom
9593                .borrow_mut()
9594                .build_from(&crate::os_lib::dom::parse_html(sm_html));
9595            match rt.eval(src) {
9596                Ok(v) if &v.to_js_string() == expect => passed += 1,
9597                Ok(v) => crate::println!(
9598                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9599                    src,
9600                    v.to_js_string(),
9601                    expect
9602                ),
9603                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9604            }
9605        }
9606    }
9607    // `<select>`配下`<optgroup>`でグループ化された`<option>`が丸ごと見えなく
9608    // なっていたバグ(`select.options`/`selectedOptions`/フォーム送信の
9609    // どちらも直接の子要素しか見ておらず、`<optgroup>`という非常によく
9610    // 使われるカテゴリ分けパターンを完全に見落としていた。2026-07-17
9611    // 発見・実装)。
9612    // 【2026-07-24】以前`optgroup_total`が固定値`5`だったが、実際の`og_cases`
9613    // 配列は4件しか無く、`total`側だけ1件多く数える「集計ずれ」の一例だった
9614    // (`pass == total`の一言判定のみでは個別FAIL行が一切出ないままカテゴリ
9615    // 丸ごとFAILになり原因不明だった)。`.len()`から動的に求めることで
9616    // 今後同じズレが再発しないようにする。
9617    let og_html = "<form id='f3'><select name='fruit'><optgroup label='Citrus'><option value='lemon'>Lemon</option><option value='lime' selected>Lime</option></optgroup><optgroup label='Berries'><option value='straw'>Strawberry</option></optgroup></select></form>";
9618    let og_cases: &[(&str, &str)] = &[
9619        (
9620            "document.querySelector('select').options.length",
9621            "3",
9622        ),
9623        (
9624            "Array.from(document.querySelector('select').options).map(o => o.value).join(',')",
9625            "lemon,lime,straw",
9626        ),
9627        (
9628            "document.querySelector('select').selectedOptions.length",
9629            "1",
9630        ),
9631        (
9632            "document.querySelector('select').value",
9633            "lime",
9634        ),
9635    ];
9636    total += og_cases.len();
9637    {
9638        for (src, expect) in og_cases {
9639            let mut rt = JsRuntime::new();
9640            rt.dom
9641                .borrow_mut()
9642                .build_from(&crate::os_lib::dom::parse_html(og_html));
9643            match rt.eval(src) {
9644                Ok(v) if &v.to_js_string() == expect => passed += 1,
9645                Ok(v) => crate::println!(
9646                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9647                    src,
9648                    v.to_js_string(),
9649                    expect
9650                ),
9651                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9652            }
9653        }
9654    }
9655    // `document.title` が実際の `<head><title>` 要素と連動すること(既存 `<title>` の
9656    // 更新、無い場合は `<head>` へ新規作成)を、実 DOM 構造を持つフィクスチャで確認する。
9657    {
9658        let title_cases: &[(&str, &str, &str)] = &[
9659            // 既存の <title> があれば、その中身が更新される。
9660            (
9661                "<html><head><title>old</title></head><body></body></html>",
9662                "document.title",
9663                "old",
9664            ),
9665            (
9666                "<html><head><title>old</title></head><body></body></html>",
9667                "document.title='new'; document.querySelector('title').textContent",
9668                "new",
9669            ),
9670            // <head> はあるが <title> が無い場合、新規作成されて連動する。
9671            (
9672                "<html><head></head><body></body></html>",
9673                "document.title='created'; document.querySelector('title').textContent",
9674                "created",
9675            ),
9676        ];
9677        total += title_cases.len();
9678        for (html, src, expect) in title_cases {
9679            let mut rt = JsRuntime::new();
9680            rt.dom
9681                .borrow_mut()
9682                .build_from(&crate::os_lib::dom::parse_html(html));
9683            match rt.eval(src) {
9684                Ok(v) if &v.to_js_string() == expect => passed += 1,
9685                Ok(v) => crate::println!(
9686                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9687                    src,
9688                    v.to_js_string(),
9689                    expect
9690                ),
9691                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9692            }
9693        }
9694    }
9695    // `document.forms`/`.images`/`.scripts`(文書全体をタグ横断で集約する定番の
9696    // HTMLCollection 群)が丸ごと未対応だった。
9697    {
9698        let coll_html = "<html><body>\
9699            <form id='f1'></form><form id='f2'></form>\
9700            <img src='a.png'><img src='b.png'><img src='c.png'>\
9701            <script>1</script>\
9702            <a href='x.html'>x</a><a name='top'>anchor</a>\
9703            <area href='y.html' alt='y'>\
9704            <embed src='z.swf'>\
9705        </body></html>";
9706        let coll_cases: &[(&str, &str)] = &[
9707            ("document.forms.length", "2"),
9708            ("document.images.length", "3"),
9709            ("document.scripts.length", "1"),
9710            // `document.links`(href 付きの a/area のみ。`name` のみの a は含まない)が
9711            // 丸ごと未対応だった。
9712            ("document.links.length", "2"),
9713            // `document.anchors`(`name` 属性付きの a のみ)も丸ごと未対応だった。
9714            ("document.anchors.length", "1"),
9715            // `document.embeds`/`.plugins`(仕様上同じ集合を指すエイリアス)/
9716            // `document.applets`(Java アプレット廃止に伴い常に空固定のレガシー
9717            // スタブ)が丸ごと未対応だった。2026-07-16 発見・実装。
9718            ("document.embeds.length", "1"),
9719            ("document.plugins.length", "1"),
9720            ("document.applets.length", "0"),
9721        ];
9722        total += coll_cases.len();
9723        for (src, expect) in coll_cases {
9724            let mut rt = JsRuntime::new();
9725            rt.dom
9726                .borrow_mut()
9727                .build_from(&crate::os_lib::dom::parse_html(coll_html));
9728            match rt.eval(src) {
9729                Ok(v) if &v.to_js_string() == expect => passed += 1,
9730                Ok(v) => crate::println!(
9731                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9732                    src,
9733                    v.to_js_string(),
9734                    expect
9735                ),
9736                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9737            }
9738        }
9739    }
9740    // getBoundingClientRect / offset* (レンダラが set_rect で注入する矩形を JS から読む)。
9741    {
9742        let rect_checks: &[(&str, &str)] = &[
9743            (
9744                "document.getElementById('t').getBoundingClientRect().width",
9745                "30",
9746            ),
9747            (
9748                "document.getElementById('t').getBoundingClientRect().height",
9749                "40",
9750            ),
9751            (
9752                "document.getElementById('t').getBoundingClientRect().left",
9753                "10",
9754            ),
9755            (
9756                "document.getElementById('t').getBoundingClientRect().bottom",
9757                "60",
9758            ),
9759            ("document.getElementById('t').offsetWidth", "30"),
9760            ("document.getElementById('t').offsetTop", "20"),
9761            // clientWidth = 30 (width) - 5 (left border) - 3 (right border) = 22
9762            ("document.getElementById('t').clientWidth", "22"),
9763            // clientHeight = 40 (height) - 2 (top border) - 4 (bottom border) = 34
9764            ("document.getElementById('t').clientHeight", "34"),
9765            // clientLeft = 5 (left border)
9766            ("document.getElementById('t').clientLeft", "5"),
9767            // clientTop = 2 (top border)
9768            ("document.getElementById('t').clientTop", "2"),
9769            // `element.getClientRects()`(丸ごと未対応だった。テキスト行フラグメント
9770            // モデルが無いこの処理系では要素1つにつき `getBoundingClientRect()` と
9771            // 同じ矩形を1個だけ持つ配列として返す簡略実装)。
9772            ("document.getElementById('t').getClientRects().length", "1"),
9773            (
9774                "document.getElementById('t').getClientRects()[0].width",
9775                "30",
9776            ),
9777            // rect が未登録(レンダラ未描画)の要素は仕様どおり空配列。
9778            ("document.getElementById('s').getClientRects().length", "0"),
9779            // `element.offsetParent`(丸ごと未対応だった。祖先に position が
9780            // static 以外の要素も `<body>` も無ければ仕様どおり `null`)。
9781            (
9782                "document.getElementById('t').offsetParent",
9783                "null",
9784            ),
9785            (
9786                "document.getElementById('wrap').style.position='relative'; \
9787                 document.getElementById('t').offsetParent.id",
9788                "wrap",
9789            ),
9790        ];
9791        total += rect_checks.len();
9792        for (src, expect) in rect_checks {
9793            let mut rt = JsRuntime::new();
9794            rt.dom
9795                .borrow_mut()
9796                .build_from(&crate::os_lib::dom::parse_html(dom2_html));
9797            // レンダラ相当: #t の矩形 (x=10,y=20,w=30,h=40) を注入。
9798            let idx = rt.dom.borrow().get_element_by_id("t");
9799            if let Some(i) = idx {
9800                rt.dom.borrow_mut().set_rect(i, 10, 20, 30, 40);
9801                rt.dom.borrow_mut().set_border_widths(i, 2, 3, 4, 5); // top=2, right=3, bottom=4, left=5
9802            }
9803            match rt.eval(src) {
9804                Ok(v) if &v.to_js_string() == expect => passed += 1,
9805                Ok(v) => crate::println!(
9806                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9807                    src,
9808                    v.to_js_string(),
9809                    expect
9810                ),
9811                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9812            }
9813        }
9814    }
9815
9816    // `document.elementFromPoint(x,y)`/`.elementsFromPoint(x,y)`(座標ヒットテスト)
9817    // が丸ごと未対応だった。`rects`(レンダラが `set_rect` で注入)から座標を含む
9818    // 矩形を集め、最も面積が小さい(=最も深くネストした)要素を返す簡略実装。
9819    {
9820        let point_checks: &[(&str, &str)] = &[
9821            ("document.elementFromPoint(20, 15).id", "t"),
9822            ("document.elementFromPoint(5, 5).id", "wrap"),
9823            ("document.elementFromPoint(500, 500)", "null"),
9824            (
9825                "document.elementsFromPoint(20, 15).map(e => e.id).join(',')",
9826                "t,wrap",
9827            ),
9828        ];
9829        total += point_checks.len();
9830        for (src, expect) in point_checks {
9831            let mut rt = JsRuntime::new();
9832            rt.dom
9833                .borrow_mut()
9834                .build_from(&crate::os_lib::dom::parse_html(dom2_html));
9835            // レンダラ相当: 'wrap'(外側、大きい矩形)に 't'(内側、小さい矩形)が
9836            // ネストした状態を注入。
9837            let wrap_idx = rt.dom.borrow().get_element_by_id("wrap");
9838            if let Some(i) = wrap_idx {
9839                rt.dom.borrow_mut().set_rect(i, 0, 0, 100, 100);
9840            }
9841            let t_idx = rt.dom.borrow().get_element_by_id("t");
9842            if let Some(i) = t_idx {
9843                rt.dom.borrow_mut().set_rect(i, 10, 10, 30, 20);
9844            }
9845            match rt.eval(src) {
9846                Ok(v) if &v.to_js_string() == expect => passed += 1,
9847                Ok(v) => crate::println!(
9848                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9849                    src,
9850                    v.to_js_string(),
9851                    expect
9852                ),
9853                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9854            }
9855        }
9856    }
9857
9858    // getComputedStyle(set_computed_style で注入した算出スタイルを camelCase/kebab/getPropertyValue で読む)。
9859    {
9860        let cs_checks: &[(&str, &str)] = &[
9861            ("getComputedStyle(document.getElementById('t')).color", "red"),
9862            ("getComputedStyle(document.getElementById('t')).backgroundColor", "blue"),
9863            ("getComputedStyle(document.getElementById('t')).getPropertyValue('background-color')", "blue"),
9864            ("window.getComputedStyle(document.getElementById('t')).fontSize", "16px"),
9865        ];
9866        total += cs_checks.len();
9867        for (src, expect) in cs_checks {
9868            let mut rt = JsRuntime::new();
9869            rt.dom
9870                .borrow_mut()
9871                .build_from(&crate::os_lib::dom::parse_html(dom2_html));
9872            let idx = rt.dom.borrow().get_element_by_id("t");
9873            if let Some(i) = idx {
9874                let mut d = rt.dom.borrow_mut();
9875                d.set_computed_style(i, "color", "red");
9876                d.set_computed_style(i, "background-color", "blue");
9877                d.set_computed_style(i, "font-size", "16px");
9878            }
9879            match rt.eval(src) {
9880                Ok(v) if &v.to_js_string() == expect => passed += 1,
9881                Ok(v) => crate::println!(
9882                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9883                    src,
9884                    v.to_js_string(),
9885                    expect
9886                ),
9887                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9888            }
9889        }
9890    }
9891
9892    // `element.checkVisibility()`(DOM 標準。丸ごと未対応だった)。
9893    {
9894        let cv_checks: &[(&str, &str, &[(&str, &str, &str)])] = &[
9895            (
9896                "document.getElementById('t').checkVisibility()",
9897                "true",
9898                &[],
9899            ),
9900            (
9901                "document.getElementById('t').checkVisibility()",
9902                "false",
9903                &[("t", "display", "none")],
9904            ),
9905            (
9906                "document.getElementById('t').checkVisibility()",
9907                "false",
9908                &[("wrap", "display", "none")],
9909            ),
9910            (
9911                "document.getElementById('t').checkVisibility()",
9912                "false",
9913                &[("t", "visibility", "hidden")],
9914            ),
9915        ];
9916        total += cv_checks.len();
9917        for (src, expect, styles) in cv_checks {
9918            let mut rt = JsRuntime::new();
9919            rt.dom
9920                .borrow_mut()
9921                .build_from(&crate::os_lib::dom::parse_html(dom2_html));
9922            for (id, prop, val) in *styles {
9923                let idx = rt.dom.borrow().get_element_by_id(id);
9924                if let Some(i) = idx {
9925                    rt.dom.borrow_mut().set_computed_style(i, prop, val);
9926                }
9927            }
9928            match rt.eval(src) {
9929                Ok(v) if &v.to_js_string() == expect => passed += 1,
9930                Ok(v) => crate::println!(
9931                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9932                    src,
9933                    v.to_js_string(),
9934                    expect
9935                ),
9936                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9937            }
9938        }
9939    }
9940
9941    // クリックディスパッチの統合確認(リスナ登録→dispatch_click→カウンタ増加)。
9942    {
9943        total += 1;
9944        let mut rt = JsRuntime::new();
9945        rt.dom
9946            .borrow_mut()
9947            .build_from(&crate::os_lib::dom::parse_html(dom_html));
9948        let _ = rt.eval("var clicks=0; document.getElementById('b').addEventListener('click', () => { clicks++; document.getElementById('out').textContent = 'clicked ' + clicks; });");
9949        let bidx = rt.dom.borrow().get_element_by_id("b");
9950        if let Some(i) = bidx {
9951            rt.dispatch_click(i);
9952            rt.dispatch_click(i);
9953        }
9954        match rt.eval("clicks + ':' + document.getElementById('out').textContent") {
9955            Ok(v) if v.to_js_string() == "2:clicked 2" => passed += 1,
9956            Ok(v) => crate::println!(
9957                "JS_SELFTEST FAIL: click dispatch => `{}` (want `2:clicked 2`)",
9958                v.to_js_string()
9959            ),
9960            Err(e) => crate::println!("JS_SELFTEST ERR:  click dispatch => {}", e),
9961        }
9962    }
9963
9964    // Event: バブリング / target・currentTarget / stopPropagation / preventDefault / stopImmediate。
9965    let event_html = "<div id='par'><button id='btn'>go</button></div>";
9966    let event_checks: &[(&str, &str, &str)] = &[
9967        // 委譲: par のリスナが子 btn のクリックで発火。target=btn, currentTarget=par。
9968        ("var L=''; document.getElementById('par').addEventListener('click', function(e){ L += 'par('+e.target.id+','+e.currentTarget.id+')'; });",
9969         "L", "par(btn,par)"),
9970        // バブリング順: target(btn) → 祖先(par)。
9971        ("var L=''; document.getElementById('btn').addEventListener('click', function(){ L+='btn'; }); document.getElementById('par').addEventListener('click', function(){ L+='-par'; });",
9972         "L", "btn-par"),
9973        // stopPropagation で祖先に伝播しない。
9974        ("var L=''; document.getElementById('btn').addEventListener('click', function(e){ L+='btn'; e.stopPropagation(); }); document.getElementById('par').addEventListener('click', function(){ L+='-par'; });",
9975         "L", "btn"),
9976        // preventDefault → defaultPrevented。
9977        ("var L=''; document.getElementById('btn').addEventListener('click', function(e){ e.preventDefault(); L += e.defaultPrevented; });",
9978         "L", "true"),
9979        // stopImmediatePropagation で同一ノードの後続リスナも止まる。
9980        ("var L=''; document.getElementById('btn').addEventListener('click', function(e){ L+='1'; e.stopImmediatePropagation(); }); document.getElementById('btn').addEventListener('click', function(){ L+='2'; });",
9981         "L", "1"),
9982        // キャプチャ→ターゲット→バブルの順。par(capture) → btn → par(bubble)。
9983        ("var L=''; document.getElementById('par').addEventListener('click', function(){ L+='cap'; }, true); document.getElementById('btn').addEventListener('click', function(){ L+='-tgt'; }); document.getElementById('par').addEventListener('click', function(){ L+='-bub'; });",
9984         "L", "cap-tgt-bub"),
9985        // eventPhase: capture=1, at target=2, bubble=3。
9986        ("var P=''; document.getElementById('par').addEventListener('click', function(e){ P+=e.eventPhase; }, true); document.getElementById('btn').addEventListener('click', function(e){ P+=e.eventPhase; }); document.getElementById('par').addEventListener('click', function(e){ P+=e.eventPhase; });",
9987         "P", "123"),
9988        // once: クリックで一度発火する(複数回 dispatch のテストは専用ブロックで実施)。
9989        ("var N=0; document.getElementById('btn').addEventListener('click', function(){ N++; }, {once:true}); N",
9990         "N", "1"),
9991        // removeEventListener: 登録解除すると発火しない。
9992        ("var L=''; function h(){ L+='x'; } document.getElementById('btn').addEventListener('click', h); document.getElementById('btn').removeEventListener('click', h);",
9993         "L", ""),
9994        // composedPath: target→祖先(btn, par, ...)。先頭2件の id を確認。
9995        ("var C=''; document.getElementById('btn').addEventListener('click', function(e){ var p=e.composedPath(); C=p[0].id+','+p[1].id; });",
9996         "C", "btn,par"),
9997    ];
9998    total += event_checks.len();
9999    for (setup, read, expect) in event_checks {
10000        let mut rt = JsRuntime::new();
10001        rt.dom
10002            .borrow_mut()
10003            .build_from(&crate::os_lib::dom::parse_html(event_html));
10004        let _ = rt.eval(setup);
10005        let bidx = rt.dom.borrow().get_element_by_id("btn");
10006        if let Some(i) = bidx {
10007            rt.dispatch_click(i);
10008        }
10009        match rt.eval(read) {
10010            Ok(v) if &v.to_js_string() == expect => passed += 1,
10011            Ok(v) => crate::println!(
10012                "JS_SELFTEST FAIL: event `{}` => `{}` (want `{}`)",
10013                read,
10014                v.to_js_string(),
10015                expect
10016            ),
10017            Err(e) => crate::println!("JS_SELFTEST ERR:  event `{}` => {}", read, e),
10018        }
10019    }
10020
10021    // dispatchEvent: 手動で Event を発火し、リスナが受け取りバブリングする
10022    // (`bubbles: true` を明示。仕様上 `new Event('foo')` の既定は `bubbles: false`
10023    // で、以前は `bubbles` を一切見ず常に無条件で祖先まで伝播していたバグが
10024    // あったため、明示的に `true` を指定してバブリングの意図を検証する)。
10025    {
10026        total += 1;
10027        let mut rt = JsRuntime::new();
10028        rt.dom
10029            .borrow_mut()
10030            .build_from(&crate::os_lib::dom::parse_html(event_html));
10031        let _ = rt.eval(
10032            "var L=''; \
10033             document.getElementById('btn').addEventListener('foo', function(e){ L+='btn:'+e.type; }); \
10034             document.getElementById('par').addEventListener('foo', function(){ L+='-par'; }); \
10035             document.getElementById('btn').dispatchEvent(new Event('foo', {bubbles:true}));",
10036        );
10037        match rt.eval("L") {
10038            Ok(v) if v.to_js_string() == "btn:foo-par" => passed += 1,
10039            Ok(v) => crate::println!(
10040                "JS_SELFTEST FAIL: dispatchEvent => `{}` (want `btn:foo-par`)",
10041                v.to_js_string()
10042            ),
10043            Err(e) => crate::println!("JS_SELFTEST ERR:  dispatchEvent => {}", e),
10044        }
10045    }
10046
10047    // dispatchEvent: `bubbles: false`(既定)なら祖先へ伝播しない。以前は
10048    // `bubbles` を一切見ず常に無条件で祖先まで伝播してしまう仕様違反バグだった。
10049    {
10050        total += 1;
10051        let mut rt = JsRuntime::new();
10052        rt.dom
10053            .borrow_mut()
10054            .build_from(&crate::os_lib::dom::parse_html(event_html));
10055        let _ = rt.eval(
10056            "var L=''; \
10057             document.getElementById('btn').addEventListener('foo', function(e){ L+='btn:'+e.type; }); \
10058             document.getElementById('par').addEventListener('foo', function(){ L+='-par'; }); \
10059             document.getElementById('btn').dispatchEvent(new Event('foo'));",
10060        );
10061        match rt.eval("L") {
10062            Ok(v) if v.to_js_string() == "btn:foo" => passed += 1,
10063            Ok(v) => crate::println!(
10064                "JS_SELFTEST FAIL: dispatchEvent non-bubbling => `{}` (want `btn:foo`)",
10065                v.to_js_string()
10066            ),
10067            Err(e) => crate::println!("JS_SELFTEST ERR:  dispatchEvent non-bubbling => {}", e),
10068        }
10069    }
10070
10071    // dispatchEvent: CustomEvent の `bubbles` オプションが正しく反映される
10072    // (以前は常に `false` 決め打ちで、`{bubbles:true}` を渡しても無視されていた)。
10073    {
10074        total += 1;
10075        let mut rt = JsRuntime::new();
10076        rt.dom
10077            .borrow_mut()
10078            .build_from(&crate::os_lib::dom::parse_html(event_html));
10079        let _ = rt.eval(
10080            "var L=''; \
10081             document.getElementById('btn').addEventListener('foo', function(e){ L+='btn:'+e.type; }); \
10082             document.getElementById('par').addEventListener('foo', function(){ L+='-par'; }); \
10083             document.getElementById('btn').dispatchEvent(new CustomEvent('foo', {bubbles:true}));",
10084        );
10085        match rt.eval("L") {
10086            Ok(v) if v.to_js_string() == "btn:foo-par" => passed += 1,
10087            Ok(v) => crate::println!(
10088                "JS_SELFTEST FAIL: CustomEvent bubbles => `{}` (want `btn:foo-par`)",
10089                v.to_js_string()
10090            ),
10091            Err(e) => crate::println!("JS_SELFTEST ERR:  CustomEvent bubbles => {}", e),
10092        }
10093    }
10094
10095    // once: 複数回 dispatch しても 1 回だけ発火。
10096    {
10097        total += 1;
10098        let mut rt = JsRuntime::new();
10099        rt.dom
10100            .borrow_mut()
10101            .build_from(&crate::os_lib::dom::parse_html(event_html));
10102        let _ = rt.eval(
10103            "var N=0; document.getElementById('btn').addEventListener('foo', function(){ N++; }, {once:true}); \
10104             document.getElementById('btn').dispatchEvent(new Event('foo')); \
10105             document.getElementById('btn').dispatchEvent(new Event('foo')); \
10106             document.getElementById('btn').dispatchEvent(new Event('foo'));",
10107        );
10108        match rt.eval("N") {
10109            Ok(v) if v.to_js_string() == "1" => passed += 1,
10110            Ok(v) => crate::println!(
10111                "JS_SELFTEST FAIL: once dispatch => `{}` (want `1`)",
10112                v.to_js_string()
10113            ),
10114            Err(e) => crate::println!("JS_SELFTEST ERR:  once dispatch => {}", e),
10115        }
10116    }
10117
10118    // dispatchEvent 戻り値: preventDefault されると false(`cancelable:true` の場合のみ。
10119    // 以前は `cancelable` が一切読まれておらず preventDefault() が常に効いていた
10120    // バグの修正に伴い、このテストが元々検証したかった「妨害可能なイベントで
10121    // preventDefault() が効く」という意図を保つため `new Event('foo', {cancelable:
10122    // true})` に明示する)。
10123    {
10124        total += 1;
10125        let mut rt = JsRuntime::new();
10126        rt.dom
10127            .borrow_mut()
10128            .build_from(&crate::os_lib::dom::parse_html(event_html));
10129        let _ = rt.eval(
10130            "document.getElementById('btn').addEventListener('foo', function(e){ e.preventDefault(); }); \
10131             var R = document.getElementById('btn').dispatchEvent(new Event('foo', {cancelable:true}));",
10132        );
10133        match rt.eval("R") {
10134            Ok(v) if v.to_js_string() == "false" => passed += 1,
10135            Ok(v) => crate::println!(
10136                "JS_SELFTEST FAIL: dispatchEvent return => `{}` (want `false`)",
10137                v.to_js_string()
10138            ),
10139            Err(e) => crate::println!("JS_SELFTEST ERR:  dispatchEvent return => {}", e),
10140        }
10141    }
10142
10143    // keydown / input / focus / blur / change / submit / mouse(座標含む) イベント。
10144    // change イベント: input/select に change リスナを登録し dispatch_event で発火。
10145    let change_html = "<form><input id='cb' type='checkbox'><select id='sel'><option value='a'>A</option><option value='b'>B</option></select></form>";
10146    let mut change_passed = 0usize;
10147    {
10148        total += 1;
10149        let mut rt = JsRuntime::new();
10150        rt.dom
10151            .borrow_mut()
10152            .build_from(&crate::os_lib::dom::parse_html(change_html));
10153        let _ = rt.eval("var c=0; document.getElementById('cb').addEventListener('change', function(){ c++; });");
10154        let cidx = rt.dom.borrow().get_element_by_id("cb");
10155        if let Some(i) = cidx {
10156            rt.dispatch_event(i, "change");
10157            rt.dispatch_event(i, "change");
10158        }
10159        match rt.eval("c") {
10160            Ok(v) if v.to_js_string() == "2" => change_passed += 1,
10161            Ok(v) => crate::println!(
10162                "JS_SELFTEST FAIL: change count => `{}` (want `2`)",
10163                v.to_js_string()
10164            ),
10165            Err(e) => crate::println!("JS_SELFTEST ERR:  change count => {}", e),
10166        }
10167    }
10168    {
10169        total += 1;
10170        // change イベントの target が発火要素であること。
10171        let mut rt = JsRuntime::new();
10172        rt.dom
10173            .borrow_mut()
10174            .build_from(&crate::os_lib::dom::parse_html(change_html));
10175        let _ = rt.eval("var t=''; document.getElementById('sel').addEventListener('change', function(e){ t=e.target.id; });");
10176        let sidx = rt.dom.borrow().get_element_by_id("sel");
10177        if let Some(i) = sidx {
10178            rt.dispatch_event(i, "change");
10179        }
10180        match rt.eval("t") {
10181            Ok(v) if v.to_js_string() == "sel" => change_passed += 1,
10182            Ok(v) => crate::println!(
10183                "JS_SELFTEST FAIL: change target => `{}` (want `sel`)",
10184                v.to_js_string()
10185            ),
10186            Err(e) => crate::println!("JS_SELFTEST ERR:  change target => {}", e),
10187        }
10188    }
10189    passed += change_passed;
10190
10191    {
10192        let kev_html = "<div id='par'><input id='inp'></div>";
10193        // 1. keydown が e.type/e.key 付きで発火。
10194        {
10195            total += 1;
10196            let mut rt = JsRuntime::new();
10197            rt.dom
10198                .borrow_mut()
10199                .build_from(&crate::os_lib::dom::parse_html(kev_html));
10200            let _ = rt.eval("var L=''; document.getElementById('inp').addEventListener('keydown', function(e){ L = e.type + ':' + e.key; });");
10201            let idx = rt.dom.borrow().get_element_by_id("inp");
10202            if let Some(i) = idx {
10203                rt.dispatch_key(i, "keydown", "x");
10204            }
10205            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "keydown:x" {
10206                passed += 1;
10207            } else {
10208                crate::println!("JS_SELFTEST FAIL: keydown e.key");
10209            }
10210        }
10211        // 2. keydown が祖先へバブリング(委譲)。
10212        {
10213            total += 1;
10214            let mut rt = JsRuntime::new();
10215            rt.dom
10216                .borrow_mut()
10217                .build_from(&crate::os_lib::dom::parse_html(kev_html));
10218            let _ = rt.eval("var L=''; document.getElementById('par').addEventListener('keydown', function(e){ L = 'par:' + e.target.id; });");
10219            let idx = rt.dom.borrow().get_element_by_id("inp");
10220            if let Some(i) = idx {
10221                rt.dispatch_key(i, "keydown", "a");
10222            }
10223            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "par:inp" {
10224                passed += 1;
10225            } else {
10226                crate::println!("JS_SELFTEST FAIL: keydown bubbling");
10227            }
10228        }
10229        // 3. keydown の preventDefault が dispatch_key の戻り値 .1 に反映。
10230        {
10231            total += 1;
10232            let mut rt = JsRuntime::new();
10233            rt.dom
10234                .borrow_mut()
10235                .build_from(&crate::os_lib::dom::parse_html(kev_html));
10236            let _ = rt.eval("document.getElementById('inp').addEventListener('keydown', function(e){ e.preventDefault(); });");
10237            let idx = rt.dom.borrow().get_element_by_id("inp");
10238            let prevented = idx
10239                .map(|i| rt.dispatch_key(i, "keydown", "a").1)
10240                .unwrap_or(false);
10241            if prevented {
10242                passed += 1;
10243            } else {
10244                crate::println!("JS_SELFTEST FAIL: keydown preventDefault");
10245            }
10246        }
10247        // 4. input イベントが発火。
10248        {
10249            total += 1;
10250            let mut rt = JsRuntime::new();
10251            rt.dom
10252                .borrow_mut()
10253                .build_from(&crate::os_lib::dom::parse_html(kev_html));
10254            let _ = rt.eval("var N=0; document.getElementById('inp').addEventListener('input', function(){ N++; });");
10255            let idx = rt.dom.borrow().get_element_by_id("inp");
10256            if let Some(i) = idx {
10257                rt.dispatch_event(i, "input");
10258            }
10259            if rt.eval("N").map(|v| v.to_js_string()).unwrap_or_default() == "1" {
10260                passed += 1;
10261            } else {
10262                crate::println!("JS_SELFTEST FAIL: input event");
10263            }
10264        }
10265        // 5. focus イベントが発火(e.type)。
10266        {
10267            total += 1;
10268            let mut rt = JsRuntime::new();
10269            rt.dom
10270                .borrow_mut()
10271                .build_from(&crate::os_lib::dom::parse_html(kev_html));
10272            let _ = rt.eval("var L=''; document.getElementById('inp').addEventListener('focus', function(e){ L = e.type; });");
10273            let idx = rt.dom.borrow().get_element_by_id("inp");
10274            if let Some(i) = idx {
10275                rt.dispatch_event(i, "focus");
10276            }
10277            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "focus" {
10278                passed += 1;
10279            } else {
10280                crate::println!("JS_SELFTEST FAIL: focus event");
10281            }
10282        }
10283        // 6. blur イベントが祖先へバブリング。
10284        {
10285            total += 1;
10286            let mut rt = JsRuntime::new();
10287            rt.dom
10288                .borrow_mut()
10289                .build_from(&crate::os_lib::dom::parse_html(kev_html));
10290            let _ = rt.eval("var L=''; document.getElementById('par').addEventListener('blur', function(e){ L = 'blur:' + e.target.id; });");
10291            let idx = rt.dom.borrow().get_element_by_id("inp");
10292            if let Some(i) = idx {
10293                rt.dispatch_event(i, "blur");
10294            }
10295            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "blur:inp" {
10296                passed += 1;
10297            } else {
10298                crate::println!("JS_SELFTEST FAIL: blur bubbling");
10299            }
10300        }
10301        // 7. change イベントが発火。
10302        {
10303            total += 1;
10304            let mut rt = JsRuntime::new();
10305            rt.dom
10306                .borrow_mut()
10307                .build_from(&crate::os_lib::dom::parse_html(kev_html));
10308            let _ = rt.eval("var L=''; document.getElementById('inp').addEventListener('change', function(e){ L = e.type; });");
10309            let idx = rt.dom.borrow().get_element_by_id("inp");
10310            if let Some(i) = idx {
10311                rt.dispatch_event(i, "change");
10312            }
10313            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "change" {
10314                passed += 1;
10315            } else {
10316                crate::println!("JS_SELFTEST FAIL: change event");
10317            }
10318        }
10319        // 8/9. submit イベントの発火と preventDefault(送信キャンセル)。
10320        let form_html = "<form id='f'><input id='inp2'></form>";
10321        {
10322            total += 1;
10323            let mut rt = JsRuntime::new();
10324            rt.dom
10325                .borrow_mut()
10326                .build_from(&crate::os_lib::dom::parse_html(form_html));
10327            let _ = rt.eval("var S=0; document.getElementById('f').addEventListener('submit', function(){ S++; });");
10328            let idx = rt.dom.borrow().get_element_by_id("f");
10329            if let Some(i) = idx {
10330                rt.dispatch_event_with(i, "submit", &[]);
10331            }
10332            if rt.eval("S").map(|v| v.to_js_string()).unwrap_or_default() == "1" {
10333                passed += 1;
10334            } else {
10335                crate::println!("JS_SELFTEST FAIL: submit event");
10336            }
10337        }
10338        {
10339            total += 1;
10340            let mut rt = JsRuntime::new();
10341            rt.dom
10342                .borrow_mut()
10343                .build_from(&crate::os_lib::dom::parse_html(form_html));
10344            let _ = rt.eval("document.getElementById('f').addEventListener('submit', function(e){ e.preventDefault(); });");
10345            let idx = rt.dom.borrow().get_element_by_id("f");
10346            let prevented = idx
10347                .map(|i| rt.dispatch_event_with(i, "submit", &[]).1)
10348                .unwrap_or(false);
10349            if prevented {
10350                passed += 1;
10351            } else {
10352                crate::println!("JS_SELFTEST FAIL: submit preventDefault");
10353            }
10354        }
10355        // 10. mouseover が発火(e.type)。
10356        {
10357            total += 1;
10358            let mut rt = JsRuntime::new();
10359            rt.dom
10360                .borrow_mut()
10361                .build_from(&crate::os_lib::dom::parse_html(kev_html));
10362            let _ = rt.eval("var L=''; document.getElementById('inp').addEventListener('mouseover', function(e){ L = e.type; });");
10363            let idx = rt.dom.borrow().get_element_by_id("inp");
10364            if let Some(i) = idx {
10365                rt.dispatch_event(i, "mouseover");
10366            }
10367            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "mouseover" {
10368                passed += 1;
10369            } else {
10370                crate::println!("JS_SELFTEST FAIL: mouseover event");
10371            }
10372        }
10373        // 11. mouseout が祖先へバブリング。
10374        {
10375            total += 1;
10376            let mut rt = JsRuntime::new();
10377            rt.dom
10378                .borrow_mut()
10379                .build_from(&crate::os_lib::dom::parse_html(kev_html));
10380            let _ = rt.eval("var L=''; document.getElementById('par').addEventListener('mouseout', function(e){ L = 'out:' + e.target.id; });");
10381            let idx = rt.dom.borrow().get_element_by_id("inp");
10382            if let Some(i) = idx {
10383                rt.dispatch_event(i, "mouseout");
10384            }
10385            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "out:inp" {
10386                passed += 1;
10387            } else {
10388                crate::println!("JS_SELFTEST FAIL: mouseout bubbling");
10389            }
10390        }
10391        // 12. mousemove が発火(回数カウント)。
10392        {
10393            total += 1;
10394            let mut rt = JsRuntime::new();
10395            rt.dom
10396                .borrow_mut()
10397                .build_from(&crate::os_lib::dom::parse_html(kev_html));
10398            let _ = rt.eval("var N=0; document.getElementById('inp').addEventListener('mousemove', function(){ N++; });");
10399            let idx = rt.dom.borrow().get_element_by_id("inp");
10400            if let Some(i) = idx {
10401                rt.dispatch_event(i, "mousemove");
10402                rt.dispatch_event(i, "mousemove");
10403            }
10404            if rt.eval("N").map(|v| v.to_js_string()).unwrap_or_default() == "2" {
10405                passed += 1;
10406            } else {
10407                crate::println!("JS_SELFTEST FAIL: mousemove event");
10408            }
10409        }
10410        // 13. dispatch_mouse が clientX/clientY を付与。
10411        {
10412            total += 1;
10413            let mut rt = JsRuntime::new();
10414            rt.dom
10415                .borrow_mut()
10416                .build_from(&crate::os_lib::dom::parse_html(kev_html));
10417            let _ = rt.eval("var L=''; document.getElementById('inp').addEventListener('click', function(e){ L = e.clientX + ',' + e.clientY; });");
10418            let idx = rt.dom.borrow().get_element_by_id("inp");
10419            if let Some(i) = idx {
10420                rt.dispatch_mouse(i, "click", 42, 17);
10421            }
10422            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "42,17" {
10423                passed += 1;
10424            } else {
10425                crate::println!("JS_SELFTEST FAIL: mouse clientX/clientY");
10426            }
10427        }
10428    }
10429
10430    // window/document.location(set_page_url 後に各成分を検証)。
10431    let location_cases: &[(&str, &str)] = &[
10432        ("location.href", "https://example.com/foo/bar?q=1#h"),
10433        ("location.protocol", "https:"),
10434        ("location.host", "example.com"),
10435        ("location.hostname", "example.com"),
10436        ("location.pathname", "/foo/bar"),
10437        ("location.search", "?q=1"),
10438        ("location.hash", "#h"),
10439        ("location.origin", "https://example.com"),
10440        ("window.location.href", "https://example.com/foo/bar?q=1#h"),
10441        ("document.location.pathname", "/foo/bar"),
10442        ("location.toString()", "https://example.com/foo/bar?q=1#h"),
10443        ("window.location === document.location", "true"),
10444        // `location.protocol`/`.host`/`.hostname`/`.port`への代入は仕様上
10445        // 再ナビゲーションを要求すべきだが、`href`/`hash`/`pathname`/`search`
10446        // だけがナビゲーション要求(`_pending_location`)に変換されており、
10447        // この4つは常に黙って見かけ上の`.props`値だけ更新され、実際には
10448        // どこへも「移動しない」バグだった。
10449        // `location_cases`は1つの`rt`を全ケースで使い回す(前のケースの代入が
10450        // 後続へ持ち越される)ため、各ケースの先頭で`protocol`/`host`を
10451        // 明示的に既知の状態へリセットしてから検証し、実行順に依存しない
10452        // ようにする。
10453        (
10454            "location.protocol='http:'; location._pending_location",
10455            "http://example.com/foo/bar?q=1",
10456        ),
10457        (
10458            "location.protocol='https:'; location.host='example.com'; \
10459             location.hostname='other.com'; location._pending_location",
10460            "https://other.com/foo/bar?q=1",
10461        ),
10462        (
10463            "location.host='example.com'; location.port='8080'; location._pending_location",
10464            "https://example.com:8080/foo/bar?q=1",
10465        ),
10466        (
10467            "location.host='new.com:9090'; location._pending_location",
10468            "https://new.com:9090/foo/bar?q=1",
10469        ),
10470        (
10471            "location.host='new.com:9090'; location.hostname + ',' + location.port",
10472            "new.com,9090",
10473        ),
10474        // `<a>.href`/`<img>.src`/`<form>.action` は仕様上、生の属性文字列ではなく
10475        // ページURL基準で解決した絶対URLを返す必要があるが、以前はこの解決が
10476        // 一切行われず相対文字列がそのまま返るバグだった。
10477        (
10478            "var a=document.createElement('a'); a.setAttribute('href','/p3'); a.href",
10479            "https://example.com/p3",
10480        ),
10481        (
10482            "var i=document.createElement('img'); i.setAttribute('src','x.png'); i.src",
10483            "https://example.com/foo/x.png",
10484        ),
10485        (
10486            "var a=document.createElement('a'); a.setAttribute('href','https://o.com/z'); a.href",
10487            "https://o.com/z",
10488        ),
10489        // `document.URL`/`document.documentURI`/`node.baseURI`(DOM 標準。丸ごと
10490        // 未対応だった。`<base>` タグ非対応のため常に `location.href` と同値)。
10491        ("document.URL", "https://example.com/foo/bar?q=1#h"),
10492        ("document.documentURI", "https://example.com/foo/bar?q=1#h"),
10493        (
10494            "document.createElement('div').baseURI",
10495            "https://example.com/foo/bar?q=1#h",
10496        ),
10497        // `document.readyState`(HTML5。丸ごと未対応だった。この処理系は常に
10498        // "complete" を返す簡略実装)。
10499        ("document.readyState", "complete"),
10500        // `document.cookie`(丸ごと未対応だった。`expires`/`path` 等の属性は
10501        // 無視する簡略実装で `name=value` のみ反映する)。
10502        (
10503            "document.cookie='ck_a=1'; document.cookie='ck_b=2; path=/'; document.cookie",
10504            "ck_a=1; ck_b=2",
10505        ),
10506        // `cookieStore`(Cookie Store API。丸ごと未対応だった。`document.cookie`
10507        // と同じ実データを共有する非同期版。他のテストと Cookie 名が衝突しない
10508        // よう `cs_` 接頭辞を使う)。
10509        (
10510            "await cookieStore.set('cs_x', '1'); (await cookieStore.get('cs_x')).value",
10511            "1",
10512        ),
10513        ("(await cookieStore.get('cs_missing'))", "null"),
10514        (
10515            "await cookieStore.set('cs_y', '2'); \
10516             (await cookieStore.getAll('cs_y')).map(c => c.name+'='+c.value).join(',')",
10517            "cs_y=2",
10518        ),
10519        (
10520            "await cookieStore.set('cs_z', '3'); await cookieStore.delete('cs_z'); \
10521             (await cookieStore.get('cs_z'))",
10522            "null",
10523        ),
10524        (
10525            "await cookieStore.set({name:'cs_w', value:'4'}); (await cookieStore.get('cs_w')).value",
10526            "4",
10527        ),
10528        // Page Visibility API(`document.hidden`/`.visibilityState`)が丸ごと
10529        // 未対応だった。タブ/バックグラウンド化の概念が無いため常に「表示中」。
10530        ("document.hidden", "false"),
10531        ("document.visibilityState", "visible"),
10532    ];
10533    total += location_cases.len();
10534    {
10535        let mut rt = JsRuntime::new();
10536        rt.set_page_url("https://example.com/foo/bar?q=1#h");
10537        for (src, expect) in location_cases {
10538            match rt.eval(src) {
10539                Ok(v) if &v.to_js_string() == expect => passed += 1,
10540                Ok(v) => crate::println!(
10541                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
10542                    src,
10543                    v.to_js_string(),
10544                    expect
10545                ),
10546                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
10547            }
10548        }
10549    }
10550
10551    // history.pushState / replaceState(location 書換え + state/length)。
10552    let history_cases: &[(&str, &str)] = &[
10553        (
10554            "history.pushState({a:1},'','/p2'); location.pathname",
10555            "/p2",
10556        ),
10557        (
10558            "history.pushState({},'','/p2'); location.href",
10559            "https://example.com/p2",
10560        ),
10561        ("history.pushState({a:9},'','/p2'); history.state.a", "9"),
10562        (
10563            "var n=history.length; history.pushState({},'','/x'); history.length - n",
10564            "1",
10565        ),
10566        (
10567            "var n=history.length; history.replaceState({},'','/y'); history.length - n",
10568            "0",
10569        ),
10570        (
10571            "history.replaceState({},'','about.html'); location.pathname",
10572            "/foo/about.html",
10573        ),
10574        ("typeof history.pushState", "function"),
10575        ("typeof history.back", "function"),
10576    ];
10577    total += history_cases.len();
10578    for (src, expect) in history_cases {
10579        let mut rt = JsRuntime::new();
10580        rt.set_page_url("https://example.com/foo/bar");
10581        match rt.eval(src) {
10582            Ok(v) if &v.to_js_string() == expect => passed += 1,
10583            Ok(v) => crate::println!(
10584                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
10585                src,
10586                v.to_js_string(),
10587                expect
10588            ),
10589            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
10590        }
10591    }
10592
10593    // 相対URL解決(resolve_url 純関数。fetch/XHR の base 基準解決)。
10594    let resolve_cases: &[(&str, &str, &str)] = &[
10595        ("http://h.com/a/b.html", "/api", "http://h.com/api"),
10596        ("http://h.com/a/b.html", "x.json", "http://h.com/a/x.json"),
10597        ("http://h.com/a/", "x.json", "http://h.com/a/x.json"),
10598        (
10599            "http://h.com/a/b.html",
10600            "https://o.com/z",
10601            "https://o.com/z",
10602        ),
10603        ("http://h.com/a/b.html", "data:,hi", "data:,hi"),
10604        ("https://h.com/", "/p/q", "https://h.com/p/q"),
10605        ("http://h.com", "y", "http://h.com/y"),
10606        ("", "/api", "/api"),
10607        // `.`/`..` セグメント解決(以前は文字列結合のみで未解決のまま残っていた)。
10608        ("https://h.com/a/b/c", "../style.css", "https://h.com/a/style.css"),
10609        ("https://h.com/a/b/c", "./style.css", "https://h.com/a/b/style.css"),
10610        ("https://h.com/a/b/c/d", "../../x", "https://h.com/a/x"),
10611    ];
10612    total += resolve_cases.len();
10613    for (base, url, expect) in resolve_cases {
10614        let got = builtins::resolve_url(base, url);
10615        if &got == expect {
10616            passed += 1;
10617        } else {
10618            crate::println!(
10619                "JS_SELFTEST FAIL: resolve_url({:?},{:?}) => `{}` (want `{}`)",
10620                base,
10621                url,
10622                got,
10623                expect
10624            );
10625        }
10626    }
10627
10628    // HTML5 制約バリデーション(checkValidity / setCustomValidity / validity)。
10629    let validity_cases: &[(&str, &str, &str)] = &[
10630        // (html, script, expect)
10631        ("<input id='a' required value=''>", "document.getElementById('a').checkValidity()", "false"),
10632        ("<input id='a' required value='x'>", "document.getElementById('a').checkValidity()", "true"),
10633        ("<input id='a' value='hi'>", "document.getElementById('a').checkValidity()", "true"),
10634        ("<input id='a' type='email' value='nope'>", "document.getElementById('a').checkValidity()", "false"),
10635        ("<input id='a' type='email' value='x@y.com'>", "document.getElementById('a').checkValidity()", "true"),
10636        ("<input id='a' type='number' min='5' value='3'>", "document.getElementById('a').checkValidity()", "false"),
10637        ("<input id='a' type='number' min='5' value='9'>", "document.getElementById('a').checkValidity()", "true"),
10638        ("<input id='a' minlength='3' value='ab'>", "document.getElementById('a').checkValidity()", "false"),
10639        ("<input id='a' maxlength='2' value='abc'>", "document.getElementById('a').checkValidity()", "false"),
10640        ("<input id='a' value='ok'>", "var e=document.getElementById('a'); e.setCustomValidity('bad'); e.checkValidity()", "false"),
10641        ("<input id='a' value='ok'>", "var e=document.getElementById('a'); e.setCustomValidity('bad'); e.setCustomValidity(''); e.checkValidity()", "true"),
10642        ("<input id='a' required value=''>", "document.getElementById('a').validity.valueMissing", "true"),
10643        ("<input id='a' required value='x'>", "document.getElementById('a').validity.valid", "true"),
10644        ("<input id='a' value='hi'>", "document.getElementById('a').willValidate", "true"),
10645        ("<input id='a' type='submit'>", "document.getElementById('a').willValidate", "false"),
10646        // `pattern` 属性が以前はメタ文字を含む複雑なパターンを「誤検知回避のため」
10647        // 無条件で通過させる簡易近似だった。本物の RegExp エンジンで `^(?:pattern)$`
10648        // として正しく検証するよう修正(`\d+`/`[0-9]{3}` 等、通常の JS RegExp 構文)。
10649        ("<input id='a' pattern='[0-9]{3}' value='123'>", "document.getElementById('a').checkValidity()", "true"),
10650        ("<input id='a' pattern='[0-9]{3}' value='ab'>", "document.getElementById('a').checkValidity()", "false"),
10651        ("<input id='a' pattern='\\d+' value='123'>", "document.getElementById('a').checkValidity()", "true"),
10652        ("<input id='a' pattern='\\d+' value='abc'>", "document.getElementById('a').checkValidity()", "false"),
10653        // 空値は非 required なら pattern チェック自体を適用しない(仕様どおり)。
10654        ("<input id='a' pattern='[0-9]{3}' value=''>", "document.getElementById('a').checkValidity()", "true"),
10655        // `stepMismatch`(HTML5 制約検証。min/max はあったが step が丸ごと未対応だった)。
10656        ("<input id='a' type='number' step='2' value='4'>", "document.getElementById('a').checkValidity()", "true"),
10657        ("<input id='a' type='number' step='2' value='3'>", "document.getElementById('a').checkValidity()", "false"),
10658        // step の基準点は min(無ければ 0)。min=1, step=2 なら 1,3,5,... が有効。
10659        ("<input id='a' type='number' min='1' step='2' value='5'>", "document.getElementById('a').checkValidity()", "true"),
10660        ("<input id='a' type='number' min='1' step='2' value='4'>", "document.getElementById('a').checkValidity()", "false"),
10661        // `step=\"any\"` は制約なし。
10662        ("<input id='a' type='number' step='any' value='3.14159'>", "document.getElementById('a').checkValidity()", "true"),
10663        // `validity` オブジェクトが `valid`/`valueMissing`/`customError` の3つしか
10664        // 公開しておらず、`patternMismatch`/`typeMismatch`/`rangeOverflow`/
10665        // `rangeUnderflow`/`stepMismatch`/`tooLong`/`tooShort`/`badInput` という
10666        // 特定の制約を狙い撃ちする定番パターンが常に `undefined` になっていた。
10667        ("<input id='a' pattern='[0-9]{3}' value='ab'>", "document.getElementById('a').validity.patternMismatch", "true"),
10668        ("<input id='a' pattern='[0-9]{3}' value='123'>", "document.getElementById('a').validity.patternMismatch", "false"),
10669        ("<input id='a' type='email' value='nope'>", "document.getElementById('a').validity.typeMismatch", "true"),
10670        ("<input id='a' type='number' min='5' value='3'>", "document.getElementById('a').validity.rangeUnderflow", "true"),
10671        ("<input id='a' type='number' max='5' value='9'>", "document.getElementById('a').validity.rangeOverflow", "true"),
10672        ("<input id='a' type='number' step='2' value='3'>", "document.getElementById('a').validity.stepMismatch", "true"),
10673        ("<input id='a' maxlength='2' value='abc'>", "document.getElementById('a').validity.tooLong", "true"),
10674        ("<input id='a' minlength='3' value='ab'>", "document.getElementById('a').validity.tooShort", "true"),
10675        ("<input id='a' type='number' value='abc'>", "document.getElementById('a').validity.badInput", "true"),
10676        // **重要**: `<form>.checkValidity()` が子孫コントロールを一切見ず、`<form>`
10677        // タグ自身に対する検証(常に無条件で true)を返すだけだったバグ。
10678        (
10679            "<form id='f3'><input required value=''></form>",
10680            "document.getElementById('f3').checkValidity()",
10681            "false",
10682        ),
10683        (
10684            "<form id='f3'><input required value='x'></form>",
10685            "document.getElementById('f3').checkValidity()",
10686            "true",
10687        ),
10688        (
10689            "<form id='f3'><input required value='x'><input type='email' value='nope'></form>",
10690            "document.getElementById('f3').checkValidity()",
10691            "false",
10692        ),
10693        // `checkValidity()`/`.validationMessage`/`.validity`が`get_attr(idx,"value")`
10694        // だけを見ており、`value`属性を持たない`<select>`(選択状態は子の
10695        // `<option selected>`が持つ)や、初期値がテキストノードで表現される
10696        // `<textarea>`では、実際には値があっても常に空文字列=未入力として
10697        // 誤って`required`違反になっていたバグ。`.value`ゲッターは既に正しい
10698        // フォールバックを持っていたが、制約バリデーション側に伝わっていなかった。
10699        (
10700            "<select id='a' required><option value='x' selected>X</option></select>",
10701            "document.getElementById('a').checkValidity()",
10702            "true",
10703        ),
10704        (
10705            "<select id='a' required><option value='x' selected>X</option></select>",
10706            "document.getElementById('a').validity.valueMissing",
10707            "false",
10708        ),
10709        (
10710            "<textarea id='a' required>hello</textarea>",
10711            "document.getElementById('a').checkValidity()",
10712            "true",
10713        ),
10714        (
10715            "<textarea id='a' required></textarea>",
10716            "document.getElementById('a').checkValidity()",
10717            "false",
10718        ),
10719        (
10720            "<form id='f4'><select required><option value='x' selected>X</option></select></form>",
10721            "document.getElementById('f4').checkValidity()",
10722            "true",
10723        ),
10724        // `required`のcheckbox/radioは「値」でなく「チェック状態」で判定すべき
10725        // (HTML5仕様)だが、以前は他のinputと同じ「value属性が空か」で判定して
10726        // いたため、value属性を持たないcheckedなチェックボックスが常にrequired
10727        // 違反(valueが空文字列扱い)、逆にvalue属性だけ設定した未チェックの
10728        // チェックボックスが常に妥当、というチェック状態と無関係な結果になっていた。
10729        (
10730            "<input id='a' type='checkbox' required>",
10731            "document.getElementById('a').checkValidity()",
10732            "false",
10733        ),
10734        (
10735            "<input id='a' type='checkbox' required checked>",
10736            "document.getElementById('a').checkValidity()",
10737            "true",
10738        ),
10739        (
10740            "<input id='a' type='checkbox' value='yes' required>",
10741            "document.getElementById('a').validity.valueMissing",
10742            "true",
10743        ),
10744        (
10745            "<input id='a' type='checkbox' required checked>",
10746            "document.getElementById('a').validity.valueMissing",
10747            "false",
10748        ),
10749        // radioはグループ内のいずれかがcheckedであれば充足(自身がcheckedで
10750        // なくても良い)。
10751        (
10752            "<input id='a' type='radio' name='g' required><input type='radio' name='g' checked>",
10753            "document.getElementById('a').checkValidity()",
10754            "true",
10755        ),
10756        (
10757            "<input id='a' type='radio' name='g' required><input type='radio' name='g'>",
10758            "document.getElementById('a').checkValidity()",
10759            "false",
10760        ),
10761        // `type="range"`は`type="number"`と同じmin/max/step制約検証規則を持つ
10762        // (`.stepUp()`/`.valueAsNumber`は既に両者を同列に扱っていたが、
10763        // `validity.rangeOverflow`/`.rangeUnderflow`/`.stepMismatch`だけ
10764        // `number`型限定になっており、range型では常にfalse固定だった)。
10765        (
10766            "<input id='a' type='range' min='0' max='10' value='20'>",
10767            "document.getElementById('a').validity.rangeOverflow",
10768            "true",
10769        ),
10770        (
10771            "<input id='a' type='range' min='0' max='10' value='-5'>",
10772            "document.getElementById('a').validity.rangeUnderflow",
10773            "true",
10774        ),
10775        (
10776            "<input id='a' type='range' min='0' max='10' value='5'>",
10777            "document.getElementById('a').checkValidity()",
10778            "true",
10779        ),
10780        (
10781            "<input id='a' type='range' min='0' max='10' step='2' value='3'>",
10782            "document.getElementById('a').validity.stepMismatch",
10783            "true",
10784        ),
10785        // `type="date"`/`type="month"`の形式チェックが丸ごと未対応だった
10786        // (`input.valueAsDate`は既存の`input_value_as_date_ms()`で妥当性を
10787        // 判定していたが、`validity.typeMismatch`には配線されておらず、
10788        // 不正な日付文字列を代入しても常に`typeMismatch: false`だった)。
10789        (
10790            "<input id='a' type='date' value='2026-07-17'>",
10791            "document.getElementById('a').validity.typeMismatch",
10792            "false",
10793        ),
10794        (
10795            "<input id='a' type='date' value='not-a-date'>",
10796            "document.getElementById('a').validity.typeMismatch",
10797            "true",
10798        ),
10799        (
10800            "<input id='a' type='month' value='2026-07'>",
10801            "document.getElementById('a').checkValidity()",
10802            "true",
10803        ),
10804        (
10805            "<input id='a' type='date' value='2026-13-99'>",
10806            "document.getElementById('a').checkValidity()",
10807            "false",
10808        ),
10809        // `<input type="email" multiple>`はカンマ区切りで複数アドレスを入力できる
10810        // 仕様だが、以前は`is_valid_email(value)`を値全体にそのまま適用しており、
10811        // 2件目以降のアドレスやカンマを含む正当な値が常に不正判定されていた。
10812        (
10813            "<input id='a' type='email' multiple value='a@b.com,c@d.com'>",
10814            "document.getElementById('a').checkValidity()",
10815            "true",
10816        ),
10817        (
10818            "<input id='a' type='email' multiple value='a@b.com, c@d.com'>",
10819            "document.getElementById('a').checkValidity()",
10820            "true",
10821        ),
10822        (
10823            "<input id='a' type='email' multiple value='a@b.com,nope'>",
10824            "document.getElementById('a').checkValidity()",
10825            "false",
10826        ),
10827        (
10828            // `multiple`が無ければ従来どおりカンマ区切りは単一アドレスとして不正。
10829            "<input id='a' type='email' value='a@b.com,c@d.com'>",
10830            "document.getElementById('a').checkValidity()",
10831            "false",
10832        ),
10833        // `type="date"`/`type="month"`の`min`/`max`範囲制約(`type="number"`と同様に
10834        // 対応可能な仕様なのに丸ごと欠けていた)。
10835        (
10836            "<input id='a' type='date' min='2026-01-01' max='2026-12-31' value='2025-12-31'>",
10837            "document.getElementById('a').validity.rangeUnderflow",
10838            "true",
10839        ),
10840        (
10841            "<input id='a' type='date' min='2026-01-01' max='2026-12-31' value='2027-01-01'>",
10842            "document.getElementById('a').validity.rangeOverflow",
10843            "true",
10844        ),
10845        (
10846            "<input id='a' type='date' min='2026-01-01' max='2026-12-31' value='2026-06-15'>",
10847            "document.getElementById('a').checkValidity()",
10848            "true",
10849        ),
10850        (
10851            "<input id='a' type='month' min='2026-03' max='2026-09' value='2026-01'>",
10852            "document.getElementById('a').checkValidity()",
10853            "false",
10854        ),
10855        // `<fieldset disabled>`の子孫コントロールは自身に`disabled`属性が無くても
10856        // 暗黙に無効化される(HTML5仕様)。以前は要素自身の`disabled`属性しか
10857        // 見ておらず、`.disabled`が常に`false`・制約バリデーションも無効化
10858        // されずに常に対象内のままだった。
10859        (
10860            "<fieldset disabled><input id='a' required></fieldset>",
10861            "document.getElementById('a').disabled",
10862            "true",
10863        ),
10864        (
10865            "<fieldset disabled><input id='a' required></fieldset>",
10866            "document.getElementById('a').checkValidity()",
10867            "true",
10868        ),
10869        (
10870            "<fieldset><input id='a' required></fieldset>",
10871            "document.getElementById('a').disabled",
10872            "false",
10873        ),
10874        (
10875            // 無効化されている以上、値が実際には空でも valueMissing にはならない
10876            // (disabled要素は制約バリデーションの対象外という仕様どおり)。
10877            "<fieldset disabled><input id='a' required value=''></fieldset>",
10878            "document.getElementById('a').validity.valueMissing",
10879            "false",
10880        ),
10881        // `form="formId"`属性(HTML5仕様の「form owner」)による、`<form>`の外に
10882        // 置かれたコントロールとの明示的な関連付けが丸ごと未対応だった。
10883        // `element.form`は祖先探索(`closest_tag`)しか見ておらず、
10884        // `<form>.checkValidity()`もフォームの子孫しか走査していなかったため、
10885        // フォーム外配置のコントロールが両方から漏れていた。
10886        (
10887            "<form id='f5'></form><input id='a' form='f5'>",
10888            "document.getElementById('a').form === document.getElementById('f5')",
10889            "true",
10890        ),
10891        (
10892            "<form id='f5'></form><input id='a' form='f5' required>",
10893            "document.getElementById('f5').checkValidity()",
10894            "false",
10895        ),
10896        (
10897            "<form id='f5'></form><input id='a' form='f5' required value='x'>",
10898            "document.getElementById('f5').checkValidity()",
10899            "true",
10900        ),
10901        (
10902            // `form`属性が無い通常の input はこれまでどおり祖先探索のみ。
10903            "<div><input id='a'></div>",
10904            "document.getElementById('a').form",
10905            "null",
10906        ),
10907        // `type="datetime-local"`の`validity.typeMismatch`/`min`/`max`が丸ごと
10908        // 未対応だった。`input_value_as_date_ms()`は既存の`parse_iso_date`が
10909        // `YYYY-MM-DDTHH:MM`形式を既にサポートしているため、`date`/`month`と
10910        // 同じ扱いに含めるだけで対応できた。
10911        (
10912            "<input id='a' type='datetime-local' value='2026-07-17T14:30'>",
10913            "document.getElementById('a').validity.typeMismatch",
10914            "false",
10915        ),
10916        (
10917            "<input id='a' type='datetime-local' value='not-a-datetime'>",
10918            "document.getElementById('a').checkValidity()",
10919            "false",
10920        ),
10921        (
10922            "<input id='a' type='datetime-local' min='2026-01-01T00:00' max='2026-12-31T23:59' value='2025-06-01T00:00'>",
10923            "document.getElementById('a').validity.rangeUnderflow",
10924            "true",
10925        ),
10926        // `<details name="...">`排他グループの初期状態正規化(HTML5。
10927        // 丸ごと未対応だった)。同じ`name`を持つ複数の`<details open>`が
10928        // HTML記述時点で並んでいた場合、文書順で最後の1つだけが開いた
10929        // ままで残り、それより前のものは`build_from`(パース直後)の
10930        // 時点で自動的に閉じられる必要がある。2026-07-18 発見・実装)。
10931        (
10932            "<details id='a' name='g' open></details><details id='b' name='g' open></details>",
10933            "document.getElementById('a').open+','+document.getElementById('b').open",
10934            "false,true",
10935        ),
10936        (
10937            "<details id='a' name='g' open></details><details id='b' open></details>",
10938            "document.getElementById('a').open+','+document.getElementById('b').open",
10939            "true,true",
10940        ),
10941        // `HTMLCollection` / `HTMLFormControlsCollection` の `item(index)` / `namedItem(name)`
10942        // および `classList.value` ゲッター/セッター。
10943        (
10944            "<div id='wrap'><p id='t' name='foo'></p><span id='s'></span></div>",
10945            "document.getElementById('wrap').children.item(0).id + ',' + document.getElementById('wrap').children.namedItem('s').tagName + ',' + document.getElementById('wrap').children.namedItem('foo').id",
10946            "t,SPAN,t",
10947        ),
10948        (
10949            "<div id='wrap'><span></span></div>",
10950            "String(document.getElementById('wrap').children.item(99))",
10951            "null",
10952        ),
10953        (
10954            "<form id='f'><input id='i' name='user'></form>",
10955            "document.getElementById('f').elements.item(0).id + ',' + document.getElementById('f').elements.namedItem('user').id",
10956            "i,i",
10957        ),
10958        (
10959            "<form id='f'><input type='radio' name='r' value='v1'><input type='radio' name='r' value='v2' checked></form>",
10960            "var f=document.getElementById('f'); var list=f.elements.namedItem('r'); var v1=list.value; var len=list.length; list.value='v1'; var v2=list.value; var c0=list[0].checked; v1+','+len+','+v2+','+c0",
10961            "v2,2,v1,true",
10962        ),
10963        (
10964            "<div id='d' class='a b'></div>",
10965            "var d=document.getElementById('d'); var v=d.classList.value; d.classList.value='c d'; v+','+d.className",
10966            "a b,c d",
10967        ),
10968    ];
10969    total += validity_cases.len();
10970    for (html, src, expect) in validity_cases {
10971        let mut rt = JsRuntime::new();
10972        rt.dom
10973            .borrow_mut()
10974            .build_from(&crate::os_lib::dom::parse_html(html));
10975        match rt.eval(src) {
10976            Ok(v) if &v.to_js_string() == expect => passed += 1,
10977            Ok(v) => crate::println!(
10978                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
10979                src,
10980                v.to_js_string(),
10981                expect
10982            ),
10983            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
10984        }
10985    }
10986
10987    // ES modules(import/export): 複数モジュールを define_module で登録し、
10988    // import 側スクリプトを評価して名前解決・default・namespace・再エクスポートを検証。
10989    {
10990        // 単純な named export を持つ算術モジュール。
10991        let math_src = "export const PI = 3; export function sq(x){ return x*x; } \
10992                        const e = 2; export { e as E };";
10993        // default export と named export の混在。
10994        let greet_src = "export default function(n){ return 'hi ' + n; } \
10995                          export const lang = 'ja';";
10996        // 別モジュールからの再エクスポート(集約モジュール)。
10997        let index_src = "export { PI, sq } from 'math'; export * from 'math';";
10998        // 循環 import: a は b を import し、b は a を import する。
10999        let cyc_a = "import { bVal } from 'cycb'; export const aVal = 10; \
11000                      export function getB(){ return bVal; }";
11001        let cyc_b = "import { aVal } from 'cyca'; export const bVal = 20;";
11002
11003        let module_checks: &[(&str, &str)] = &[
11004            // named import + 関数呼び出し。
11005            ("import { PI, sq } from 'math'; sq(PI)", "9"),
11006            // as による別名 export を import。
11007            ("import { E } from 'math'; E", "2"),
11008            // default import。
11009            ("import greet from 'greet'; greet('bob')", "hi bob"),
11010            // default + named を同時 import。
11011            ("import greet, { lang } from 'greet'; greet(lang)", "hi ja"),
11012            // namespace import。
11013            ("import * as M from 'math'; M.sq(4) + M.PI", "19"),
11014            // 再エクスポート(named)。
11015            ("import { PI } from 'index'; PI", "3"),
11016            // 再エクスポート(export *)経由の関数。
11017            ("import { sq } from 'index'; sq(5)", "25"),
11018            // 循環 import: a 経由で b の値を取得。
11019            ("import { getB } from 'cyca'; getB()", "20"),
11020            // 副作用のみ import(エラーにならず undefined 評価が通ること)。
11021            ("import 'math'; 42", "42"),
11022        ];
11023        total += module_checks.len();
11024        for (src, expect) in module_checks {
11025            let mut rt = JsRuntime::new();
11026            rt.define_module("math", math_src);
11027            rt.define_module("greet", greet_src);
11028            rt.define_module("index", index_src);
11029            rt.define_module("cyca", cyc_a);
11030            rt.define_module("cycb", cyc_b);
11031            match rt.eval(src) {
11032                Ok(v) if &v.to_js_string() == expect => passed += 1,
11033                Ok(v) => crate::println!(
11034                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
11035                    src,
11036                    v.to_js_string(),
11037                    expect
11038                ),
11039                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
11040            }
11041        }
11042    }
11043
11044    // `window.scrollTo(x, y)`/`window.scrollBy(x, y)`(丸ごと未対応だった。
11045    // 以前は`scrollTo`が孤立した`SCROLL_Y` staticへ書くだけ、`scrollBy`は
11046    // `dom_noop`のままで、どちらも`window.scrollY`や実際の描画位置に一切
11047    // 反映されない死んだ経路だった。2026-07-16 発見・実装)。実際の視覚的
11048    // 反映は`web_engine/render.rs`の`draw()`側で行われヘッドレスJS自己テスト
11049    // からは駆動できないため、ここではJS→`DomBridge`への配線(`window_
11050    // scroll_to`/`window_scroll_by`が正しい保留値を書き込むこと)のみを
11051    // 直接Rustレベルで検証する。
11052    {
11053        total += 1;
11054        let mut rt = JsRuntime::new();
11055        let _ = rt.eval("window.scrollTo(0, 120);");
11056        let y = rt.dom.borrow().pending_scroll_abs_y;
11057        if y == Some(120) {
11058            passed += 1;
11059        } else {
11060            crate::println!(
11061                "JS_SELFTEST FAIL: window.scrollTo pending_scroll_abs_y => `{:?}` (want `Some(120)`)",
11062                y
11063            );
11064        }
11065    }
11066    {
11067        total += 1;
11068        let mut rt = JsRuntime::new();
11069        let _ = rt.eval("window.scrollBy(0, 30); window.scrollBy(0, 15);");
11070        let dy = rt.dom.borrow().pending_scroll_by_y;
11071        if dy == Some(45) {
11072            passed += 1;
11073        } else {
11074            crate::println!(
11075                "JS_SELFTEST FAIL: window.scrollBy pending_scroll_by_y (累積) => `{:?}` (want `Some(45)`)",
11076                dy
11077            );
11078        }
11079    }
11080    // `element.scrollIntoView()`が丸ごとno-opだった(実サイト www.sugi-lab.net の
11081    // ナビゲーションリンクの`target.scrollIntoView({behavior:'smooth',
11082    // block:'start'})`が完全に無効化されていた。2026-07-22発見・修正)。
11083    // `window.scrollTo`と同じ`pending_scroll_abs_y`機構を再利用しているかを
11084    // 直接Rustレベルで検証する(対象要素のY座標250px、scrollY=0の状態から
11085    // `scrollIntoView()`を呼ぶと、絶対Y座標250へスクロールする保留値が
11086    // 設定されるはず)。
11087    {
11088        total += 1;
11089        let mut rt = JsRuntime::new();
11090        rt.dom
11091            .borrow_mut()
11092            .build_from(&crate::os_lib::dom::parse_html("<div id='t'></div>"));
11093        let idx = rt.dom.borrow().get_element_by_id("t");
11094        if let Some(i) = idx {
11095            rt.dom.borrow_mut().set_rect(i, 0, 250, 100, 50);
11096        }
11097        let _ = rt.eval("document.getElementById('t').scrollIntoView();");
11098        let y = rt.dom.borrow().pending_scroll_abs_y;
11099        if y == Some(250) {
11100            passed += 1;
11101        } else {
11102            crate::println!(
11103                "JS_SELFTEST FAIL: element.scrollIntoView pending_scroll_abs_y => `{:?}` (want `Some(250)`)",
11104                y
11105            );
11106        }
11107    }
11108
11109    let dom_parser_cases: &[(&str, &str)] = &[
11110        (
11111            "var p = new DOMParser(); var doc = p.parseFromString('<div id=\"dp\">hello</div>', 'text/html'); doc.getElementById('dp').textContent",
11112            "hello",
11113        ),
11114        (
11115            "var p = new DOMParser(); var doc = p.parseFromString('<svg viewBox=\"0 0 100 100\"><clipPath id=\"cp\"><rect width=\"50\"/></clipPath></svg>', 'image/svg+xml'); doc.contentType === 'image/svg+xml' && doc.documentElement.tagName === 'svg' && doc.querySelector('clipPath').getAttribute('id') === 'cp' && doc.querySelector('rect').getAttribute('width') === '50'",
11116            "true",
11117        ),
11118        (
11119            "var p = new DOMParser(); var doc = p.parseFromString('<root><child></other></root>', 'application/xml'); var errs = doc.getElementsByTagName('parsererror'); errs.length === 1 && errs[0].textContent.includes('XML Parsing Error')",
11120            "true",
11121        ),
11122        (
11123            "var ok = false; try { new DOMParser().parseFromString('<a/>', 'text/invalid'); } catch(e) { ok = e.toString().includes('TypeError'); }; ok",
11124            "true",
11125        ),
11126    ];
11127    total += dom_parser_cases.len();
11128    for (code, expect) in dom_parser_cases {
11129        let mut rt = JsRuntime::new();
11130        let res = rt.eval(code);
11131        let got = res.map(|v| v.to_js_string()).unwrap_or_else(|e| alloc::format!("ERR: {:?}", e));
11132        if &got == expect {
11133            passed += 1;
11134        } else {
11135            crate::println!(
11136                "JS_SELFTEST FAIL: DOMParser code {:?} => `{}` (want `{}`)",
11137                code,
11138                got,
11139                expect
11140            );
11141        }
11142    }
11143
11144    // DOMMatrix / DOMPoint / URLPattern / Iterator.concat / Iterator.from 自己テスト
11145    let geom_web_cases = [
11146        ("const m = new DOMMatrix([1,0,0,1,10,20]); m.a === 1 && m.e === 10", "true"),
11147        ("const m = new DOMMatrix(); m.translateSelf(10, 20); m.e === 10 && m.f === 20", "true"),
11148        ("const m = new DOMMatrix(); m.scaleSelf(2, 3); m.a === 2 && m.d === 3", "true"),
11149        ("const m = new DOMMatrix(); const p = new DOMPoint(5, 10); const p2 = m.translate(10, 20).transformPoint(p); p2.x === 15 && p2.y === 30", "true"),
11150        ("const m2 = DOMMatrix.fromMatrix({a: 2, d: 3, e: 5, f: 10}); m2.a === 2 && m2.e === 5", "true"),
11151        ("const p3 = new DOMPoint(1, 2).matrixTransform(new DOMMatrix([2,0,0,3,10,20])); p3.x === 12 && p3.y === 26", "true"),
11152        ("const pat = new URLPattern({ pathname: '/api/*' }); pat.test('/api/users')", "true"),
11153        ("const res = new URLPattern({ pathname: '/users/:id' }).exec('https://example.com/users/42'); res.pathname.groups.id", "42"),
11154        ("const it = Iterator.concat([1, 2], [3, 4]); it.toArray().join(',')", "1,2,3,4"),
11155        ("const it = Iterator.from([10, 20, 30]); it.take(2).toArray().join(',')", "10,20"),
11156        ("new Intl.DurationFormat('en', {style:'long'}).format({hours:1, minutes:30})", "1 hour, 30 minutes"),
11157        ("new Intl.DurationFormat('en').resolvedOptions().locale", "en-US"),
11158        ("Iterator.range(1, 5).toArray().join(',')", "1,2,3,4"),
11159        ("Iterator.range(0, 10, 2).toArray().join(',')", "0,2,4,6,8"),
11160        ("Iterator.range(5, 1, -1).toArray().join(',')", "5,4,3,2"),
11161    ];
11162    total += geom_web_cases.len();
11163    for (code, expect) in geom_web_cases {
11164        let mut rt = JsRuntime::new();
11165        let res = rt.eval(code);
11166        let got = res.map(|v| v.to_js_string()).unwrap_or_else(|e| alloc::format!("ERR: {:?}", e));
11167        if &got == expect {
11168            passed += 1;
11169        } else {
11170            crate::println!(
11171                "JS_SELFTEST FAIL: Geom/Web API code {:?} => `{}` (want `{}`)",
11172                code,
11173                got,
11174                expect
11175            );
11176        }
11177    }
11178
11179    let template_html = r#"<div><template id="tmpl"><span>Hello</span></template></div>"#;
11180    let template_cases = [
11181        ("document.createElement('template') instanceof HTMLTemplateElement", "true"),
11182        ("const t = document.createElement('template'); t.content.nodeType === 11", "true"),
11183        ("const t = document.getElementById('tmpl'); t.content.childNodes.length", "1"),
11184        ("const t = document.getElementById('tmpl'); t.content.children[0].tagName", "SPAN"),
11185        ("const t = document.getElementById('tmpl'); const f = t.content.cloneNode(true); f.nodeType === 11 && f.children[0].textContent === 'Hello'", "true"),
11186        ("const s = document.createElement('select'); const o = document.createElement('option'); o.value='v1'; o.text='t1'; s.add(o); s.options.length === 1 && s.value === 'v1'", "true"),
11187        ("const u = new URL('https://www.sugi-lab.net/?a=1'); u.searchParams.set('b', '2'); u.search === '?a=1&b=2'", "true"),
11188        ("const div = document.createElement('div'); div.setAttribute('id', 'd1'); const c = div.cloneNode(true); c.id === 'd1' && c !== div", "true"),
11189        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx !== null && ctx.canvas === cv", "true"),
11190        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.measureText('test').width > 0", "true"),
11191        ("const cv = document.createElement('canvas'); cv.toDataURL().startsWith('data:image/png;base64,')", "true"),
11192        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.lineWidth = 10; ctx.reset(); ctx.lineWidth === 1", "true"),
11193        ("const p = new Path2D('M10 10 H 90 V 90 H 10 Z'); typeof p.moveTo === 'function' && typeof p.roundRect === 'function'", "true"),
11194        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const p = new Path2D('M 10 10 H 90 V 90 Z'); ctx.fill(p); ctx.stroke(p); ctx.clip(p); true", "true"),
11195        ("const p1 = new Path2D(); p1.rect(0, 0, 10, 10); const p2 = new Path2D(p1); p2.addPath(p1); typeof p2.closePath === 'function'", "true"),
11196        ("const div = document.createElement('div'); div.getRootNode() === div", "true"),
11197        ("const div = document.getElementById('tmpl'); div.getRootNode() === document", "true"),
11198        ("const el = document.createElement('div'); document.adoptNode(el) === el", "true"),
11199        ("const el = document.createElement('div'); document.importNode(el, true) !== null", "true"),
11200        ("ArrayBuffer.isView(new Uint8Array(5)) && !ArrayBuffer.isView(new ArrayBuffer(5))", "true"),
11201        ("Math.clamp(15, 0, 10) === 10 && Math.clamp(-5, 0, 10) === 0 && Math.clamp(5, 0, 10) === 5", "true"),
11202        ("const io = new IntersectionObserver(()=>{}); typeof io.takeRecords === 'function' && io.takeRecords().length === 0", "true"),
11203        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.fillStyle = 'red'; ctx.save(); ctx.fillStyle = 'blue'; ctx.restore(); ctx.fillStyle === 'red'", "true"),
11204        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.strokeStyle = 'green'; ctx.font = '24px serif'; ctx.save(); ctx.strokeStyle = 'purple'; ctx.font = '12px sans-serif'; ctx.restore(); ctx.strokeStyle === 'green' && ctx.font === '24px serif'", "true"),
11205        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); typeof ctx.strokeText === 'function'", "true"),
11206        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.imageSmoothingEnabled === true", "true"),
11207        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.imageSmoothingEnabled = false; ctx.imageSmoothingEnabled === false", "true"),
11208        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.imageSmoothingEnabled = false; ctx.save(); ctx.imageSmoothingEnabled = true; ctx.restore(); ctx.imageSmoothingEnabled === false", "true"),
11209        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const g = ctx.createLinearGradient(0, 0, 100, 0); g.addColorStop(0, 'red'); g.addColorStop(1, 'blue'); ctx.fillStyle = g; typeof ctx.fillRect === 'function'", "true"),
11210        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const g = ctx.createRadialGradient(50, 50, 0, 50, 50, 50); g.addColorStop(0, 'white'); g.addColorStop(1, 'black'); ctx.fillStyle = g; ctx.fillRect(0, 0, 100, 100); true", "true"),
11211        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.beginPath(); ctx.moveTo(0, 0); ctx.arcTo(100, 0, 100, 100, 20); ctx.stroke(); typeof ctx.arcTo === 'function'", "true"),
11212        ("const p = new Path2D(); p.moveTo(0, 0); p.arcTo(50, 0, 50, 50, 10); typeof p.arcTo === 'function'", "true"),
11213        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const g = ctx.createConicGradient(0, 50, 50); g.addColorStop(0, 'red'); g.addColorStop(1, 'yellow'); ctx.fillStyle = g; ctx.fillRect(0, 0, 100, 100); true", "true"),
11214        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.beginPath(); ctx.rect(10, 10, 80, 80); ctx.isPointInPath(50, 50) === true && ctx.isPointInPath(5, 5) === false", "true"),
11215        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.beginPath(); ctx.moveTo(0, 50); ctx.lineTo(100, 50); ctx.lineWidth = 10; ctx.isPointInStroke(50, 52) === true && ctx.isPointInStroke(50, 60) === false", "true"),
11216        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const m = ctx.getTransform(); m.a === 1 && m.d === 1 && m.e === 0 && m.f === 0 && m.is2D === true && m.isIdentity === true", "true"),
11217        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.translate(30, 40); ctx.scale(2, 3); const m = ctx.getTransform(); m.a === 2 && m.d === 3 && m.e === 30 && m.f === 40 && m.isIdentity === false", "true"),
11218        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.setTransform(1, 2, 3, 4, 5, 6); const m = ctx.getTransform(); m.a === 1 && m.b === 2 && m.c === 3 && m.d === 4 && m.e === 5 && m.f === 6", "true"),
11219        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.beginPath(); ctx.roundRect(0, 0, 100, 100, [10, 20]); true", "true"),
11220        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); let caught = false; try { ctx.roundRect(0, 0, 10, 10, -5); } catch (e) { caught = e.name === 'RangeError'; } caught", "true"),
11221        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); let caught = false; try { ctx.roundRect(0, 0, 10, 10, [1, 2, 3, 4, 5]); } catch (e) { caught = e.name === 'RangeError'; } caught", "true"),
11222        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); let caught = false; try { ctx.roundRect(0, 0, 10, 10, []); } catch (e) { caught = e.name === 'RangeError'; } caught", "true"),
11223        ("let caught = false; try { const p = new Path2D(); p.roundRect(0, 0, 10, 10, -1); } catch (e) { caught = e.name === 'RangeError'; } caught", "true"),
11224        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.shadowColor === 'rgba(0, 0, 0, 0)' && ctx.shadowBlur === 0 && ctx.shadowOffsetX === 0 && ctx.shadowOffsetY === 0", "true"),
11225        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.shadowColor = 'red'; ctx.shadowBlur = 5; ctx.shadowOffsetX = 10; ctx.shadowOffsetY = 15; ctx.shadowColor === 'red' && ctx.shadowBlur === 5 && ctx.shadowOffsetX === 10 && ctx.shadowOffsetY === 15", "true"),
11226        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.shadowColor = 'red'; ctx.shadowOffsetX = 8; ctx.save(); ctx.shadowColor = 'blue'; ctx.shadowOffsetX = 20; ctx.restore(); ctx.shadowColor === 'red' && ctx.shadowOffsetX === 8", "true"),
11227        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.shadowColor = 'rgba(0,0,0,0.5)'; ctx.shadowOffsetX = 4; ctx.shadowOffsetY = 4; ctx.fillRect(0, 0, 50, 50); ctx.strokeRect(0, 0, 50, 50); ctx.fillText('test', 10, 10); ctx.strokeText('test', 10, 30); true", "true"),
11228        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.filter === 'none'", "true"),
11229        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.filter = 'blur(5px) grayscale(100%)'; ctx.filter === 'blur(5px) grayscale(100%)'", "true"),
11230        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.filter = 'invert(100%)'; ctx.save(); ctx.filter = 'contrast(200%)'; ctx.restore(); ctx.filter === 'invert(100%)'", "true"),
11231        ("const p = new Path2D('M 0 0 A 50 50 0 0 1 100 0'); const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.fill(p); ctx.stroke(p); true", "true"),
11232        ("const p = new Path2D('M 0 0 Q 10 20 50 0 T 100 0 Z'); const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.fill(p); true", "true"),
11233        ("const p1 = new Path2D(); const p2 = new Path2D('M 10 10 L 20 20'); p1.addPath(p2, {a: 1, b: 0, c: 0, d: 1, e: 100, f: 100}); const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.stroke(p1); true", "true"),
11234        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.globalCompositeOperation === 'source-over'", "true"),
11235        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.globalCompositeOperation = 'destination-out'; ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.restore(); ctx.globalCompositeOperation === 'destination-out'", "true"),
11236        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.globalCompositeOperation = 'destination-out'; ctx.fillRect(0, 0, 10, 10); true", "true"),
11237        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const img = ctx.createImageData(2, 2); const pat = ctx.createPattern(img, 'repeat'); typeof pat === 'object' && typeof pat.setTransform === 'function'", "true"),
11238        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const img = ctx.createImageData(2, 2); const pat = ctx.createPattern(img, 'repeat'); pat.setTransform({a: 2, b: 0, c: 0, d: 2, e: 10, f: 10}); ctx.fillStyle = pat; ctx.fillRect(0, 0, 50, 50); true", "true"),
11239        ("const cv1 = document.createElement('canvas'); cv1.width = 10; cv1.height = 10; const cv2 = document.createElement('canvas'); const ctx2 = cv2.getContext('2d'); const pat = ctx2.createPattern(cv1, 'no-repeat'); ctx2.fillStyle = pat; ctx2.beginPath(); ctx2.rect(0, 0, 100, 100); ctx2.fill(); true", "true"),
11240        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.direction === 'inherit'", "true"),
11241        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.direction = 'rtl'; ctx.direction === 'rtl'", "true"),
11242        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.direction = 'rtl'; ctx.save(); ctx.direction = 'ltr'; ctx.restore(); ctx.direction === 'rtl'", "true"),
11243        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.direction = 'rtl'; ctx.textAlign = 'start'; ctx.fillText('abc', 100, 100); ctx.strokeText('abc', 100, 100); true", "true"),
11244        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const g = ctx.createLinearGradient(0, 0, 100, 0); g.addColorStop(0, 'red'); g.addColorStop(1, 'blue'); ctx.strokeStyle = g; ctx.lineWidth = 4; ctx.strokeRect(10, 10, 80, 80); true", "true"),
11245        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const g = ctx.createLinearGradient(0, 0, 100, 0); g.addColorStop(0, 'red'); g.addColorStop(1, 'blue'); ctx.strokeStyle = g; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(10, 10); ctx.lineTo(90, 90); ctx.stroke(); true", "true"),
11246        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const img = ctx.createImageData(2, 2); const pat = ctx.createPattern(img, 'repeat'); ctx.strokeStyle = pat; ctx.lineWidth = 6; ctx.strokeRect(10, 10, 80, 80); true", "true"),
11247        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const img = ctx.createImageData(100, 50); img.width === 100 && img.height === 50 && img.data.length === 20000 && img.colorSpace === 'srgb'", "true"),
11248        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const img = ctx.createImageData(10, 10, { colorSpace: 'display-p3' }); img.colorSpace === 'display-p3'", "true"),
11249        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); const img1 = ctx.createImageData(20, 30); const img2 = ctx.createImageData(img1); img2.width === 20 && img2.height === 30 && img2.colorSpace === 'srgb'", "true"),
11250        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 10, 10); const d = ctx.getImageData(0, 0, 2, 2, { colorSpace: 'display-p3' }); d.width === 2 && d.height === 2 && d.data[0] === 255 && d.data[1] === 0 && d.data[2] === 0 && d.data[3] === 255 && d.colorSpace === 'display-p3'", "true"),
11251        ("const img = new ImageData(30, 20); img.width === 30 && img.height === 20 && img.colorSpace === 'srgb'", "true"),
11252        ("const img = new ImageData(30, 20, { colorSpace: 'display-p3' }); img.colorSpace === 'display-p3'", "true"),
11253        ("typeof window.ImageData === 'function' && typeof ImageData === 'function'", "true"),
11254        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.beginPath(); ctx.moveTo(0, 50); ctx.lineTo(100, 50); ctx.lineWidth = 10; ctx.setLineDash([20, 20]); ctx.isPointInStroke(10, 50) === true && ctx.isPointInStroke(30, 50) === false", "true"),
11255        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.lineWidth = 10; const p = new Path2D('M 0 50 L 100 50'); ctx.isPointInStroke(p, 50, 50) === true && ctx.isPointInStroke(p, 60) === false", "true"),
11256        ("const ro = new ResizeObserver(()=>{}); typeof ro.unobserve === 'function' && typeof ro.disconnect === 'function'", "true"),
11257        ("let count = 0; const el = document.createElement('div'); const ro = new ResizeObserver(entries => { count += entries.length; }); ro.observe(el); ro.observe(el); count === 1", "true"),
11258        ("let count = 0; const el = document.createElement('div'); const ro = new ResizeObserver(entries => { count++; }); ro.unobserve(el); ro.disconnect(); count === 0", "true"),
11259        ("let matchTarget = false; const el = document.createElement('div'); const ro = new ResizeObserver(entries => { matchTarget = (entries[0].target === el); }); ro.observe(el, { box: 'border-box' }); matchTarget === true", "true"),
11260        ("NodeFilter.SHOW_ELEMENT === 1 && NodeFilter.FILTER_ACCEPT === 1 && NodeFilter.FILTER_REJECT === 2 && NodeFilter.FILTER_SKIP === 3", "true"),
11261        ("window.NodeFilter.SHOW_ALL === 0xFFFFFFFF && NodeFilter.SHOW_TEXT === 4", "true"),
11262        ("const root = document.createElement('div'); const tw = document.createTreeWalker(root, NodeFilter.SHOW_ALL); tw.root === root && tw.currentNode === root", "true"),
11263        ("const p = document.createElement('p'); const b = document.createElement('b'); p.appendChild(b); const tw = document.createTreeWalker(p, NodeFilter.SHOW_ELEMENT); tw.firstChild() === b && tw.currentNode === b && tw.parentNode() === p", "true"),
11264        ("const div = document.createElement('div'); const s1 = document.createElement('span'); const s2 = document.createElement('b'); div.appendChild(s1); div.appendChild(s2); const tw = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, (n) => n.tagName === 'SPAN' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP); tw.nextNode() === s1 && tw.nextNode() === null", "true"),
11265        ("const div = document.createElement('div'); const c1 = document.createElement('p'); const c2 = document.createElement('span'); div.appendChild(c1); div.appendChild(c2); const ni = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT); ni.root === div && ni.referenceNode === div && ni.pointerBeforeReferenceNode === true", "true"),
11266        ("const div = document.createElement('div'); const c1 = document.createElement('p'); div.appendChild(c1); const ni = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT); ni.nextNode() === div && ni.nextNode() === c1 && ni.nextNode() === null && ni.previousNode() === c1", "true"),
11267        ("typeof document.createRange().createContextualFragment === 'function'", "true"),
11268        ("const r = document.createRange(); const frag = r.createContextualFragment('<span>A</span><b>B</b>'); frag.nodeType === 11 && frag.childNodes.length === 2 && frag.childNodes[0].tagName === 'SPAN'", "true"),
11269        ("const r = document.createRange(); const frag = r.createContextualFragment('<div>Hello</div>'); const container = document.createElement('div'); container.appendChild(frag); container.children.length === 1 && container.children[0].textContent === 'Hello'", "true"),
11270        ("const table = document.createElement('table'); const r = document.createRange(); r.selectNodeContents(table); const frag = r.createContextualFragment('<tr><td>Cell</td></tr>'); frag.childNodes.length === 1 && frag.childNodes[0].tagName === 'TR'", "true"),
11271        ("MediaSource.isTypeSupported('video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=30') && !MediaSource.isTypeSupported('video/mp4; codecs=\"avc1.4d401f\"; width=99999') && !MediaSource.isTypeSupported('video/mp4; codecs=\"avc1.4d401f\"; framerate=999')", "true"),
11272        ("const ms = new MediaSource(); const sb = ms.addSourceBuffer('video/mp4; codecs=\"avc1.4d401f\"'); sb.mode === 'segments' && sb.timestampOffset === 0 && sb.appendWindowStart === 0 && sb.appendWindowEnd === Infinity", "true"),
11273        ("const v = document.createElement('video'); const ms = new MediaSource(); v.src = URL.createObjectURL(ms); v.volume = 0.5; v.playbackRate = 1.5; const q = v.getVideoPlaybackQuality(); v.networkState === 2 && v.volume === 0.5 && v.playbackRate === 1.5 && typeof q.totalVideoFrames === 'number'", "true"),
11274        ("const ms = new MediaSource(); const sb = ms.addSourceBuffer('video/mp4; codecs=\"avc1.42E01E\"'); sb.changeType('video/mp4; codecs=\"avc1.640028\"'); ms.setLiveSeekableRange(5, 30); ms.clearLiveSeekableRange(); sb.mimeType === 'video/mp4; codecs=\"avc1.640028\"'", "true"),
11275        ("let fsCount = 0; document.addEventListener('fullscreenchange', () => { fsCount++; }); const el = document.getElementById('tmpl'); el.webkitRequestFullscreen(); const inFs = (document.webkitFullscreenElement === el); document.webkitExitFullscreen(); inFs && document.fullscreenElement === null && fsCount === 2", "true"),
11276    ];
11277    total += template_cases.len();
11278    for (src, expect) in template_cases {
11279        let mut rt = JsRuntime::new();
11280        rt.dom
11281            .borrow_mut()
11282            .build_from(&crate::os_lib::dom::parse_html(template_html));
11283        match rt.eval(src) {
11284            Ok(v) if &v.to_js_string() == expect => passed += 1,
11285            Ok(v) => crate::println!(
11286                "JS_SELFTEST FAIL: Template test `{}` => `{}` (want `{}`)",
11287                src,
11288                v.to_js_string(),
11289                expect
11290            ),
11291            Err(e) => crate::println!("JS_SELFTEST ERR:  Template test `{}` => {}", src, e),
11292        }
11293    }
11294
11295    // 子 `<iframe>` の `window.parent` / `window.top` 分離およびフレーム間 `postMessage` 配送の検証
11296    {
11297        total += 1;
11298        let mut child_rt = JsRuntime::new();
11299        child_rt.set_page_url("https://www.youtube.com/embed/abc");
11300        child_rt.set_frame_depth(1);
11301        let check = child_rt
11302            .eval("window.self !== window.parent && window.self !== window.top && (window.parent.postMessage('yt-ready', '*'), true)")
11303            .map(|v| v.to_js_string())
11304            .unwrap_or_default();
11305        let queued = child_rt.dom.borrow_mut().pending_parent_messages.pop();
11306        let mut parent_rt = JsRuntime::new();
11307        let _ = parent_rt.eval("window._gotMsg = ''; window.addEventListener('message', e => { window._gotMsg = e.data + '@' + e.origin; });");
11308        if let Some((data, origin)) = queued {
11309            parent_rt.deliver_message(data, &origin);
11310        }
11311        let parent_got = parent_rt
11312            .eval("window._gotMsg")
11313            .map(|v| v.to_js_string())
11314            .unwrap_or_default();
11315        if check == "true" && parent_got == "yt-ready@https://www.youtube.com" {
11316            passed += 1;
11317        } else {
11318            crate::println!(
11319                "JS_SELFTEST FAIL: cross-frame postMessage check={} parent_got={}",
11320                check,
11321                parent_got
11322            );
11323        }
11324    }
11325
11326    (passed, total)
11327}
11328
11329
11330
11331