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        ("let n = 0; while (n < 3) n++; n", "3"),
42        ("let x = 10; if (x > 5) { x = 1 } else { x = 2 } x", "1"),
43        // ラベル付き break/continue(ES3以降の基礎機能。以前は AST に Label 相当の
44        // variant 自体が存在せず完全に未サポートだった)。
45        (
46            "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",
47            "001020",
48        ),
49        (
50            "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",
51            "11",
52        ),
53        // ラベル無しの break/continue は従来通り最も内側のループのみに作用する
54        // (ラベル対応追加による回帰が無いことの確認)。
55        (
56            "var out=''; for(var i=0;i<3;i++){ for(var j=0;j<3;j++){ if(j===1) break; out+=i+''+j; } } out",
57            "001020",
58        ),
59        // 三項演算子の `:` とラベル構文の衝突が無いこと(`x ? 1 : 2` の `:` は
60        // ラベル判定の対象にならない)。
61        ("var x=5; x > 0 ? 1 : 2", "1"),
62        // `**` の右結合性(`2**3**2` は `2**(3**2)`=512。左結合だと `(2**3)**2`=64になり誤り)。
63        ("2 ** 3 ** 2", "512"),
64        // 論理代入演算子(ES2021): 値としての結果。
65        ("var a=0; a ||= 5; a", "5"),
66        ("var a=1; a ||= 5; a", "1"),
67        ("var a=1; a &&= 5; a", "5"),
68        ("var a=0; a &&= 5; a", "0"),
69        ("var a=null; a ??= 5; a", "5"),
70        ("var a=0; a ??= 5; a", "0"),
71        // 論理代入演算子は仕様上の短絡評価が必須: 条件を満たさない場合、右辺の副作用は
72        // 一切実行されてはならない(以前は関数の先頭で右辺を無条件に評価してから分岐して
73        // いたため、代入されない場合でも副作用が毎回発生してしまう仕様違反バグがあった)。
74        (
75            "var calls=0; function rhs(){calls++; return 9;} var a=1; a ||= rhs(); calls",
76            "0",
77        ),
78        (
79            "var calls=0; function rhs(){calls++; return 9;} var a=0; a &&= rhs(); calls",
80            "0",
81        ),
82        (
83            "var calls=0; function rhs(){calls++; return 9;} var a=5; a ??= rhs(); calls",
84            "0",
85        ),
86        // 条件を満たす場合は右辺が(1回だけ)評価される。
87        (
88            "var calls=0; function rhs(){calls++; return 9;} var a=0; a ||= rhs(); calls+','+a",
89            "1,9",
90        ),
91        // 関数・クロージャ・再帰
92        ("function add(a, b) { return a + b } add(3, 4)", "7"),
93        ("let f = (a, b) => a + b; f(5, 6)", "11"),
94        ("function fib(n){ return n < 2 ? n : fib(n-1) + fib(n-2) } fib(10)", "55"),
95        ("let mk = () => { let c = 0; return () => ++c }; let g = mk(); g(); g(); g()", "3"),
96        // 配列
97        ("[1,2,3].map(x => x * 2).join(',')", "2,4,6"),
98        ("[1,2,3,4].filter(x => x % 2 === 0).join(',')", "2,4"),
99        ("[1,2,3,4].reduce((s, x) => s + x, 0)", "10"),
100        ("[3,1,2].sort((a,b) => a - b).join('')", "123"),
101        ("let a = [1]; a.push(2); a.push(3); a.length", "3"),
102        // オブジェクト
103        ("var o = {a:1, b:2}; o.a + o.b", "3"),
104        ("var o = {}; o['k'] = 9; o.k", "9"),
105        ("Object.keys({x:1, y:2, z:3}).length", "3"),
106        // typeof / 例外
107        ("typeof undefinedVar", "undefined"),
108        ("typeof 42", "number"),
109        ("try { throw 'boom' } catch (e) { e }", "boom"),
110        // JSON
111        ("JSON.stringify({x:[1,2], y:'z'})", "{\"x\":[1,2],\"y\":\"z\"}"),
112        ("JSON.parse('{\"n\":5}').n", "5"),
113        // Math
114        ("Math.max(3, 7, 2)", "7"),
115        ("Math.floor(3.9)", "3"),
116        ("Math.abs(-5)", "5"),
117        // switch(フォールスルー・default 含む)
118        ("function f(x){ switch(x){ case 1: return 'one'; case 2: return 'two'; default: return 'other' } } f(2)", "two"),
119        ("function f(x){ switch(x){ case 1: return 'one'; default: return 'd' } } f(9)", "d"),
120        ("var r=''; switch(1){ case 1: r+='a'; case 2: r+='b'; break; case 3: r+='c' } r", "ab"),
121        // スプレッド/レスト
122        ("var a=[1,2]; var b=[...a,3,4]; b.join(',')", "1,2,3,4"),
123        ("function sum(...xs){ return xs.reduce((s,x)=>s+x,0) } sum(1,2,3,4)", "10"),
124        ("function add(a,b,c){ return a+b+c } add(...[1,2,3])", "6"),
125        ("var o={a:1}; var p={...o, b:2}; p.a + p.b", "3"),
126        ("Math.max(...[5,2,9,1])", "9"),
127        ("function tail(first, ...rest){ return rest.join('-') } tail(1,2,3,4)", "2-3-4"),
128        // Map
129        ("var m=new Map(); m.set('a',1).set('b',2); m.get('a') + m.size", "3"),
130        ("var m=new Map([['x',10],['y',20]]); m.has('y') + ',' + m.get('y')", "true,20"),
131        ("var m=new Map(); m.set(1,'one'); m.delete(1); m.has(1)", "false"),
132        // `new Map(iterable)` の各要素が非オブジェクト(ペアでない)場合は仕様どおり
133        // TypeError を投げる必要があるが、以前は黙って {undefined: undefined} という
134        // 意味のない1エントリだけの Map が静かに作られていた。
135        (
136            "try { new Map([1,2,3]); 'no throw'; } catch(e) { 'threw'; }",
137            "threw",
138        ),
139        (
140            "try { new Map(['ab']); 'no throw'; } catch(e) { 'threw'; }",
141            "threw",
142        ),
143        // Set
144        ("var s=new Set([1,2,2,3,3,3]); s.size", "3"),
145        ("var s=new Set(); s.add(5).add(5).add(6); s.has(5) + ',' + s.size", "true,2"),
146        ("var s=new Set([1,2,3]); var t=0; s.forEach(v=>t+=v); t", "6"),
147        ("[...new Set([1,1,2,3,3])].join(',')", "1,2,3"),
148        // 分割代入(オブジェクト)
149        ("var {a, b} = {a:1, b:2}; a + b", "3"),
150        ("var {a: x, b: y} = {a:10, b:20}; x + y", "30"),
151        ("var {a, c = 9} = {a:1}; a + c", "10"),
152        ("var {a, ...rest} = {a:1, b:2, c:3}; rest.b + rest.c", "5"),
153        ("var {p: {q}} = {p:{q:42}}; q", "42"),
154        // 分割代入(配列)
155        ("var [a, b] = [1, 2]; a + b", "3"),
156        ("var [a, , c] = [1, 2, 3]; a + c", "4"),
157        ("var [a, ...rest] = [1, 2, 3, 4]; rest.join('-')", "2-3-4"),
158        ("var [a = 5, b = 6] = [1]; a + b", "7"),
159        ("var [[a], [b]] = [[1],[2]]; a + b", "3"),
160        ("var t=0; for (const [k,v] of [['a',1],['b',2]]) t += v; t", "3"),
161        // デフォルト引数
162        ("function f(a, b = 10) { return a + b } f(5)", "15"),
163        ("function f(a, b = 10) { return a + b } f(5, 7)", "12"),
164        // 引数の分割代入
165        ("function f({x, y}) { return x * y } f({x:3, y:4})", "12"),
166        ("function f([a, b]) { return a - b } f([9, 2])", "7"),
167        // 算出キー
168        ("var k = 'dyn'; var o = {[k]: 99}; o.dyn", "99"),
169        ("var o = {['a' + 'b']: 1}; o.ab", "1"),
170        // class(基本)
171        ("class A { constructor(x){ this.x = x } get(){ return this.x } } new A(7).get()", "7"),
172        ("class A { constructor(){ this.n = 0 } inc(){ this.n++; return this.n } } var a=new A(); a.inc(); a.inc()", "2"),
173        ("class P { area(){ return 0 } } class C extends P { area(){ return 5 } } new C().area()", "5"),
174        // class(継承・super)
175        ("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"),
176        ("class A { greet(){ return 'A' } } class B extends A { greet(){ return super.greet() + 'B' } } new B().greet()", "AB"),
177        ("class A { constructor(){ this.v = 1 } } class B extends A {} new B().v", "1"),
178        // class field 宣言(ES2022。コンストラクタ本体の先頭で this.x = init される)
179        ("class C { x = 5; } new C().x", "5"),
180        ("class C { x; } typeof new C().x", "undefined"),
181        ("class C { x = 1; y = this.x + 1; } var c = new C(); c.x + ',' + c.y", "1,2"),
182        (
183            "class C { x = 1; constructor(){ this.x = this.x + 10 } } new C().x",
184            "11",
185        ),
186        (
187            "class A { x = 1; } class B extends A { y = 2; } var b = new B(); b.x + ',' + b.y",
188            "1,2",
189        ),
190        // private field(#x)。'#' が識別子の一部として扱われ、通常のプロパティアクセス
191        // 経路をそのまま通る(真の隠蔽ではないが、構文としては正しく動作する)。
192        (
193            "class Counter { #n = 0; inc(){ this.#n++; return this.#n } } var c = new Counter(); c.inc(); c.inc()",
194            "2",
195        ),
196        (
197            "class C { #secret = 'hidden'; reveal(){ return this.#secret } } new C().reveal()",
198            "hidden",
199        ),
200        // private メソッド(#method())
201        (
202            "class C { #double(n){ return n * 2 } calc(n){ return this.#double(n) } } new C().calc(21)",
203            "42",
204        ),
205        // プライベートフィールドのブランドチェック `#x in obj`(ES2022)が丸ごと
206        // 未対応で、`#x is not defined` の例外に必ずなっていた。
207        (
208            "class C { #x = 1; static has(o){ return #x in o; } } C.has(new C()) + ',' + C.has({})",
209            "true,false",
210        ),
211        // static field
212        ("class C { static count = 7; } C.count", "7"),
213        // static メソッド
214        ("class M { static twice(n){ return n * 2 } } M.twice(21)", "42"),
215        // メソッドからメソッド呼び出し
216        ("class C { a(){ return 2 } b(){ return this.a() * 10 } } new C().b()", "20"),
217        // Promise / async / await(await はマイクロタスクを駆動して同期解決)
218        ("await Promise.resolve(42)", "42"),
219        // **重要**: 汎用 thenable(本物の Promise ではないが呼び出し可能な `.then` を
220        // 持つオブジェクト。古いライブラリの独自 Promise 実装等でよく使われる)の
221        // 同化が丸ごと未対応で、`await`/`Promise.resolve` がオブジェクト自体を
222        // 履行値にしてしまうバグだった。
223        (
224            "await Promise.resolve({ then(resolve){ resolve(99); } })",
225            "99",
226        ),
227        (
228            "await { then(resolve){ resolve(7); } }",
229            "7",
230        ),
231        (
232            "try { await { then(resolve, reject){ reject('bad'); } } } catch(e) { e }",
233            "bad",
234        ),
235        // thenable の連鎖(thenable の then が別の thenable で resolve する)も
236        // 再帰的に同化されること。
237        (
238            "await { then(resolve){ resolve({ then(r){ r(5); } }); } }",
239            "5",
240        ),
241        // **重要**: thenable 検出が own プロパティしか見ていなかったため、
242        // `then` メソッドが `.prototype` 側にある class インスタンスの thenable
243        // を取りこぼすバグだった(自己検証で発見)。
244        (
245            "class MyThenable { then(resolve){ resolve(42); } } await new MyThenable()",
246            "42",
247        ),
248        (
249            "class MyThenable { then(_, reject){ reject('bad'); } } try { await new MyThenable() } catch(e) { e }",
250            "bad",
251        ),
252        ("await Promise.resolve(1).then(v => v + 1)", "2"),
253        ("await Promise.resolve(1).then(v => v + 1).then(v => v * 10)", "20"),
254        ("await Promise.reject('e').catch(e => 'caught:' + e)", "caught:e"),
255        ("async function f(){ return 7 } await f()", "7"),
256        ("async function f(){ let x = await Promise.resolve(10); return x * 2 } await f()", "20"),
257        ("async function f(){ try { await Promise.reject('x') } catch(e){ return 'C' + e } } await f()", "Cx"),
258        ("await (async () => 5)()", "5"),
259        ("await new Promise((res) => res(99))", "99"),
260        ("await new Promise((res, rej) => rej('bad')).catch(e => 'got:' + e)", "got:bad"),
261        ("await Promise.all([Promise.resolve(1), Promise.resolve(2), 3]).then(a => a.join(','))", "1,2,3"),
262        // **重要**: `Promise.all` の配列要素に汎用 thenable(本物の Promise ではないが
263        // 呼び出し可能な `.then` を持つオブジェクト)が混ざっていると、`Promise.resolve`/
264        // `await` 単体では既に同化対応済みだったのに、要素がそのままオブジェクトとして
265        // 混入してしまう非対称なバグだった。
266        (
267            "await Promise.all([1, { then(r){ r(2); } }, Promise.resolve(3)]).then(a => a.join(','))",
268            "1,2,3",
269        ),
270        (
271            "await Promise.all([{ then(resolve, reject){ reject('bad'); } }]).catch(e => 'caught:' + e)",
272            "caught:bad",
273        ),
274        ("typeof Promise.resolve(1)", "object"),
275        ("var log=''; await Promise.resolve(1).finally(() => log = 'F'); log", "F"),
276        // `finally` は元の値/理由をそのまま次へ渡す(コールバックの戻り値は無視)。
277        ("await Promise.resolve(1).finally(() => 2)", "1"),
278        // `finally` のコールバックが例外を投げた場合、それが settle を上書きすべき
279        // (以前は副作用としてだけ実行し戻り値/例外を完全に無視していたバグ)。
280        (
281            "await Promise.resolve(1).finally(() => { throw 'x'; }).catch(e => 'caught:' + e)",
282            "caught:x",
283        ),
284        // `finally` のコールバックが reject する Promise を返した場合も同様。
285        (
286            "await Promise.resolve(1).finally(() => Promise.reject('y')).catch(e => 'caught:' + e)",
287            "caught:y",
288        ),
289        // reject 経路でも `finally` はコールバック実行後、元の reason で reject し続ける。
290        (
291            "await Promise.reject('orig').finally(() => 'ignored').catch(e => 'caught:' + e)",
292            "caught:orig",
293        ),
294        ("async function sq(n){ return n*n } var s=0; for (const n of [1,2,3]) s += await sq(n); s", "14"),
295        // generator(遅延評価 / replay 駆動)
296        ("function* g(){ yield 1; yield 2; yield 3 } [...g()].join(',')", "1,2,3"),
297        ("function* g(){ yield* [1,2]; yield 3 } [...g()].join(',')", "1,2,3"),
298        ("function* g(){ for (let i=0;i<3;i++) yield i*i } [...g()].join(',')", "0,1,4"),
299        ("function* g(){ yield 1; yield 2; yield 3 } var s=0; for (const v of g()) s += v; s", "6"),
300        ("function* g(){ yield 'a'; yield 'b' } var it=g(); it.next().value + it.next().value + it.next().done", "abtrue"),
301        ("function* g(){ yield 1; return 99; } var it=g(); it.next(); it.next().done", "true"),
302        // 無限 generator を有限個だけ消費(真の遅延評価。eager だと無限ループ)
303        ("function* nat(){ let i=0; while(true) yield i++; } var it=nat(); it.next().value + ',' + it.next().value + ',' + it.next().value", "0,1,2"),
304        ("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"),
305        // .next(v) による双方向の値受け渡し
306        ("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"),
307        ("function* g(){ var x = yield 'first'; yield 'got:' + x; } var it=g(); it.next(); it.next('hello').value", "got:hello"),
308        // yield* で別の generator へ委譲(generator 同士のネスト)
309        ("function* inner(){ yield 1; yield 2; } function* outer(){ yield 0; yield* inner(); yield 3; } [...outer()].join(',')", "0,1,2,3"),
310        // **重要**: `async function*`(非同期 generator。ES2018)の `.next()` が
311        // 仕様上必ず Promise を返すべきところ、`is_generator` の分岐が `is_async` を
312        // 完全に無視しており、普通の同期 generator と同じ `{value,done}` プレーン
313        // オブジェクトを直接返してしまっていたバグ。
314        (
315            "async function* g(){ yield 1; yield 2; } typeof g().next().then",
316            "function",
317        ),
318        (
319            "async function* g(){ yield 1; yield 2; } var it = g(); await it.next().then(r => r.value + ',' + r.done)",
320            "1,false",
321        ),
322        (
323            "async function* g(){ yield await Promise.resolve(42); } var it = g(); await it.next().then(r => r.value)",
324            "42",
325        ),
326        ("function* g(){ yield* 'abc'; } [...g()].join('-')", "a-b-c"),
327        // return() で早期終了
328        ("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"),
329        // return() と try...finally の連携(return() 時に finally が実行される)
330        ("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"),
331        // finally の中で return がある場合はそちらが優先される
332        ("function* g(){ try { yield 1; } finally { return 42; } } var it=g(); it.next(); it.return(99).value", "42"),
333        // finally の中で yield がある場合はそこで一時停止し、次の再開で強制Return値で完了する。
334        // value/done は正しいが、下の replay 特性(368行目)どおり finally 冒頭の
335        // `log+='f'` は forced-return 契機の replay と次の .next() の replay の
336        // 計2回実行されるため 'f' が2つになる(2026-07-14、期待値の計算ミスを
337        // QEMU JS_SELFTEST FAIL で発見・修正。本体側の finally 実行ロジックは正しい)。
338        ("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"),
339        // replay 方式の帰結: resume のたびに本体を先頭から再実行するため、外部副作用は
340        // 複数回発生する(yield される値・.next(v) の受け渡しは正しい)。この再実行特性を
341        // 明示的に検証する(2 回目の next で log+='a' が再実行され 'aab' になる)。
342        ("var log=''; function* g(){ log+='a'; yield 1; log+='b'; yield 2; } var it=g(); it.next(); it.next(); log", "aab"),
343        // Array.from が generator を消費できる(+ mapFn)
344        ("function* g(){ yield 1; yield 2; yield 3; } Array.from(g()).join(',')", "1,2,3"),
345        ("function* g(){ yield 1; yield 2; yield 3; } Array.from(g(), x => x * 10).join(',')", "10,20,30"),
346        // try/finally を跨ぐ yield(中断中は finally を実行せず、resume で継続)
347        ("function* g(){ try { yield 1; yield 2; } finally { } yield 3; } [...g()].join(',')", "1,2,3"),
348        // Symbol.match/replace/search/split のオブジェクト委譲
349        ("var custom = { [Symbol.match](s) { return s === 'hello' ? 'yes' : 'no'; } }; 'hello'.match(custom) + ',' + 'world'.match(custom)", "yes,no"),
350        ("var custom = { [Symbol.replace](s, r) { return s + '-' + r; } }; 'abc'.replace(custom, '123')", "abc-123"),
351        ("var custom = { [Symbol.search](s) { return s.length; } }; 'abc'.search(custom)", "3"),
352        ("var custom = { [Symbol.split](s, lim) { return [s.length, lim]; } }; 'abc'.split(custom, 5).join(',')", "3,5"),
353        // RegExp.prototype[Symbol.*] 直接呼び出し & RegExp 委譲動作
354        ("typeof /a/[Symbol.match]", "function"),
355        ("/b/[Symbol.match]('abc').join(',')", "b"),
356        ("/b/[Symbol.replace]('abc', 'X')", "aXc"),
357        ("/b/[Symbol.search]('abc')", "1"),
358        ("/b/[Symbol.split]('abc').join(',')", "a,c"),
359        // Symbol.species 静的アクセサ
360        ("Array[Symbol.species] === Array", "true"),
361        ("RegExp[Symbol.species] === RegExp", "true"),
362        ("Promise[Symbol.species] === Promise", "true"),
363        // Array.prototype[Symbol.unscopables]
364        ("Array.prototype[Symbol.unscopables].includes", "true"),
365        ("Array.prototype[Symbol.unscopables].at", "true"),
366        // setTimeout(マクロタスク。Promise を介して await で駆動)
367        ("var x=0; await new Promise(r => setTimeout(() => { x = 5; r(); }, 0)); x", "5"),
368        ("var done=false; await new Promise(r => setTimeout(() => { done = true; r(); }, 0)); done", "true"),
369        ("await new Promise(res => setTimeout(() => res(7), 0))", "7"),
370        // requestAnimationFrame(マクロタスク。timestamp 引数 + cancelAnimationFrame)
371        ("typeof requestAnimationFrame", "function"),
372        ("var x=0; await new Promise(r => requestAnimationFrame(() => { x = 5; r(); })); x", "5"),
373        ("var ty=''; await new Promise(r => requestAnimationFrame(t => { ty = typeof t; r(); })); ty", "number"),
374        ("var L=''; requestAnimationFrame(() => L+='a'); var id=requestAnimationFrame(() => L+='b'); cancelAnimationFrame(id); await new Promise(r => requestAnimationFrame(() => r())); L", "a"),
375        // localStorage / sessionStorage(静的マップ共有のため各テスト先頭で clear)
376        ("localStorage.clear(); localStorage.setItem('a','1'); localStorage.getItem('a')", "1"),
377        ("localStorage.clear(); localStorage.getItem('missing')", "null"),
378        ("localStorage.clear(); localStorage.setItem('x','9'); localStorage.length", "1"),
379        ("localStorage.clear(); localStorage.setItem('a','1'); localStorage.removeItem('a'); localStorage.getItem('a')", "null"),
380        ("localStorage.clear(); localStorage.foo='bar'; localStorage.getItem('foo')", "bar"),
381        ("sessionStorage.clear(); sessionStorage.setItem('s','2'); sessionStorage.getItem('s')", "2"),
382        // `delete localStorage.foo` も同じ種類の黙殺バグだった(`removeItem` と同義のはずが
383        // Host プロキシが `.props` を持たないため何も起きていなかった)。
384        (
385            "localStorage.clear(); localStorage.foo='bar'; delete localStorage.foo; localStorage.getItem('foo')",
386            "null",
387        ),
388        // `Object.keys/values/entries(localStorage)` と `for...in localStorage` が実データ
389        // (専用マップ側にある) を一切見ておらず常に空になっていたバグの修正確認。
390        (
391            "localStorage.clear(); localStorage.setItem('a','1'); localStorage.setItem('b','2'); Object.keys(localStorage).sort().join(',')",
392            "a,b",
393        ),
394        (
395            "localStorage.clear(); localStorage.setItem('a','1'); localStorage.setItem('b','2'); Object.values(localStorage).sort().join(',')",
396            "1,2",
397        ),
398        (
399            "localStorage.clear(); localStorage.setItem('a','1'); Object.entries(localStorage)[0].join(':')",
400            "a:1",
401        ),
402        (
403            "localStorage.clear(); localStorage.setItem('a','1'); localStorage.setItem('b','2'); var out=[]; for (var k in localStorage) out.push(k); out.sort().join(',')",
404            "a,b",
405        ),
406        (
407            "localStorage.clear(); localStorage.setItem('a','1'); Object.assign({}, localStorage).a",
408            "1",
409        ),
410        // `Object.keys/values/entries(el.dataset)` と `for...in el.dataset` も同種の
411        // バグで実データ(DOM 要素側の `data-*` 属性)を一切見ておらず常に空になって
412        // いた(`localStorage`/`sessionStorage` で修正済みのバグと同型)。
413        (
414            "var e=document.createElement('div'); e.dataset.userId='42'; e.dataset.fooBar='x'; Object.keys(e.dataset).sort().join(',')",
415            "fooBar,userId",
416        ),
417        (
418            "var e=document.createElement('div'); e.dataset.a='1'; e.dataset.b='2'; Object.values(e.dataset).sort().join(',')",
419            "1,2",
420        ),
421        (
422            "var e=document.createElement('div'); e.dataset.a='1'; Object.entries(e.dataset)[0].join(':')",
423            "a:1",
424        ),
425        (
426            "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(',')",
427            "a,b",
428        ),
429        (
430            "var e=document.createElement('div'); e.dataset.a='1'; Object.assign({}, e.dataset).a",
431            "1",
432        ),
433        // タグ付きテンプレート `` tag`...${e}...` ``(ES2015)が丸ごと未対応で、`Template`
434        // トークンが式の直後に来ると静かにパースが崩れるバグだった。基本形・複数補間・
435        // `this` 束縛(メソッド呼出形)・組込み `String.raw` を確認。
436        (
437            "function tag(s, ...v){ return s.join('|') + '::' + v.join(','); } tag`a${1}b${2}c`",
438            "a|b|c::1,2",
439        ),
440        (
441            "var o={ tag(s){ return this===o ? s[0] : 'bad'; } }; o.tag`hi`",
442            "hi",
443        ),
444        ("String.raw`a\\nb`", "a\\nb"),
445        ("String.raw`x${1+1}y`", "x2y"),
446        // `strings.raw` はエスケープ未解決のまま渡る(cooked とは別)ことの確認。
447        (
448            "function tag(s){ return s[0] + '|' + s.raw[0]; } tag`a\\nb`",
449            "a\nb|a\\nb",
450        ),
451        // `Object.is(a, b)`(ES2015、SameValue)が丸ごと欠落していた。`===` と違い
452        // `NaN` 同士は等しく、`+0`/`-0` は区別する。
453        ("Object.is(NaN, NaN)", "true"),
454        ("NaN === NaN", "false"),
455        ("Object.is(0, -0)", "false"),
456        ("0 === -0", "true"),
457        ("Object.is(1, 1)", "true"),
458        ("Object.is('a', 'a')", "true"),
459        ("Object.is({}, {})", "false"),
460        // `Reflect.setPrototypeOf`/`isExtensible`/`preventExtensions`/
461        // `getOwnPropertyDescriptor` が丸ごと欠落していた。
462        (
463            "var o={}; var p={x:1}; Reflect.setPrototypeOf(o,p); o.x",
464            "1",
465        ),
466        ("var o={}; Reflect.isExtensible(o)", "true"),
467        (
468            "var o={}; Reflect.preventExtensions(o); Reflect.isExtensible(o)",
469            "false",
470        ),
471        (
472            "Reflect.getOwnPropertyDescriptor({x:1}, 'x').value",
473            "1",
474        ),
475        // `Array.prototype.fill(value, start, end)` が第2/第3引数を完全に無視し、
476        // 常に配列全体を上書きしていた。
477        ("[1,2,3,4,5].fill(0,1,3).join(',')", "1,0,0,4,5"),
478        ("[1,2,3,4,5].fill(9).join(',')", "9,9,9,9,9"),
479        ("[1,2,3,4,5].fill(0,-2).join(',')", "1,2,3,0,0"),
480        ("[1,2,3].fill(0,1).join(',')", "1,0,0"),
481        // `Array.prototype.indexOf`/`includes` の `fromIndex`(第2引数)が完全に無視され、
482        // 常に先頭から探索していた。
483        ("[1,2,3,2,1].indexOf(2, 2)", "3"),
484        ("[1,2,3].indexOf(1, 1)", "-1"),
485        ("[1,2,3].indexOf(2, -2)", "1"),
486        ("[1,2,3].includes(1, 1)", "false"),
487        ("[1,2,3].includes(2, 1)", "true"),
488        // `lastIndexOf` にも同じ `fromIndex` 無視バグがあった。
489        ("[1,2,1,2,1].lastIndexOf(2, 2)", "1"),
490        ("[1,2,3].lastIndexOf(3, 1)", "-1"),
491        ("[1,2,3].lastIndexOf(1, -1)", "0"),
492        ("[1,2,3,2].lastIndexOf(2)", "3"),
493        // `fromIndex` に NaN を渡すと仕様上 `ToIntegerOrInfinity(NaN)=0` として扱われ
494        // index 0 のみを見るはずだが、以前は無条件に -1 を返していた。
495        ("[5,1,2].lastIndexOf(5, NaN)", "0"),
496        ("[5,1,2].lastIndexOf(1, NaN)", "-1"),
497        // URLSearchParams
498        ("new URLSearchParams('a=1&b=2').get('a')", "1"),
499        ("new URLSearchParams('?a=1&b=2').get('b')", "2"),
500        ("new URLSearchParams('a=1&a=2').getAll('a').join(',')", "1,2"),
501        ("new URLSearchParams('a=1').has('a') + ',' + new URLSearchParams('a=1').has('z')", "true,false"),
502        ("var p=new URLSearchParams('a=1'); p.append('b','2'); p.toString()", "a=1&b=2"),
503        ("var p=new URLSearchParams('a=1&b=2'); p.delete('a'); p.toString()", "b=2"),
504        ("var p=new URLSearchParams('a=1'); p.set('a','9'); p.get('a')", "9"),
505        // `new URLSearchParams(init)` の init がオブジェクトの場合(sequence of
506        // pairs / record / 既存インスタンス)が丸ごと壊れていたバグ(`to_js_string`
507        // 丸投げで常に `"[object Object]"` 相当に落ちていた)。
508        ("new URLSearchParams([['a','1'],['b','2']]).toString()", "a=1&b=2"),
509        ("new URLSearchParams({a:'1',b:'2'}).toString()", "a=1&b=2"),
510        (
511            "var p1=new URLSearchParams('a=1&b=2'); new URLSearchParams(p1).toString()",
512            "a=1&b=2",
513        ),
514        // URLSearchParams: %XX / + デコード(form-urlencoded)
515        ("new URLSearchParams('a=hello%20world').get('a')", "hello world"),
516        ("new URLSearchParams('a=hello+world').get('a')", "hello world"),
517        ("var p=new URLSearchParams(); p.set('a','hello world'); p.toString()", "a=hello+world"),
518        ("var p=new URLSearchParams(); p.set('a','a&b=c'); p.get('a')", "a&b=c"),
519        // `entries`/`keys`/`values`/`forEach` が `URLSearchParams` に丸ごと欠落していた
520        // (`FormData` は既にあったが、内部表現が同じなのに移植されていなかった)。
521        (
522            "[...new URLSearchParams('a=1&b=2').entries()].map(e=>e[0]+':'+e[1]).join(',')",
523            "a:1,b:2",
524        ),
525        ("new URLSearchParams('a=1&b=2').keys().join(',')", "a,b"),
526        ("new URLSearchParams('a=1&b=2').values().join(',')", "1,2"),
527        (
528            "var s=''; new URLSearchParams('a=1&b=2').forEach(function(v,k){ s+=k+'='+v+';'; }); s",
529            "a=1;b=2;",
530        ),
531        // `Symbol.iterator` 未登録で `for (const [k,v] of params)`(`entries()` と同義の
532        // 既定イテレーション)が丸ごと非対応だったバグ。`FormData` にも同じ修正を適用。
533        (
534            "var out=[]; for (const [k,v] of new URLSearchParams('a=1&b=2')) out.push(k+':'+v); out.join(',')",
535            "a:1,b:2",
536        ),
537        (
538            "var fd=new FormData(); fd.set('x','9'); var out=[]; for (const [k,v] of fd) out.push(k+':'+v); out.join(',')",
539            "x:9",
540        ),
541        // URLPattern API (HTML Standard)
542        ("new URLPattern('/users/:id').test('/users/123')", "true"),
543        ("new URLPattern({ pathname: '/about' }).test('/about')", "true"),
544        ("new URLPattern('/users/:id').exec('/users/123').pathname.input", "/users/123"),
545        ("new URLPattern('/admin/*').test('/users/123')", "false"),
546        // encodeURIComponent / decodeURIComponent
547        ("encodeURIComponent('hello world')", "hello%20world"),
548        ("encodeURIComponent('a=1&b=2')", "a%3D1%26b%3D2"),
549        ("decodeURIComponent('hello%20world')", "hello world"),
550        ("decodeURIComponent(encodeURIComponent('日本語'))", "日本語"),
551        ("encodeURIComponent(\"!'()*\")", "!'()*"),
552        // encodeURI / decodeURI(予約文字は残す)
553        ("encodeURI('https://example.com/a b?x=1&y=2')", "https://example.com/a%20b?x=1&y=2"),
554        ("decodeURI('https://example.com/a%20b')", "https://example.com/a b"),
555        // `escape`/`unescape`(Annex B.2.1)が丸ごと未対応だった。
556        ("escape('a b?c')", "a%20b%3Fc"),
557        ("unescape(escape('a b?c'))", "a b?c"),
558        // ASCII 範囲外は `%uXXXX` 形式になる。
559        ("escape('日')", "%u65E5"),
560        ("unescape('%u65E5')", "日"),
561        // Annex B.2.3 の `String.prototype` HTML ラッパーメソッド群が丸ごと未対応だった。
562        ("'hi'.bold()", "<b>hi</b>"),
563        ("'hi'.italics()", "<i>hi</i>"),
564        ("'hi'.big()", "<big>hi</big>"),
565        ("'hi'.small()", "<small>hi</small>"),
566        ("'hi'.strike()", "<strike>hi</strike>"),
567        ("'hi'.sub()", "<sub>hi</sub>"),
568        ("'hi'.sup()", "<sup>hi</sup>"),
569        ("'hi'.fixed()", "<tt>hi</tt>"),
570        ("'hi'.blink()", "<blink>hi</blink>"),
571        ("'hi'.anchor('top')", "<a name=\"top\">hi</a>"),
572        ("'hi'.link('x.html')", "<a href=\"x.html\">hi</a>"),
573        ("'hi'.fontcolor('red')", "<font color=\"red\">hi</font>"),
574        ("'hi'.fontsize(3)", "<font size=\"3\">hi</font>"),
575        // 属性値の `\"` は `&quot;` へエスケープする(仕様どおり)。
576        ("'hi'.anchor('a\"b')", "<a name=\"a&quot;b\">hi</a>"),
577        // `trimLeft`/`trimRight`(Annex B.2.2。`trimStart`/`trimEnd` の別名)が
578        // 丸ごと未対応だった。
579        ("'  hi  '.trimLeft()", "hi  "),
580        ("'  hi  '.trimRight()", "  hi"),
581        // FormData (空生成 + API)
582        ("var f=new FormData(); f.append('a','1'); f.append('b','2'); f.get('a')", "1"),
583        ("var f=new FormData(); f.append('a','1'); f.append('a','2'); f.getAll('a').join(',')", "1,2"),
584        ("var f=new FormData(); f.append('a','1'); f.append('b','2'); f.toString()", "a=1&b=2"),
585        ("var f=new FormData(); f.append('a','1'); f.set('a','9'); f.get('a')", "9"),
586        ("var f=new FormData(); f.append('a','1'); f.has('a')+','+f.has('z')", "true,false"),
587        ("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"),
588        ("var f=new FormData(); f.append('a','1'); f.append('b','2'); f.keys().join(',')", "a,b"),
589        ("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;"),
590        // URL
591        ("new URL('https://e.com/p?x=1#h').pathname", "/p"),
592        ("new URL('https://e.com/p?x=1').searchParams.get('x')", "1"),
593        // `new URL(url, base)` の第2引数 base が丸ごと無視され、相対URL解決という
594        // URL API 最も基本的な使い方が機能しなかったバグ(既存の `resolve_url`
595        // ヘルパは `fetch`/XHR では既に使われていたが `URL` コンストラクタには
596        // 配線されていなかった。`.`/`..` セグメントの解決は非対応の簡略実装
597        // という既存の制約はそのまま)。
598        ("new URL('/a/b', 'https://e.com/x/y').href", "https://e.com/a/b"),
599        ("new URL('c', 'https://e.com/a/b').href", "https://e.com/a/c"),
600        // `user:pass@host` 形式のユーザー情報(userinfo)が丸ごと未対応で、
601        // `hostname`(引いては `host`/`port`/`origin`)にそのまま混入し
602        // 壊れた値になるバグだった(`user` が `hostname` の一部に化ける)。
603        ("new URL('https://user:pass@example.com/p').hostname", "example.com"),
604        ("new URL('https://user:pass@example.com/p').username", "user"),
605        ("new URL('https://user:pass@example.com/p').password", "pass"),
606        ("new URL('https://user:pass@example.com/p').origin", "https://example.com"),
607        // ユーザー情報無しの通常URLでは既定値が空文字列のまま(回帰確認)。
608        ("new URL('https://example.com/p').username", ""),
609        // `url.href = '...'`(URL全体を再パースして全コンポーネントを書き換える
610        // 定番の再代入パターン)が丸ごと未対応で、セッターが無いため代入が
611        // 黙って無視されていた。
612        (
613            "var u=new URL('https://a.com/x'); u.href='https://b.com/y?z=1'; u.hostname",
614            "b.com",
615        ),
616        (
617            "var u=new URL('https://a.com/x'); u.href='https://b.com/y?z=1'; u.pathname",
618            "/y",
619        ),
620        (
621            "var u=new URL('https://a.com/x'); u.href='https://b.com/y?z=1'; u.searchParams.get('z')",
622            "1",
623        ),
624        (
625            // オブジェクト自体は再代入せず同じインスタンスを書き換えるため、
626            // 元の変数参照からも新しい値が見える。
627            "var u=new URL('https://a.com/x'); var u2=u; u.href='https://b.com/y'; u2.href",
628            "https://b.com/y",
629        ),
630        // `url.pathname`/`.search`/`.hash`の個別再代入が丸ごと未対応だった。
631        // 特に`.search`は`url.toString()`/`.href`が`searchParams`だけを見て
632        // `search`プロパティ自体は読まないため、代入しても何も反映されない
633        // という`.href`丸ごと差し替えとは別種の静かな不整合があった。
634        (
635            "var u=new URL('https://a.com/x'); u.pathname='y'; u.href",
636            "https://a.com/y",
637        ),
638        (
639            "var u=new URL('https://a.com/x'); u.search='z=1'; u.href",
640            "https://a.com/x?z=1",
641        ),
642        (
643            "var u=new URL('https://a.com/x'); u.search='z=1'; u.searchParams.get('z')",
644            "1",
645        ),
646        (
647            "var u=new URL('https://a.com/x?old=1'); u.search='new=2'; u.href",
648            "https://a.com/x?new=2",
649        ),
650        (
651            "var u=new URL('https://a.com/x'); u.hash='frag'; u.href",
652            "https://a.com/x#frag",
653        ),
654        (
655            // 空文字列への代入はコンポーネントを除去する(クエリ/フラグメント無しに戻る)。
656            "var u=new URL('https://a.com/x?y=1#z'); u.search=''; u.hash=''; u.href",
657            "https://a.com/x",
658        ),
659        // `url.protocol`/`.host`/`.hostname`/`.port`の個別再代入も丸ごと
660        // 未対応だった。これら4つは互いに依存する派生値(`host`=`hostname`+
661        // `:`+`port`、`origin`=`protocol`+`//`+`host`)を持つため、どれを
662        // 代入しても`host`/`origin`を再計算し直す必要がある。
663        (
664            "var u=new URL('https://a.com:8080/x'); u.hostname='b.com'; u.href",
665            "https://b.com:8080/x",
666        ),
667        (
668            "var u=new URL('https://a.com:8080/x'); u.hostname='b.com'; u.origin",
669            "https://b.com:8080",
670        ),
671        (
672            "var u=new URL('https://a.com/x'); u.port='9090'; u.host",
673            "a.com:9090",
674        ),
675        (
676            "var u=new URL('https://a.com:8080/x'); u.host='c.com:7070'; u.hostname + ',' + u.port",
677            "c.com,7070",
678        ),
679        (
680            "var u=new URL('http://a.com/x'); u.protocol='https'; u.href",
681            "https://a.com/x",
682        ),
683        // クエリのみ/フラグメントのみの相対参照(`?x=2`/`#frag`)が、他の
684        // 相対パス解決と同じ「ディレクトリ相対」ロジックに落ちてベースの
685        // パスの最後のセグメントごと消えるバグだった。
686        ("new URL('?x=2', 'https://e.com/a/b?x=1').href", "https://e.com/a/b?x=2"),
687        ("new URL('#frag', 'https://e.com/a/b?x=1').href", "https://e.com/a/b?x=1#frag"),
688        // プロトコル相対URL(`//host/path`)が、ベースのホスト配下の相対パスと
689        // して誤解決されるバグだった(`//cdn.example.com/x.js` は別ホストへの
690        // 参照であり、ベースホスト配下のパスにしてはいけない)。
691        ("new URL('//cdn.example.com/x.js', 'https://e.com/a/b').href", "https://cdn.example.com/x.js"),
692        // `URL.canParse(url, base?)`(ES2023/WHATWG)が丸ごと未対応だった。
693        ("URL.canParse('https://example.com')", "true"),
694        ("URL.canParse('not a url')", "false"),
695        ("URL.canParse('/path', 'https://example.com')", "true"),
696        ("URL.canParse('/path')", "false"),
697        // `URL.parse(url, base?)`(ES2024/WHATWG)が丸ごと未対応だった。
698        // `try { new URL(x) } catch { null }` の1メソッド版。
699        ("URL.parse('https://example.com/p').pathname", "/p"),
700        ("URL.parse('not a url') === null", "true"),
701        ("URL.parse('/path', 'https://example.com').host", "example.com"),
702        ("URL.parse('/path') === null", "true"),
703        // `URL.createObjectURL(blob)`/`.revokeObjectURL(url)` が丸ごと未対応だった。
704        ("URL.createObjectURL(new Blob(['x'])).startsWith('blob:')", "true"),
705        // 呼び出す毎に一意な URL を発行する。
706        (
707            "URL.createObjectURL(new Blob(['x'])) === URL.createObjectURL(new Blob(['x']))",
708            "false",
709        ),
710        ("typeof URL.revokeObjectURL('blob:x')", "undefined"),
711        // `URLSearchParams.prototype.size`(ES2023)が丸ごと未対応だった。
712        ("new URLSearchParams('a=1&b=2').size", "2"),
713        (
714            "var p=new URLSearchParams('a=1'); p.append('b','2'); p.size",
715            "2",
716        ),
717        (
718            "var p=new URLSearchParams('a=1&b=2'); p.delete('a'); p.size",
719            "1",
720        ),
721        ("new URLSearchParams('').size", "0"),
722        // `has(name, value)`/`delete(name, value)`(ES2023 の第2引数)が丸ごと
723        // 無視され、値を問わず同名エントリを判定/削除してしまうバグだった。
724        ("new URLSearchParams('a=1&a=2').has('a', '1')", "true"),
725        ("new URLSearchParams('a=1&a=2').has('a', '9')", "false"),
726        (
727            "var p=new URLSearchParams('a=1&a=2'); p.delete('a','1'); p.getAll('a').join(',')",
728            "2",
729        ),
730        (
731            "var p=new URLSearchParams('a=1&a=2'); p.delete('a'); p.getAll('a').join(',')",
732            "",
733        ),
734        // `URLSearchParams.prototype.sort()`(ES2020)が丸ごと未対応だった。
735        (
736            "var p=new URLSearchParams('c=3&a=1&b=2'); p.sort(); p.toString()",
737            "a=1&b=2&c=3",
738        ),
739        (
740            "var p=new URLSearchParams('b=2&a=1&a=0'); p.sort(); p.getAll('a').join(',')",
741            "1,0",
742        ),
743        // `url.toString()`(暗黙の文字列化 `fetch(url)` 等でも使われる)が `href` の
744        // 構築時点スナップショットのままで、`searchParams` の変更を反映しないバグ。
745        (
746            "var u=new URL('https://e.com/p?x=1'); u.searchParams.set('x','9'); u.toString()",
747            "https://e.com/p?x=9",
748        ),
749        (
750            "var u=new URL('https://e.com/p'); u.searchParams.append('a','1'); u.searchParams.append('b','2'); u.toString()",
751            "https://e.com/p?a=1&b=2",
752        ),
753        (
754            "var u=new URL('https://e.com/p?x=1#h'); u.searchParams.delete('x'); u.toString()",
755            "https://e.com/p#h",
756        ),
757        // `URL.prototype.toJSON`(仕様上 `toString()` と同じ href を返すだけの
758        // エイリアス)が丸ごと未対応で、`JSON.stringify(url)` がプロパティ丸ごとの
759        // JSON になってしまうバグだった。
760        (
761            "JSON.stringify({u: new URL('https://e.com/p?x=1')})",
762            "{\"u\":\"https://e.com/p?x=1\"}",
763        ),
764        // base64
765        ("btoa('hello')", "aGVsbG8="),
766        ("atob('aGVsbG8=')", "hello"),
767        ("atob(btoa('AtmOS roundtrip'))", "AtmOS roundtrip"),
768        // navigator / performance
769        ("navigator.platform", "AtmOS"),
770        ("typeof navigator.userAgent", "string"),
771        ("navigator.onLine", "true"),
772        // `navigator.cookieEnabled`/`.hardwareConcurrency`/`.maxTouchPoints`/
773        // `.languages`/`.webdriver`(丸ごと未対応だった)。
774        ("navigator.cookieEnabled", "true"),
775        ("navigator.hardwareConcurrency", "4"),
776        ("navigator.maxTouchPoints", "0"),
777        ("navigator.languages[0]", "ja-JP"),
778        ("navigator.webdriver", "false"),
779        // `navigator.userAgentData`(Client Hints API。丸ごと未対応だった。
780        // `navigator.userAgent`文字列パースに代わる構造化版の定番フィー
781        // チャー検出パターン。2026-07-17 発見・実装)。
782        (
783            "navigator.userAgentData.brands[0].brand + ',' + navigator.userAgentData.mobile + ',' + \
784             navigator.userAgentData.platform",
785            "AtmOSBrowser,false,AtmOS",
786        ),
787        (
788            "await navigator.userAgentData.getHighEntropyValues(['architecture','bitness']).then(v => \
789             v.architecture + ',' + v.bitness + ',' + v.platform)",
790            "arm,64,AtmOS",
791        ),
792        // `navigator.storage`(StorageManager API。`.estimate()`/`.persist()`/
793        // `.persisted()`が丸ごと未対応だった。2026-07-17 発見・実装。実際の
794        // ディスク使用量計測機構が無いため`usage:0`の誠実な簡略値、
795        // `persist`/`persisted`は常に`true`)。
796        (
797            "await navigator.storage.estimate().then(e => typeof e.usage + ',' + typeof e.quota)",
798            "number,number",
799        ),
800        ("await navigator.storage.persist()", "true"),
801        ("await navigator.storage.persisted()", "true"),
802        ("typeof performance.now()", "number"),
803        // Performance Timeline / User Timing API(`mark`/`measure`/
804        // `getEntriesByType`/`getEntriesByName`/`clearMarks`/`clearMeasures`)
805        // が `now()` 以外丸ごと未対応だった。
806        ("performance.mark('a').entryType", "mark"),
807        (
808            "performance.mark('s1'); performance.mark('e1'); \
809             performance.measure('m1','s1','e1').entryType",
810            "measure",
811        ),
812        // `performance.timeOrigin`(丸ごと未対応だった。2026-07-16 発見・
813        // 実装)。自己テストはブート初期段階(壁時計がまだ NTP 同期前)に
814        // 実行されるため `epoch_ms_now()` が `0` を返しうる。数値型である
815        // ことのみ検証し、正の値は断定しない。
816        ("typeof performance.timeOrigin", "number"),
817        ("performance.timeOrigin >= 0", "true"),
818        // `performance.getEntries()`(フィルタ無し全件取得。丸ごと未対応
819        // だった。2026-07-16 発見・実装)。
820        (
821            "performance.mark('ge1'); performance.getEntries().some(e => e.name === 'ge1')",
822            "true",
823        ),
824        ("performance.mark('a'); performance.getEntriesByType('mark').length > 0", "true"),
825        ("performance.mark('a'); performance.getEntriesByName('a', 'mark').length", "1"),
826        (
827            "performance.mark('to-clear'); performance.clearMarks('to-clear'); \
828             performance.getEntriesByName('to-clear').length",
829            "0",
830        ),
831        (
832            "performance.mark('sX'); performance.mark('eX'); \
833             performance.measure('mX','sX','eX'); performance.clearMeasures(); \
834             performance.getEntriesByType('measure').length",
835            "0",
836        ),
837        // `PerformanceObserver`(丸ごと未対応だった。`observe({entryTypes})`
838        // で監視中の種別に一致する `mark`/`measure` を同期通知する)。
839        (
840            "let got; new PerformanceObserver((list) => { got = list.getEntries()[0].name; }) \
841             .observe({entryTypes:['mark']}); performance.mark('obs1'); got",
842            "obs1",
843        ),
844        (
845            "let n = 0; new PerformanceObserver((list) => { n += list.getEntries().length; }) \
846             .observe({entryTypes:['measure']}); performance.mark('s2'); \
847             performance.mark('e2'); performance.measure('m2','s2','e2'); n",
848            "1",
849        ),
850        (
851            "let hit = false; let ob = new PerformanceObserver(() => { hit = true; }); \
852             ob.observe({entryTypes:['mark']}); ob.disconnect(); performance.mark('after-disconnect'); hit",
853            "false",
854        ),
855        // `PerformanceObserver.supportedEntryTypes`(丸ごと未対応だった。
856        // 2026-07-16 発見・実装)。
857        (
858            "PerformanceObserver.supportedEntryTypes.join(',')",
859            "mark,measure",
860        ),
861        // `observe({type: '...', buffered: true})`(単一種別+既存エントリ
862        // 即時配信の新形式)が丸ごと未対応だった(`entryTypes`しか読んで
863        // おらず`type`単体では一切発火しない黙殺バグだった。2026-07-17
864        // 発見・実装)。
865        (
866            "let got; new PerformanceObserver((list) => { got = list.getEntries()[0].name; }) \
867             .observe({type:'mark'}); performance.mark('obs2'); got",
868            "obs2",
869        ),
870        (
871            "performance.mark('pre-existing'); let got=null; \
872             new PerformanceObserver((list) => { got = list.getEntries()[0].name; }) \
873             .observe({type:'mark', buffered:true}); got",
874            "pre-existing",
875        ),
876        (
877            "let n=0; new PerformanceObserver((list) => { n += list.getEntries().length; }) \
878             .observe({type:'mark'}); n",
879            "0",
880        ),
881        // MessageChannel/MessagePort(丸ごと未対応だった。`.port1`/`.port2` 間の
882        // 双方向メッセージング)。
883        (
884            "let mc = new MessageChannel(); let got; \
885             mc.port2.onmessage = e => { got = e.data; }; \
886             mc.port1.postMessage('hi'); got",
887            "hi",
888        ),
889        (
890            "let mc2 = new MessageChannel(); let n = 0; \
891             mc2.port2.addEventListener('message', e => { n += e.data; }); \
892             mc2.port1.postMessage(1); mc2.port1.postMessage(2); n",
893            "3",
894        ),
895        (
896            "let mc3 = new MessageChannel(); let hit = false; \
897             mc3.port2.onmessage = () => { hit = true; }; \
898             mc3.port2.close(); mc3.port1.postMessage('x'); hit",
899            "false",
900        ),
901        // BroadcastChannel(丸ごと未対応だった。同名インスタンス全体への配送、
902        // 自分自身への配送除外、他名チャネルへの非配送を確認)。
903        (
904            "let a1 = new BroadcastChannel('ch'); let b1 = new BroadcastChannel('ch'); \
905             let got; b1.onmessage = e => { got = e.data; }; \
906             a1.postMessage('hi'); got",
907            "hi",
908        ),
909        (
910            "let a2 = new BroadcastChannel('ch2'); let selfHit = false; \
911             a2.onmessage = () => { selfHit = true; }; \
912             a2.postMessage('x'); selfHit",
913            "false",
914        ),
915        (
916            "let a3 = new BroadcastChannel('chX'); let b3 = new BroadcastChannel('chY'); \
917             let hit = false; b3.onmessage = () => { hit = true; }; \
918             a3.postMessage('x'); hit",
919            "false",
920        ),
921        // `window.screen`(丸ごと未対応だった。`screen.width`/`.height`/
922        // `.availWidth`/`.availHeight`/`.colorDepth`/`.orientation.type` という
923        // レスポンシブ分岐の定番パターン)。
924        ("typeof screen.width", "number"),
925        ("screen.width === window.innerWidth", "true"),
926        ("screen.availHeight === screen.height", "true"),
927        ("screen.colorDepth", "24"),
928        ("screen.orientation.type", "landscape-primary"),
929        ("window.screen === screen", "true"),
930        // `screen.orientation.lock()`/`.unlock()`(丸ごと未対応だった。実際に
931        // 画面の向きを固定する機構は無いため状態のみ追跡する簡略実装)。
932        (
933            "typeof screen.orientation.lock('portrait-primary').then",
934            "function",
935        ),
936        (
937            "screen.orientation.lock('portrait-primary'); screen.orientation.type",
938            "portrait-primary",
939        ),
940        (
941            "screen.orientation.lock('portrait-primary'); screen.orientation.unlock(); \
942             screen.orientation.type",
943            "landscape-primary",
944        ),
945        // `reportError()`(丸ごと未対応だった。`window.onerror`/
946        // `window.addEventListener('error', fn)` へ未捕捉例外と同じ経路で
947        // 通知する標準グローバル関数)。
948        (
949            "let got; window.onerror = (msg) => { got = msg; }; \
950             reportError(new Error('boom')); got",
951            "boom",
952        ),
953        (
954            "let ev; window.addEventListener('error', e => { ev = e; }); \
955             reportError(new Error('e2')); ev.error.message",
956            "e2",
957        ),
958        ("typeof reportError('plain string')", "undefined"),
959        // `window.confirm`/`window.prompt`(丸ごと未対応だった。`alert`は
960        // 既に`console_log`で代替されていたのに、対になるこの2つだけ
961        // グローバルにも`window`にも未登録で`TypeError`になっていた。
962        // ブロッキングUIが無いこの処理系では常に「OK」相当を返す簡略実装。
963        // 2026-07-16 発見・実装)。
964        ("confirm('are you sure?')", "true"),
965        ("window.confirm('are you sure?')", "true"),
966        ("prompt('name?', 'bob')", "bob"),
967        ("window.prompt('name?')", "null"),
968        // `window.open`/`.close`/`.focus`/`.blur`(丸ごと未対応だった。同じ
969        // 監査で発見。呼び出すと`TypeError`になっていた。この処理系には
970        // 複数ウィンドウ機構が無いため`open`はポップアップブロック相当の
971        // `null`、他は無害なno-op。2026-07-16 発見・実装)。
972        ("typeof open", "function"),
973        ("open('https://example.com')", "null"),
974        ("window.open('https://example.com', '_blank')", "null"),
975        ("typeof close", "function"),
976        ("close(); window.close(); 'ok'", "ok"),
977        // `window.stop()`(丸ごと未対応だった。ページ読み込み中止ボタン
978        // 相当の定番API。2026-07-17 発見・実装。`close`/`.print`と同じ
979        // 無害なno-op)。
980        ("typeof stop", "function"),
981        ("stop(); window.stop(); 'ok'", "ok"),
982        ("typeof focus", "function"),
983        ("focus(); window.focus(); blur(); window.blur(); 'ok'", "ok"),
984        // `window.getSelection()`(丸ごと未対応だった。`document.
985        // getSelection()`は既に実装済みだったが、仕様上こちらが本来の
986        // 正規の場所で、より一般的に使われる`window.getSelection().
987        // toString()`等のイディオムが`TypeError`になっていた。
988        // 2026-07-16 発見・実装。同じ実装を再利用するため戻り値も
989        // `document.getSelection()`と同一の空文字列)。
990        ("typeof window.getSelection", "function"),
991        ("typeof getSelection", "function"),
992        ("window.getSelection().toString()", ""),
993        // `self`/`top`/`parent`/`frames`(丸ごと未対応だった。`globalThis`
994        // は既に実装済みだったのに、同じ「windowを指す別名」仲間のこの4つ
995        // だけ登録が漏れていた。この処理系には独立JSレルムを持つiframeが
996        // 無く常に単一フレームのため、仕様どおり自分自身を指す。
997        // 2026-07-16 発見・実装)。
998        ("self === window", "true"),
999        ("window.top === window", "true"),
1000        ("parent === window", "true"),
1001        ("window.frames === window", "true"),
1002        ("typeof window.name", "string"),
1003        // `iframe.contentWindow`/`.contentDocument`(同じ「フレームが無い
1004        // ので自分自身を指す」規定。丸ごと未対応だった。2026-07-17
1005        // 発見・実装)。
1006        (
1007            "document.createElement('iframe').contentWindow === window",
1008            "true",
1009        ),
1010        (
1011            "document.createElement('iframe').contentDocument === document",
1012            "true",
1013        ),
1014        (
1015            "document.createElement('div').contentWindow",
1016            "undefined",
1017        ),
1018        // `object.contentDocument`(丸ごと未対応だった。`HTMLObjectElement`
1019        // は`contentWindow`を持たず`contentDocument`のみが仕様上の対象。
1020        // 2026-07-17 発見・実装)。
1021        (
1022            "document.createElement('object').contentDocument === document",
1023            "true",
1024        ),
1025        (
1026            "document.createElement('object').contentWindow",
1027            "undefined",
1028        ),
1029        // `navigator.sendBeacon(url, data)`(丸ごと未対応だった。ページ離脱時の
1030        // 計測データ送信に使われる定番パターン。既存の `fetch()` POST経路を
1031        // 再利用して送信し常に `true` を返す)。
1032        ("typeof navigator.sendBeacon", "function"),
1033        ("navigator.sendBeacon('data:,x')", "true"),
1034        ("navigator.sendBeacon('data:,x', 'payload')", "true"),
1035        // `XMLSerializer`(丸ごと未対応だった。`.serializeToString(node)` は
1036        // 既存の `outerHTML` ゲッターと同じ `get_outer_html` を再利用する)。
1037        (
1038            "var e=document.createElement('div'); e.setAttribute('id','x'); \
1039             e.setAttribute('class','a b'); e.innerHTML='hi2'; \
1040             new XMLSerializer().serializeToString(e) === e.outerHTML",
1041            "true",
1042        ),
1043        (
1044            "var e=document.createElement('span'); e.textContent='z'; \
1045             new XMLSerializer().serializeToString(e) === e.outerHTML",
1046            "true",
1047        ),
1048        ("typeof new XMLSerializer().serializeToString", "function"),
1049        // `navigator.clipboard`(丸ごと未対応だった。`writeText`/`readText` という
1050        // コピー&ペースト UI の定番パターン)。実クリップボードは無いため単純な
1051        // プロセス内文字列保持で近似する。
1052        (
1053            "await navigator.clipboard.writeText('hello'); await navigator.clipboard.readText()",
1054            "hello",
1055        ),
1056        ("typeof navigator.clipboard.writeText('x')", "object"),
1057        // Array: at / flatMap / findLast / findLastIndex
1058        ("[10,20,30].at(-1)", "30"),
1059        ("[10,20,30].at(0)", "10"),
1060        ("[1,2,3].flatMap(x => [x, x*10]).join(',')", "1,10,2,20,3,30"),
1061        ("[1,2,3,4].findLast(x => x % 2 === 1)", "3"),
1062        ("[1,2,3,4].findLastIndex(x => x % 2 === 0)", "3"),
1063        // String: at
1064        ("'hello'.at(-1)", "o"),
1065        ("'hello'.at(1)", "e"),
1066        // 通常の呼び出しは影響を受けないことも確認。
1067        ("'hello world'.includes('world')", "true"),
1068        ("'hello'.startsWith('he')", "true"),
1069        ("'hello'.endsWith('lo')", "true"),
1070        // **重要**: `includes`/`startsWith`/`endsWith` は仕様上、第1引数に
1071        // `RegExp` を渡すと `TypeError` を投げる必要がある(うっかり正規表現を
1072        // 渡した誤用に対する意図的なガード)が、丸ごと未対応で常に無言で
1073        // リテラル文字列 `"/x/"` として検索してしまっていた。
1074        (
1075            "try { 'x'.includes(/x/); 'no-throw' } catch(e) { 'threw' }",
1076            "threw",
1077        ),
1078        (
1079            "try { 'x'.startsWith(/x/); 'no-throw' } catch(e) { 'threw' }",
1080            "threw",
1081        ),
1082        (
1083            "try { 'x'.endsWith(/x/); 'no-throw' } catch(e) { 'threw' }",
1084            "threw",
1085        ),
1086        // Promise: allSettled / race / any
1087        ("(await Promise.allSettled([Promise.resolve(1), Promise.reject('e')])).map(r => r.status).join(',')", "fulfilled,rejected"),
1088        ("await Promise.race([Promise.resolve('first'), Promise.resolve('second')])", "first"),
1089        ("await Promise.any([Promise.reject('x'), Promise.resolve('ok')])", "ok"),
1090        // `allSettled`/`race`/`any` も `Promise.all` と同じく配列要素の汎用 thenable を
1091        // 同化しない非対称なバグだった(共通ヘルパ `resolve_maybe_thenable` で解消)。
1092        (
1093            "(await Promise.allSettled([{ then(r){ r(1); } }, { then(_,rj){ rj('e'); } }])).map(r => r.status).join(',')",
1094            "fulfilled,rejected",
1095        ),
1096        (
1097            "await Promise.race([{ then(r){ r('first'); } }, Promise.resolve('second')])",
1098            "first",
1099        ),
1100        (
1101            "await Promise.any([{ then(_,rj){ rj('x'); } }, { then(r){ r('ok'); } }])",
1102            "ok",
1103        ),
1104        // TextEncoder / TextDecoder
1105        ("new TextEncoder().encode('Hi').join(',')", "72,105"),
1106        ("new TextDecoder().decode([72,105])", "Hi"),
1107        ("new TextDecoder().decode(new TextEncoder().encode('AtmOS'))", "AtmOS"),
1108        // `new TextDecoder(label)` が丸ごとラベル引数を無視し常に `utf-8` 決め打ち
1109        // だったバグ(`utf-16le`/`utf-16be` データを渡すと静かに文字化けしていた)。
1110        ("new TextDecoder('utf-16le').encoding", "utf-16le"),
1111        ("new TextDecoder('utf-16be').encoding", "utf-16be"),
1112        // 'H'=0x48 'i'=0x69 を UTF-16LE(下位バイト→上位バイト)で並べたバイト列。
1113        ("new TextDecoder('utf-16le').decode([0x48,0x00,0x69,0x00])", "Hi"),
1114        ("new TextDecoder('utf-16be').decode([0x00,0x48,0x00,0x69])", "Hi"),
1115        ("new TextDecoder('utf-16').encoding", "utf-16le"),
1116        // `decode(chunk, {stream: true})`(丸ごと未対応だった。チャンク境界で
1117        // マルチバイト文字が分断された場合の定番パターン。2026-07-14 発見・
1118        // 実装)。'€'(U+20AC)の UTF-8 表現 [0xE2,0x82,0xAC] を先頭2バイトと
1119        // 残り1バイトに分割して渡す。
1120        (
1121            "var d=new TextDecoder(); d.decode(new Uint8Array([0xE2,0x82]), {stream:true}) + \
1122             d.decode(new Uint8Array([0xAC]))",
1123            "€",
1124        ),
1125        // stream:true 無し(既定 false)で不完全なバイト列を渡すと、従来どおり
1126        // 即座に置換文字化される(持ち越されない)。
1127        ("new TextDecoder().decode(new Uint8Array([0xE2,0x82]))", "\u{FFFD}"),
1128        // crypto
1129        ("crypto.randomUUID().length", "36"),
1130        ("crypto.randomUUID().charAt(14)", "4"),
1131        ("crypto.getRandomValues([0,0,0]).length", "3"),
1132        // `crypto.getRandomValues` が種別を無視して常に `& 0xff`(0-255)で埋めており、
1133        // 8bit より広い型(`Uint16Array` 等)では値域のごく一部しか使わないバグだった。
1134        // 十分な試行数の中に 255 を超える値が1つでも現れれば、下位8bitマスクではなく
1135        // 実際に16bit幅を使っている証拠になる。
1136        (
1137            "crypto.getRandomValues(new Uint16Array(200)).some(v => v > 255)",
1138            "true",
1139        ),
1140        // `Uint8Array` は元々の挙動(0-255 の範囲内)を維持する。
1141        (
1142            "crypto.getRandomValues(new Uint8Array(50)).every(v => v >= 0 && v <= 255)",
1143            "true",
1144        ),
1145        // structuredClone(深いコピー: 元を変えても複製は不変)
1146        ("var o={a:{b:1}}; var c=structuredClone(o); o.a.b=9; c.a.b", "1"),
1147        ("structuredClone([1,[2,3]])[1][0]", "2"),
1148        // structuredClone(Error) — 汎用の plain object 複製に落ちると proto が
1149        // 引き継がれず `instanceof Error` を失っていたバグ(2026-07-14 発見・修正。
1150        // message/name/stack は元々コピーされていたが、プロトタイプ連鎖が
1151        // 途切れていた)。サブタイプ(TypeError 等)も正しいプロトタイプで
1152        // 複製されることを確認。
1153        (
1154            "var c=structuredClone(new TypeError('x')); c instanceof Error && c instanceof TypeError",
1155            "true",
1156        ),
1157        ("structuredClone(new Error('boom')).message", "boom"),
1158        ("structuredClone(new TypeError('x')).stack", "TypeError: x"),
1159        // `structuredClone(function)`が仕様上の`DataCloneError`を投げず、
1160        // 汎用plain object複製に落ちて「呼出不能な壊れたオブジェクト」に
1161        // 静かに化けていたバグ(`deep_clone_value`を`Result`化して修正。
1162        // 2026-07-17 発見・実装)。
1163        (
1164            "try { structuredClone(function(){}); 'no-throw' } catch(e) { e.name }",
1165            "DataCloneError",
1166        ),
1167        (
1168            "try { structuredClone({fn: () => 1}); 'no-throw' } catch(e) { e.name }",
1169            "DataCloneError",
1170        ),
1171        ("structuredClone({a: 1, b: [2, 3]}).a", "1"),
1172        // 上記`DataCloneError`修正の副次効果として、`URLSearchParams`/
1173        // `FormData`/`Headers`(メソッド群が`.props`上の生の関数値として
1174        // 実装されている「疑似クラス」オブジェクト)を`structuredClone`
1175        // すると、以前は関数プロパティごと汎用複製されて壊れたオブジェクト
1176        // (メソッド呼び出し不能)に静かに化けていたのが、今回の修正で
1177        // 正しく`DataCloneError`を投げるようになったことを確認・記録する
1178        // (2026-07-17)。
1179        (
1180            "try { structuredClone(new URLSearchParams('a=1')); 'no-throw' } catch(e) { e.name }",
1181            "DataCloneError",
1182        ),
1183        (
1184            "try { structuredClone(new FormData()); 'no-throw' } catch(e) { e.name }",
1185            "DataCloneError",
1186        ),
1187        (
1188            "try { structuredClone(new Headers()); 'no-throw' } catch(e) { e.name }",
1189            "DataCloneError",
1190        ),
1191        // `structuredClone(Promise)`も仕様上構造化複製不可(`DataCloneError`)
1192        // だが未対応で、`.then`/`.catch`が消えた呼出不能な壊れたオブジェクト
1193        // に静かに化けていた(丸ごと未対応だった。2026-07-17 発見・実装)。
1194        (
1195            "try { structuredClone(Promise.resolve(1)); 'no-throw' } catch(e) { e.name }",
1196            "DataCloneError",
1197        ),
1198        // `WeakMap`/`WeakSet`も`URLSearchParams`/`FormData`/`Headers`と同じ
1199        // 「メソッド群が`.props`上の生の関数値」パターンの疑似クラスで、
1200        // 仕様上も構造化複製不可(`DataCloneError`)。前サイクルまでの
1201        // `deep_clone_value`関数検出修正の副次効果で既に正しく動作している
1202        // ことを確認・記録する(2026-07-17)。
1203        (
1204            "try { structuredClone(new WeakMap()); 'no-throw' } catch(e) { e.name }",
1205            "DataCloneError",
1206        ),
1207        (
1208            "try { structuredClone(new WeakSet()); 'no-throw' } catch(e) { e.name }",
1209            "DataCloneError",
1210        ),
1211        // `AbortController`/`AbortSignal`/`MessagePort`/`MessageChannel`も
1212        // 同じ「メソッド群が`.props`上の生の関数値」パターンの疑似クラス
1213        // で、仕様上も構造化複製不可。前サイクルまでの`deep_clone_value`
1214        // 関数検出修正の副次効果で正しく動作していることを確認・記録し、
1215        // `DataCloneError`検証の横断監査を完了する(2026-07-17)。
1216        (
1217            "try { structuredClone(new AbortController()); 'no-throw' } catch(e) { e.name }",
1218            "DataCloneError",
1219        ),
1220        (
1221            "try { structuredClone(new AbortController().signal); 'no-throw' } catch(e) { e.name }",
1222            "DataCloneError",
1223        ),
1224        (
1225            "try { structuredClone(new MessageChannel()); 'no-throw' } catch(e) { e.name }",
1226            "DataCloneError",
1227        ),
1228        (
1229            "try { structuredClone(new MessageChannel().port1); 'no-throw' } catch(e) { e.name }",
1230            "DataCloneError",
1231        ),
1232        // structuredClone(Map/Set) — 中身(エントリ/要素)まで正しく複製され、元を変えても
1233        // 複製に影響しないこと(以前は ObjKind::MapObj/SetObj 自体が複製されず消えていたバグ)。
1234        (
1235            "var m=new Map([['a',1],['b',2]]); var c=structuredClone(m); m.set('a',99); c.get('a')+','+c.get('b')+','+c.size",
1236            "1,2,2",
1237        ),
1238        (
1239            "var s=new Set([1,2,3]); var c=structuredClone(s); s.add(4); c.has(4)+','+c.size",
1240            "false,3",
1241        ),
1242        // structuredClone(TypedArray) — TypedArray も内部表現は ObjKind::Array 流用
1243        // (`_ta_kind` タグで区別)なため、素の配列複製経路に落ちると型情報
1244        // (`_ta_kind`/`BYTES_PER_ELEMENT`/`set`/`slice`/`fill`)が丸ごと失われる
1245        // バグだった(Date/RegExp/Map/Set と同種)。
1246        ("var a=new Int8Array([200,-100]); var c=structuredClone(a); c.join(',')", "-56,-100"),
1247        (
1248            "var a=new Int8Array(1); var c=structuredClone(a); c.BYTES_PER_ELEMENT",
1249            "1",
1250        ),
1251        ("var a=new Int8Array(1); var c=structuredClone(a); c[0]=200; c[0]", "-56"),
1252        (
1253            "var a=new Int8Array([1,2]); var c=structuredClone(a); a[0]=9; c[0]",
1254            "1",
1255        ),
1256        // structuredClone(buf, {transfer:[buf]}) — 第2引数 transfer が丸ごと
1257        // 無視されており、転送指定した ArrayBuffer が detached にならなかった
1258        // バグ。
1259        (
1260            "var b=new ArrayBuffer(4); var c=structuredClone(b,{transfer:[b]}); b.detached+','+c.byteLength",
1261            "true,4",
1262        ),
1263        // structuredClone(Blob/DataView) — どちらも `Obj::plain()` + 隠しプロパティで
1264        // 実バイト列を持つ実装のため、汎用の plain object 複製に落ちると
1265        // `slice`/`text`/`getUint8` 等のネイティブ関数プロパティが呼出不能な壊れた
1266        // オブジェクトに複製されてしまうバグだった(TypedArray と同種)。
1267        (
1268            "var b=new Blob(['hi']); var c=structuredClone(b); await c.text()",
1269            "hi",
1270        ),
1271        ("var b=new Blob(['hi']); var c=structuredClone(b); c.size", "2"),
1272        (
1273            "var dv=new DataView(new ArrayBuffer(1)); dv.setUint8(0,65); var c=structuredClone(dv); c.getUint8(0)",
1274            "65",
1275        ),
1276        // structuredClone(ArrayBuffer) — `slice` がネイティブ関数プロパティのため
1277        // 同種のバグで複製後は呼出不能になっていた。
1278        (
1279            "var buf=new ArrayBuffer(4); var c=structuredClone(buf); c.byteLength",
1280            "4",
1281        ),
1282        (
1283            "var buf=new ArrayBuffer(4); var c=structuredClone(buf); typeof c.slice",
1284            "function",
1285        ),
1286        // structuredClone(Proxy) — 実データが `.props` ではなく `ObjKind::Proxy`
1287        // 側にあるため、汎用の plain object 複製に落ちると常に空の `{}` になる
1288        // バグだった(`Date`/`Map`/`Set` 等と同種)。仕様上は本来複製不可
1289        // (`DataCloneError`)だが、他の Proxy 透過性修正と同じ「target へ
1290        // フォワード」方針で target を複製する簡略実装とした。
1291        (
1292            "var p=new Proxy({a:1,b:2},{}); var c=structuredClone(p); c.a+','+c.b",
1293            "1,2",
1294        ),
1295        (
1296            "var t={a:1}; var p=new Proxy(t,{}); var c=structuredClone(p); t.a=99; c.a",
1297            "1",
1298        ),
1299        (
1300            "var buf = await new Blob(['A']).arrayBuffer(); var c=structuredClone(buf); new DataView(c).getUint8(0)",
1301            "65",
1302        ),
1303        // Object: fromEntries / getOwnPropertyNames / freeze
1304        ("Object.fromEntries([['a',1],['b',2]]).b", "2"),
1305        ("Object.getOwnPropertyNames({x:1,y:2}).join(',')", "x,y"),
1306        ("var o=Object.freeze({a:1}); o.a", "1"),
1307        // Number 静的
1308        ("Number.isInteger(5)", "true"),
1309        ("Number.isInteger(5.5)", "false"),
1310        ("Number.isInteger('5')", "false"),
1311        ("Number.isSafeInteger(9007199254740991)", "true"),
1312        ("Number.MAX_SAFE_INTEGER", "9007199254740991"),
1313        ("Number.isNaN(NaN) + ',' + Number.isNaN(5)", "true,false"),
1314        // `Number.MIN_VALUE`/`Number.NaN`(ES1 以来の定数)が丸ごと未登録だった。
1315        // `MIN_VALUE` は「0に最も近い正の値」(非正規化数まで含む)であり
1316        // `MIN_SAFE_INTEGER`(大きな負の整数)とは別物であることに注意。
1317        ("Number.MIN_VALUE > 0", "true"),
1318        ("Number.MIN_VALUE < Number.EPSILON", "true"),
1319        ("Number.isNaN(Number.NaN)", "true"),
1320        // Math 追加
1321        ("Math.hypot(3,4)", "5"),
1322        ("Math.log2(8)", "3"),
1323        ("Math.round(Math.log10(1000))", "3"),
1324        ("Math.round(Math.cbrt(27))", "3"),
1325        ("Math.atan2(0,5)", "0"),
1326        // Math 追加2(ES2015。fround/clz32/imul/sinh系/expm1/log1p/定数群が丸ごと未対応だった)。
1327        ("Math.fround(1.5)", "1.5"),
1328        ("Math.fround(1.1) !== 1.1", "true"),
1329        ("Math.f16round(1.5)", "1.5"),
1330        ("Math.f16round(1.337) !== 1.337", "true"),
1331        ("Math.sumPrecise([1, 2, 3])", "6"),
1332        ("Math.sumPrecise([0.1, 0.2])", "0.30000000000000004"),
1333        ("Math.sumPrecise([1, Infinity])", "Infinity"),
1334        ("isNaN(Math.sumPrecise([Infinity, -Infinity]))", "true"),
1335        ("isNaN(Math.sumPrecise([1, NaN]))", "true"),
1336        ("new Headers({'Set-Cookie':'a=1'}).getSetCookie().join(',')", "a=1"),
1337        ("var h=new Headers(); h.append('Set-Cookie','a=1'); h.append('Set-Cookie','b=2'); h.getSetCookie().join('; ')", "a=1; b=2"),
1338        ("Math.clz32(1)", "31"),
1339        ("Math.clz32(0)", "32"),
1340        ("Math.clz32(1000)", "22"),
1341        ("Math.imul(3,4)", "12"),
1342        ("Math.imul(0xffffffff, 5)", "-5"),
1343        ("Math.round(Math.sinh(0))", "0"),
1344        ("Math.cosh(0)", "1"),
1345        ("Math.tanh(0)", "0"),
1346        ("Math.asinh(0)", "0"),
1347        ("Math.acosh(1)", "0"),
1348        ("Math.atanh(0)", "0"),
1349        ("Math.round(Math.expm1(0))", "0"),
1350        ("Math.log1p(0)", "0"),
1351        ("Math.round(Math.LN2*1000)", "693"),
1352        ("Math.round(Math.LN10*1000)", "2303"),
1353        ("Math.round(Math.SQRT2*1000)", "1414"),
1354        ("Math.round(Math.SQRT1_2*1000)", "707"),
1355        // AbortController
1356        ("var c=new AbortController(); var b=c.signal.aborted; c.abort(); b+','+c.signal.aborted", "false,true"),
1357        // `signal.addEventListener('abort', fn)` が丸ごと no-op で、`abort()` を呼んでも
1358        // 登録済みリスナが一切発火しないバグだった。`signal.reason` も未対応だった。
1359        (
1360            "var c=new AbortController(); var fired=false; c.signal.addEventListener('abort', function(){fired=true;}); c.abort(); fired",
1361            "true",
1362        ),
1363        ("var c=new AbortController(); c.abort(); c.signal.reason.name", "AbortError"),
1364        ("var c=new AbortController(); c.abort('custom'); c.signal.reason", "custom"),
1365        // 二重 abort() は2回目以降が黙殺される(リスナが2回発火しない)。
1366        (
1367            "var c=new AbortController(); var n=0; c.signal.addEventListener('abort', function(){n++;}); c.abort(); c.abort(); n",
1368            "1",
1369        ),
1370        // `AbortSignal.abort(reason)`(ES2022)が、`AbortController` 経由でしか
1371        // signal を得られず丸ごと未対応だった。
1372        ("AbortSignal.abort().aborted", "true"),
1373        ("AbortSignal.abort('why').reason", "why"),
1374        ("AbortSignal.abort().reason.name", "AbortError"),
1375        // `DOMException`(丸ごと未対応だった。`new DOMException(message, name)`
1376        // というユーザーコードからの直接構築、レガシー `.code` 数値定数)。
1377        ("new DOMException('boom', 'NotFoundError').message", "boom"),
1378        ("new DOMException('boom', 'NotFoundError').name", "NotFoundError"),
1379        ("new DOMException('boom', 'NotFoundError').code", "8"),
1380        ("DOMException.NOT_FOUND_ERR", "8"),
1381        ("new DOMException().name", "Error"),
1382        ("new DOMException('x', 'CustomError').code", "0"),
1383        // 既存の AbortError も同じ DOMException 経路で構築されるようになった
1384        // (`.code` が正しく `20`=`ABORT_ERR` になることを確認)。
1385        ("AbortSignal.abort().reason.code", "20"),
1386        // `signal.removeEventListener('abort', fn)` が `addEventListener` は対応済み
1387        // なのに対をなす削除側だけ無条件 no-op のままだったバグ。
1388        (
1389            "var c=new AbortController(); var n=0; var fn=function(){n++;}; c.signal.addEventListener('abort', fn); c.signal.removeEventListener('abort', fn); c.abort(); n",
1390            "0",
1391        ),
1392        (
1393            "var c=new AbortController(); var n=0; c.signal.addEventListener('abort', function(){n++;}); c.signal.removeEventListener('abort', function(){}); c.abort(); n",
1394            "1",
1395        ),
1396        // `AbortSignal.any(iterable)`(ES2024)— 複数の中断条件を1つの signal にまとめる。
1397        (
1398            "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",
1399            "false,true,two",
1400        ),
1401        (
1402            "var c1=new AbortController(); c1.abort('already'); AbortSignal.any([c1.signal]).reason",
1403            "already",
1404        ),
1405        // `signal.throwIfAborted()` が丸ごと未対応だった。中断前は no-op。
1406        ("var c=new AbortController(); c.signal.throwIfAborted()", "undefined"),
1407        // 中断後は `reason` を投げる。
1408        (
1409            "var c=new AbortController(); c.abort('boom'); \
1410             var caught; try { c.signal.throwIfAborted(); } catch (e) { caught = e; } caught",
1411            "boom",
1412        ),
1413        // `EventTarget` が丸ごと未対応だった(DOM 要素以外で独自のイベント発行/購読の
1414        // 基盤として使う定番パターン)。他の EventTarget 風オブジェクトと違い型ごとに
1415        // 複数リスナを保持する。
1416        (
1417            "var t=new EventTarget(); var log=''; t.addEventListener('x', ()=>log+='a'); t.addEventListener('x', ()=>log+='b'); t.dispatchEvent(new Event('x')); log",
1418            "ab",
1419        ),
1420        (
1421            "var t=new EventTarget(); var n=0; var fn=()=>n++; t.addEventListener('x', fn); t.addEventListener('x', fn); t.dispatchEvent(new Event('x')); n",
1422            "1",
1423        ),
1424        (
1425            "var t=new EventTarget(); var n=0; var fn=()=>n++; t.addEventListener('x', fn); t.removeEventListener('x', fn); t.dispatchEvent(new Event('x')); n",
1426            "0",
1427        ),
1428        // 同一関数を capture/bubble 両方で登録すると重複扱いされず2件とも
1429        // 保持される(仕様上 `(listener, capture)` の組で区別すべきところ、
1430        // 以前は `capture` を見ずに `cb` だけで重複判定していたため2回目の
1431        // 登録が黙って無視されるバグだった。`document`のcapture対応
1432        // 〔2026-07-16〕で顕在化・同日発見・修正)。
1433        (
1434            "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",
1435            "2",
1436        ),
1437        // `removeEventListener(type, fn)`(capture省略=既定false)はcapture
1438        // 登録済みの同一関数を巻き込んで消してはいけない。
1439        (
1440            "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",
1441            "1",
1442        ),
1443        // `addEventListener(type, fn, {signal})`(丸ごと未対応だった。
1444        // `AbortController` でリスナを一括解除する定番パターン。2026-07-14
1445        // 発見・実装)。abort 後は発火しなくなる。
1446        (
1447            "var t=new EventTarget(); var c=new AbortController(); var n=0; \
1448             t.addEventListener('x', ()=>n++, {signal: c.signal}); \
1449             t.dispatchEvent(new Event('x')); c.abort(); t.dispatchEvent(new Event('x')); n",
1450            "1",
1451        ),
1452        // 登録前に既に abort 済みの signal を渡した場合は登録自体が行われない。
1453        (
1454            "var t=new EventTarget(); var c=new AbortController(); c.abort(); var n=0; \
1455             t.addEventListener('x', ()=>n++, {signal: c.signal}); \
1456             t.dispatchEvent(new Event('x')); n",
1457            "0",
1458        ),
1459        // DOM 要素側(`Element.prototype.addEventListener`)でも同じく対応。
1460        (
1461            "var el=document.createElement('div'); var c=new AbortController(); var n=0; \
1462             el.addEventListener('click', ()=>n++, {signal: c.signal}); \
1463             el.click(); c.abort(); el.click(); n",
1464            "1",
1465        ),
1466        // `event.preventDefault()` が丸ごと no-op で `defaultPrevented` を一切
1467        // 追跡せず、`dispatchEvent()` の戻り値が常に `true` になるバグだった。
1468        (
1469            "var t=new EventTarget(); t.addEventListener('x', e => e.preventDefault()); t.dispatchEvent(new Event('x', {cancelable:true}))",
1470            "false",
1471        ),
1472        (
1473            "var t=new EventTarget(); t.addEventListener('x', e => e.preventDefault()); t.dispatchEvent(new Event('x'))",
1474            "true",
1475        ),
1476        // `event.isTrusted` が丸ごと未対応だった。スクリプトから発火した
1477        // イベントは仕様上常に `false`。
1478        (
1479            "var t=new EventTarget(); var r; t.addEventListener('x', e => { r = e.isTrusted; }); t.dispatchEvent(new Event('x')); r",
1480            "false",
1481        ),
1482        // `event.composed`(丸ごと未対応だった。`Event`/`CustomEvent`両方の
1483        // コンストラクタで`options.composed`が一切読まれず常に`undefined`に
1484        // なっていた。2026-07-16 発見・実装)。
1485        ("new Event('x', {composed: true}).composed", "true"),
1486        ("new Event('x').composed", "false"),
1487        (
1488            "new CustomEvent('x', {detail: 1, composed: true}).composed",
1489            "true",
1490        ),
1491        // `EventTarget` のイベントに `stopImmediatePropagation` が丸ごと
1492        // 未対応で、呼び出すと TypeError になっていた(そもそも呼び出しようが
1493        // ないため残りのリスナーを止める効果も一切なかった)。
1494        (
1495            "var t=new EventTarget(); var log=''; \
1496             t.addEventListener('x', e => { log+='a'; e.stopImmediatePropagation(); }); \
1497             t.addEventListener('x', () => { log+='b'; }); \
1498             t.dispatchEvent(new Event('x')); log",
1499            "a",
1500        ),
1501        // `EventTarget.addEventListener` の第3引数 `{once:true}` が丸ごと
1502        // 読まれておらず、一度発火しても解除されず何度でも呼ばれ続けていた。
1503        (
1504            "var t=new EventTarget(); var n=0; t.addEventListener('x', ()=>n++, {once:true}); \
1505             t.dispatchEvent(new Event('x')); t.dispatchEvent(new Event('x')); n",
1506            "1",
1507        ),
1508        // once 指定でも removeEventListener で明示的に解除できる。
1509        (
1510            "var t=new EventTarget(); var n=0; var fn=()=>n++; t.addEventListener('x', fn, {once:true}); \
1511             t.removeEventListener('x', fn); t.dispatchEvent(new Event('x')); n",
1512            "0",
1513        ),
1514        // `EventTarget` 発の `Event`/`CustomEvent` に `composedPath()` が丸ごと
1515        // 未対応で、呼び出すと TypeError になっていた(DOM 要素側は既に対応済み)。
1516        (
1517            "var t=new EventTarget(); var p; t.addEventListener('x', e => { p = e.composedPath(); }); \
1518             t.dispatchEvent(new Event('x')); p.length === 1 && p[0] === t",
1519            "true",
1520        ),
1521        // 発火前(target 未設定)は composedPath() が空配列を返す。
1522        ("new CustomEvent('x').composedPath().length", "0"),
1523        // `KeyboardEvent`/`MouseEvent`(丸ごと未対応だった。`el.dispatchEvent
1524        // (new KeyboardEvent('keydown', {key:'a'}))` というシミュレーション
1525        // イベント構築の定番パターン)。
1526        ("new KeyboardEvent('keydown', {key:'a', code:'KeyA'}).key", "a"),
1527        ("new KeyboardEvent('keydown', {key:'a', code:'KeyA'}).code", "KeyA"),
1528        (
1529            "var t=new EventTarget(); var got; \
1530             t.addEventListener('keydown', e => { got = e.ctrlKey + ':' + e.key; }); \
1531             t.dispatchEvent(new KeyboardEvent('keydown', {key:'Enter', ctrlKey:true})); got",
1532            "true:Enter",
1533        ),
1534        ("new MouseEvent('click', {clientX:10, clientY:20}).clientX", "10"),
1535        (
1536            "var t=new EventTarget(); var got; \
1537             t.addEventListener('click', e => { got = e.clientY + ':' + e.button; }); \
1538             t.dispatchEvent(new MouseEvent('click', {clientY:5, button:2})); got",
1539            "5:2",
1540        ),
1541        ("new MouseEvent('click').bubbles", "false"),
1542        ("new MouseEvent('click', {bubbles:true}).bubbles", "true"),
1543        // `WheelEvent`/`FocusEvent`(丸ごと未対応だった。`MouseEvent` を継承
1544        // する `WheelEvent` は座標系プロパティも合わせて持つべき仕様)。
1545        ("new WheelEvent('wheel', {deltaY:100}).deltaY", "100"),
1546        ("new WheelEvent('wheel', {deltaY:1, clientX:5}).clientX", "5"),
1547        ("new WheelEvent('wheel').deltaMode", "0"),
1548        ("new FocusEvent('focusout', {relatedTarget: null}).relatedTarget === null", "true"),
1549        (
1550            "var t=new EventTarget(); var r=new EventTarget(); var got; \
1551             t.addEventListener('focusout', e => { got = e.relatedTarget; }); \
1552             t.dispatchEvent(new FocusEvent('focusout', {relatedTarget: r})); got === r",
1553            "true",
1554        ),
1555        // `Touch`/`TouchEvent`(丸ごと未対応だった。この処理系にはタッチ
1556        // スクリーン入力機構自体が無いが、コンストラクタでのシミュレーション
1557        // パターンには対応する。2026-07-15 発見・実装)。
1558        ("new Touch({identifier:1, clientX:10, clientY:20}).clientX", "10"),
1559        ("new Touch({}).force", "1"),
1560        (
1561            "var t=new Touch({identifier:1}); \
1562             new TouchEvent('touchstart', {touches:[t], changedTouches:[t]}).touches[0] === t",
1563            "true",
1564        ),
1565        // `CommandEvent`/`ToggleEvent`/`FormDataEvent`(WHATWG HTML Standard / Invoker Commands API)
1566        ("new CommandEvent('command', {command:'show-modal'}).command", "show-modal"),
1567        ("new CommandEvent('command', {source:null}).source === null", "true"),
1568        ("new ToggleEvent('toggle', {oldState:'closed', newState:'open'}).newState", "open"),
1569        ("new ToggleEvent('toggle').oldState", "closed"),
1570        ("new FormDataEvent('formdata', {formData:null}).formData === null", "true"),
1571
1572        ("new TouchEvent('touchend').changedTouches.length", "0"),
1573        // `HashChangeEvent`/`PageTransitionEvent`(丸ごと未対応だった。
1574        // 2026-07-15 発見・実装)。
1575        (
1576            "new HashChangeEvent('hashchange', {oldURL:'a', newURL:'b'}).oldURL + ',' + \
1577             new HashChangeEvent('hashchange', {oldURL:'a', newURL:'b'}).newURL",
1578            "a,b",
1579        ),
1580        (
1581            "new HashChangeEvent('hashchange').type",
1582            "hashchange",
1583        ),
1584        (
1585            "new PageTransitionEvent('pageshow', {persisted:true}).persisted",
1586            "true",
1587        ),
1588        // `PopStateEvent`(丸ごと未対応だった。実ナビゲーション経由の発火は
1589        // 既に`state`付きで動いていたが、手動シミュレーション用の専用
1590        // コンストラクタが無かった。2026-07-17 発見・実装)。
1591        (
1592            "new PopStateEvent('popstate', {state:{a:1}}).state.a",
1593            "1",
1594        ),
1595        ("new PopStateEvent('popstate').state", "null"),
1596        (
1597            "var got=null; window.addEventListener('popstate', e => got = e.state); \
1598             window.dispatchEvent(new PopStateEvent('popstate', {state:'s'})); got",
1599            "s",
1600        ),
1601        // `DOMRect`/`DOMRectReadOnly`コンストラクタ(丸ごと未対応だった。
1602        // `getBoundingClientRect()`と同じ形状で手動構築する定番パターン。
1603        // 2026-07-17 発見・実装)。
1604        (
1605            "var r=new DOMRect(10,20,30,40); \
1606             [r.x,r.y,r.width,r.height,r.left,r.top,r.right,r.bottom].join(',')",
1607            "10,20,30,40,10,20,40,60",
1608        ),
1609        ("new DOMRect().width", "0"),
1610        ("new DOMRectReadOnly(1,2,3,4).right", "4"),
1611        ("JSON.stringify(new DOMRect(1,2,3,4).toJSON()).includes('\"width\":3')", "true"),
1612        // `DOMPoint`/`DOMPointReadOnly`コンストラクタおよび DOMMatrix/DOMQuad (Geometry Interfaces)
1613        (
1614            "var p=new DOMPoint(1,2); p.x+','+p.y+','+p.z+','+p.w",
1615            "1,2,0,1",
1616        ),
1617        ("new DOMPoint(1,2,3,4).w", "4"),
1618        ("new DOMPointReadOnly().x", "0"),
1619        ("JSON.stringify(new DOMPoint(1,2).toJSON()).includes('\"y\":2')", "true"),
1620        ("new DOMPoint(10, 20).matrixTransform(new DOMMatrix().translate(5, 5)).x", "15"),
1621        ("new DOMMatrix().isIdentity", "true"),
1622        ("new DOMMatrix().is2D", "true"),
1623        ("new DOMMatrix([1,0,0,1,10,20]).toString()", "matrix(1, 0, 0, 1, 10, 20)"),
1624        ("new DOMMatrix().translate(10, 20).e", "10"),
1625        ("new DOMMatrix().scale(2, 3).d", "3"),
1626        ("DOMMatrix.fromFloat32Array(new Float32Array([1,0,0,1,10,20])).e", "10"),
1627        ("new DOMMatrix().rotate(90).b", "1"),
1628        ("new DOMMatrix([1,0,0,1,10,20]).invert().e", "-10"),
1629        ("new DOMMatrix().translateSelf(10, 20).e", "10"),
1630        ("new DOMMatrix().rotateSelf(90).b", "1"),
1631        ("new DOMMatrix().scaleSelf(2, 3).d", "3"),
1632        ("new DOMMatrix([1,0,0,1,10,20]).invertSelf().e", "-10"),
1633        ("DOMQuad.fromRect(new DOMRect(10, 20, 30, 40)).getBounds().width", "30"),
1634        ("DOMRect.fromRect({x: 5, y: 10, width: 15, height: 20}).width", "15"),
1635        ("DOMPoint.fromPoint({x: 3, y: 4, z: 5, w: 1}).z", "5"),
1636        ("'가'.normalize('NFD').normalize('NFC')", "가"),
1637
1638        // `window.dispatchEvent(event)`(丸ごと未対応だった。前サイクルで
1639        // 発見した既知ギャップを本サイクルで解消。2026-07-15 発見・実装)。
1640        (
1641            "var got=null; window.addEventListener('hashchange', e => got = e.newURL); \
1642             window.dispatchEvent(new HashChangeEvent('hashchange', {newURL:'x'})); got",
1643            "x",
1644        ),
1645        (
1646            "window.dispatchEvent(new Event('unknown-type'))",
1647            "true",
1648        ),
1649        // `window.dispatchEvent()` は `addEventListener` 登録分だけでなく
1650        // `window.on<type>`(IDL属性スタイル)も合わせて呼び出す(丸ごと
1651        // 未対応だった。2026-07-15 発見・実装)。
1652        (
1653            "var n=0; window.onhashchange = () => n++; \
1654             window.dispatchEvent(new HashChangeEvent('hashchange')); n",
1655            "1",
1656        ),
1657        (
1658            "var order=[]; window.onresize = () => order.push('on'); \
1659             window.addEventListener('resize', () => order.push('listener')); \
1660             window.dispatchEvent(new Event('resize')); order.join(',')",
1661            "on,listener",
1662        ),
1663        // `window.removeEventListener()`(丸ごと未対応だった。以前は無害な
1664        // no-op のままで、`AbortSignal` 経由以外ではリスナが二度と解除
1665        // できなかった。2026-07-15 発見・実装)。
1666        (
1667            "var n=0; var cb = () => n++; window.addEventListener('resize', cb); \
1668             window.removeEventListener('resize', cb); \
1669             window.dispatchEvent(new Event('resize')); n",
1670            "0",
1671        ),
1672        (
1673            "var log=[]; var cb1 = () => log.push('1'); var cb2 = () => log.push('2'); \
1674             window.addEventListener('resize', cb1); window.addEventListener('resize', cb2); \
1675             window.removeEventListener('resize', cb1); \
1676             window.dispatchEvent(new Event('resize')); log.join(',')",
1677            "2",
1678        ),
1679        // `document.removeEventListener()`(丸ごと未対応だった。`addEventListener`
1680        // は `window` 版を再利用していたが、対になる `removeEventListener`
1681        // 自体が未登録で「関数ではない」`TypeError` になっていた。2026-07-15
1682        // 発見・実装)。
1683        (
1684            "typeof document.removeEventListener",
1685            "function",
1686        ),
1687        (
1688            "var n=0; var cb = () => n++; document.addEventListener('hashchange', cb); \
1689             document.removeEventListener('hashchange', cb); \
1690             window.dispatchEvent(new Event('hashchange')); n",
1691            "0",
1692        ),
1693        // `document.dispatchEvent`(丸ごと未対応だった。`addEventListener`/
1694        // `removeEventListener`と同じ兄弟ギャップ。2026-07-15 発見・実装)。
1695        (
1696            "var got=null; document.addEventListener('hashchange', e => got = e.newURL); \
1697             document.dispatchEvent(new HashChangeEvent('hashchange', {newURL:'y'})); got",
1698            "y",
1699        ),
1700        // `window.dispatchEvent()`/`document.dispatchEvent()` の戻り値が仕様
1701        // どおり `!defaultPrevented` になること(以前は無条件 `true` を返し、
1702        // リスナが `preventDefault()` を呼んでも常に `true` だった。要素用
1703        // `dom_dispatch_event`/`event_target_dispatch_event` は正しく実装済みで
1704        // window/document 経路にだけ同型バグが残っていた。2026-07-16 発見・修正)。
1705        (
1706            "window.addEventListener('resize', e => e.preventDefault()); \
1707             window.dispatchEvent(new Event('resize', {cancelable:true}))",
1708            "false",
1709        ),
1710        (
1711            "document.addEventListener('scroll', e => e.preventDefault()); \
1712             document.dispatchEvent(new Event('scroll', {cancelable:true}))",
1713            "false",
1714        ),
1715        // `preventDefault()` を呼ばなければ従来どおり `true` を返す。
1716        (
1717            "window.addEventListener('popstate', () => {}); \
1718             window.dispatchEvent(new Event('popstate'))",
1719            "true",
1720        ),
1721        // dispatch 中に `event.target` が dispatch 対象へ設定されること(以前は
1722        // window/document 経路では一切設定されず `undefined` だった。2026-07-16
1723        // 発見・修正)。
1724        (
1725            "var t='none'; window.addEventListener('message', e => t = (e.target === window)); \
1726             window.dispatchEvent(new Event('message')); t",
1727            "true",
1728        ),
1729        // `window.dispatchEvent(new ErrorEvent(...))` 経由でも `window.onerror`
1730        // がレガシー5引数シグネチャ `(message, source, lineno, colno, error)` で
1731        // 呼ばれること(以前は Event オブジェクトを1引数でそのまま渡していて
1732        // `reportError()`〔既に正しい5引数呼び出し〕と経路によって呼び出し形が
1733        // 食い違っていた。2026-07-16 発見・修正)。
1734        (
1735            "var got=''; window.onerror = (msg, src, line, col) => { got = msg+'|'+src+'|'+line+'|'+col; }; \
1736             window.dispatchEvent(new ErrorEvent('error', {message:'boom', filename:'a.js', lineno:3, colno:7})); got",
1737            "boom|a.js|3|7",
1738        ),
1739        // `online`/`offline`(丸ごと未対応だった。`addEventListener('online',
1740        // ...)` が登録経路自体無く黙って何もしなかった。2026-07-16 発見・実装)。
1741        (
1742            "var n=0; window.addEventListener('online', () => n++); \
1743             window.dispatchEvent(new Event('online')); n",
1744            "1",
1745        ),
1746        (
1747            "var n=0; var cb = () => n++; window.addEventListener('offline', cb); \
1748             window.removeEventListener('offline', cb); \
1749             window.dispatchEvent(new Event('offline')); n",
1750            "0",
1751        ),
1752        // `beforeunload`/`unload`/`pageshow`/`pagehide`/`visibilitychange`/
1753        // `languagechange`(丸ごと未対応だった。2026-07-16 発見・実装)。
1754        (
1755            "var log=[]; \
1756             window.addEventListener('beforeunload', () => log.push('bu')); \
1757             window.addEventListener('unload', () => log.push('u')); \
1758             window.addEventListener('pageshow', () => log.push('ps')); \
1759             window.addEventListener('pagehide', () => log.push('ph')); \
1760             document.addEventListener('visibilitychange', () => log.push('vc')); \
1761             window.addEventListener('languagechange', () => log.push('lc')); \
1762             window.dispatchEvent(new Event('beforeunload')); \
1763             window.dispatchEvent(new Event('unload')); \
1764             window.dispatchEvent(new Event('pageshow')); \
1765             window.dispatchEvent(new Event('pagehide')); \
1766             document.dispatchEvent(new Event('visibilitychange')); \
1767             window.dispatchEvent(new Event('languagechange')); \
1768             log.join(',')",
1769            "bu,u,ps,ph,vc,lc",
1770        ),
1771        // `document.visibilityState`/`.hidden`(丸ごと未対応だった。2026-07-16
1772        // 発見・実装)。
1773        ("document.visibilityState", "visible"),
1774        ("document.hidden", "false"),
1775        // `document.characterSet`/`.compatMode`/`.contentType`/`.doctype`
1776        // (丸ごと未対応だった。2026-07-16 発見・実装)。
1777        ("document.characterSet", "UTF-8"),
1778        ("document.compatMode", "CSS1Compat"),
1779        ("document.contentType", "text/html"),
1780        ("document.doctype", "null"),
1781        // `document.currentScript`(丸ごと未対応だった。2026-07-16 発見・
1782        // 実装。実行中スクリプト要素の追跡機構が無いため常に `null` を返す
1783        // 誠実な簡略実装)。
1784        ("document.currentScript", "null"),
1785        // `document.implementation`(`DOMImplementation`。丸ごと未対応
1786        // だった。2026-07-16 発見・実装。`hasFeature()`は仕様どおり常に
1787        // `true`を返すレガシー no-op、`createDocumentType()`は独立した
1788        // 読み取り専用簡略オブジェクトを返す)。
1789        ("document.implementation.hasFeature('x','1.0')", "true"),
1790        (
1791            "var dt=document.implementation.createDocumentType('html',' ',''); dt.name+','+dt.nodeType",
1792            "html,10",
1793        ),
1794        (
1795            "var doc=document.implementation.createHTMLDocument('MyDoc'); doc.title+','+doc.nodeType",
1796            "MyDoc,9",
1797        ),
1798        (
1799            "var doc=document.implementation.createDocument('http://www.w3.org/1999/xhtml','html'); doc.qualifiedName+','+doc.nodeType",
1800            "html,9",
1801        ),
1802        // `storage` イベント(丸ごと未対応だった。`StorageEvent` コンストラクタ
1803        // 自体は既に対応済みだったが登録経路が無かった。2026-07-16 発見・実装)。
1804        (
1805            "var got=null; window.addEventListener('storage', e => got = e.key); \
1806             window.dispatchEvent(new StorageEvent('storage', {key:'k'})); got",
1807            "k",
1808        ),
1809        // `document.addEventListener('readystatechange', ...)`(丸ごと未対応
1810        // だった。`document.readyState` は常に `"complete"` のため
1811        // `load`/`DOMContentLoaded` と同じ即時発火。2026-07-16 発見・実装)。
1812        (
1813            "var n=0; document.addEventListener('readystatechange', () => n++); n",
1814            "1",
1815        ),
1816        // `window.print()`/`beforeprint`/`afterprint`(丸ごと未対応だった。
1817        // 2026-07-16 発見・実装)。
1818        ("window.print(); true", "true"),
1819        (
1820            "var log=[]; \
1821             window.addEventListener('beforeprint', () => log.push('bp')); \
1822             window.addEventListener('afterprint', () => log.push('ap')); \
1823             window.dispatchEvent(new Event('beforeprint')); \
1824             window.dispatchEvent(new Event('afterprint')); \
1825             log.join(',')",
1826            "bp,ap",
1827        ),
1828        // `PointerEvent`/`InputEvent`(丸ごと未対応だった。`PointerEvent` は
1829        // `MouseEvent` を継承するため座標系プロパティも合わせて持つべき仕様)。
1830        ("new PointerEvent('pointerdown', {pointerId:7, pointerType:'touch'}).pointerId", "7"),
1831        ("new PointerEvent('pointerdown', {pointerType:'touch', clientX:9}).clientX", "9"),
1832        ("new PointerEvent('pointerdown').isPrimary", "false"),
1833        ("new PointerEvent('pointerdown', {isPrimary:true}).width", "1"),
1834        ("new InputEvent('input', {data:'a', inputType:'insertText'}).data", "a"),
1835        ("new InputEvent('input', {data:'a', inputType:'insertText'}).inputType", "insertText"),
1836        ("new InputEvent('beforeinput').isComposing", "false"),
1837        // `setPointerCapture`/`releasePointerCapture`/`hasPointerCapture`
1838        // (Pointer Events。丸ごと未対応だった)。
1839        (
1840            "var e=document.createElement('div'); e.hasPointerCapture(1)",
1841            "false",
1842        ),
1843        (
1844            "var e=document.createElement('div'); e.setPointerCapture(1); e.hasPointerCapture(1)",
1845            "true",
1846        ),
1847        (
1848            "var e=document.createElement('div'); e.setPointerCapture(1); \
1849             e.releasePointerCapture(1); e.hasPointerCapture(1)",
1850            "false",
1851        ),
1852        // 別 pointerId には影響しない。
1853        (
1854            "var e=document.createElement('div'); e.setPointerCapture(1); e.hasPointerCapture(2)",
1855            "false",
1856        ),
1857        // `ProgressEvent`/`StorageEvent`(丸ごと未対応だった)。
1858        ("new ProgressEvent('progress', {loaded:50, total:100}).loaded", "50"),
1859        ("new ProgressEvent('progress', {lengthComputable:true}).lengthComputable", "true"),
1860        ("new ProgressEvent('progress').total", "0"),
1861        ("new StorageEvent('storage', {key:'k', newValue:'v'}).key", "k"),
1862        ("new StorageEvent('storage', {key:'k', newValue:'v'}).newValue", "v"),
1863        ("new StorageEvent('storage').oldValue === null", "true"),
1864        // `CompositionEvent`/`ClipboardEvent`(丸ごと未対応だった)。
1865        ("new CompositionEvent('compositionupdate', {data:'あ'}).data", "あ"),
1866        ("new CompositionEvent('compositionstart').data", ""),
1867        (
1868            "var t=new EventTarget(); var got; \
1869             t.addEventListener('paste', e => { got = e.clipboardData; }); \
1870             t.dispatchEvent(new ClipboardEvent('paste', {clipboardData:'X'})); got",
1871            "X",
1872        ),
1873        ("new ClipboardEvent('copy').clipboardData === null", "true"),
1874        // `AnimationEvent`/`TransitionEvent`/`DragEvent`/`SubmitEvent`(丸ごと
1875        // 未対応だった。`DragEvent` は `MouseEvent` を継承するため座標系
1876        // プロパティも合わせて持つべき仕様)。
1877        ("new AnimationEvent('animationend', {animationName:'spin'}).animationName", "spin"),
1878        ("new AnimationEvent('animationend').elapsedTime", "0"),
1879        ("new TransitionEvent('transitionend', {propertyName:'opacity'}).propertyName", "opacity"),
1880        ("new DragEvent('drop', {dataTransfer:'X'}).dataTransfer", "X"),
1881        ("new DragEvent('drop', {clientX:3}).clientX", "3"),
1882        ("new SubmitEvent('submit', {submitter:'BTN'}).submitter", "BTN"),
1883        ("new SubmitEvent('submit').submitter === null", "true"),
1884        // `MessageEvent`/`ErrorEvent`(丸ごと未対応だった。`window.
1885        // postMessage()`/`reportError()` の内部イベント形と同じ形をユーザー
1886        // コードから直接構築できる)。
1887        ("new MessageEvent('message', {data:'hi', origin:'https://a.com'}).data", "hi"),
1888        ("new MessageEvent('message', {data:'hi', origin:'https://a.com'}).origin", "https://a.com"),
1889        ("new MessageEvent('message').ports.length", "0"),
1890        ("new ErrorEvent('error', {message:'boom', lineno:5}).message", "boom"),
1891        ("new ErrorEvent('error', {message:'boom', lineno:5}).lineno", "5"),
1892        ("new ErrorEvent('error').error === null", "true"),
1893        // `EventTarget` 発のイベントで `currentTarget`/`eventPhase` が丸ごと
1894        // 未設定で常に undefined だった(DOM 要素側は既に対応済み)。
1895        (
1896            "var t=new EventTarget(); var ct, ph; \
1897             t.addEventListener('x', e => { ct = e.currentTarget; ph = e.eventPhase; }); \
1898             t.dispatchEvent(new Event('x')); ct === t && ph === 2",
1899            "true",
1900        ),
1901        // 発火完了後は currentTarget が null、eventPhase が 0 に戻る。
1902        (
1903            "var t=new EventTarget(); var e=new Event('x'); t.dispatchEvent(e); \
1904             e.currentTarget === null && e.eventPhase",
1905            "0",
1906        ),
1907        // `Event.timeStamp` がこのエンジン全体に丸ごと未対応で常に undefined
1908        // だった(DOM要素・EventTarget どちらの経路でも欠落していた)。
1909        ("typeof new Event('x').timeStamp", "number"),
1910        (
1911            "var b=document.createElement('button'); var ts; \
1912             b.addEventListener('click', e => { ts = e.timeStamp; }); \
1913             b.dispatchEvent({type:'click'}); typeof ts",
1914            "number",
1915        ),
1916        // レガシー DOM Level 0 の `returnValue`/`cancelBubble`/`srcElement` が
1917        // 丸ごと未対応だった。`e.returnValue = false` は `preventDefault()` と
1918        // 等価であるべき。
1919        (
1920            "var t=new EventTarget(); var r; \
1921             t.addEventListener('x', e => { e.returnValue = false; }); \
1922             r = t.dispatchEvent(new Event('x', {cancelable:true})); r",
1923            "false",
1924        ),
1925        // `e.cancelBubble = true` は `stopPropagation()` と等価であるべき。
1926        (
1927            "var b=document.createElement('button'); var p=document.createElement('div'); \
1928             p.appendChild(b); var s=''; \
1929             p.addEventListener('click', ()=>s+='P'); \
1930             b.addEventListener('click', e => { s+='B'; e.cancelBubble = true; }); \
1931             b.dispatchEvent({type:'click', bubbles:true}); s",
1932            "B",
1933        ),
1934        // `srcElement` は `target` と同じ値の別名。
1935        (
1936            "var t=new EventTarget(); var se; \
1937             t.addEventListener('x', e => { se = e.srcElement; }); \
1938             t.dispatchEvent(new Event('x')); se === t",
1939            "true",
1940        ),
1941        // `Event.NONE`/`.CAPTURING_PHASE`/`.AT_TARGET`/`.BUBBLING_PHASE`
1942        // (`Node.ELEMENT_NODE`と同型の標準定数)が丸ごと未対応だった。
1943        (
1944            "Event.NONE + ',' + Event.CAPTURING_PHASE + ',' + Event.AT_TARGET + ',' + Event.BUBBLING_PHASE",
1945            "0,1,2,3",
1946        ),
1947        (
1948            "var t=new EventTarget(); var ph; \
1949             t.addEventListener('x', e => { ph = e.eventPhase === Event.AT_TARGET; }); \
1950             t.dispatchEvent(new Event('x')); ph",
1951            "true",
1952        ),
1953        // `form.requestSubmit()`/`form.submit()`(HTML5)が丸ごと未対応だった。
1954        // `requestSubmit()` は `submit` イベントを発火する。
1955        (
1956            "var f=document.createElement('form'); var n=0; \
1957             f.addEventListener('submit', ()=>n++); f.requestSubmit(); n",
1958            "1",
1959        ),
1960        // `preventDefault()` で中止可能。
1961        (
1962            "var f=document.createElement('form'); \
1963             f.addEventListener('submit', e=>e.preventDefault()); \
1964             typeof f.requestSubmit()",
1965            "undefined",
1966        ),
1967        // `submit()` は無害な no-op(実際のフォーム送信/ページ遷移パイプラインが
1968        // 無いため)で、少なくとも例外を投げない。
1969        ("var f=document.createElement('form'); f.submit(); true", "true"),
1970        // `input.showPicker()`(丸ごと未対応だった。ネイティブピッカー UI 自体が
1971        // 無いため `submit()` と同じ無害な no-op。2026-07-15 発見・実装)。
1972        (
1973            "var i=document.createElement('input'); i.type='date'; i.showPicker(); true",
1974            "true",
1975        ),
1976        // `input.stepUp(n?)`/`.stepDown(n?)`(HTML5)が丸ごと未対応だった。
1977        (
1978            "var i=document.createElement('input'); i.type='number'; i.value='5'; \
1979             i.stepUp(); i.value",
1980            "6",
1981        ),
1982        ("var i=document.createElement('input'); i.type='number'; i.value='5'; i.stepDown(3); i.value", "2"),
1983        // `step` 属性を刻み幅として使う。
1984        (
1985            "var i=document.createElement('input'); i.type='number'; i.value='0'; i.step='0.5'; \
1986             i.stepUp(); i.value",
1987            "0.5",
1988        ),
1989        // `max` があればクランプする。
1990        (
1991            "var i=document.createElement('input'); i.type='number'; i.value='9'; i.max='10'; \
1992             i.stepUp(5); i.value",
1993            "10",
1994        ),
1995        // String / Array 静的・console・globalThis
1996        ("String.fromCharCode(72,105)", "Hi"),
1997        ("String.fromCodePoint(65)", "A"),
1998        ("Array.of(1,2,3).join(',')", "1,2,3"),
1999        // Array イテレータ系 + reduceRight
2000        ("[...[10,20].keys()].join(',')", "0,1"),
2001        ("[...[10,20].values()].join(',')", "10,20"),
2002        // entries() は本物の Array Iterator(.map 等の Array メソッドは持たない。仕様どおり)
2003        // を返すため、配列メソッドを使うにはまず spread/Array.from で配列化する必要がある。
2004        ("[...[5,6].entries()].map(e => e[0]+':'+e[1]).join(',')", "0:5,1:6"),
2005        ("[1,2,3].reduceRight((a,b) => a+'-'+b)", "3-2-1"),
2006        ("[1,2,3,4].reduceRight((a,b) => a+b, 0)", "10"),
2007        // document.title 読み書き
2008        ("document.title = 'AtmOS'; document.title", "AtmOS"),
2009        ("document.readyState", "complete"),
2010        // `document.compatMode`/`.characterSet`/`.charset`/`.contentType`/
2011        // `.scrollingElement`(HTML5。丸ごと未対応だった)。
2012        ("document.compatMode", "CSS1Compat"),
2013        ("document.characterSet", "UTF-8"),
2014        ("document.charset", "UTF-8"),
2015        ("document.contentType", "text/html"),
2016        ("document.scrollingElement === document.documentElement", "true"),
2017        // `document.children`/`.firstElementChild`/`.lastElementChild`/
2018        // `.childElementCount`(`ParentNode`ミックスインの`Document`側配線
2019        // 漏れ。丸ごと未対応だった。2026-07-17 発見・実装。この配列は
2020        // bare `JsRuntime::new()` で `<html>` 自体が未構築のため、実際の
2021        // 検証は `dom_cases`(HTML フィクスチャ付き)側で行う)。
2022        ("document.firstElementChild === document.documentElement", "true"),
2023        ("document.lastElementChild === document.documentElement", "true"),
2024        ("document.children.length", "0"),
2025        ("document.childElementCount", "0"),
2026        // `document.referrer`/`.lastModified`/`.domain`(HTML5。丸ごと未対応
2027        // だった)。
2028        ("document.referrer", ""),
2029        (
2030            "/^\\d{2}\\/\\d{2}\\/\\d{4} \\d{2}:\\d{2}:\\d{2}$/.test(document.lastModified)",
2031            "true",
2032        ),
2033        ("typeof document.domain", "string"),
2034        // `navigator.geolocation`(丸ごと未対応だった。実ハードウェアが無いため
2035        // 常に `PERMISSION_DENIED` で `error` コールバックを同期的に呼ぶ簡略実装)。
2036        ("typeof navigator.geolocation.getCurrentPosition", "function"),
2037        (
2038            "var code; navigator.geolocation.getCurrentPosition(() => {}, e => { code = e.code; }); \
2039             code === 1",
2040            "true",
2041        ),
2042        (
2043            "typeof navigator.geolocation.watchPosition(() => {}, () => {})",
2044            "number",
2045        ),
2046        ("typeof navigator.geolocation.clearWatch(1)", "undefined"),
2047        // `navigator.wakeLock.request(type)`(Screen Wake Lock API。丸ごと
2048        // 未対応だった。実際の電源管理機構は無いため状態のみ追跡する簡略実装)。
2049        (
2050            "(await navigator.wakeLock.request('screen')).released",
2051            "false",
2052        ),
2053        ("(await navigator.wakeLock.request('screen')).type", "screen"),
2054        (
2055            "var s = await navigator.wakeLock.request('screen'); await s.release(); s.released",
2056            "true",
2057        ),
2058        // `navigator.share(data)`/`navigator.canShare(data)`(Web Share API。
2059        // 丸ごと未対応だった。実際の共有先 UI が無いため `canShare` は常に
2060        // `false`、`share` は仕様どおり `AbortError` で拒否する)。
2061        ("navigator.canShare({title:'x'})", "false"),
2062        (
2063            "try { await navigator.share({title:'x'}); 'resolved' } \
2064             catch(e) { e.name }",
2065            "AbortError",
2066        ),
2067        // `new EyeDropper().open()`(丸ごと未対応だった。実際のピッカー UI が
2068        // 無いため、ユーザーがキャンセルした場合と同じ `AbortError` で拒否)。
2069        (
2070            "try { await new EyeDropper().open(); 'resolved' } \
2071             catch(e) { e.name }",
2072            "AbortError",
2073        ),
2074        // `navigator.vibrate(pattern)`(Vibration API。丸ごと未対応だった。
2075        // 戻り値はリクエスト受理の可否のみを表しハードウェアの有無とは
2076        // 無関係なため、常に `true`)。
2077        ("navigator.vibrate(200)", "true"),
2078        ("navigator.vibrate([100,50,100])", "true"),
2079        // `navigator.getBattery()`(Battery Status API。丸ごと未対応だった。
2080        // バッテリー非搭載・常時 AC 電源動作というこの OS のターゲット
2081        // 実態に即した「常時満充電で給電中」を返す簡略実装)。
2082        ("(await navigator.getBattery()).charging", "true"),
2083        ("(await navigator.getBattery()).level", "1"),
2084        ("(await navigator.getBattery()).chargingTime", "0"),
2085        // `navigator.connection`(Network Information API。丸ごと未対応
2086        // だった。実測機構は無いため一般的なブロードバンド相当の代表値を返す
2087        // 簡略実装)。
2088        ("navigator.connection.saveData", "false"),
2089        ("navigator.connection.effectiveType", "4g"),
2090        ("typeof navigator.connection.downlink", "number"),
2091        // `navigator.locks.request(name, callback)`(Web Locks API。丸ごと
2092        // 未対応だった。複数タブ間の実際の排他機構が無いため、コールバックを
2093        // 即座に同期呼び出しし戻り値で解決する簡略実装。2026-07-15 発見・実装)。
2094        ("await navigator.locks.request('x', lock => lock.name)", "x"),
2095        ("await navigator.locks.request('x', lock => lock.mode)", "exclusive"),
2096        (
2097            "var ran=false; await navigator.locks.request('x', () => { ran=true; }); ran",
2098            "true",
2099        ),
2100        ("(await navigator.locks.query()).held.length", "0"),
2101        // Storage Access API(`document.hasStorageAccess()`/
2102        // `.requestStorageAccess()`。丸ごと未対応だった。サードパーティ
2103        // Cookie 分離機構自体が無いため常に成功する簡略実装)。
2104        ("await document.hasStorageAccess()", "true"),
2105        ("typeof await document.requestStorageAccess()", "undefined"),
2106        // `navigator.clipboard.write(items)`/`.read()`(`ClipboardItem` 配列版。
2107        // 丸ごと未対応だった。`text/plain` エントリのみ対応する簡略実装で、
2108        // `writeText`/`readText` と同じ内部状態を共有する)。
2109        (
2110            "await navigator.clipboard.write([new ClipboardItem({'text/plain': \
2111             new Blob(['hello-clip'], {type:'text/plain'})})]); \
2112             await navigator.clipboard.readText()",
2113            "hello-clip",
2114        ),
2115        (
2116            "await navigator.clipboard.writeText('via-text'); \
2117             (await navigator.clipboard.read())[0].types[0]",
2118            "text/plain",
2119        ),
2120        (
2121            "await navigator.clipboard.writeText('via-text2'); \
2122             var items = await navigator.clipboard.read(); \
2123             var blob = await items[0].getType('text/plain'); await blob.text()",
2124            "via-text2",
2125        ),
2126        // `navigator.permissions.query({name})`(丸ごと未対応だった。`geolocation`
2127        // は常に `PERMISSION_DENIED` を返す実装と、`notifications` は
2128        // `Notification.permission === 'granted'` と、それぞれ整合させる)。
2129        (
2130            "(await navigator.permissions.query({name:'geolocation'})).state",
2131            "denied",
2132        ),
2133        (
2134            "(await navigator.permissions.query({name:'notifications'})).state",
2135            "granted",
2136        ),
2137        (
2138            "(await navigator.permissions.query({name:'camera'})).state",
2139            "denied",
2140        ),
2141        // IntersectionObserver(コールバック即時発火・isIntersecting=true)
2142        ("var seen=false; var io=new IntersectionObserver(entries => { seen=entries[0].isIntersecting; }); io.observe(document.body); seen", "true"),
2143        // `IntersectionObserverEntry` の `boundingClientRect`/`intersectionRect`/
2144        // `rootBounds`/`time`(丸ごと未対応だった。2026-07-16 発見・実装)。
2145        (
2146            "var e=null; var io=new IntersectionObserver(entries => { e=entries[0]; }); \
2147             io.observe(document.body); \
2148             typeof e.boundingClientRect.width + ',' + \
2149             (e.intersectionRect === e.boundingClientRect ? 'same' : 'diff') + ',' + \
2150             (e.rootBounds === null) + ',' + (typeof e.time)",
2151            "number,same,true,number",
2152        ),
2153        // `IntersectionObserver`のコンストラクタ第2引数`options`
2154        // (`root`/`rootMargin`/`threshold`)が丸ごと無視されており、対応
2155        // する`.root`/`.rootMargin`/`.thresholds`読み取り専用プロパティも
2156        // 存在しなかった(丸ごと未対応だった。2026-07-17 発見・実装)。
2157        (
2158            "var io=new IntersectionObserver(()=>{}); \
2159             (io.root===null)+','+io.rootMargin+','+io.thresholds.join(',')",
2160            "true,0px 0px 0px 0px,0",
2161        ),
2162        (
2163            "var r=document.body; \
2164             var io=new IntersectionObserver(()=>{}, {root:r, rootMargin:'10px', threshold:0.5}); \
2165             (io.root===r)+','+io.rootMargin+','+io.thresholds.join(',')",
2166            "true,10px,0.5",
2167        ),
2168        (
2169            "var io=new IntersectionObserver(()=>{}, {threshold:[0,0.25,1]}); io.thresholds.join(',')",
2170            "0,0.25,1",
2171        ),
2172        // `ResizeObserverEntry.contentRect` が常に 0/0/0/0 固定だったバグと、
2173        // `borderBoxSize`/`contentBoxSize`(丸ごと未対応だった)を修正・追加。
2174        // 2026-07-16 発見・実装。
2175        (
2176            "var e=null; var ro=new ResizeObserver(entries => { e=entries[0]; }); \
2177             ro.observe(document.createElement('div')); \
2178             typeof e.contentRect.width + ',' + \
2179             Array.isArray(e.borderBoxSize) + ',' + \
2180             typeof e.borderBoxSize[0].inlineSize + ',' + \
2181             Array.isArray(e.contentBoxSize) + ',' + \
2182             typeof e.contentBoxSize[0].blockSize",
2183            "number,true,number,true,number",
2184        ),
2185        // `ResizeObserverEntry.devicePixelContentBoxSize`が丸ごと未対応
2186        // だった(`borderBoxSize`/`contentBoxSize`のみ対応済みで漏れて
2187        // いた。2026-07-17 発見・実装。`devicePixelRatio`が常に`1.0`固定
2188        // のため数値上は`contentBoxSize`と同一になる簡略実装)。
2189        (
2190            "var e=null; var ro=new ResizeObserver(entries => { e=entries[0]; }); \
2191             ro.observe(document.createElement('div')); \
2192             Array.isArray(e.devicePixelContentBoxSize) + ',' + \
2193             typeof e.devicePixelContentBoxSize[0].inlineSize",
2194            "true,number",
2195        ),
2196        // window.matchMedia(max-width 判定)
2197        ("window.matchMedia('(max-width: 9999px)').matches", "true"),
2198        ("window.matchMedia('(max-width: 0px)').matches", "false"),
2199        ("window.matchMedia('(min-width: 1px)').matches", "true"),
2200        // `window.matchMedia()` が `max-width`/`min-width` しか判定できず、CSS 側の
2201        // `@media` 評価(`prefers-color-scheme` 等)と食い違う非対称なバグだった。
2202        // 同じ判定ロジックへ統一した後の確認(既定は light テーマ)。
2203        ("window.matchMedia('(prefers-color-scheme: light)').matches", "true"),
2204        ("window.matchMedia('(prefers-color-scheme: dark)').matches", "false"),
2205        // window.scrollY / scrollTo
2206        ("typeof scrollY", "number"),
2207        ("scrollTo({top:100}); scrollY", "0"),
2208        // window.addEventListener load → 即時コールバック
2209        ("var loaded=false; window.addEventListener('load', () => { loaded=true; }); loaded", "true"),
2210        // `document.addEventListener('DOMContentLoaded', fn)` が丸ごと no-op だった
2211        // バグ(`window` 版は既に即時発火していたが `document` 側の配線が漏れていた)。
2212        // `window` 版よりもむしろよく使われる定番パターン。
2213        (
2214            "var ready=false; document.addEventListener('DOMContentLoaded', () => { ready=true; }); ready",
2215            "true",
2216        ),
2217        // `input.checked`(チェックボックス/ラジオボタンの選択状態)が JS プロパティとして
2218        // 丸ごと未実装で、`checkbox.checked`/`checkbox.checked = true` が常に
2219        // `undefined`/黙殺になっていた。
2220        (
2221            "var c=document.createElement('input'); c.type='checkbox'; c.checked",
2222            "false",
2223        ),
2224        (
2225            "var c=document.createElement('input'); c.type='checkbox'; c.checked=true; c.checked",
2226            "true",
2227        ),
2228        (
2229            "var c=document.createElement('input'); c.type='checkbox'; c.checked=true; c.checked=false; c.checked",
2230            "false",
2231        ),
2232        // `input.files`(`<input type="file">`のFileList。丸ごと未対応
2233        // だった。2026-07-17 発見・実装。実際のファイルピッカーUIが無い
2234        // ため常に空配列を返す誠実な簡略実装)。
2235        (
2236            "var f=document.createElement('input'); f.type='file'; f.files.length",
2237            "0",
2238        ),
2239        (
2240            "var f=document.createElement('input'); f.type='text'; f.files",
2241            "undefined",
2242        ),
2243        // `input[type=file].value`はセキュリティ上重要な仕様(`value`content
2244        // 属性は常に無視/未反映で、選択済みファイルが無ければ常に空文字列を
2245        // 返すべき)だが、以前は汎用の属性直結フォールバックに落ちており、
2246        // `<input type="file" value="...">`の属性値がそのまま漏れる
2247        // 静かなバグだった。
2248        (
2249            "var f=document.createElement('input'); f.type='file'; \
2250             f.setAttribute('value','c:\\\\secret.txt'); f.value",
2251            "",
2252        ),
2253        (
2254            "var f=document.createElement('input'); f.type='file'; f.value='x'; f.value",
2255            "",
2256        ),
2257        // `a`/`area`要素のURL分解プロパティ(`URLUtils`ミックスイン。
2258        // `.protocol`/`.host`/`.hostname`/`.port`/`.pathname`/`.search`/
2259        // `.hash`/`.origin`)が丸ごと未対応だった(`location`/`URL`は
2260        // 既に対応済みだったが`<a>`側への配線漏れ。2026-07-17 発見・実装)。
2261        (
2262            "var a=document.createElement('a'); a.href='https://sub.example.org:8080/x/y?z=1#top'; \
2263             [a.protocol,a.host,a.hostname,a.port,a.pathname,a.search,a.hash,a.origin].join('|')",
2264            "https:|sub.example.org:8080|sub.example.org|8080|/x/y|?z=1|#top|https://sub.example.org:8080",
2265        ),
2266        (
2267            "var a=document.createElement('area'); a.href='http://x.com/'; a.protocol",
2268            "http:",
2269        ),
2270        // `checkbox.indeterminate`(不確定表示状態。HTML 属性としては反映されない
2271        // JS 専用プロパティ)が丸ごと未対応だった。
2272        (
2273            "var c=document.createElement('input'); c.type='checkbox'; c.indeterminate",
2274            "false",
2275        ),
2276        (
2277            "var c=document.createElement('input'); c.type='checkbox'; c.indeterminate=true; c.indeterminate",
2278            "true",
2279        ),
2280        (
2281            "var c=document.createElement('input'); c.type='checkbox'; c.indeterminate=true; c.hasAttribute('indeterminate')",
2282            "false",
2283        ),
2284        // `input`/`textarea.selectionStart`/`.selectionEnd`/
2285        // `.selectionDirection`/`textarea.textLength`(丸ごと未対応
2286        // だった。2026-07-17 発見・実装。実際のキャレット機構が無いため
2287        // 状態追跡のみの簡略実装。未設定時は仕様どおり`0`/`"none"`)。
2288        (
2289            "var i=document.createElement('input'); i.selectionStart + ',' + i.selectionEnd + ',' + i.selectionDirection",
2290            "0,0,none",
2291        ),
2292        (
2293            "var i=document.createElement('input'); i.selectionStart=2; i.selectionEnd=5; i.selectionDirection='forward'; \
2294             i.selectionStart + ',' + i.selectionEnd + ',' + i.selectionDirection",
2295            "2,5,forward",
2296        ),
2297        (
2298            "var t=document.createElement('textarea'); t.value='hello'; t.textLength",
2299            "5",
2300        ),
2301        // `setSelectionRange`/`setRangeText`(丸ごと未対応だった。
2302        // `selectionStart`等と対になる操作メソッド。2026-07-17 発見・
2303        // 実装)。
2304        (
2305            "var i=document.createElement('input'); i.setSelectionRange(1,3,'forward'); \
2306             i.selectionStart + ',' + i.selectionEnd + ',' + i.selectionDirection",
2307            "1,3,forward",
2308        ),
2309        (
2310            "var i=document.createElement('input'); i.value='hello world'; \
2311             i.setRangeText('there', 6, 11); i.value",
2312            "hello there",
2313        ),
2314        (
2315            "var i=document.createElement('input'); i.value='hello world'; \
2316             i.setRangeText('there', 6, 11, 'select'); \
2317             i.value + '|' + i.selectionStart + ',' + i.selectionEnd",
2318            "hello there|6,11",
2319        ),
2320        (
2321            "var i=document.createElement('input'); i.value='hello world'; \
2322             i.selectionStart=0; i.selectionEnd=5; i.setRangeText('hi'); i.value",
2323            "hi world",
2324        ),
2325        // `checkbox.click()` は仕様上、リスナ実行前に自身の `checked` を切り替える。
2326        (
2327            "var c=document.createElement('input'); c.type='checkbox'; c.click(); c.checked",
2328            "true",
2329        ),
2330        (
2331            "var c=document.createElement('input'); c.type='checkbox'; c.click(); c.click(); c.checked",
2332            "false",
2333        ),
2334        (
2335            "var r=document.createElement('input'); r.type='radio'; r.click(); r.checked",
2336            "true",
2337        ),
2338        // `node.ownerDocument`(`element.ownerDocument.createElement(...)` という
2339        // 定番イディオムで使われる)が丸ごと未対応だった。
2340        (
2341            "document.createElement('div').ownerDocument === document",
2342            "true",
2343        ),
2344        (
2345            "typeof document.createElement('div').ownerDocument.createElement",
2346            "function",
2347        ),
2348        // **重要**: ラジオボタンの最も基本的な仕様上の挙動(同じ `name` を持つグループ内で
2349        // 1つだけが `checked` になる排他選択)が以前は非対応の簡略化として明示的に
2350        // 見送られていた。`element.click()` と `.checked = true` 代入の両方で効くこと。
2351        (
2352            "var r1=document.createElement('input'); r1.type='radio'; r1.name='g'; \
2353             var r2=document.createElement('input'); r2.type='radio'; r2.name='g'; \
2354             r1.click(); r2.click(); r1.checked + ',' + r2.checked",
2355            "false,true",
2356        ),
2357        (
2358            "var r1=document.createElement('input'); r1.type='radio'; r1.name='g2'; \
2359             var r2=document.createElement('input'); r2.type='radio'; r2.name='g2'; \
2360             r1.checked=true; r2.checked=true; r1.checked + ',' + r2.checked",
2361            "false,true",
2362        ),
2363        // 異なる `name` を持つ radio 同士は互いに影響しない。
2364        (
2365            "var r1=document.createElement('input'); r1.type='radio'; r1.name='ga'; \
2366             var r2=document.createElement('input'); r2.type='radio'; r2.name='gb'; \
2367             r1.click(); r2.click(); r1.checked + ',' + r2.checked",
2368            "true,true",
2369        ),
2370        // `input.disabled`/`option.selected`/`input.placeholder`/`input.name` も
2371        // `checked`/`type` と同じ理由で JS プロパティとして丸ごと未実装だった。
2372        (
2373            "var b=document.createElement('button'); b.disabled=true; b.disabled",
2374            "true",
2375        ),
2376        (
2377            "var b=document.createElement('button'); b.disabled=true; b.disabled=false; b.disabled",
2378            "false",
2379        ),
2380        (
2381            "var o=document.createElement('option'); o.selected=true; o.selected",
2382            "true",
2383        ),
2384        // `option.text`(`textContent` の別名。`new Option(...)`/`option.text = '...'`
2385        // という定番パターン)が丸ごと未対応だった。
2386        (
2387            "var o=document.createElement('option'); o.text='Label'; o.text",
2388            "Label",
2389        ),
2390        (
2391            "var o=document.createElement('option'); o.text='Label'; o.textContent",
2392            "Label",
2393        ),
2394        // `option`/`optgroup.label`(丸ごと未対応だった。`option.label`は
2395        // 仕様上`label`属性が無ければテキスト内容にフォールバックする。
2396        // 2026-07-17 発見・実装)。
2397        (
2398            "var o=document.createElement('option'); o.textContent='hi'; o.label",
2399            "hi",
2400        ),
2401        (
2402            "var o=document.createElement('option'); o.textContent='hi'; o.label='explicit'; o.label",
2403            "explicit",
2404        ),
2405        (
2406            "var g=document.createElement('optgroup'); g.textContent='hi'; g.label",
2407            "",
2408        ),
2409        (
2410            "var g=document.createElement('optgroup'); g.label='Group A'; g.label",
2411            "Group A",
2412        ),
2413        // `input.required`/`input.readOnly`/`input.multiple`/`img.src`/`img.alt`/
2414        // `a.href` も同じ理由で JS プロパティとして丸ごと未実装だった。
2415        (
2416            "var i=document.createElement('input'); i.required=true; i.required",
2417            "true",
2418        ),
2419        (
2420            "var i=document.createElement('input'); i.readOnly=true; i.readOnly",
2421            "true",
2422        ),
2423        (
2424            "var s=document.createElement('select'); s.multiple=true; s.multiple",
2425            "true",
2426        ),
2427        (
2428            "var img=document.createElement('img'); img.src='a.png'; img.alt='pic'; img.src+','+img.alt",
2429            "a.png,pic",
2430        ),
2431        (
2432            "var a=document.createElement('a'); a.href='/x'; a.href",
2433            "/x",
2434        ),
2435        // `label.htmlFor`/`form.action`/`form.method`/`input.min`/`max`/`step`/
2436        // `maxLength`/`a.target`/`rel`/`download` も同種の未実装だった。
2437        (
2438            "var l=document.createElement('label'); l.htmlFor='name'; l.htmlFor",
2439            "name",
2440        ),
2441        (
2442            "var f=document.createElement('form'); f.action='/submit'; f.method='post'; f.action+','+f.method",
2443            "/submit,post",
2444        ),
2445        // `form.method`(`method`属性が無いか既知の列挙値以外なら既定
2446        // `"get"`にフォールバックする。`fieldset.type`等と同じバグ
2447        // パターンで丸ごと未対応だった。2026-07-17 発見・実装)。
2448        ("document.createElement('form').method", "get"),
2449        (
2450            "var f=document.createElement('form'); f.setAttribute('method','DIALOG'); f.method",
2451            "dialog",
2452        ),
2453        (
2454            "var f=document.createElement('form'); f.setAttribute('method','bogus'); f.method",
2455            "get",
2456        ),
2457        // `form.enctype`/`.encoding`(丸ごと未対応だった。`encoding`は
2458        // `enctype`のレガシー別名で同じ属性を指す。既知の列挙値3種以外
2459        // なら既定`"application/x-www-form-urlencoded"`にフォールバック
2460        // する。2026-07-17 発見・実装)。
2461        (
2462            "document.createElement('form').enctype",
2463            "application/x-www-form-urlencoded",
2464        ),
2465        (
2466            "var f=document.createElement('form'); f.enctype='multipart/form-data'; f.enctype+','+f.encoding",
2467            "multipart/form-data,multipart/form-data",
2468        ),
2469        (
2470            "var f=document.createElement('form'); f.encoding='text/plain'; f.enctype",
2471            "text/plain",
2472        ),
2473        // `form.acceptCharset`(`accept-charset`属性のcamelCase IDL
2474        // プロパティ版。`htmlFor`と同じパターンだが丸ごと未対応だった。
2475        // 2026-07-17 発見・実装)。
2476        (
2477            "var f=document.createElement('form'); f.acceptCharset='UTF-8'; f.getAttribute('accept-charset')",
2478            "UTF-8",
2479        ),
2480        (
2481            "var f=document.createElement('form'); f.setAttribute('accept-charset','UTF-8'); f.acceptCharset",
2482            "UTF-8",
2483        ),
2484        // `button`/`input`の「送信オーバーライド」IDLプロパティ(`formAction`/
2485        // `formEnctype`/`formMethod`/`formNoValidate`/`formTarget`)が丸ごと
2486        // 未対応だった。`form.action`/`.method`/`.enctype`と同じ既定値
2487        // フォールバックパターンを踏襲する。2026-07-17 発見・実装)。
2488        (
2489            "var b=document.createElement('button'); b.formAction='/x'; b.getAttribute('formaction')",
2490            "/x",
2491        ),
2492        (
2493            "document.createElement('input').formEnctype",
2494            "application/x-www-form-urlencoded",
2495        ),
2496        (
2497            "var b=document.createElement('button'); b.formEnctype='multipart/form-data'; b.formEnctype",
2498            "multipart/form-data",
2499        ),
2500        ("document.createElement('button').formMethod", "get"),
2501        (
2502            "var i=document.createElement('input'); i.formMethod='post'; i.formMethod",
2503            "post",
2504        ),
2505        (
2506            "document.createElement('button').formNoValidate",
2507            "false",
2508        ),
2509        (
2510            "var b=document.createElement('button'); b.formNoValidate=true; b.getAttribute('formnovalidate')",
2511            "formnovalidate",
2512        ),
2513        (
2514            "var i=document.createElement('input'); i.formTarget='_blank'; i.formTarget",
2515            "_blank",
2516        ),
2517        (
2518            "var i=document.createElement('input'); i.defaultValue='hello'; i.defaultValue+','+i.getAttribute('value')",
2519            "hello,hello",
2520        ),
2521        (
2522            "var i=document.createElement('input'); i.defaultChecked=true; i.defaultChecked+','+i.hasAttribute('checked')",
2523            "true,true",
2524        ),
2525        (
2526            "var o=document.createElement('option'); o.defaultSelected=true; o.defaultSelected+','+o.hasAttribute('selected')",
2527            "true,true",
2528        ),
2529        // `img.loading`/`.decoding`(既知の列挙値以外なら仕様上の既定値
2530        // `"auto"`にフォールバックする必要があるが、`fieldset.type`等と
2531        // 同じバグパターンで空文字列になっていた。2026-07-17 発見・実装)。
2532        ("document.createElement('img').loading", "auto"),
2533        (
2534            "var i=document.createElement('img'); i.loading='lazy'; i.loading",
2535            "lazy",
2536        ),
2537        (
2538            "var i=document.createElement('img'); i.setAttribute('loading','bogus'); i.loading",
2539            "auto",
2540        ),
2541        ("document.createElement('img').decoding", "auto"),
2542        (
2543            "var i=document.createElement('img'); i.decoding='sync'; i.decoding",
2544            "sync",
2545        ),
2546        (
2547            "var i=document.createElement('img'); i.setAttribute('decoding','bogus'); i.decoding",
2548            "auto",
2549        ),
2550        // `audio`/`video.preload`(欠落時既定値`"metadata"`、不正値既定値
2551        // `"auto"`。`fieldset.type`等と同じバグパターンで空文字列になって
2552        // いた。2026-07-17 発見・実装)。
2553        ("document.createElement('audio').preload", "metadata"),
2554        (
2555            "var v=document.createElement('video'); v.preload='none'; v.preload",
2556            "none",
2557        ),
2558        (
2559            "var v=document.createElement('video'); v.setAttribute('preload','bogus'); v.preload",
2560            "auto",
2561        ),
2562        // `textarea.wrap`(`"soft"`/`"hard"`。既知値以外・欠落時は仕様上の
2563        // 既定値`"soft"`にフォールバックする必要があるが、丸ごと未対応
2564        // だった。2026-07-17 発見・実装)。
2565        ("document.createElement('textarea').wrap", "soft"),
2566        (
2567            "var t=document.createElement('textarea'); t.wrap='hard'; t.wrap",
2568            "hard",
2569        ),
2570        (
2571            "var t=document.createElement('textarea'); t.setAttribute('wrap','bogus'); t.wrap",
2572            "soft",
2573        ),
2574        // `track.kind`(欠落時既定`"subtitles"`/不正値既定`"metadata"`の
2575        // 2段階フォールバック。プロパティ自体が丸ごと未対応だった。
2576        // 2026-07-17 発見・実装)。
2577        ("document.createElement('track').kind", "subtitles"),
2578        (
2579            "var t=document.createElement('track'); t.kind='captions'; t.kind",
2580            "captions",
2581        ),
2582        (
2583            "var t=document.createElement('track'); t.setAttribute('kind','bogus'); t.kind",
2584            "metadata",
2585        ),
2586        // `track.label`/`.srclang`(単純な文字列反映)と`track.default`
2587        // (ブール型)が丸ごと未対応だった。あわせて`option`/`optgroup`/
2588        // `track`共通の`label`セッターが丸ごと存在しなかった重複バグも
2589        // 解消。2026-07-18 発見・実装)。
2590        (
2591            "var t=document.createElement('track'); t.label='English'; t.getAttribute('label')",
2592            "English",
2593        ),
2594        (
2595            "var t=document.createElement('track'); t.srclang='en'; t.srclang",
2596            "en",
2597        ),
2598        ("document.createElement('track').default", "false"),
2599        (
2600            "var t=document.createElement('track'); t.default=true; t.getAttribute('default')",
2601            "default",
2602        ),
2603        // `link.as`(`rel="preload"`/`"prefetch"`の取得先種別ヒント。単純な
2604        // 文字列反映だが丸ごと未対応だった。2026-07-18 発見・実装)。
2605        (
2606            "var l=document.createElement('link'); l.as='script'; l.getAttribute('as')",
2607            "script",
2608        ),
2609        (
2610            "var l=document.createElement('link'); l.setAttribute('as','style'); l.as",
2611            "style",
2612        ),
2613        // `video.videoWidth`/`.videoHeight`(実デコードパイプラインが
2614        // 無いため常に`0`)と`audio`/`video.seeking`(実際の非同期シーク
2615        // 処理が無いため常に`false`)が丸ごと未対応だった。2026-07-18
2616        // 発見・実装)。
2617        (
2618            "document.createElement('video').videoWidth+','+document.createElement('video').videoHeight",
2619            "0,0",
2620        ),
2621        ("document.createElement('video').seeking", "false"),
2622        ("document.createElement('audio').seeking", "false"),
2623        // `select.size`(欠落・不正値時の既定値`0`)と`input.size`(欠落・
2624        // 不正値時の既定値`20`)が丸ごと未対応だった。2026-07-18 発見・
2625        // 実装)。
2626        ("document.createElement('select').size", "0"),
2627        (
2628            "var s=document.createElement('select'); s.size=5; s.size",
2629            "5",
2630        ),
2631        ("document.createElement('input').size", "20"),
2632        (
2633            "var i=document.createElement('input'); i.size=10; i.size",
2634            "10",
2635        ),
2636        (
2637            "var i=document.createElement('input'); i.setAttribute('size','0'); i.size",
2638            "20",
2639        ),
2640        // `textarea.rows`/`.cols`(欠落・不正値時の既定値`2`/`20`)が
2641        // 丸ごと未対応だった。2026-07-18 発見・実装)。
2642        ("document.createElement('textarea').rows", "2"),
2643        (
2644            "var t=document.createElement('textarea'); t.rows=8; t.rows",
2645            "8",
2646        ),
2647        ("document.createElement('textarea').cols", "20"),
2648        (
2649            "var t=document.createElement('textarea'); t.cols=40; t.cols",
2650            "40",
2651        ),
2652        (
2653            "var t=document.createElement('textarea'); t.setAttribute('rows','0'); t.rows",
2654            "2",
2655        ),
2656        // `dialog.requestClose(returnValue?)`(`.close()`と違い、閉じる
2657        // 直前に取消可能な`cancel`イベントを発火する。丸ごと未対応
2658        // だった。2026-07-18 発見・実装)。
2659        (
2660            "var d=document.createElement('dialog'); d.open=true; d.requestClose('x'); \
2661             d.open+','+d.returnValue",
2662            "false,x",
2663        ),
2664        (
2665            "var d=document.createElement('dialog'); d.open=true; \
2666             d.addEventListener('cancel', e=>e.preventDefault()); d.requestClose(); d.open",
2667            "true",
2668        ),
2669        (
2670            "var d=document.createElement('dialog'); d.open=true; var fired=false; \
2671             d.addEventListener('close', ()=>fired=true); d.requestClose(); fired",
2672            "true",
2673        ),
2674        // `progress.position`(廃止予定だが仕様に残る読み取り専用IDL。
2675        // `value`未設定なら不確定状態`-1`、それ以外は`value/max`。丸ごと
2676        // 未対応だった。2026-07-18 発見・実装)。
2677        ("document.createElement('progress').position", "-1"),
2678        (
2679            "var p=document.createElement('progress'); p.max=10; p.value=5; p.position",
2680            "0.5",
2681        ),
2682        (
2683            "var p=document.createElement('progress'); p.value=1; p.position",
2684            "1",
2685        ),
2686        // `output.htmlFor`(`for`属性を空白区切りトークンとして扱う
2687        // `DOMTokenList`。`label.htmlFor`(単一文字列)とは異なる意味。
2688        // `relList`/`sandbox`と同じ読み取り専用トークン配列の簡略実装。
2689        // 丸ごと未対応だった。2026-07-18 発見・実装)。
2690        (
2691            "var o=document.createElement('output'); o.htmlFor='a b c'; \
2692             o.htmlFor.length+','+o.htmlFor[1]",
2693            "3,b",
2694        ),
2695        (
2696            "var o=document.createElement('output'); o.setAttribute('for','x y'); \
2697             Array.from(o.htmlFor).join(',')",
2698            "x,y",
2699        ),
2700        ("document.createElement('output').htmlFor.length", "0"),
2701        // `input[type=checkbox/radio].value`(`value`属性が無い場合の仕様上
2702        // の既定値は`""`ではなく`"on"`。フォーム送信ロジック
2703        // 〔`collect_form_data`〕は既に正しくこの既定値を使っていたが、
2704        // JSプロパティの`.value`読み出し側だけ空文字列になる不整合が
2705        // あった。2026-07-18 発見・実装)。
2706        (
2707            "var c=document.createElement('input'); c.type='checkbox'; c.value",
2708            "on",
2709        ),
2710        (
2711            "var r=document.createElement('input'); r.type='radio'; r.value",
2712            "on",
2713        ),
2714        (
2715            "var c=document.createElement('input'); c.type='checkbox'; c.value='yes'; c.value",
2716            "yes",
2717        ),
2718        (
2719            "document.createElement('input').value",
2720            "",
2721        ),
2722        // `input[type=color].value`(`value`属性が無いか7文字の`#rrggbb`
2723        // 小文字16進形式でない不正値の場合の既定値は`"#000000"`。
2724        // `checkbox`/`radio`の`"on"`と同じバグパターンで空文字列に
2725        // なっていた。2026-07-18 発見・実装)。
2726        (
2727            "var c=document.createElement('input'); c.type='color'; c.value",
2728            "#000000",
2729        ),
2730        (
2731            "var c=document.createElement('input'); c.type='color'; c.value='#ff00aa'; c.value",
2732            "#ff00aa",
2733        ),
2734        // `input[type=color].value = v`の書き込み側sanitizationが丸ごと
2735        // 未対応だった。ゲッター側は「既に小文字16進形式か」しか見ておらず、
2736        // セッター側で大文字→小文字への正規化が行われていなかったため、
2737        // 仕様上は妥当な色(大文字16進)である`'#FF00AA'`を代入しても
2738        // 小文字化されずゲッターの検証に弾かれ`#000000`になってしまう
2739        // 食い違いがあった(2026-07-18 発見・修正)。
2740        (
2741            "var c=document.createElement('input'); c.type='color'; c.value='#FF00AA'; c.value",
2742            "#ff00aa",
2743        ),
2744        (
2745            // 16進形式として無効な値(色名等)は仕様どおり`#000000`へ正規化される。
2746            "var c=document.createElement('input'); c.type='color'; c.value='red'; c.value",
2747            "#000000",
2748        ),
2749        // `input[type=range].value`(`value`属性が無いか不正値の場合の
2750        // 既定値は`min`と`max`の中点。`min`/`max`省略時は既定`0`/`100`
2751        // のため無指定時は`50`。`checkbox`/`color`と同じバグパターンで
2752        // 空文字列になっていた。2026-07-18 発見・実装)。
2753        (
2754            "var r=document.createElement('input'); r.type='range'; r.value",
2755            "50",
2756        ),
2757        (
2758            "var r=document.createElement('input'); r.type='range'; r.min='0'; r.max='10'; r.value",
2759            "5",
2760        ),
2761        (
2762            "var r=document.createElement('input'); r.type='range'; r.value='7'; r.value",
2763            "7",
2764        ),
2765        (
2766            "var r=document.createElement('input'); r.type='range'; r.min='5'; r.max='2'; r.value",
2767            "5",
2768        ),
2769        // `input[type=range].value`の仕様上のvalue sanitization algorithmは
2770        // 数値変換できない不正値の既定値フォールバックだけでなく`min`/`max`
2771        // へのクランプも含むが、パース自体には成功する範囲外の値
2772        // (`max=10`で`value=999`等)はクランプされずそのまま漏れていた。
2773        (
2774            "var r=document.createElement('input'); r.type='range'; r.min='0'; r.max='10'; r.value='999'; r.value",
2775            "10",
2776        ),
2777        (
2778            "var r=document.createElement('input'); r.type='range'; r.min='0'; r.max='10'; r.value='-5'; r.value",
2779            "0",
2780        ),
2781        (
2782            // `setAttribute`経由(IDLセッターを介さない直接の属性書き込み)でも
2783            // 同じくクランプされる。
2784            "var r=document.createElement('input'); r.type='range'; r.min='0'; r.max='10'; \
2785             r.setAttribute('value','999'); r.value",
2786            "10",
2787        ),
2788        // `table.tHead`/`.tFoot`/`.caption`のセッター(読み出し側は既に
2789        // 実装済みだったが、対になる書き込み側が丸ごと未対応だった。
2790        // 代入すると既存の同名セクションを除去し、新しい要素を正しい
2791        // 位置へ挿入する。2026-07-18 発見・実装)。
2792        (
2793            "var t=document.createElement('table'); var h=document.createElement('thead'); \
2794             t.tHead=h; t.tHead===h",
2795            "true",
2796        ),
2797        (
2798            "var t=document.createElement('table'); var h1=document.createElement('thead'); \
2799             var h2=document.createElement('thead'); t.tHead=h1; t.tHead=h2; \
2800             (t.tHead===h2)+','+t.children.length",
2801            "true,1",
2802        ),
2803        (
2804            "var t=document.createElement('table'); var h=document.createElement('thead'); \
2805             t.tHead=h; t.tHead=null; t.tHead",
2806            "null",
2807        ),
2808        (
2809            "var t=document.createElement('table'); var c=document.createElement('caption'); \
2810             var b=document.createElement('tbody'); t.appendChild(b); t.caption=c; \
2811             t.firstChild===c",
2812            "true",
2813        ),
2814        // `select.add(element, before)`(`select.options.add`と同じ意味の
2815        // 便利メソッド。丸ごと未対応だった。2026-07-18 発見・実装)。
2816        (
2817            "var s=document.createElement('select'); var o=document.createElement('option'); \
2818             s.add(o); s.options.length+','+(s.options[0]===o)",
2819            "1,true",
2820        ),
2821        (
2822            "var s=document.createElement('select'); var o1=document.createElement('option'); \
2823             var o2=document.createElement('option'); s.add(o1); s.add(o2, o1); \
2824             s.options[0]===o2",
2825            "true",
2826        ),
2827        // `audio`/`video.played`(`TimeRanges`。`buffered`/`.seekable`と
2828        // 同じ理由で常に空。丸ごと未対応だった。2026-07-18 発見・実装)。
2829        ("document.createElement('video').played.length", "0"),
2830        ("document.createElement('audio').played.length", "0"),
2831        // `element.role`/`.ariaLabel`等(`ARIAMixin`。`role`/`aria-*`属性の
2832        // IDLプロパティ版)が丸ごと未対応だった。`aria`接頭辞後の
2833        // PascalCase部分を全て小文字化して`aria-`と連結する命名規則を
2834        // 汎用アームで一括対応。2026-07-18 発見・実装)。
2835        (
2836            "var d=document.createElement('div'); d.role='button'; d.getAttribute('role')",
2837            "button",
2838        ),
2839        (
2840            "var d=document.createElement('div'); d.setAttribute('role','tab'); d.role",
2841            "tab",
2842        ),
2843        (
2844            "var d=document.createElement('div'); d.ariaLabel='close'; d.getAttribute('aria-label')",
2845            "close",
2846        ),
2847        (
2848            "var d=document.createElement('div'); d.setAttribute('aria-hidden','true'); d.ariaHidden",
2849            "true",
2850        ),
2851        (
2852            "var d=document.createElement('div'); d.ariaValueNow='5'; d.getAttribute('aria-valuenow')",
2853            "5",
2854        ),
2855        (
2856            "var d=document.createElement('div'); d.ariaLabel='x'; d.ariaLabel=null; \
2857             d.getAttribute('aria-label')",
2858            "null",
2859        ),
2860        ("document.createElement('div').ariaLabel", "null"),
2861        // `element.outerText`(読み出しは`innerText`と同じだが、書き込みは
2862        // 自身の中身ではなく自身「そのもの」をテキストノードへ置き換える
2863        // 点が違う。丸ごと未対応だった。2026-07-18 発見・実装)。
2864        (
2865            "var d=document.createElement('div'); d.textContent='hi'; d.outerText",
2866            "hi",
2867        ),
2868        (
2869            "var p=document.createElement('div'); var c=document.createElement('span'); \
2870             c.textContent='x'; p.appendChild(c); c.outerText='y'; p.textContent",
2871            "y",
2872        ),
2873        (
2874            "var p=document.createElement('div'); var c=document.createElement('span'); \
2875             p.appendChild(c); c.outerText='z'; p.children.length",
2876            "0",
2877        ),
2878        // `element.part`(CSS Shadow Parts。`relList`/`sandbox`と同じ
2879        // 読み取り専用トークン配列の簡略実装。丸ごと未対応だった。
2880        // 2026-07-18 発見・実装)。
2881        (
2882            "var d=document.createElement('div'); d.setAttribute('part','a b'); \
2883             d.part.length+','+d.part[1]",
2884            "2,b",
2885        ),
2886        ("document.createElement('div').part.length", "0"),
2887        // `link.sizes`は`img`/`source.sizes`(単純なDOMString)とは異なり
2888        // 仕様上`DOMTokenList`。`part`/`relList`と同じ読み取り専用トークン
2889        // 配列の簡略実装で区別する。以前は同名の文字列反映アームに
2890        // 一括で落ちて型が誤っていた。2026-07-18 発見・実装)。
2891        (
2892            "var l=document.createElement('link'); l.setAttribute('sizes','16x16 32x32'); \
2893             l.sizes.length+','+l.sizes[1]",
2894            "2,32x32",
2895        ),
2896        (
2897            "var i=document.createElement('img'); i.sizes='(min-width: 600px) 50vw, 100vw'; i.sizes",
2898            "(min-width: 600px) 50vw, 100vw",
2899        ),
2900        // `input.accept`(`type="file"`が許可するファイル種別フィルタ。
2901        // 単純な文字列反映だが丸ごと未対応だった。2026-07-18 発見・実装)。
2902        (
2903            "var i=document.createElement('input'); i.accept='image/*'; i.getAttribute('accept')",
2904            "image/*",
2905        ),
2906        (
2907            "var i=document.createElement('input'); i.setAttribute('accept','.pdf,.doc'); i.accept",
2908            ".pdf,.doc",
2909        ),
2910        // `script.charset`(廃止予定だが仕様に残る単純な文字列反映。
2911        // ゲッターが`meta`タグ限定のままで他タグでは未対応だった。
2912        // 2026-07-18 発見・実装)。
2913        (
2914            "var s=document.createElement('script'); s.charset='utf-8'; s.getAttribute('charset')",
2915            "utf-8",
2916        ),
2917        (
2918            "var s=document.createElement('script'); s.setAttribute('charset','shift-jis'); s.charset",
2919            "shift-jis",
2920        ),
2921        // `a`/`area.protocol`/`.host`/`.hostname`/`.port`/`.pathname`/
2922        // `.search`/`.hash`のセッター(`URLUtils`ミックスイン。ゲッターは
2923        // 既に実装済みだったが対になるセッターが丸ごと未対応だった。
2924        // 2026-07-18 発見・実装)。
2925        (
2926            "var a=document.createElement('a'); a.href='https://x.com/p'; a.hostname='y.com'; a.href",
2927            "https://y.com/p",
2928        ),
2929        (
2930            "var a=document.createElement('a'); a.href='https://x.com/p'; a.protocol='http'; a.protocol",
2931            "http:",
2932        ),
2933        (
2934            "var a=document.createElement('a'); a.href='https://x.com/p'; a.pathname='q'; a.pathname",
2935            "/q",
2936        ),
2937        (
2938            "var a=document.createElement('a'); a.href='https://x.com/p'; a.search='x=1'; a.search",
2939            "?x=1",
2940        ),
2941        (
2942            "var a=document.createElement('a'); a.href='https://x.com/p'; a.hash='frag'; a.hash",
2943            "#frag",
2944        ),
2945        (
2946            "var a=document.createElement('a'); a.href='https://x.com:80/p'; a.host='y.com:90'; \
2947             a.hostname+','+a.port",
2948            "y.com,90",
2949        ),
2950        (
2951            "var a=document.createElement('a'); a.href='https://x.com/p'; a.username='u'; \
2952             a.password='pw'; a.href",
2953            "https://u:pw@x.com/p",
2954        ),
2955        // `element.willValidate`がタグを一切見ておらず、`<output>`/`<div>`
2956        // のような本来常に`false`であるべき非フォームコントロールでも
2957        // `true`を返してしまうバグだった。2026-07-18 発見・修正)。
2958        ("document.createElement('output').willValidate", "false"),
2959        ("document.createElement('div').willValidate", "false"),
2960        ("document.createElement('fieldset').willValidate", "false"),
2961        ("document.createElement('input').willValidate", "true"),
2962        (
2963            "var b=document.createElement('button'); b.disabled=true; b.willValidate",
2964            "false",
2965        ),
2966        // `validity_flags`(`.validity`が内部で使う共通ロジック)が
2967        // `willValidate`と同根の「タグを見ない」バグを持っていた。
2968        // `<output required>`のような本来検証対象外の要素へ`required`
2969        // 属性を付けても`valueMissing`が立たないことを確認。2026-07-18
2970        // 発見・修正)。
2971        (
2972            "var o=document.createElement('output'); o.setAttribute('required','required'); \
2973             o.validity.valid",
2974            "true",
2975        ),
2976        (
2977            "var d=document.createElement('div'); d.setAttribute('required','required'); \
2978             d.validity.valueMissing",
2979            "false",
2980        ),
2981        (
2982            "var i=document.createElement('input'); i.setAttribute('required','required'); \
2983             i.validity.valueMissing",
2984            "true",
2985        ),
2986        // `validate_field`(`element.checkValidity()`が直接呼ぶ経路)も
2987        // 同根の「タグを見ない」バグを持っていた。`<output required>`へ
2988        // `checkValidity()`を直接呼んでも常に`true`のままであることを
2989        // 確認。`form.checkValidity()`側は既に`input`/`textarea`/
2990        // `select`へ絞り込み済みのため実害が無かった経路との違いに
2991        // 注意。2026-07-18 発見・修正)。
2992        (
2993            "var o=document.createElement('output'); o.setAttribute('required','required'); \
2994             o.checkValidity()",
2995            "true",
2996        ),
2997        (
2998            "var i=document.createElement('input'); i.setAttribute('required','required'); \
2999             i.checkValidity()",
3000            "false",
3001        ),
3002        // `video.disableRemotePlayback`(`disablePictureInPicture`と同じ
3003        // 単純なブール属性反映だが丸ごと未対応だった。2026-07-18
3004        // 発見・実装)。
3005        (
3006            "document.createElement('video').disableRemotePlayback",
3007            "false",
3008        ),
3009        (
3010            "var v=document.createElement('video'); v.disableRemotePlayback=true; \
3011             v.getAttribute('disableremoteplayback')",
3012            "disableremoteplayback",
3013        ),
3014        // `link`/`script.blocking`(`part`/`relList`/`sandbox`と同じ読み
3015        // 取り専用トークン配列。丸ごと未対応だった。2026-07-18 発見・
3016        // 実装)。
3017        (
3018            "var l=document.createElement('link'); l.setAttribute('blocking','render'); \
3019             l.blocking.length+','+l.blocking[0]",
3020            "1,render",
3021        ),
3022        ("document.createElement('script').blocking.length", "0"),
3023        // `output.value`/`.defaultValue`(`<output>`は`value`content属性を
3024        // 持たず、未書込み時は`.textContent`にフォールバックする。
3025        // `textarea.value`と同じパターンだが丸ごと未対応だった。
3026        // 2026-07-18 発見・実装)。
3027        (
3028            "var o=document.createElement('output'); o.textContent='42'; o.value",
3029            "42",
3030        ),
3031        (
3032            "var o=document.createElement('output'); o.textContent='42'; o.value='7'; o.value",
3033            "7",
3034        ),
3035        (
3036            "var o=document.createElement('output'); o.textContent='42'; o.value='7'; \
3037             o.textContent",
3038            "42",
3039        ),
3040        // `video.getVideoPlaybackQuality()`(丸ごと未対応だった。実
3041        // デコードパイプラインが無いため全カウンタ`0`の誠実な簡略実装。
3042        // 2026-07-18 発見・実装)。
3043        (
3044            "var q=document.createElement('video').getVideoPlaybackQuality(); \
3045             q.totalVideoFrames+','+q.droppedVideoFrames+','+q.corruptedVideoFrames",
3046            "0,0,0",
3047        ),
3048        // `element.namespaceURI`/`.localName`/`.prefix`(丸ごと未対応
3049        // だった。XML名前空間を一切モデル化していないため常にHTML
3050        // 名前空間定数/タグ名そのもの/`null`を返す誠実な簡略実装。
3051        // 2026-07-18 発見・実装)。
3052        (
3053            "document.createElement('div').namespaceURI",
3054            "http://www.w3.org/1999/xhtml",
3055        ),
3056        ("document.createElement('SPAN').localName", "span"),
3057        ("document.createElement('div').prefix", "null"),
3058        (
3059            "document.createTextNode('x').namespaceURI",
3060            "null",
3061        ),
3062        (
3063            "document.createTextNode('x').localName",
3064            "null",
3065        ),
3066        // `HTMLScriptElement.supports(type)`(静的メソッド。丸ごと未対応
3067        // だった。`classic`/`module`は実サポート済みで`true`、
3068        // `importmap`はインポートマップ自体が未実装のため`false`)。
3069        (
3070            "HTMLScriptElement.supports('classic')+','+HTMLScriptElement.supports('module')",
3071            "true,true",
3072        ),
3073        (
3074            "document.createElement('div').ariaActiveDescendantElement",
3075            "null",
3076        ),
3077        (
3078            "(function(){var a=document.createElement('div');a.setAttribute('aria-activedescendant','nope');return a.ariaActiveDescendantElement;})()",
3079            "null",
3080        ),
3081        // `element.getHTML()`/`.setHTMLUnsafe()`(`innerHTML`ゲッター/
3082        // セッターの明示的メソッド版。丸ごと未対応だった。この処理系には
3083        // Trusted Types自体が無いため`innerHTML`と可視的な違いは無い
3084        // 簡略実装。2026-07-18 発見・実装)。
3085        (
3086            "var d=document.createElement('div'); d.innerHTML='<b>x</b>'; d.getHTML()",
3087            "<b>x</b>",
3088        ),
3089        (
3090            "var d=document.createElement('div'); d.setHTMLUnsafe('<i>y</i>'); d.innerHTML",
3091            "<i>y</i>",
3092        ),
3093        // `CSS.number()`/`.percent()`/`.px()`/`.em()`/`.rem()`/`.deg()`
3094        // (CSS Typed OMの値+単位ファクトリ関数群。丸ごと未対応だった。
3095        // 2026-07-18 発見・実装)。
3096        ("CSS.px(5).value+','+CSS.px(5).unit+','+CSS.px(5).toString()", "5,px,5px"),
3097        ("CSS.percent(50).toString()", "50%"),
3098        ("CSS.number(3).toString()", "3"),
3099        ("CSS.em(1.5).toString()", "1.5em"),
3100        ("CSS.rem(2).toString()", "2rem"),
3101        ("CSS.deg(90).toString()", "90deg"),
3102        ("HTMLScriptElement.supports('importmap')", "false"),
3103        ("HTMLScriptElement.supports('bogus')", "false"),
3104        // `link.imageSrcset`/`.imageSizes`(`rel="preload" as="image"`用の
3105        // レスポンシブ画像プリロードヒント。`link.as`と同じ単純な文字列
3106        // 反映だが丸ごと未対応だった。2026-07-18 発見・実装)。
3107        (
3108            "(function(){var l=document.createElement('link');l.imageSrcset='a.jpg 1x, b.jpg 2x';l.imageSizes='100vw';return l.imageSrcset+'|'+l.imageSizes;})()",
3109            "a.jpg 1x, b.jpg 2x|100vw",
3110        ),
3111        (
3112            "document.createElement('link').imageSrcset+'|'+document.createElement('link').imageSizes",
3113            "|",
3114        ),
3115        // `iframe.credentialless`(COEP credentialless。`allowFullscreen`と
3116        // 同じ単純なブール属性反映だが丸ごと未対応だった。2026-07-18
3117        // 発見・実装)。
3118        (
3119            "(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;})()",
3120            "false,true,false",
3121        ),
3122        // `area.coords`/`.shape`(`shape`は既知値以外・欠落時は仕様上の
3123        // 既定値`"rect"`にフォールバックし、レガシー別名`"circ"`/
3124        // `"polygon"`も正規化する。プロパティ自体が丸ごと未対応だった。
3125        // 2026-07-17 発見・実装)。
3126        (
3127            "var a=document.createElement('area'); a.coords='0,0,10,10'; a.coords",
3128            "0,0,10,10",
3129        ),
3130        ("document.createElement('area').shape", "rect"),
3131        (
3132            "var a=document.createElement('area'); a.shape='circle'; a.shape",
3133            "circle",
3134        ),
3135        (
3136            "var a=document.createElement('area'); a.setAttribute('shape','circ'); a.shape",
3137            "circle",
3138        ),
3139        (
3140            "var a=document.createElement('area'); a.setAttribute('shape','polygon'); a.shape",
3141            "poly",
3142        ),
3143        // `ol.type`/`.start`/`.reversed`(`type`は既知値以外・欠落時は
3144        // 仕様上の既定値`"1"`にフォールバックする。`start`は欠落・不正値
3145        // 時の既定値`1`。どちらも丸ごと未対応だった。2026-07-17 発見・
3146        // 実装)。
3147        ("document.createElement('ol').type", "1"),
3148        (
3149            "var o=document.createElement('ol'); o.type='A'; o.type",
3150            "A",
3151        ),
3152        (
3153            "var o=document.createElement('ol'); o.setAttribute('type','bogus'); o.type",
3154            "1",
3155        ),
3156        ("document.createElement('ol').start", "1"),
3157        (
3158            "var o=document.createElement('ol'); o.start=5; o.start",
3159            "5",
3160        ),
3161        (
3162            "document.createElement('ol').reversed",
3163            "false",
3164        ),
3165        (
3166            "var o=document.createElement('ol'); o.reversed=true; o.getAttribute('reversed')",
3167            "reversed",
3168        ),
3169        // `meter.low`/`.high`/`.optimum`(`min`/`max`と同じく丸ごと未対応
3170        // だった。既定値はそれぞれ`min`/`max`/`min`と`max`の中点。
3171        // 2026-07-17 発見・実装)。
3172        (
3173            "var m=document.createElement('meter'); m.min='2'; m.max='10'; m.low+','+m.high+','+m.optimum",
3174            "2,10,6",
3175        ),
3176        (
3177            "var m=document.createElement('meter'); m.max='10'; m.low='4'; m.low",
3178            "4",
3179        ),
3180        (
3181            "var m=document.createElement('meter'); m.max='10'; m.high='20'; m.high",
3182            "10",
3183        ),
3184        // `td`/`th.colSpan`/`.rowSpan`(欠落・不正値時の既定値`1`、
3185        // `colSpan`は最大`1000`、`rowSpan`は最大`65534`にクランプ。
3186        // どちらも丸ごと未対応だった。2026-07-17 発見・実装)。
3187        ("document.createElement('td').colSpan", "1"),
3188        (
3189            "var c=document.createElement('td'); c.colSpan=3; c.colSpan",
3190            "3",
3191        ),
3192        (
3193            "var c=document.createElement('td'); c.setAttribute('colspan','9999'); c.colSpan",
3194            "1000",
3195        ),
3196        ("document.createElement('th').rowSpan", "1"),
3197        (
3198            "var c=document.createElement('th'); c.rowSpan=0; c.rowSpan",
3199            "0",
3200        ),
3201        // `td`/`th.headers`(単純な文字列反映)と`th.scope`(既知値以外・
3202        // 欠落時は仕様上の既定値`""`にフォールバック)、`th.abbr`(単純な
3203        // 文字列反映)が丸ごと未対応だった。2026-07-17 発見・実装)。
3204        (
3205            "var c=document.createElement('td'); c.headers='h1 h2'; c.headers",
3206            "h1 h2",
3207        ),
3208        ("document.createElement('th').scope", ""),
3209        (
3210            "var t=document.createElement('th'); t.scope='col'; t.scope",
3211            "col",
3212        ),
3213        (
3214            "var t=document.createElement('th'); t.setAttribute('scope','bogus'); t.scope",
3215            "",
3216        ),
3217        (
3218            "var t=document.createElement('th'); t.abbr='Description'; t.abbr",
3219            "Description",
3220        ),
3221        // `col`/`colgroup.span`(欠落・不正値時の既定値`1`、最大`1000`に
3222        // クランプ。丸ごと未対応だった。2026-07-17 発見・実装)。
3223        ("document.createElement('col').span", "1"),
3224        (
3225            "var c=document.createElement('colgroup'); c.span=3; c.span",
3226            "3",
3227        ),
3228        (
3229            "var c=document.createElement('col'); c.setAttribute('span','9999'); c.span",
3230            "1000",
3231        ),
3232        // `object.data`(`href`/`src`/`action`と同じくURL属性を反映し、
3233        // 現在のページURL基準の絶対URLを返す。テキストノードの
3234        // `CharacterData.data`と同名で衝突するためタグ限定で扱う必要が
3235        // あったが、プロパティ自体が丸ごと未対応だった。2026-07-17
3236        // 発見・実装)。
3237        (
3238            "var o=document.createElement('object'); o.data='/x'; o.getAttribute('data')",
3239            "/x",
3240        ),
3241        (
3242            "var t=document.createTextNode('hi'); t.data",
3243            "hi",
3244        ),
3245        // `meta.httpEquiv`/`.content`/`.charset`(`httpEquiv`は`http-equiv`
3246        // 属性の camelCase IDL プロパティ版で`htmlFor`と同じマッピング
3247        // パターン、`content`/`.charset`は単純な属性反映。3つとも丸ごと
3248        // 未対応だった。2026-07-17 発見・実装)。
3249        (
3250            "var m=document.createElement('meta'); m.httpEquiv='refresh'; m.getAttribute('http-equiv')",
3251            "refresh",
3252        ),
3253        (
3254            "var m=document.createElement('meta'); m.setAttribute('http-equiv','refresh'); m.httpEquiv",
3255            "refresh",
3256        ),
3257        (
3258            "var m=document.createElement('meta'); m.content='width=device-width'; m.content",
3259            "width=device-width",
3260        ),
3261        (
3262            "var m=document.createElement('meta'); m.charset='utf-8'; m.charset",
3263            "utf-8",
3264        ),
3265        // `blockquote`/`q`/`ins`/`del.cite`(単純な文字列反映)と
3266        // `ins`/`del`/`time.dateTime`(`datetime`属性のcamelCase IDL
3267        // プロパティ版)が丸ごと未対応だった。2026-07-17 発見・実装)。
3268        (
3269            "var b=document.createElement('blockquote'); b.cite='https://example.com'; b.getAttribute('cite')",
3270            "https://example.com",
3271        ),
3272        (
3273            "var t=document.createElement('time'); t.dateTime='2026-07-17'; t.getAttribute('datetime')",
3274            "2026-07-17",
3275        ),
3276        (
3277            "var i=document.createElement('ins'); i.setAttribute('datetime','2026-07-17'); i.dateTime",
3278            "2026-07-17",
3279        ),
3280        // `video.disablePictureInPicture`/`iframe.allowFullscreen`(単純な
3281        // ブール属性反映だが丸ごと未対応だった。2026-07-17 発見・実装)。
3282        (
3283            "document.createElement('video').disablePictureInPicture",
3284            "false",
3285        ),
3286        (
3287            "var v=document.createElement('video'); v.disablePictureInPicture=true; v.getAttribute('disablepictureinpicture')",
3288            "disablepictureinpicture",
3289        ),
3290        (
3291            "var f=document.createElement('iframe'); f.allowFullscreen=true; f.getAttribute('allowfullscreen')",
3292            "allowfullscreen",
3293        ),
3294        // `canvas.width`/`.height`(欠落・不正値時の既定値`300`/`150`)と
3295        // `img`/`video.width`/`.height`(既定値`0`)が丸ごと未対応
3296        // だった。2026-07-17 発見・実装)。
3297        (
3298            "document.createElement('canvas').width+','+document.createElement('canvas').height",
3299            "300,150",
3300        ),
3301        (
3302            "var c=document.createElement('canvas'); c.width=640; c.height=480; c.width+','+c.height",
3303            "640,480",
3304        ),
3305        (
3306            "document.createElement('img').width+','+document.createElement('img').height",
3307            "0,0",
3308        ),
3309        (
3310            "var v=document.createElement('video'); v.width=320; v.width",
3311            "320",
3312        ),
3313        // `embed`/`object`/`iframe.width`/`.height`(`DOMString`型の単純な
3314        // 文字列反映。`canvas`/`img`/`video`の`unsigned long`型とは異なり
3315        // 数値クランプ・既定値フォールバックが無い。丸ごと未対応
3316        // だった。2026-07-17 発見・実装)。
3317        (
3318            "var e=document.createElement('embed'); e.width='50%'; e.width",
3319            "50%",
3320        ),
3321        (
3322            "var o=document.createElement('object'); o.height='300'; o.height",
3323            "300",
3324        ),
3325        ("document.createElement('iframe').width", ""),
3326        (
3327            "var f=document.createElement('iframe'); f.setAttribute('width','640'); f.width",
3328            "640",
3329        ),
3330        // `input`/`textarea.defaultValue`(`value`属性/初期テキストを
3331        // 反映)と`input.defaultChecked`/`option.defaultSelected`(`checked`/
3332        // `selected`属性の有無を反映するブール型)が丸ごと未対応
3333        // だった。2026-07-17 発見・実装)。
3334        (
3335            "var i=document.createElement('input'); i.defaultValue='hi'; i.getAttribute('value')",
3336            "hi",
3337        ),
3338        (
3339            "var i=document.createElement('input'); i.setAttribute('value','preset'); i.defaultValue",
3340            "preset",
3341        ),
3342        (
3343            "var i=document.createElement('input'); i.setAttribute('value','preset'); i.value='changed'; i.defaultValue",
3344            "preset",
3345        ),
3346        (
3347            "var i=document.createElement('input'); i.setAttribute('value','preset'); i.value='changed'; i.value",
3348            "changed",
3349        ),
3350        (
3351            "document.createElement('input').defaultChecked",
3352            "false",
3353        ),
3354        (
3355            "var i=document.createElement('input'); i.defaultChecked=true; i.getAttribute('checked')",
3356            "checked",
3357        ),
3358        (
3359            "var i=document.createElement('input'); i.defaultChecked=true; i.checked=false; i.defaultChecked",
3360            "true",
3361        ),
3362        (
3363            "document.createElement('option').defaultSelected",
3364            "false",
3365        ),
3366        (
3367            "var o=document.createElement('option'); o.defaultSelected=true; o.getAttribute('selected')",
3368            "selected",
3369        ),
3370        // `output.type`(仕様上、属性値に関わらず常に固定文字列
3371        // `"output"`を返す読み取り専用IDLプロパティ。`<output>`要素
3372        // 自体が丸ごと未対応だった。2026-07-17 発見・実装)。
3373        ("document.createElement('output').type", "output"),
3374        (
3375            "var o=document.createElement('output'); o.setAttribute('type','bogus'); o.type",
3376            "output",
3377        ),
3378        // `iframe.srcdoc`(単純な文字列反映だが丸ごと未対応だった。
3379        // 2026-07-17 発見・実装)。
3380        (
3381            "var f=document.createElement('iframe'); f.srcdoc='<p>hi</p>'; f.getAttribute('srcdoc')",
3382            "<p>hi</p>",
3383        ),
3384        (
3385            "var f=document.createElement('iframe'); f.setAttribute('srcdoc','<b>x</b>'); f.srcdoc",
3386            "<b>x</b>",
3387        ),
3388        // `li.value`(順序リストの番号上書き用`long`型IDLプロパティ。
3389        // 欠落・不正値時の既定値は`0`。丸ごと未対応だった。汎用の`value`
3390        // アームだと数値ではなく文字列になってしまう衝突があった。
3391        // 2026-07-17 発見・実装)。
3392        ("document.createElement('li').value", "0"),
3393        (
3394            "var l=document.createElement('li'); l.value=5; l.value",
3395            "5",
3396        ),
3397        (
3398            "var l=document.createElement('li'); l.setAttribute('value','bogus'); l.value",
3399            "0",
3400        ),
3401        // `input.pattern`(`placeholder`と同じ単純な文字列反映だが丸ごと
3402        // 未対応だった。2026-07-17 発見・実装)。
3403        (
3404            "var i=document.createElement('input'); i.pattern='[0-9]+'; i.getAttribute('pattern')",
3405            "[0-9]+",
3406        ),
3407        (
3408            "var i=document.createElement('input'); i.setAttribute('pattern','[a-z]+'); i.pattern",
3409            "[a-z]+",
3410        ),
3411        // `input.capture`(`type="file"`のカメラ/マイク取り込みヒント。
3412        // 単純な文字列反映だが丸ごと未対応だった。2026-07-17 発見・実装)。
3413        (
3414            "var i=document.createElement('input'); i.capture='environment'; i.getAttribute('capture')",
3415            "environment",
3416        ),
3417        (
3418            "var i=document.createElement('input'); i.setAttribute('capture','user'); i.capture",
3419            "user",
3420        ),
3421        // `input.autocomplete`(単純な文字列反映)と`element.autofocus`
3422        // (ブール型。実際のフォーカス自動移動機構は無いため属性値の
3423        // 反映のみ)が丸ごと未対応だった。2026-07-18 発見・実装)。
3424        (
3425            "var i=document.createElement('input'); i.autocomplete='email'; i.getAttribute('autocomplete')",
3426            "email",
3427        ),
3428        (
3429            "var i=document.createElement('input'); i.setAttribute('autocomplete','off'); i.autocomplete",
3430            "off",
3431        ),
3432        (
3433            "document.createElement('input').autofocus",
3434            "false",
3435        ),
3436        (
3437            "var i=document.createElement('input'); i.autofocus=true; i.getAttribute('autofocus')",
3438            "autofocus",
3439        ),
3440        (
3441            "var i=document.createElement('input'); i.min='0'; i.max='10'; i.step='2'; i.min+','+i.max+','+i.step",
3442            "0,10,2",
3443        ),
3444        (
3445            "var i=document.createElement('input'); i.maxLength='5'; i.maxLength",
3446            "5",
3447        ),
3448        // `maxLength`/`minLength`はWebIDL上`long`(数値型)のIDL属性で、無指定時
3449        // の既定値は`-1`だが、以前は`maxLength`が文字列型(属性が無ければ空
3450        // 文字列)で返っており、`minLength`はゲッター自体が丸ごと未対応だった
3451        // (`min`/`max`/`step`は`DOMString`型が正しいIDL定義のため対象外)。
3452        (
3453            "typeof document.createElement('input').maxLength",
3454            "number",
3455        ),
3456        (
3457            "document.createElement('input').maxLength",
3458            "-1",
3459        ),
3460        (
3461            "var i=document.createElement('input'); i.minLength=3; typeof i.minLength + ',' + i.minLength",
3462            "number,3",
3463        ),
3464        (
3465            "document.createElement('textarea').minLength",
3466            "-1",
3467        ),
3468        (
3469            "var a=document.createElement('a'); a.target='_blank'; a.rel='noopener'; a.download='f.txt'; a.target+','+a.rel+','+a.download",
3470            "_blank,noopener,f.txt",
3471        ),
3472        (
3473            "var i=document.createElement('input'); i.placeholder='enter name'; i.placeholder",
3474            "enter name",
3475        ),
3476        (
3477            "var d=document.createElement('details'); d.open",
3478            "false",
3479        ),
3480        (
3481            "var d=document.createElement('details'); d.open=true; d.open",
3482            "true",
3483        ),
3484        (
3485            "var d=document.createElement('details'); d.setAttribute('open',''); d.open",
3486            "true",
3487        ),
3488        (
3489            "var i=document.createElement('input'); i.name='email'; i.name",
3490            "email",
3491        ),
3492        // `select.value`/`select.selectedIndex` が丸ごと未対応だった。`<select>` は
3493        // 自身に `value` 属性を持たず、子の `<option selected>` が選択状態を持つため、
3494        // 汎用の属性直結 getter では常に空文字列になっていた。
3495        (
3496            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.value",
3497            "a",
3498        ),
3499        (
3500            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.value='b'; s.value",
3501            "b",
3502        ),
3503        (
3504            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.value='b'; s.selectedIndex",
3505            "1",
3506        ),
3507        (
3508            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.selectedIndex=1; s.value",
3509            "b",
3510        ),
3511        // `option.index`(`select.selectedIndex`と対になる、`<option>`側の0始まり
3512        // 位置を返すIDL属性)が丸ごと未対応だった。
3513        (
3514            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.options[1].index",
3515            "1",
3516        ),
3517        (
3518            "document.createElement('option').index",
3519            "0",
3520        ),
3521        // `select.length`(`select.options.length`と同じ値を返すIDL属性、および
3522        // 末尾optionを切り捨てる/空optionで埋めるセッター)が丸ごと未対応だった。
3523        (
3524            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option><option value='b'>B</option>\"; s.length",
3525            "2",
3526        ),
3527        (
3528            "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",
3529            "1,a",
3530        ),
3531        (
3532            "var s=document.createElement('select'); s.length=2; s.options.length",
3533            "2",
3534        ),
3535        // `select.remove(index)`(`HTMLSelectElement`固有のオーバーロード。
3536        // 指定インデックスのoptionを削除する)が、同名の`ChildNode.remove()`
3537        // (引数無しで自身を親から削除する)と混同され、`select.remove(1)`が
3538        // 常にselect自身を親ごと削除してしまうバグだった。
3539        (
3540            "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",
3541            "1,b",
3542        ),
3543        (
3544            // 引数無しの `.remove()` は従来どおり自身を親から削除する
3545            // (ChildNode.remove()と同じ挙動を維持)。
3546            "var d=document.createElement('div'); var s=document.createElement('select'); d.appendChild(s); s.remove(); d.children.length",
3547            "0",
3548        ),
3549        (
3550            // 負のインデックスはno-op(`as usize`の飽和変換で誤って先頭が
3551            // 削除されるバグを防いだ確認)。
3552            "var s=document.createElement('select'); s.innerHTML=\"<option value='a'>A</option>\"; s.remove(-1); s.options.length",
3553            "1",
3554        ),
3555        // `textarea.value` も同種のバグだった(`<textarea>` の初期値は本来テキスト
3556        // ノードの内容で表現されるべきだが、`.value` 書込み後の読み出し往復は
3557        // 最低限このとおり動くことを確認する)。
3558        (
3559            "var t=document.createElement('textarea'); t.value='hi'; t.value",
3560            "hi",
3561        ),
3562        ("typeof console.table", "function"),
3563        ("typeof globalThis", "object"),
3564        ("typeof globalThis.document", "object"),
3565        // fetch(data: URL でネット非依存にフルチェーン検証: Promise→Response→text()/json())
3566        ("typeof fetch", "function"),
3567        ("(await fetch('data:,hello')).status", "200"),
3568        ("(await fetch('data:,hello')).ok", "true"),
3569        ("await (await fetch('data:,hello world')).text()", "hello world"),
3570        ("await fetch('data:text/plain,abc').then(r => r.text())", "abc"),
3571        ("(await (await fetch('data:application/json,{\"a\":7,\"b\":[1,2]}')).json()).a", "7"),
3572        ("(await (await fetch('data:application/json,{\"a\":7,\"b\":[1,2]}')).json()).b.length", "2"),
3573        // `Response.prototype.blob()`(丸ごと未対応で `blob is not defined` だった)。
3574        ("(await (await fetch('data:,hello')).blob()).size", "5"),
3575        ("await (await (await fetch('data:,hello')).blob()).text()", "hello"),
3576        // `Response.prototype.arrayBuffer()` も同種の欠落だった。実バイト列付きの
3577        // ArrayBuffer で解決するため、そこから作った DataView で正しく読み戻せる。
3578        (
3579            "var buf = await (await fetch('data:,A')).arrayBuffer(); new DataView(buf).getUint8(0)",
3580            "65",
3581        ),
3582        ("await fetch('ftp://x/y').then(() => 'ok', e => 'err:' + (e.message || e))", "err:fetch: only absolute http(s) and data URLs are supported"),
3583        // `Response.prototype.statusText` が丸ごと未対応で常に `undefined` だった
3584        // (エラーログの定番パターン `console.log(response.status, response.statusText)`
3585        // で参照される)。
3586        ("(await fetch('data:,hi')).statusText", "OK"),
3587        // `Response.prototype.clone()`(`response.clone().json()` のように本文を
3588        // 2回読みたい場合に使う定番パターン)が丸ごと未対応で `clone is not a
3589        // function` だった。
3590        (
3591            "var r = await fetch('data:,hello'); var c = r.clone(); (await c.text()) + ',' + (await r.text())",
3592            "hello,hello",
3593        ),
3594        ("(await fetch('data:,x')).clone().status", "200"),
3595        // `Response.prototype.url`(リダイレクト後の最終 URL 確認等で使われる
3596        // 定番プロパティ)が丸ごと未対応で常に `undefined` だった。
3597        (
3598            "(await fetch('data:,hi')).url",
3599            "data:,hi",
3600        ),
3601        // `Response` コンストラクタ自体が丸ごと未対応だった(`fetch()` 内部からしか
3602        // 生成できず、`new Response(...)` が呼び出すと TypeError になっていた)。
3603        ("new Response('hi').status", "200"),
3604        ("await new Response('hi', {status: 201}).text()", "hi"),
3605        (
3606            "(await new Response(JSON.stringify({a:1}), {headers:{'content-type':'application/json'}}).json()).a",
3607            "1",
3608        ),
3609        // `Response.json(data, init)`(ES2022 static factory)。
3610        ("(await Response.json({a:5}).json()).a", "5"),
3611        ("Response.json({}).headers.get('content-type')", "application/json"),
3612        // `Response.error()`。
3613        ("Response.error().ok", "false"),
3614        ("Response.error().type", "error"),
3615        // `Response.redirect(url, status?)`。
3616        ("Response.redirect('https://e.com/x').status", "302"),
3617        ("Response.redirect('https://e.com/x', 301).headers.get('location')", "https://e.com/x"),
3618        (
3619            "(await fetch('data:,x')).clone().url",
3620            "data:,x",
3621        ),
3622        // `fetch(url, {signal})` が丸ごと未対応で、既に aborted な signal を渡しても
3623        // 常にリクエストを実行してしまっていた。
3624        (
3625            "await fetch('data:,hi', {signal: AbortSignal.abort('why')}).then(() => 'no-throw', e => 'caught:' + e)",
3626            "caught:why",
3627        ),
3628        (
3629            "await fetch('data:,hi', {signal: new AbortController().signal}).then(r => r.status)",
3630            "200",
3631        ),
3632        // `new Request(url, options)` が丸ごと未対応で `Request is not defined`
3633        // だった。`fetch(request)` という定番パターンも合わせて確認する。
3634        ("new Request('data:,hi').url", "data:,hi"),
3635        ("new Request('data:,hi', {method:'POST'}).method", "POST"),
3636        // `request.headers` が仕様上常に本物の `Headers` インスタンスであるべき
3637        // ところ、生の値をそのまま格納するだけで `.get(...)` が壊れていた。
3638        (
3639            "new Request('data:,hi', {headers:{'X-Foo':'bar'}}).headers.get('x-foo')",
3640            "bar",
3641        ),
3642        ("typeof new Request('data:,hi').headers.get", "function"),
3643        // `response.type`/`.redirected` が丸ごと未対応で常に `undefined` だった。
3644        ("(await fetch('data:,hi')).type", "basic"),
3645        ("(await fetch('data:,hi')).redirected", "false"),
3646        ("await fetch(new Request('data:,hello')).then(r => r.text())", "hello"),
3647        (
3648            "await fetch(new Request('data:,x'), {method:'GET'}).then(r => r.status)",
3649            "200",
3650        ),
3651        // `Headers`(Fetch API の get/set/has/append/delete 付き専用クラス)が
3652        // 丸ごと未対応だった。ヘッダ名は大小無視で正規化される。
3653        ("new Headers({'Content-Type':'text/plain'}).get('content-type')", "text/plain"),
3654        // `[[k,v], ...]` 配列形式の init(`make_headers` の `is_array` 分岐。
3655        // 2026-07-14、clippy `single_match` 修正時に既存テストで一度もこの
3656        // 分岐が通っていなかったことに気づき追加)。
3657        ("new Headers([['A','1'],['b','2']]).get('a')", "1"),
3658        ("new Headers().has('x')", "false"),
3659        (
3660            "var h=new Headers(); h.set('X-Foo','1'); h.has('x-foo')",
3661            "true",
3662        ),
3663        (
3664            "var h=new Headers(); h.append('a','1'); h.append('a','2'); h.get('a')",
3665            "1, 2",
3666        ),
3667        (
3668            "var h=new Headers({a:'1'}); h.delete('a'); h.has('a')",
3669            "false",
3670        ),
3671        (
3672            "var h=new Headers({a:'1',b:'2'}); var s=''; h.forEach(function(v,k){s+=k+'='+v+';'}); s",
3673            "a=1;b=2;",
3674        ),
3675        // `entries`/`keys`/`values`/`Symbol.iterator`(`for (const [k,v] of headers)`
3676        // という定番イディオムを含む)が丸ごと未対応だった。
3677        (
3678            "Array.from(new Headers({b:'2',a:'1'}).entries()).map(function(e){return e[0]+'='+e[1]}).join(',')",
3679            "a=1,b=2",
3680        ),
3681        ("Array.from(new Headers({a:'1',b:'2'}).keys()).join(',')", "a,b"),
3682        ("Array.from(new Headers({a:'1',b:'2'}).values()).join(',')", "1,2"),
3683        (
3684            "var s=''; for (var e of new Headers({a:'1',b:'2'})) { s += e[0]+e[1]; } s",
3685            "a1b2",
3686        ),
3687        // `response.headers` が丸ごと欠落しておらず、本物の `Headers`(メソッド持ち)
3688        // が返ること。この処理系はレスポンスヘッダを捕捉していないため中身は
3689        // 常に空になる。
3690        (
3691            "typeof (await fetch('data:,hi')).headers.get",
3692            "function",
3693        ),
3694        // `xhr.getResponseHeader(name)` が丸ごと未対応で「関数ではない」の
3695        // TypeError になっていた。
3696        (
3697            "var x=new XMLHttpRequest(); x.open('GET','data:,hi'); x.send(); x.getResponseHeader('x') === null",
3698            "true",
3699        ),
3700        // XMLHttpRequest(同期。data: でオフライン検証)
3701        ("typeof XMLHttpRequest", "function"),
3702        ("var x=new XMLHttpRequest(); x.open('GET','data:,hi'); x.send(); x.responseText", "hi"),
3703        ("var x=new XMLHttpRequest(); x.open('GET','data:,hi'); x.send(); x.status", "200"),
3704        ("var x=new XMLHttpRequest(); x.open('GET','data:,hi'); x.send(); x.readyState", "4"),
3705        // `xhr.statusText` がコンストラクタで空文字列初期化されたきり `send()` 完了後も
3706        // 一切更新されないバグだった(`Response.prototype.statusText` と同種)。
3707        ("var x=new XMLHttpRequest(); x.open('GET','data:,hi'); x.send(); x.statusText", "OK"),
3708        ("var g=''; var x=new XMLHttpRequest(); x.onload=function(){g=x.responseText}; x.open('GET','data:,YO'); x.send(); g", "YO"),
3709        ("var g=''; var x=new XMLHttpRequest(); x.addEventListener('load',function(){g='L'+x.status}); x.open('GET','data:,q'); x.send(); g", "L200"),
3710        // `xhr.removeEventListener` が丸ごと未対応で、呼び出すと「関数ではない」の
3711        // TypeError になっていた。
3712        (
3713            "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",
3714            "",
3715        ),
3716        (
3717            "typeof new XMLHttpRequest().removeEventListener",
3718            "function",
3719        ),
3720        ("var x=new XMLHttpRequest(); x.open('GET','data:application/json,{\"n\":5}'); x.send(); JSON.parse(x.responseText).n", "5"),
3721        // fetch/XHR の POST オプション配線(data: は method 非依存で本文を返す=経路確認)
3722        ("await (await fetch('data:,hi', {method:'POST', body:'x'})).text()", "hi"),
3723        ("(await fetch('data:,ok', {method:'POST', headers:{'Content-Type':'application/json'}, body:'{}'})).status", "200"),
3724        ("var x=new XMLHttpRequest(); x.open('POST','data:,ok'); x.setRequestHeader('Content-Type','application/json'); x.send('{\"a\":1}'); x.responseText", "ok"),
3725        ("var x=new XMLHttpRequest(); x.open('POST','data:,z'); x.send(); x.status", "200"),
3726        // RegExp
3727        ("/\\d+/.test('abc123')", "true"),
3728        ("/^\\d+$/.test('abc')", "false"),
3729        ("'2026-06-15'.match(/(\\d+)-(\\d+)-(\\d+)/)[2]", "06"),
3730        ("'hello world'.replace(/o/g, '0')", "hell0 w0rld"),
3731        ("'a1b2c3'.replace(/\\d/g, m => '[' + m + ']')", "a[1]b[2]c[3]"),
3732        ("'one,two;three four'.split(/[,; ]/).join('|')", "one|two|three|four"),
3733        ("'foobarbaz'.match(/a/g).length", "2"),
3734        ("new RegExp('[A-Z]+').test('hello WORLD')", "true"),
3735        ("'Hello'.replace(/(\\w)(\\w+)/, '$2$1')", "elloH"),
3736        ("'price: $42'.match(/\\$(\\d+)/)[1]", "42"),
3737        ("/cat/i.test('CAT')", "true"),
3738        ("'a,b,,c'.split(/,/).length", "4"),
3739        // RegExp `y`(sticky)フラグ(ES2015): lastIndex の位置でのみマッチを試みる
3740        // (`g` と違い前方探索しない)。dotAll/sticky ゲッタープロパティも合わせて検証。
3741        ("/x/y.sticky", "true"),
3742        ("/x/g.sticky", "false"),
3743        ("/./s.dotAll", "true"),
3744        ("/./.dotAll", "false"),
3745        (
3746            "var re=/foo/y; re.lastIndex=3; re.test('xxxfoo')",
3747            "true",
3748        ),
3749        (
3750            "var re=/foo/y; re.lastIndex=2; re.test('xxxfoo')",
3751            "false",
3752        ),
3753        (
3754            "var re=/foo/y; var a=re.exec('foofoo').index; var b=re.exec('foofoo').index; a+','+b",
3755            "0,3",
3756        ),
3757        // RegExp `d`(hasIndices)フラグ(ES2022): exec() 結果に各キャプチャの
3758        // [start,end) 文字インデックスを持つ indices 配列を追加する。
3759        ("/x/d.hasIndices", "true"),
3760        ("/x/.hasIndices", "false"),
3761        (
3762            "var m=/(foo)(bar)/d.exec('xxfoobar'); m.indices[0].join(',')",
3763            "2,8",
3764        ),
3765        (
3766            "var m=/(foo)(bar)/d.exec('xxfoobar'); m.indices[1].join(',')+'/'+m.indices[2].join(',')",
3767            "2,5/5,8",
3768        ),
3769        (
3770            "var m=/(foo)(baz)?/d.exec('foo'); typeof m.indices[2]",
3771            "undefined",
3772        ),
3773        ("/x/.exec('x').indices", "undefined"),
3774        // setInterval: 発火確認は同一 eval 内では行えない(コールバックは
3775        // event_loop の次 tick で発火するため、登録直後は n=0 が正しい)。
3776        // 複数回発火の検証は下の「2段階評価テスト」で行う。
3777        // clearInterval: 登録直後にクリアすれば未発火のまま n=0。
3778        ("var n=0; var id=setInterval(function(){n++;}, 10); clearInterval(id); n", "0"),
3779        // window.innerWidth / innerHeight
3780        ("typeof window.innerWidth", "number"),
3781        ("typeof window.innerHeight", "number"),
3782        // XMLHttpRequest 任意メソッド(PUT/DELETE/PATCH)。data: URL は同期・無ネットワークで
3783        // メソッド非依存に 200+ボディを返すため、do_http_request→web_request 経路が
3784        // GET/POST 以外でも例外なく通ることを決定論的に検証できる。
3785        ("var x=new XMLHttpRequest(); x.open('PUT','data:text/plain,HELLO'); x.send('body'); x.status", "200"),
3786        ("var x=new XMLHttpRequest(); x.open('PUT','data:text/plain,HELLO'); x.send('body'); x.responseText", "HELLO"),
3787        ("var x=new XMLHttpRequest(); x.open('DELETE','data:text/plain,GONE'); x.send(); x.status", "200"),
3788        ("var x=new XMLHttpRequest(); x.open('DELETE','data:text/plain,GONE'); x.send(); x.responseText", "GONE"),
3789        ("var x=new XMLHttpRequest(); x.open('PATCH','data:text/plain,Pdata'); x.send('d'); x.responseText", "Pdata"),
3790        // 小文字メソッドも web_request 側で大文字正規化され通ること。
3791        ("var x=new XMLHttpRequest(); x.open('put','data:text/plain,low'); x.send('b'); x.responseText", "low"),
3792        // history.back/forward/go: ナビ要求を history._pending_nav(相対移動量)に積む。
3793        // 実ナビはホスト側 process_pending_nav が回収・実行するため、ここでは積算値を検証。
3794        ("history.back(); history._pending_nav", "-1"),
3795        ("history.forward(); history._pending_nav", "1"),
3796        ("history.go(-2); history._pending_nav", "-2"),
3797        ("history.back(); history.back(); history._pending_nav", "-2"),
3798        ("history.go(0); history._pending_nav || 0", "0"),
3799        // popstate リスナ登録は window の隠し配列へ蓄積される(発火はホスト主導)。
3800        ("window.addEventListener('popstate', function(){}); window._popstate_listeners.length", "1"),
3801        // scroll リスナも window の隠し配列 _scroll_listeners へ蓄積される(発火はホスト主導)。
3802        ("window.addEventListener('scroll', function(){}); window._scroll_listeners.length", "1"),
3803        ("window.addEventListener('scroll', function(){}); window.addEventListener('scroll', function(){}); window._scroll_listeners.length", "2"),
3804        // window.scrollY 初期値は 0(ホストが fire_scroll で更新する)。
3805        ("window.scrollY", "0"),
3806        // `window.postMessage()`(丸ごと未対応だった。フレーム分離が無いため
3807        // 同一 window 上の 'message' リスナへ同期配送する簡略実装)。
3808        (
3809            "var got=null; window.addEventListener('message', function(e){ got=e.data; }); \
3810             window.postMessage({x:1}); got.x",
3811            "1",
3812        ),
3813        (
3814            "var t=null; window.addEventListener('message', function(e){ t=e.type; }); \
3815             window.postMessage('hi'); t",
3816            "message",
3817        ),
3818        // `window.addEventListener(type, fn, {signal})`(丸ごと未対応だった。
3819        // `AbortController` でリスナを一括解除する定番パターン。2026-07-14
3820        // 発見・実装)。abort 後は発火しなくなる。
3821        (
3822            "var c=new AbortController(); var n=0; \
3823             window.addEventListener('message', function(){n++;}, {signal: c.signal}); \
3824             window.postMessage('a'); c.abort(); window.postMessage('b'); n",
3825            "1",
3826        ),
3827        // 登録前に既に abort 済みの signal を渡した場合は登録自体が行われない。
3828        (
3829            "var c=new AbortController(); c.abort(); var n=0; \
3830             window.addEventListener('message', function(){n++;}, {signal: c.signal}); \
3831             window.postMessage('a'); n",
3832            "0",
3833        ),
3834        // ---- Proxy / Reflect ----
3835        // get トラップ: プロパティ読み取りを横取りする。
3836        ("var p=new Proxy({}, {get:function(t,k){return 'G:'+k;}}); p.foo", "G:foo"),
3837        // get トラップ未定義なら target へフォワード。
3838        ("var p=new Proxy({a:42}, {}); p.a", "42"),
3839        // set トラップ: 書き込みを横取りして target に別キーで格納。
3840        ("var log=''; var p=new Proxy({}, {set:function(t,k,v){t['_'+k]=v;return true;}}); p.x=9; p._x", "9"),
3841        // set トラップ未定義なら target に直接書く。
3842        ("var p=new Proxy({}, {}); p.y=7; p.y", "7"),
3843        // has トラップ: in 演算子を横取り。
3844        ("var p=new Proxy({}, {has:function(t,k){return k==='magic';}}); ('magic' in p)", "true"),
3845        ("var p=new Proxy({}, {has:function(t,k){return k==='magic';}}); ('other' in p)", "false"),
3846        // has トラップ未定義なら target の存在判定にフォワード。
3847        ("var p=new Proxy({z:1}, {}); ('z' in p)", "true"),
3848        ("var p=new Proxy({z:1}, {}); ('w' in p)", "false"),
3849        // deleteProperty トラップ: `delete p.prop` を横取り。以前はここが未対応で、
3850        // トラップ呼び出しはおろか target へのフォワードすら行われず常に何も削除せず
3851        // `true` を返すだけの黙殺バグだった。
3852        (
3853            "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)",
3854            "a:false",
3855        ),
3856        // deleteProperty トラップ未定義なら target への delete にフォワードする。
3857        ("var p=new Proxy({a:1}, {}); delete p.a; ('a' in p)", "false"),
3858        // `Index`(`delete p['a']`)経由でも同じくフォワードされる。
3859        ("var p=new Proxy({a:1}, {}); delete p['a']; ('a' in p)", "false"),
3860        // `Object.defineProperty`/`Object.getOwnPropertyDescriptor` も Proxy 自身の
3861        // 意味の無い `.props` に書き込む/読み取るだけで target に一切反映・反射されない
3862        // バグだった。target フォワードで対応(trap 呼び出しまでは非対応)。
3863        (
3864            "var t={}; var p=new Proxy(t, {}); Object.defineProperty(p, 'x', {value:5}); t.x",
3865            "5",
3866        ),
3867        (
3868            "var p=new Proxy({x:5}, {}); Object.getOwnPropertyDescriptor(p, 'x').value",
3869            "5",
3870        ),
3871        // apply トラップ: 関数呼び出しを横取り。
3872        ("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"),
3873        // apply トラップ未定義なら target を普通に呼ぶ。
3874        ("var f=function(a,b){return a+b;}; var p=new Proxy(f, {}); p(3,4)", "7"),
3875        // construct トラップ: new を横取り。
3876        ("function C(){} var p=new Proxy(C, {construct:function(t,args){return {made:args[0]};}}); (new p(99)).made", "99"),
3877        // construct トラップ未定義なら target を new する。
3878        ("function C(x){this.v=x;} var p=new Proxy(C, {}); (new p(5)).v", "5"),
3879        // Reflect.get / set
3880        ("var o={a:1}; Reflect.set(o,'a',8); Reflect.get(o,'a')", "8"),
3881        // Reflect.has
3882        ("Reflect.has({k:1},'k')", "true"),
3883        ("Reflect.has({k:1},'x')", "false"),
3884        // Reflect.deleteProperty
3885        ("var o={a:1,b:2}; Reflect.deleteProperty(o,'a'); ('a' in o)", "false"),
3886        // Reflect.ownKeys
3887        ("Reflect.ownKeys({a:1,b:2,c:3}).length", "3"),
3888        // Reflect.defineProperty
3889        ("var o={}; Reflect.defineProperty(o,'x',{value:42}); o.x", "42"),
3890        // Reflect.apply
3891        ("Reflect.apply(function(a,b){return a-b;}, null, [10,3])", "7"),
3892        // Reflect.apply/construct の argumentsList は仕様上 array-like 全般
3893        // (実配列に限らない)を受け付けるべきだが、以前は `&mut Interp` を持たない
3894        // `iterable_values` を使っており、素の array-like を渡すと引数が消えていた。
3895        (
3896            "Reflect.apply(function(a,b){return a-b;}, null, {0:10,1:3,length:2})",
3897            "7",
3898        ),
3899        // Reflect.construct
3900        ("function P(x){this.x=x;} Reflect.construct(P,[77]).x", "77"),
3901        (
3902            "function P(x){this.x=x;} Reflect.construct(P,{0:77,length:1}).x",
3903            "77",
3904        ),
3905        // Reflect.get がプロトタイプ連鎖を辿る。
3906        ("var base={greet:'hi'}; var o=Object.create(base); Reflect.get(o,'greet')", "hi"),
3907        // Proxy + Reflect 併用: get をログしつつ target にフォワード。
3908        ("var p=new Proxy({n:5}, {get:function(t,k){return Reflect.get(t,k)*2;}}); p.n", "10"),
3909        // 配列を包む Proxy の get(数値インデックス)。
3910        ("var p=new Proxy([10,20,30], {get:function(t,k){return Reflect.get(t,k);}}); p[1]", "20"),
3911        // ネストした Proxy(二重ラップ)。
3912        ("var inner=new Proxy({v:1},{get:function(t,k){return 100;}}); var outer=new Proxy(inner,{}); outer.v", "100"),
3913        // WeakMap
3914        ("var wm=new WeakMap(); var k={}; wm.set(k,42); wm.get(k)", "42"),
3915        ("var wm=new WeakMap(); var k={}; wm.set(k,1); wm.has(k)", "true"),
3916        ("var wm=new WeakMap(); var k={}; wm.set(k,1); wm.delete(k); wm.has(k)", "false"),
3917        // WeakSet
3918        ("var ws=new WeakSet(); var o={}; ws.add(o); ws.has(o)", "true"),
3919        ("var ws=new WeakSet(); var o={}; ws.add(o); ws.delete(o); ws.has(o)", "false"),
3920        // `WeakMap`/`WeakSet` は仕様上オブジェクト以外のキー/値を拒否し `TypeError` を
3921        // 投げるべきだが、以前は型を一切検証せず静かに成功していたバグ。
3922        (
3923            "try { new WeakMap().set(1,'x'); 'no-throw' } catch(e) { 'caught' }",
3924            "caught",
3925        ),
3926        (
3927            "try { new WeakSet().add('x'); 'no-throw' } catch(e) { 'caught' }",
3928            "caught",
3929        ),
3930        // `new WeakMap(iterable)`/`new WeakSet(iterable)` の初期化引数が `Map` と違い
3931        // 完全に無視されていた(常に空になるバグ)。
3932        ("var k1={}; var wm=new WeakMap([[k1,1]]); wm.get(k1)", "1"),
3933        ("var o1={}; var ws=new WeakSet([o1]); ws.has(o1)", "true"),
3934        // WeakRef
3935        ("var o={v:7}; var wr=new WeakRef(o); wr.deref().v", "7"),
3936        // Symbol 関数(AtmOS では文字列近似)
3937        ("typeof Symbol('x')", "string"),
3938        ("Symbol('a') === Symbol('a')", "false"),
3939        ("Symbol.for('x') === Symbol.for('x')", "true"),
3940        // `Symbol.keyFor`(`Symbol.for` の逆引き)が丸ごと未対応だった。
3941        ("Symbol.keyFor(Symbol.for('mykey'))", "mykey"),
3942        ("typeof Symbol.keyFor(Symbol('local'))", "undefined"),
3943        ("typeof Symbol.iterator", "string"),
3944        // MutationObserver — typeof で存在確認。
3945        ("typeof MutationObserver", "function"),
3946        // MutationObserver — コンストラクタが呼べる。
3947        ("var mo = new MutationObserver(function(){}); typeof mo.observe", "function"),
3948        // MutationObserver — disconnect と takeRecords が存在する。
3949        ("var mo = new MutationObserver(function(){}); typeof mo.disconnect + ',' + typeof mo.takeRecords", "function,function"),
3950        // MutationObserver — takeRecords は初期状態で空配列。
3951        ("var mo = new MutationObserver(function(){}); mo.takeRecords().length", "0"),
3952        // MutationObserver — attributeOldValue が丸ごと未対応で oldValue が常に
3953        // null 固定だったバグ(以前は old_value 自体を捕捉していなかった)。
3954        (
3955            "var el=document.createElement('div'); el.setAttribute('foo','bar'); \
3956             var mo=new MutationObserver(function(){}); \
3957             mo.observe(el,{attributes:true,attributeOldValue:true}); \
3958             el.setAttribute('foo','baz'); var r=mo.takeRecords(); \
3959             r.length+','+r[0].attributeName+','+r[0].oldValue",
3960            "1,foo,bar",
3961        ),
3962        // MutationObserver — attributeFilter が丸ごと未対応で、フィルタ対象外の
3963        // 属性変化まで常に通知されてしまっていたバグ。
3964        (
3965            "var el=document.createElement('div'); \
3966             var mo=new MutationObserver(function(){}); \
3967             mo.observe(el,{attributes:true,attributeFilter:['foo']}); \
3968             el.setAttribute('bar','x'); el.setAttribute('foo','y'); \
3969             var r=mo.takeRecords(); r.length+','+r[0].attributeName",
3970            "1,foo",
3971        ),
3972        // MutationObserver — removeAttribute() が非対称に通知漏れしていたバグ
3973        // (setAttribute() は既に notify_attribute を呼んでいたが removeAttribute()
3974        // 側だけこの呼び出し自体が丸ごと欠けていた)。
3975        (
3976            "var el=document.createElement('div'); el.setAttribute('foo','bar'); \
3977             var mo=new MutationObserver(function(){}); \
3978             mo.observe(el,{attributes:true,attributeOldValue:true}); \
3979             el.removeAttribute('foo'); var r=mo.takeRecords(); \
3980             r.length+','+r[0].attributeName+','+r[0].oldValue",
3981            "1,foo,bar",
3982        ),
3983        // MutationObserver — classList.add()/.remove() が「class」属性を書き換える
3984        // にもかかわらず notify_attribute を一切呼んでおらず通知漏れしていたバグ
3985        // (setAttribute('class',...)/removeAttribute() 経由の変更は通知されるのに
3986        // classList 経由だけ抜けていた非対称ペア)。
3987        (
3988            "var el=document.createElement('div'); el.className='a'; \
3989             var mo=new MutationObserver(function(){}); \
3990             mo.observe(el,{attributes:true,attributeOldValue:true}); \
3991             el.classList.add('b'); var r=mo.takeRecords(); \
3992             r.length+','+r[0].attributeName+','+r[0].oldValue",
3993            "1,class,a",
3994        ),
3995        (
3996            "var el=document.createElement('div'); el.className='a b'; \
3997             var mo=new MutationObserver(function(){}); \
3998             mo.observe(el,{attributes:true,attributeOldValue:true}); \
3999             el.classList.remove('b'); var r=mo.takeRecords(); \
4000             r.length+','+r[0].attributeName+','+r[0].oldValue",
4001            "1,class,a b",
4002        ),
4003        // MutationObserver — style.xxx=/.cssText= が「style」属性を書き換える
4004        // にもかかわらず notify_attribute を一切呼んでいなかったバグ(classList と
4005        // 同型の非対称ペア)。
4006        (
4007            "var el=document.createElement('div'); el.style.color='red'; \
4008             var mo=new MutationObserver(function(){}); \
4009             mo.observe(el,{attributes:true,attributeOldValue:true}); \
4010             el.style.color='blue'; var r=mo.takeRecords(); \
4011             r.length+','+r[0].attributeName+','+r[0].oldValue",
4012            "1,style,color: red",
4013        ),
4014        // MutationObserver — 仕様上`attributeOldValue`/`attributeFilter`が
4015        // 指定され`attributes`自体が省略された場合は`attributes`を暗黙的に
4016        // `true`とみなすべきところ、この暗黙有効化ロジックが丸ごと無く
4017        // `observe(el,{attributeOldValue:true})`という定番の省略記法
4018        // (`attributes:true`を明示せずとも動くのが仕様)が黙って何も
4019        // 監視しないバグだった。2026-07-17 発見・実装。
4020        (
4021            "var el=document.createElement('div'); el.setAttribute('data-x','1'); \
4022             var mo=new MutationObserver(function(){}); \
4023             mo.observe(el,{attributeOldValue:true}); \
4024             el.setAttribute('data-x','2'); var r=mo.takeRecords(); \
4025             r.length+','+r[0].type",
4026            "1,attributes",
4027        ),
4028        // `attributeFilter`のみ指定(`attributes`省略)でも同様に暗黙有効化される。
4029        (
4030            "var el=document.createElement('div'); \
4031             var mo=new MutationObserver(function(){}); \
4032             mo.observe(el,{attributeFilter:['data-x']}); \
4033             el.setAttribute('data-x','1'); var r=mo.takeRecords(); \
4034             r.length",
4035            "1",
4036        ),
4037        // `characterDataOldValue`のみ指定(`characterData`省略)でも同様。
4038        (
4039            "var el=document.createElement('div'); el.textContent='hi'; \
4040             var mo=new MutationObserver(function(){}); \
4041             mo.observe(el,{characterDataOldValue:true, subtree:true}); \
4042             el.textContent='bye'; var r=mo.takeRecords(); \
4043             r.length+','+r[0].type+','+r[0].oldValue",
4044            "1,characterData,hi",
4045        ),
4046        // `attributes:false`を明示している場合は暗黙有効化しない(明示指定を
4047        // 尊重する。`attributes_specified.is_none()`条件の裏側の確認)。
4048        (
4049            "var el=document.createElement('div'); el.setAttribute('data-x','1'); \
4050             var mo=new MutationObserver(function(){}); \
4051             mo.observe(el,{attributes:false, attributeOldValue:true}); \
4052             el.setAttribute('data-x','2'); var r=mo.takeRecords(); \
4053             r.length",
4054            "0",
4055        ),
4056        // MutationObserver — el.innerHTML=... が childList の変化を一切通知して
4057        // いなかったバグ(appendChild/removeChild/insertBefore/replaceChild は
4058        // 既に notify_child_list を呼んでいたのに、最も頻繁に使われる innerHTML
4059        // 代入だけがこの呼び出しを欠いていた)。
4060        (
4061            "var el=document.createElement('div'); \
4062             var mo=new MutationObserver(function(){}); \
4063             mo.observe(el,{childList:true}); \
4064             el.innerHTML='<span>hi</span>'; var r=mo.takeRecords(); \
4065             r.length+','+r[0].type+','+r[0].addedNodes.length",
4066            "1,childList,1",
4067        ),
4068        // MutationObserver — 子が1件も無い要素への textContent=... 代入
4069        // (新規テキストノードを1件追加する経路)も childList を通知していなかった。
4070        (
4071            "var el=document.createElement('div'); \
4072             var mo=new MutationObserver(function(){}); \
4073             mo.observe(el,{childList:true}); \
4074             el.textContent='hi'; var r=mo.takeRecords(); \
4075             r.length+','+r[0].type+','+r[0].addedNodes.length",
4076            "1,childList,1",
4077        ),
4078        // MutationObserver — `characterData`オプションが丸ごと未対応だった
4079        // (`childList`/`attributes`は既に対応済みで`characterData`だけ抜けて
4080        // いた兄弟ギャップ。テキストノードが既に存在する要素への
4081        // `textContent`再代入は`characterData`変化として通知されるべき
4082        // ところ、対応する通知経路自体が存在せず`observe(el,
4083        // {characterData:true})`を指定してもコールバックが一切呼ばれ
4084        // なかった。2026-07-17 発見・実装)。
4085        (
4086            "var el=document.createElement('div'); el.textContent='hi'; \
4087             var mo=new MutationObserver(function(){}); \
4088             mo.observe(el,{characterData:true, subtree:true}); \
4089             el.textContent='bye'; var r=mo.takeRecords(); \
4090             r.length+','+r[0].type",
4091            "1,characterData",
4092        ),
4093        // `characterDataOldValue:true`で変更前のテキストを取得できる。
4094        (
4095            "var el=document.createElement('div'); el.textContent='hi'; \
4096             var mo=new MutationObserver(function(){}); \
4097             mo.observe(el,{characterData:true, characterDataOldValue:true, subtree:true}); \
4098             el.textContent='bye'; var r=mo.takeRecords(); \
4099             r[0].oldValue",
4100            "hi",
4101        ),
4102        // 値が実質変化していない代入(同じ文字列への再代入)は通知しない。
4103        (
4104            "var el=document.createElement('div'); el.textContent='hi'; \
4105             var mo=new MutationObserver(function(){}); \
4106             mo.observe(el,{characterData:true, subtree:true}); \
4107             el.textContent='hi'; var r=mo.takeRecords(); r.length",
4108            "0",
4109        ),
4110        // `characterData`未指定(既定false)ならテキスト変化を通知しない
4111        // (`childList:true`のみ指定している場合、テキストノードの中身が
4112        // 変わっただけでは子ノードの追加/削除ではないため無関係)。
4113        (
4114            "var el=document.createElement('div'); el.textContent='hi'; \
4115             var mo=new MutationObserver(function(){}); \
4116             mo.observe(el,{childList:true}); \
4117             el.textContent='bye'; var r=mo.takeRecords(); r.length",
4118            "0",
4119        ),
4120        // Object.defineProperty — データ記述子。
4121        ("var o={}; Object.defineProperty(o,'x',{value:42,writable:true}); o.x", "42"),
4122        // Object.defineProperty — getter。
4123        ("var o={_v:7}; Object.defineProperty(o,'v',{get:function(){return this._v*2;}}); o.v", "14"),
4124        // Object.defineProperty — setter + getter。
4125        ("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"),
4126        // Object.defineProperty — setterのみ(getterなし→undefined)。
4127        ("var o={_s:''}; Object.defineProperty(o,'s',{set:function(v){this._s=v;}}); o.s='hi'; o._s", "hi"),
4128        // `__defineGetter__`/`__defineSetter__`/`__lookupGetter__`/
4129        // `__lookupSetter__`(Annex B.3.1)が丸ごと未対応だった。
4130        ("var o={_v:7}; o.__defineGetter__('v', function(){return this._v*2;}); o.v", "14"),
4131        ("var o={_x:0}; o.__defineSetter__('x', function(v){this._x=v+1;}); o.x=9; o._x", "10"),
4132        // getter/setter を別々に定義しても互いを上書きしない。
4133        (
4134            "var o={_x:1}; o.__defineGetter__('x', function(){return this._x;}); \
4135             o.__defineSetter__('x', function(v){this._x=v;}); o.x=5; o.x",
4136            "5",
4137        ),
4138        ("var o={}; o.__defineGetter__('v', function(){return 1;}); typeof o.__lookupGetter__('v')", "function"),
4139        ("var o={}; typeof o.__lookupGetter__('nope')", "undefined"),
4140        // Object.defineProperties — 複数。
4141        ("var o={}; Object.defineProperties(o,{a:{value:1},b:{value:2}}); o.a+o.b", "3"),
4142        // getter で動的値を返す。
4143        ("var o={n:0}; Object.defineProperty(o,'next',{get:function(){return ++this.n;}}); o.next; o.next; o.next", "3"),
4144        // Object.getOwnPropertyDescriptor — データ記述子。
4145        ("var o={x:5}; Object.getOwnPropertyDescriptor(o,'x').value", "5"),
4146        // Object.getOwnPropertyDescriptor — アクセサ記述子。
4147        ("var o={}; Object.defineProperty(o,'v',{get:function(){return 99;}}); typeof Object.getOwnPropertyDescriptor(o,'v').get", "function"),
4148        // Object.getOwnPropertyDescriptor — 存在しないキー→undefined。
4149        ("Object.getOwnPropertyDescriptor({x:1},'y')", "undefined"),
4150        // Object.getOwnPropertyDescriptors — 全記述子。
4151        ("var o={a:1}; Object.defineProperty(o,'b',{get:function(){return 2;}}); Object.getOwnPropertyDescriptors(o).a.value + Object.getOwnPropertyDescriptors(o).b.get()", "3"),
4152        // getter/setter リテラル構文(ES2015。Object.defineProperty 経由でのみ可能だった
4153        // アクセサ定義を、オブジェクトリテラル/クラス本体で直接書けるようにする)。
4154        // オブジェクトリテラルの getter。
4155        ("var o={_v:7, get v(){return this._v*2;}}; o.v", "14"),
4156        // オブジェクトリテラルの getter+setter。
4157        ("var o={_x:0, get x(){return this._x;}, set x(v){this._x=v+1;}}; o.x=9; o.x", "10"),
4158        // "get"/"set" という名前そのもののプロパティ/メソッドは従来通り動作する
4159        // (2トークン先読みでアクセサと誤認しないこと)。
4160        ("var o={get: 5}; o.get", "5"),
4161        ("var o={get(){return 7;}}; o.get()", "7"),
4162        // クラスの getter。
4163        (
4164            "class Circle { constructor(r){this.r=r;} get area(){return Math.round(Math.PI*this.r*this.r);} } new Circle(2).area",
4165            "13",
4166        ),
4167        // クラスの getter+setter(private フィールド風の下線プレフィックス)。
4168        (
4169            "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",
4170            "0",
4171        ),
4172        // static getter。
4173        (
4174            "class Config { static get version(){return '1.0';} } Config.version",
4175            "1.0",
4176        ),
4177        // Array.prototype.splice — 要素削除と返値。
4178        ("var a=[1,2,3,4]; var r=a.splice(1,2); r.join(',')+'/'+a.join(',')", "2,3/1,4"),
4179        // splice — 挿入。
4180        ("var a=[1,4]; a.splice(1,0,2,3); a.join(',')", "1,2,3,4"),
4181        // lastIndexOf。
4182        ("[1,2,3,2,1].lastIndexOf(2)", "3"),
4183        // copyWithin。
4184        ("[1,2,3,4,5].copyWithin(0,3).join(',')", "4,5,3,4,5"),
4185        // copyWithin — 負のインデックス(末尾からのオフセット)。
4186        ("[1,2,3,4,5].copyWithin(-2,0).join(',')", "1,2,3,1,2"),
4187        // copyWithin — end 引数で範囲を限定。
4188        ("[1,2,3,4,5].copyWithin(0,3,4).join(',')", "4,2,3,4,5"),
4189        // toSorted — 元配列を変更しない。
4190        ("var a=[3,1,2]; var s=a.toSorted(); a[0]+'/'+s.join(',')", "3/1,2,3"),
4191        // sort — undefined は比較関数に渡さず常に末尾に送る(SortCompare の特別扱い)。
4192        ("[undefined,3,undefined,1,2].sort((a,b)=>a-b).join(',')", "1,2,3,,"),
4193        ("[undefined,3,1,2].sort().join(',')", "1,2,3,"),
4194        ("[undefined,3,1,2].toSorted((a,b)=>a-b).join(',')", "1,2,3,"),
4195        // 配列の数値変換(ToPrimitive→ToNumber)。以前は Object 全般と同じく常に NaN だった。
4196        ("[5]*2", "10"),
4197        ("[]+1", "1"),
4198        ("['5']-2", "3"),
4199        ("[1,2]*1", "NaN"),
4200        // `Value::to_number` にも同種の Proxy 素通しバグがあり、`Array` は
4201        // 特別扱いされているのに `Proxy` で包むと常に `NaN` になっていた
4202        // (`Array.isArray`/`to_js_string` 等は既に透過性修正済みだった)。
4203        ("new Proxy([5],{})*2", "10"),
4204        ("+new Proxy([5],{})", "5"),
4205        // 緩やか等価(==)で Object(配列)と Number/String を比較。以前はどの分岐にも
4206        // 該当せず常に false になっていた。
4207        ("[5]==5", "true"),
4208        ("[5]=='5'", "true"),
4209        ("''==[]", "true"),
4210        ("[1,2]==5", "false"),
4211        // Number("0x..")/("0o..")/("0b..") 整数リテラル変換。以前は NaN 固定だった。
4212        ("Number('0x1F')", "31"),
4213        ("Number('0o17')", "15"),
4214        ("Number('0b101')", "5"),
4215        ("+'0xff'", "255"),
4216        // String.prototype.normalize — 丸ごと欠落していた。Unicode 分解/合成テーブルが無い
4217        // ため恒等変換の簡略実装だが、少なくとも呼び出せて有効な form は通ることを確認。
4218        ("'abc'.normalize()", "abc"),
4219        ("'abc'.normalize('NFD')", "abc"),
4220        ("(function(){ try { 'x'.normalize('bogus'); return 'no-throw'; } catch(e) { return 'threw'; } })()", "threw"),
4221        // 西欧言語の分音符付きラテン文字に限定した部分実装の NFC/NFD 検証
4222        ("'e\\u0301'.normalize('NFC') === '\\u00e9'", "true"),
4223        ("'\\u00e9'.normalize('NFD') === 'e\\u0301'", "true"),
4224        ("'\\u00e9'.normalize('NFC') === '\\u00e9'", "true"),
4225        // オプショナルチェイニング `a?.[b]`(computed member)。以前は Index に optional
4226        // フラグ自体が存在せず、常に通常のプロパティアクセスとして評価され null/undefined
4227        // で TypeError になっていた(`a?.b` の識別子版は動いていたが `[]` 版だけ壊れていた)。
4228        ("var a=null; a?.['x']", "undefined"),
4229        ("var a=undefined; a?.[0]", "undefined"),
4230        ("var a=[1,2,3]; a?.[1]", "2"),
4231        ("var f=null; f?.['x'](1,2)", "undefined"),
4232        // オプショナルチェイン全体の短絡伝播。以前は直近1段しか短絡せず `a?.b.c` は
4233        // `a` が null/undefined のとき後続の `.c` アクセスで TypeError になっていた。
4234        ("var a=null; a?.b.c", "undefined"),
4235        ("var a=undefined; a?.b.c.d", "undefined"),
4236        ("var a=null; a?.b[0].c", "undefined"),
4237        ("var a=null; a?.b()", "undefined"),
4238        ("var o={b:{c:5}}; o?.b.c", "5"),
4239        // for...in がプロトタイプ連鎖上の継承プロパティを列挙しないバグ。以前は自身の
4240        // プロパティのみだった。
4241        ("(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"),
4242        // 自身のプロパティがプロトタイプ側の同名キーを覆い隠す(重複しない)ことも確認。
4243        ("(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"),
4244        // `in` 演算子・for-in が accessors(getter/setter 限定プロパティ)専用マップを
4245        // 見ておらず「無い」扱いになっていたバグ。
4246        ("'x' in {get x(){return 1;}}", "true"),
4247        ("(function(){ var o={get x(){return 1;}}; var ks=[]; for(var k in o) ks.push(k); return ks.join(','); })()", "x"),
4248        // Object.keys/values/entries/assign も同じ accessors 見落としバグがあった。
4249        ("Object.keys({get x(){return 1;}}).join(',')", "x"),
4250        ("Object.values({get x(){return 42;}}).join(',')", "42"),
4251        ("JSON.stringify(Object.entries({get x(){return 7;}}))", "[[\"x\",7]]"),
4252        ("Object.assign({}, {get x(){return 9;}}).x", "9"),
4253        // JSON.stringify も同じ accessors 見落としバグがあった。
4254        ("JSON.stringify({get x(){return 5;}})", "{\"x\":5}"),
4255        // オブジェクトスプレッド {...src} も同じ accessors 見落としバグがあった。
4256        ("({...{get x(){return 3;}}}).x", "3"),
4257        ("var s={get x(){return 1;}}; var o={...s, y:2}; o.x+','+o.y", "1,2"),
4258        // Number.prototype.toExponential/toPrecision/valueOf — 丸ごと欠落していた。
4259        ("(150).toExponential(2)", "1.50e+2"),
4260        ("(0.0012345).toExponential(3)", "1.235e-3"),
4261        ("(123.456).toPrecision(5)", "123.46"),
4262        ("(0.0001234).toPrecision(2)", "0.00012"),
4263        ("(123456).toPrecision(2)", "1.2e+5"),
4264        ("(5).valueOf()", "5"),
4265        // `Number.prototype.toFixed(fractionDigits)` は仕様上 `fractionDigits` が
4266        // 0〜100 の範囲外なら RangeError を投げる必要があるが、以前は範囲外/非有限値を
4267        // 黙って0にクランプするだけで上限チェックが無く、`(1).toFixed(500)` のような
4268        // 呼び出しが `10^500`(Infinity)経由で壊れた出力になり得た。
4269        ("(1).toFixed(0)", "1"),
4270        ("(1.5).toFixed(100).length", "102"),
4271        (
4272            "try { (1).toFixed(-1); 'no-throw' } catch(e) { 'threw' }",
4273            "threw",
4274        ),
4275        (
4276            "try { (1).toFixed(101); 'no-throw' } catch(e) { 'threw' }",
4277            "threw",
4278        ),
4279        ("(NaN).toFixed(2)", "NaN"),
4280        ("(Infinity).toFixed(2)", "Infinity"),
4281        // Array.prototype.toLocaleString / Number.prototype.toLocaleString — 丸ごと欠落。
4282        ("[1,2,3].toLocaleString()", "1,2,3"),
4283        ("[1,null,undefined,2].toLocaleString()", "1,,,2"),
4284        // Number.prototype.toLocaleString は `toString()` の別名でしかなく、実際のブラウザなら
4285        // `Intl` 非搭載でも入る3桁区切りのカンマが欠落していた(2026-07-09に修正)。
4286        ("(1234.5).toLocaleString()", "1,234.5"),
4287        ("(1234567).toLocaleString()", "1,234,567"),
4288        ("(123).toLocaleString()", "123"),
4289        ("(-1234567.89).toLocaleString()", "-1,234,567.89"),
4290        // `Intl` グローバルが丸ごと未対応だった。ロケール/オプション引数は無視するが、
4291        // `new Intl.NumberFormat().format(n)` という最頻出イディオムだけは救う最小実装。
4292        ("new Intl.NumberFormat().format(1234567)", "1,234,567"),
4293        ("new Intl.NumberFormat('en-US').format(-1234.5)", "-1,234.5"),
4294        ("typeof Intl.NumberFormat().format", "function"),
4295        ("(0).toLocaleString()", "0"),
4296        ("(NaN).toLocaleString()", "NaN"),
4297        ("(Infinity).toLocaleString()", "Infinity"),
4298        // Boolean プリミティブへのプロパティアクセスが一律 undefined になり、メソッド呼出
4299        // 自体が丸ごと欠落していたバグ(プリミティブ中 Boolean だけメソッドが皆無だった)。
4300        ("true.toString()", "true"),
4301        ("false.toString()", "false"),
4302        ("true.valueOf()", "true"),
4303        ("(false).valueOf() === false", "true"),
4304        // 関数の name/length が丸ごと欠落していたバグ。
4305        ("function foo(a,b){} foo.name", "foo"),
4306        ("function foo(a,b){} foo.length", "2"),
4307        ("function foo(a,b=1,...c){} foo.length", "1"),
4308        ("(function(){}).length", "0"),
4309        ("Math.max.length", "0"),
4310        // `Function.prototype.bind()` が返す関数の `.name`/`.length` が丸ごと未対応で
4311        // 常に `undefined` になっていた。
4312        ("function foo(a,b,c){} foo.bind(null).name", "bound foo"),
4313        ("function foo(a,b,c){} foo.bind(null).length", "3"),
4314        ("function foo(a,b,c){} foo.bind(null,1).length", "2"),
4315        ("function foo(a,b,c){} foo.bind(null,1,2,3,4).length", "0"),
4316        // 束縛済み関数の文字列化(`String(fn.bind(...))`)も専用ケースが無く
4317        // `[object Object]` に落ちるバグだった。
4318        (
4319            "function foo(){} String(foo.bind(null)).includes('native code')",
4320            "true",
4321        ),
4322        // `Object.prototype.toString.call(x)` が `this` の実際の種別を一切見ず
4323        // 常に `[object Object]` を返すバグだった(`lodash` 等で広く使われる
4324        // 型判定イディオムが配列/Map/Set/Date/RegExp/関数のいずれに対しても
4325        // 機能しなくなる、実用上かなり影響の大きいバグ)。
4326        ("Object.prototype.toString.call([1,2])", "[object Array]"),
4327        ("Object.prototype.toString.call({})", "[object Object]"),
4328        ("Object.prototype.toString.call(new Date())", "[object Date]"),
4329        ("Object.prototype.toString.call(/a/)", "[object RegExp]"),
4330        ("Object.prototype.toString.call(new Map())", "[object Map]"),
4331        ("Object.prototype.toString.call(new Set())", "[object Set]"),
4332        ("Object.prototype.toString.call(function(){})", "[object Function]"),
4333        ("Object.prototype.toString.call(null)", "[object Null]"),
4334        ("Object.prototype.toString.call(undefined)", "[object Undefined]"),
4335        // `Symbol.toStringTag`(クラスでカスタムタグを定義する仕組み)も一切考慮して
4336        // いなかった。データプロパティ/アクセサ(getter)どちらの定義方法も動くこと。
4337        (
4338            "class Foo { get [Symbol.toStringTag](){ return 'Foo'; } } Object.prototype.toString.call(new Foo())",
4339            "[object Foo]",
4340        ),
4341        (
4342            "var o={}; o['Symbol(Symbol.toStringTag)']='Bar'; Object.prototype.toString.call(o)",
4343            "[object Bar]",
4344        ),
4345        // NamedEvaluation(ES2015): `const f = function(){}`/`const f = () => {}` の
4346        // ような無名関数式を単純な識別子へ代入すると、その識別子名を `.name` として
4347        // 継承する仕様が丸ごと未対応で、常に空文字列のままだった。
4348        ("var f = function(){}; f.name", "f"),
4349        ("var g = () => {}; g.name", "g"),
4350        // 既に名前を持つ関数式(`function foo(){}`)は変数名で上書きされない。
4351        ("var h = function foo(){}; h.name", "foo"),
4352        // object literal のプロパティ値としての無名関数式も同様にキー名を継承する。
4353        ("({bar: function(){}}).bar.name", "bar"),
4354        ("({baz: () => {}}).baz.name", "baz"),
4355        // メソッド短縮記法は元々キー名を持つため、この継承の影響を受けない
4356        // (上書きではなく「無名の場合のみ設定」であることの確認)。
4357        ("({qux(){}}).qux.name", "qux"),
4358        // structuredClone(Date/RegExp) — 実データが props に無く plain object 化に落ちて
4359        // 消えてしまうバグ(Map/Set と同種)。
4360        ("var d=new Date(2020,0,1); var c=structuredClone(d); c.getTime()===d.getTime() && c!==d", "true"),
4361        ("var r=/abc/gi; var c=structuredClone(r); c.source+','+c.flags+','+(c!==r)", "abc,gi,true"),
4362        // 分割代入の rest ({...rest}) も同じ accessors 見落としバグがあった。
4363        ("var o={get x(){return 1;}, y:2}; var {y, ...rest} = o; rest.x+','+rest.y", "1,undefined"),
4364        // hasOwnProperty/Object.hasOwn が accessors と配列インデックス/length を
4365        // 見落とすバグ。
4366        ("({get x(){return 1;}}).hasOwnProperty('x')", "true"),
4367        ("[1,2,3].hasOwnProperty(0)", "true"),
4368        ("[1,2,3].hasOwnProperty('length')", "true"),
4369        ("[1,2,3].hasOwnProperty(5)", "false"),
4370        ("Object.hasOwn({get x(){return 1;}}, 'x')", "true"),
4371        ("({}).toLocaleString()", "[object Object]"),
4372        ("[{}, {}].toLocaleString()", "[object Object],[object Object]"),
4373        // Object.getOwnPropertyDescriptor(s) が配列の数値インデックス/length を見落とし
4374        // 常に undefined になるバグ。
4375        ("Object.getOwnPropertyDescriptor([1,2,3], '0').value", "1"),
4376        ("Object.getOwnPropertyDescriptor([1,2,3], 'length').value", "3"),
4377        ("Object.keys(Object.getOwnPropertyDescriptors([1,2])).sort().join(',')", "0,1,length"),
4378        // Object.defineProperty が配列の数値インデックス/length に書き込んでも
4379        // 実データ(ObjKind::Array)側に反映されないバグ。
4380        ("var a=[1,2,3]; Object.defineProperty(a,'0',{value:99}); a[0]", "99"),
4381        ("var a=[1,2]; Object.defineProperty(a,'5',{value:9}); a.length+','+a[5]", "6,9"),
4382        ("var a=[1,2,3]; Object.defineProperty(a,'length',{value:1}); a.join(',')", "1"),
4383        // Array/Map/Set の Symbol.iterator が丸ごと未登録で、明示呼出し
4384        // (`arr[Symbol.iterator]()`)が「関数ではない」になっていたバグ。for...of は
4385        // 内部高速経路のため気づかれなかった。
4386        ("[...[1,2,3][Symbol.iterator]()].join(',')", "1,2,3"),
4387        ("var m=new Map([['a',1]]); [...m[Symbol.iterator]()][0].join(',')", "a,1"),
4388        ("[...new Set([1,2])[Symbol.iterator]()].join(',')", "1,2"),
4389        // entries/keys/values が本物の Iterator(`.next()`/`Symbol.iterator` 持ち)ではなく
4390        // 単なる配列を返しており、明示的なイテレータプロトコル駆動
4391        // (`const it = arr.values(); it.next()`) が「next が存在しない」で壊れていたバグ。
4392        ("var it=[10,20].values(); it.next().value+','+it.next().value+','+it.next().done", "10,20,true"),
4393        ("var it=[1,2].entries(); it.next().value.join(',')", "0,1"),
4394        ("var it=[1,2].keys(); it.next().value+','+it.next().value", "0,1"),
4395        ("var it=new Map([['a',1]]).entries(); it.next().value.join(',')", "a,1"),
4396        ("var it=new Set([5,6]).values(); it.next().value+','+it.next().value", "5,6"),
4397        ("var it=[1].values(); it[Symbol.iterator]()===it", "true"),
4398        // String.prototype[Symbol.iterator] が丸ごと欠落していたバグ。
4399        ("[...'ab'[Symbol.iterator]()].join(',')", "a,b"),
4400        ("var it='xy'[Symbol.iterator](); it.next().value+it.next().value+it.next().done", "xytrue"),
4401        // 上記の Iterator 化に伴う回帰: Array.from/new Map/new Set が新しい軽量イテレータ
4402        // オブジェクト(entries/keys/values の戻り値)を「iterable」として認識できず
4403        // 空になっていたバグ(本イテレータ導入時に発見・即修正)。
4404        ("Array.from([10,20].values()).join(',')", "10,20"),
4405        ("Array.from([1,2].entries()).map(e=>e.join(':')).join(',')", "0:1,1:2"),
4406        ("var m=new Map(new Map([['a',1]]).entries()); m.get('a')", "1"),
4407        ("var s=new Set([1,2,2,3].values()); [...s].join(',')", "1,2,3"),
4408        // イテレータの内部実装詳細(_items/_pos)が Object.keys/JSON.stringify/spread から
4409        // 見えてしまう漏れ(make_iterator 導入時に発見・即修正)。
4410        ("Object.keys([1,2].values()).length", "0"),
4411        ("JSON.stringify([1,2].values())", "{}"),
4412        ("Object.keys({...[1,2].values()}).length", "0"),
4413        // AggregateError(ES2021)が丸ごと欠落していた。Promise.any が全滅した際に
4414        // 最後の reject 理由しか見えず、`.errors` を持つ AggregateError も
4415        // 投げられていなかった。
4416        ("new AggregateError([1,2],'m').errors.join(',')", "1,2"),
4417        ("new AggregateError([],'m') instanceof Error", "true"),
4418        (
4419            "await Promise.any([Promise.reject('a'),Promise.reject('b')]).catch(e => e.name+':'+e.errors.join(','))",
4420            "AggregateError:a,b",
4421        ),
4422        // Date の日時文字列パース(ISO 8601)が丸ごと非対応だった(`new Date("2024-01-15")`
4423        // が Invalid Date になっていた)。
4424        ("new Date('2024-01-15').getFullYear()+'-'+(new Date('2024-01-15').getMonth()+1)+'-'+new Date('2024-01-15').getDate()", "2024-1-15"),
4425        ("Date.parse('2024-01-15T00:00:00Z')", "1705276800000"),
4426        ("Date.parse('2024-01-15T02:00:00+02:00')", "1705276800000"),
4427        // コロン無しタイムゾーンオフセット(`+0900` 形式。ISO 8601/ECMA-262 双方で正当)
4428        ("Date.parse('2024-01-15T09:00:00+0900')", "1705276800000"),
4429        ("Date.parse('2024-01-15T00:00:00+00')", "1705276800000"),
4430        ("new Date('2024-01-15T00:00:00.500Z').getTime()", "1705276800500"),
4431        ("isNaN(Date.parse('not a date'))", "true"),
4432        // RFC 2822 形式(HTTP日付ヘッダ等の定番書式)が丸ごと非対応で、ISO 8601以外の
4433        // 文字列は常に NaN になっていたバグ。曜日名の有無・タイムゾーンオフセット双方を
4434        // 確認する。
4435        ("Date.parse('Mon, 15 Jan 2024 00:00:00 GMT')", "1705276800000"),
4436        ("Date.parse('15 Jan 2024 00:00:00 GMT')", "1705276800000"),
4437        ("Date.parse('Mon, 15 Jan 2024 02:00:00 +0200')", "1705276800000"),
4438        // `Date.now`/`Date.parse` は登録済みだったが `Date.UTC` が丸ごと未対応だった。
4439        ("Date.UTC(2024, 0, 15) === Date.parse('2024-01-15T00:00:00Z')", "true"),
4440        ("new Date(Date.UTC(2024,0,15,10,30,0)).getUTCHours()", "10"),
4441        // 省略引数の既定値(day のみ1、他は0)。
4442        ("new Date(Date.UTC(2024,0)).getDate()", "1"),
4443        ("new Date('Mon, 15 Jan 2024 00:00:00 GMT').getFullYear()", "2024"),
4444        // RegExp 名前付きキャプチャグループ (?<name>...)(ES2018)が丸ごと非対応で
4445        // `(?<`(lookbehind と誤認され `:` フォールバックと同じ経路)を通ると壊れた
4446        // パース結果になり、`match.groups` は常に undefined だった。
4447        ("'2024-01-15'.match(/(?<year>\\d+)-(?<month>\\d+)-(?<day>\\d+)/).groups.year", "2024"),
4448        ("'2024-01-15'.match(/(?<year>\\d+)-(?<month>\\d+)-(?<day>\\d+)/).groups.month", "01"),
4449        ("/(?<a>x)(y)/.exec('xy')[2]", "y"),
4450        ("/(x)/.exec('x').groups", "undefined"),
4451        ("[...'a1 b2'.matchAll(/(?<letter>[a-z])(?<num>\\d)/g)].map(m=>m.groups.letter+m.groups.num).join(',')", "a1,b2"),
4452        // replace() の $<name> 置換パターン(ES2018)が丸ごと非対応だった。合わせて
4453        // $`(一致より前)/$'(一致より後)も未対応だった。
4454        ("'2024-01-15'.replace(/(?<y>\\d+)-(?<m>\\d+)-(?<d>\\d+)/, '$<d>/$<m>/$<y>')", "15/01/2024"),
4455        ("'abc'.replace(/b/, \"[$`|$']\")", "a[a|c]c"),
4456        ("'x'.replace(/(?<a>x)/, (m,a,off,str,groups)=>groups.a+'!')", "x!"),
4457        // RegExp の \uXXXX/\u{X...}/\xXX エスケープが丸ごと未対応で、`A` が文字コード
4458        // 指定ではなく `u0041`(5文字のリテラル)として誤解釈されていたバグ。
4459        ("/\\u0041/.test('A')", "true"),
4460        ("/\\u{1F600}/u.test('\\u{1F600}')", "true"),
4461        ("/\\x41/.test('A')", "true"),
4462        ("'A1'.match(/\\u0041\\d/)[0]", "A1"),
4463        // 文字クラス内の \uXXXX/\xXX(範囲の端点含む)も同じバグがあった。
4464        ("/[\\u0041-\\u005A]/.test('M')", "true"),
4465        ("/[\\x41-\\x5A]/.test('Z')", "true"),
4466        ("/[\\u0041-\\u005A]/.test('m')", "false"),
4467        // String.prototype.split(regexp) がキャプチャグループを結果に含めないバグ
4468        // (仕様上、区切りに使った正規表現のキャプチャは結果配列に挿入される)。
4469        ("'a1b2c'.split(/(\\d)/).join('|')", "a|1|b|2|c"),
4470        ("'axb'.split(/(x)|(y)/).join(',')", "a,x,,b"),
4471        ("'abc'.split(/b/).join(',')", "a,c"),
4472        // クラス/オブジェクトリテラルの async/generator メソッド短縮記法が丸ごと
4473        // 未認識で、`*`/`async` に遭遇するとキー解析がその場で失敗し、メンバ自体が
4474        // 消えるだけでなく後続メンバの解析まで壊れる重大バグだった。
4475        ("class C { *gen(){ yield 1; yield 2; } } [...new C().gen()].join(',')", "1,2"),
4476        ("await (new (class C { async f(){ return 42; } })()).f()", "42"),
4477        ("class C { *gen(){ yield 'a'; } method(){ return 'm'; } } var c=new C(); [...c.gen()].join(',')+c.method()", "am"),
4478        ("[...{ *gen(){ yield 1; yield 2; } }.gen()].join(',')", "1,2"),
4479        ("await { async f(){ return 'ok'; } }.f()", "ok"),
4480        // static + async/generator の組み合わせ、および算出メソッド名との組み合わせ。
4481        ("await (class C { static async f(){ return 7; } }).f()", "7"),
4482        ("class C { static *gen(){ yield 9; } } [...C.gen()].join(',')", "9"),
4483        ("class C { *[Symbol.iterator](){ yield 3; } } [...new C()].join(',')", "3"),
4484        ("({ async *gen(){}, method(){ return 'ok'; } }).method()", "ok"),
4485        // 分割代入の算出プロパティ名 `{[expr]: target}` が丸ごと未認識で、`[` に遭遇すると
4486        // パターン解析全体が壊れる(後続プロパティも巻き添え)バグだった。
4487        ("var k='x'; var {[k]: v} = {x: 42}; v", "42"),
4488        ("var k='a'; var {[k]: v, y} = {a: 1, y: 2}; v+','+y", "1,2"),
4489        ("var k='n'; function f({[k]: val}){ return val; } f({n: 7})", "7"),
4490        // 直前の async/generator 修飾子検出の実装ミス("async"/"*" を常に修飾子として
4491        // 消費してしまい、"async" という名前の通常プロパティ/フィールド自体が壊れる
4492        // 新規回帰)を発見・即修正。
4493        ("({ async: 1, b: 2 }).async + ({ async: 1, b: 2 }).b", "3"),
4494        ("class C { async = 5; b = 6; } var c = new C(); c.async + c.b", "11"),
4495        // "static" の修飾子誤認識も同じバグ族(`static`/`async`/`*` 全てに共通の
4496        // 「次が `(` でなければ無条件で修飾子」という緩すぎる判定)だった。
4497        ("class C { static = 5; b = 6; } var c = new C(); c.static + c.b", "11"),
4498        ("class C { static x = 1; static method(){ return 2; } } C.x + C.method()", "3"),
4499        // get/set の算出プロパティ名 `get [expr](){}` がアクセサとして認識されず、
4500        // "get"/"set" という名前の孤立フィールド+無関係なメソッドに分解される
4501        // バグだった。
4502        ("var k='x'; var o={ get [k](){ return 42; } }; o.x", "42"),
4503        ("var k='y'; var o={y:0, set [k](v){ this._y=v; } }; o.y=5; o._y", "5"),
4504        ("var k='z'; class C { get [k](){ return 9; } } new C().z", "9"),
4505        // for(let i=...) の反復ごとの束縛(per-iteration binding)が丸ごと未対応で、
4506        // ループ内で作ったクロージャが全て同じ最終値を捕捉してしまうバグだった
4507        // (`var` は対象外で仕様どおり単一束縛のまま)。
4508        ("var a=[]; for(let i=0;i<3;i++){ a.push(()=>i); } a.map(f=>f()).join(',')", "0,1,2"),
4509        ("var a=[]; for(var i=0;i<3;i++){ a.push(()=>i); } a.map(f=>f()).join(',')", "3,3,3"),
4510        ("var a=[]; for(let i=0;i<3;i++){ if(i===1) continue; a.push(()=>i); } a.map(f=>f()).join(',')", "0,2"),
4511        // for-of/for-in の let/const も同じ反復ごとの束縛が必要(var は対象外)。
4512        ("var a=[]; for(const x of [1,2,3]){ a.push(()=>x); } a.map(f=>f()).join(',')", "1,2,3"),
4513        ("var a=[]; for(var x of [1,2,3]){ a.push(()=>x); } a.map(f=>f()).join(',')", "3,3,3"),
4514        ("var a=[]; for(let k in {a:1,b:2}){ a.push(()=>k); } a.map(f=>f()).join(',')", "a,b"),
4515        // 配列プロトタイプメソッドのほぼ全てが使う共通ヘルパ this_items/this_objref が
4516        // Proxy を素通しできず、Proxy でラップした配列へメソッド呼出し
4517        // (`.map()`/`.push()` 等)すると常に空扱いになるバグだった(for...of/スプレッドは
4518        // 別経路で既に対応済みだったが、メソッド呼出し経由は漏れていた)。
4519        ("new Proxy([1,2,3],{}).map(x=>x*2).join(',')", "2,4,6"),
4520        ("var p=new Proxy([1,2],{}); p.push(3); p.join(',')", "1,2,3"),
4521        ("new Proxy([5,6,7],{}).indexOf(6)", "1"),
4522        // `String(proxy)`/文字列連結/テンプレートリテラル補間が使う
4523        // `Value::to_js_string` にも同種の Proxy 素通しバグがあり、`ObjKind::Proxy`
4524        // 専用ケースが無く汎用の "[object Object]" に落ちていた。
4525        ("String(new Proxy([1,2,3], {}))", "1,2,3"),
4526        ("'' + new Proxy([7,8], {})", "7,8"),
4527        // Map/Set の全メソッドが使う with_map/with_set にも同じ Proxy 素通しバグがあった。
4528        ("var p=new Proxy(new Map([['a',1]]),{}); p.get('a')", "1"),
4529        ("var p=new Proxy(new Set([1,2]),{}); p.add(3); [...p].join(',')", "1,2,3"),
4530        // WeakSet / Promise / Generator の共通ヘルパにも同じ Proxy 素通しバグがあった。
4531        ("var o={}; var p=new Proxy(new WeakSet([o]),{}); p.has(o)", "true"),
4532        ("await new Proxy(Promise.resolve(5), {}).then(x=>x*2)", "10"),
4533        ("function* g(){ yield 1; yield 2; } var p=new Proxy(g(),{}); [...p].join(',')", "1,2"),
4534        // String.fromCharCode/fromCodePoint が同じ実装を共有しており、fromCharCode の
4535        // サロゲートペア結合(絵文字1文字を表す ES1 以来の定番イディオム)が壊れていた。
4536        ("String.fromCharCode(72,105)", "Hi"),
4537        ("String.fromCharCode(0xD83D,0xDE00)", "\u{1F600}"),
4538        ("String.fromCodePoint(0x1F600)", "\u{1F600}"),
4539        ("String.fromCodePoint(72,105)", "Hi"),
4540        // Object.create(proto, propertiesObject) の第2引数が丸ごと無視されていたバグ。
4541        ("Object.create(null, {x:{value:5}}).x", "5"),
4542        ("var o=Object.create(null, {y:{value:9}, z:{value:1}}); o.y + o.z", "10"),
4543        // JSON.stringify の toJSON() フックが Date 専用の特別扱いのみで、任意のオブジェクトに
4544        // 定義できる汎用フックとして機能していなかった。
4545        ("JSON.stringify({toJSON(){ return 42; }})", "42"),
4546        ("JSON.stringify({a: {toJSON(){ return 'x'; }}})", "{\"a\":\"x\"}"),
4547        ("JSON.stringify([{toJSON(){ return 1; }}, 2])", "[1,2]"),
4548        // 数値セパレータ `_`(ES2021)が丸ごと未対応で、`1_000` の `_` の時点で数値
4549        // リテラルが打ち切られ、残りが別トークン(識別子)として構文解析を静かに
4550        // 壊すバグだった。
4551        ("1_000_000", "1000000"),
4552        ("0x1_000", "4096"),
4553        ("1_000.5_5", "1000.55"),
4554        // `0o`(8進)/`0b`(2進)リテラルがソースコード上で丸ごと未対応で、`0` だけ
4555        // 数値トークンとして打ち切られ `o17`/`b101` が別の識別子トークンになり
4556        // 構文解析が静かに壊れるバグだった(16進 `0x` は既存で対応済みだったのと非対称)。
4557        ("0o17", "15"),
4558        ("0b101", "5"),
4559        ("0o17 + 0b101", "20"),
4560        ("0b101n + 2n", "7"),
4561        // 文字列リテラル中の行継続(`\` の直後の改行)が仕様上「何も追加しない」はずが、
4562        // 改行文字そのものを値に混入させていたバグ。
4563        ("'abc\\\ndef'", "abcdef"),
4564        ("'abc\\\r\ndef'.length", "6"),
4565        // toReversed — 元配列を変更しない。
4566        ("var a=[1,2,3]; var r=a.toReversed(); a[0]+'/'+r.join(',')", "1/3,2,1"),
4567        // toSpliced — 元配列を変更しない。
4568        ("var a=[1,2,3]; var s=a.toSpliced(1,1,9); a.join(',')+'/'+s.join(',')", "1,2,3/1,9,3"),
4569        // toSpliced — deleteCount 省略時は末尾まで削除するが、明示的な `undefined` は
4570        // 仕様上 `ToIntegerOrInfinity(undefined)` = 0(省略とは異なる)扱いになる。
4571        ("[1,2,3].toSpliced(1).join(',')", "1"),
4572        ("[1,2,3].toSpliced(1, undefined).join(',')", "1,2,3"),
4573        // Array.prototype.concat — `Symbol.isConcatSpreadable` が丸ごと未対応で、
4574        // 常に `ObjKind::Array` かどうかだけで展開の有無を決めていたバグ。
4575        // 配列に `[Symbol.isConcatSpreadable]=false` を明示すると展開されず単一要素になる。
4576        (
4577            "var a=[1,2]; a[Symbol.isConcatSpreadable]=false; [0].concat(a).length",
4578            "2",
4579        ),
4580        // 非配列の array-like に `Symbol.isConcatSpreadable=true` を明示すると
4581        // `length`+添字プロパティから展開される。
4582        (
4583            "var o={0:'x',1:'y',length:2}; o[Symbol.isConcatSpreadable]=true; [0].concat(o).join(',')",
4584            "0,x,y",
4585        ),
4586        // フラグ未指定時は従来どおり配列のみ自動展開、非配列は単一要素のまま。
4587        ("[1].concat([2,3],4).join(',')", "1,2,3,4"),
4588        // `Array.prototype.join` は仕様上ジェネリックメソッド(`this` の `length` +
4589        // 添字プロパティだけを見る)だが、以前は `ObjKind::Array` 以外を無条件で空扱いし
4590        // 配列インスタンスから取り出した `join` を array-like へ `.call()` しても
4591        // 常に `""` になっていた(この処理系はメソッド解決を `ObjKind::Array` の
4592        // プロパティ取得時にのみ行う簡略実装のため、実際に呼び出し可能な関数値を
4593        // 得るには `Array.prototype.join` ではなく実配列インスタンスから取り出す)。
4594        (
4595            "var f=[].join; f.call({0:'a',1:'b',length:2}, '-')",
4596            "a-b",
4597        ),
4598        // `push`/`pop`/`shift`/`unshift` は仕様上ミューテートするメソッドの中でも
4599        // 特にジェネリック(`length` を介して任意の array-like を書き換える)
4600        // ことが要求される定番のイディオム(`Array.prototype.push.call(arguments, x)` 等)。
4601        (
4602            "var o={length:0}; var f=[].push; f.call(o,'a','b')+','+o.length+','+o[0]+','+o[1]",
4603            "2,2,a,b",
4604        ),
4605        (
4606            "var o={0:'a',1:'b',length:2}; var f=[].pop; f.call(o)+','+o.length",
4607            "b,1",
4608        ),
4609        (
4610            "var o={0:'a',1:'b',length:2}; var f=[].shift; f.call(o)+','+o.length+','+o[0]",
4611            "a,1,b",
4612        ),
4613        (
4614            "var o={0:'a',length:1}; var f=[].unshift; f.call(o,'x','y')+','+o.length+','+o[0]+','+o[1]+','+o[2]",
4615            "3,3,x,y,a",
4616        ),
4617        // 通常の配列に対する挙動には回帰なし。
4618        ("[1,2,3].join('-')", "1-2-3"),
4619        // `arguments.callee`(Annex B。無名関数の自己再帰呼び出しイディオム)が
4620        // 丸ごと未対応だった。
4621        (
4622            "var fact = function(n){ return n <= 1 ? 1 : n * arguments.callee(n - 1); }; fact(5)",
4623            "120",
4624        ),
4625        ("var g = function(){ return arguments.callee === g; }; g()", "true"),
4626        // 名前付き関数式の自己参照束縛(`(function f(){ ...f... })()` で `f` を
4627        // 本体内から参照できる仕様どおりの挙動)が丸ごと未対応だった。
4628        ("(function f(n){ return n<=1?1:n*f(n-1); })(5)", "120"),
4629        ("(function f(){ return typeof f; })()", "function"),
4630        // 外側スコープからは関数式自身の名前が見えない(仕様どおり)。
4631        ("(function f(){})(); typeof f", "undefined"),
4632        // `Array.prototype.slice` も同じジェネリックメソッド未対応バグがあった
4633        // (`join`/`concat` と同型。`this_items` が `ObjKind::Array` 以外を無条件で
4634        // 空 `Vec` 扱いしていたため array-like への適用が常に `[]` になっていた)。
4635        (
4636            "var s=[].slice; s.call({0:'a',1:'b',length:2}, 0, 1).join(',')",
4637            "a",
4638        ),
4639        (
4640            "var s=[].slice; s.call({0:'a',1:'b',2:'c',length:3}, 1).join(',')",
4641            "b,c",
4642        ),
4643        // 通常の配列に対する挙動には回帰なし。
4644        ("[1,2,3,4].slice(1,3).join(',')", "2,3"),
4645        // `forEach`/`map`/`filter`/`indexOf`/`includes` にも同型のジェネリックメソッド
4646        // 未対応バグがあった(`join`/`slice`/`concat` で修正済みの続き)。array-like
4647        // (`arguments`/`NodeList` 等への `.call()` の定番イディオム)への適用が
4648        // 「何も反復しない/常に見つからない」結果になっていた。
4649        (
4650            "var out=''; var fe=[].forEach; fe.call({0:'a',1:'b',length:2}, function(v){ out+=v }); out",
4651            "ab",
4652        ),
4653        (
4654            "var m=[].map; m.call({0:1,1:2,length:2}, function(v){ return v*2 }).join(',')",
4655            "2,4",
4656        ),
4657        (
4658            "var fl=[].filter; fl.call({0:1,1:2,2:3,length:3}, function(v){ return v>1 }).join(',')",
4659            "2,3",
4660        ),
4661        (
4662            "var io=[].indexOf; io.call({0:'a',1:'b',length:2}, 'b')",
4663            "1",
4664        ),
4665        (
4666            "var inc=[].includes; inc.call({0:'a',1:'b',length:2}, 'b')",
4667            "true",
4668        ),
4669        // `reduce`/`find`/`findIndex`/`some`/`every` にも同型のジェネリックメソッド
4670        // 未対応バグがあった(`forEach`/`map`/`filter`/`indexOf`/`includes` の続き)。
4671        (
4672            "var r=[].reduce; r.call({0:1,1:2,2:3,length:3}, function(a,v){ return a+v }, 0)",
4673            "6",
4674        ),
4675        (
4676            "var fd=[].find; fd.call({0:1,1:2,2:3,length:3}, function(v){ return v>1 })",
4677            "2",
4678        ),
4679        (
4680            "var fi=[].findIndex; fi.call({0:1,1:2,2:3,length:3}, function(v){ return v>1 })",
4681            "1",
4682        ),
4683        (
4684            "var sm=[].some; sm.call({0:1,1:2,length:2}, function(v){ return v>1 })",
4685            "true",
4686        ),
4687        (
4688            "var ev=[].every; ev.call({0:1,1:2,length:2}, function(v){ return v>0 })",
4689            "true",
4690        ),
4691        (
4692            "var at=[].at; at.call({0:1,1:2,length:2}, -1)",
4693            "2",
4694        ),
4695        (
4696            "var fl=[].findLast; fl.call({0:1,1:2,length:2}, function(v){ return v<2 })",
4697            "1",
4698        ),
4699        (
4700            "var fli=[].findLastIndex; fli.call({0:1,1:2,length:2}, function(v){ return v<2 })",
4701            "0",
4702        ),
4703        (
4704            "var fm=[].flatMap; fm.call({0:1,1:2,length:2}, function(v){ return v*2 }).join(',')",
4705            "2,4",
4706        ),
4707        (
4708            "var rr=[].reduceRight; rr.call({0:1,1:2,length:2}, function(a,v){ return a+v })",
4709            "3",
4710        ),
4711        (
4712            "var lio=[].lastIndexOf; lio.call({0:1,1:1,length:2}, 1)",
4713            "1",
4714        ),
4715        (
4716            "var fla=[].flat; fla.call({0:1,1:[2,3],length:2}).join(',')",
4717            "1,2,3",
4718        ),
4719        (
4720            "var en=[].entries; var itr=en.call({0:1,1:2,length:2}); itr.next().value.join(',')",
4721            "0,1",
4722        ),
4723        (
4724            "var ky=[].keys; var itr=ky.call({0:1,1:2,length:2}); itr.next().value+','+itr.next().value",
4725            "0,1",
4726        ),
4727        (
4728            "var vl=[].values; var itr=vl.call({0:1,1:2,length:2}); itr.next().value+','+itr.next().value",
4729            "1,2",
4730        ),
4731        (
4732            "var tls=[].toLocaleString; tls.call({0:1,1:2,length:2})",
4733            "1,2",
4734        ),
4735        (
4736            "var o={0:1,1:2,length:2}; o[Symbol.isConcatSpreadable]=true; [].concat.call(o, 3).join(',')",
4737            "1,2,3",
4738        ),
4739        (
4740            "var o={0:1,1:2,length:2}; [].concat.call(o, 3).length",
4741            "2",
4742        ),
4743        (
4744            "var ts=[].toSorted; ts.call({0:3,1:1,length:2}).join(',')",
4745            "1,3",
4746        ),
4747        (
4748            "var tr=[].toReversed; tr.call({0:1,1:2,length:2}).join(',')",
4749            "2,1",
4750        ),
4751        (
4752            "var tsp=[].toSpliced; tsp.call({0:1,1:2,length:2}, 1, 0, 9).join(',')",
4753            "1,9,2",
4754        ),
4755        (
4756            "var w=[].with; w.call({0:1,1:2,length:2}, 0, 9).join(',')",
4757            "9,2",
4758        ),
4759        // 通常の配列に対する挙動には回帰なし。
4760        ("[1,2,3].map(function(v){return v*2}).join(',')", "2,4,6"),
4761        // with — 元配列を変更しない。
4762        ("var a=[1,2,3]; var b=a.with(1,9); a[1]+'/'+b[1]", "2/9"),
4763        // TypeError コンストラクタ。
4764        ("(function(){ try { throw new TypeError('bad'); } catch(e) { return e.name+':'+e.message; } })()", "TypeError:bad"),
4765        // RangeError コンストラクタ。
4766        ("(function(){ try { throw new RangeError('oor'); } catch(e) { return e.name; } })()", "RangeError"),
4767        // FinalizationRegistry — register は no-op (object が取れる)。
4768        ("typeof new FinalizationRegistry(function(){}).register", "function"),
4769        // requestIdleCallback — コールバックが呼ばれ、didTimeout が false。
4770        ("var ok=false; requestIdleCallback(function(d){ ok = !d.didTimeout && d.timeRemaining()>0; }); ok", "true"),
4771        // Object.hasOwn — あり/なし。
4772        ("Object.hasOwn({x:1},'x')+','+Object.hasOwn({x:1},'y')", "true,false"),
4773        // Object.groupBy — 偶奇グループ。
4774        ("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"),
4775        // Map.groupBy(ES2024)— Object.groupBy と違いキーを文字列化せず任意の値のまま使える。
4776        ("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"),
4777        (
4778            "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(',')",
4779            "2/1,3",
4780        ),
4781        ("Map.groupBy([1,2,3],function(n){return n}).size", "3"),
4782        // Object.groupBy/Map.groupBy は仕様上任意の iterable を受け付ける(配列限定ではない)。
4783        // 以前は実配列以外は無条件で空扱いになり Set/Map/ジェネレータが常に空グループになるバグだった。
4784        (
4785            "var g=Object.groupBy(new Set([1,2,3,4]),function(n){return n%2===0?'e':'o'}); g.e.join(',')+'/'+g.o.join(',')",
4786            "2,4/1,3",
4787        ),
4788        (
4789            "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(',')",
4790            "2/1,3",
4791        ),
4792        (
4793            "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(',')",
4794            "2,4/1,3",
4795        ),
4796        // Map/Set/WeakMap/WeakSet コンストラクタ・Promise.all 系・Object.fromEntries も
4797        // 同じく `Interp` 不要の純関数 `iterable_values` を使っており、Generator を渡すと
4798        // 常に空になる同型のバグがあった(`it.iter_to_vec` に統一して解消)。
4799        (
4800            "function* gen(){yield [1,'a'];yield [2,'b'];} var m=new Map(gen()); m.get(1)+','+m.get(2)",
4801            "a,b",
4802        ),
4803        (
4804            "function* gen(){yield 1;yield 2;yield 2;} [...new Set(gen())].join(',')",
4805            "1,2",
4806        ),
4807        (
4808            "function* gen(){yield ['a',1];} Object.fromEntries(gen()).a",
4809            "1",
4810        ),
4811        (
4812            "function* gen(){yield Promise.resolve(1);yield Promise.resolve(2);} await Promise.all(gen()).then(a => a.join(','))",
4813            "1,2",
4814        ),
4815        // Uint8Array — 長さ指定・要素代入・length。
4816        ("var a=new Uint8Array(3); a[0]=10; a[1]=20; a.length+','+a[0]+','+a[1]", "3,10,20"),
4817        // Uint8Array — 配列/イテラブルから構築。
4818        ("Array.from(new Uint8Array([1,2,3])).join(',')", "1,2,3"),
4819        // Uint8Array.from — 静的メソッド。
4820        ("Uint8Array.from([4,5,6]).join(',')", "4,5,6"),
4821        // Uint8Array コンストラクタは Generator も受け付けるべき(仕様上どの iterable でも可)。
4822        (
4823            "function* gen(){yield 1;yield 2;yield 3;} new Uint8Array(gen()).join(',')",
4824            "1,2,3",
4825        ),
4826        // AggregateError も同様に Generator を errors として受け付ける。
4827        (
4828            "function* gen(){yield 'a';yield 'b';} new AggregateError(gen(), 'msg').errors.join(',')",
4829            "a,b",
4830        ),
4831        // Uint8Array — 値のラップ(256 は mod 256 で 0、-1 は 255、300 は 44)。
4832        ("var a=new Uint8Array([256,-1,300]); a.join(',')", "0,255,44"),
4833        // TypedArray コンストラクタに負の length を渡すと仕様上 RangeError(`ToIndex` が
4834        // `integer >= 0` を要求)だが、以前は `.max(0.0)` で黙って0にクランプしていた。
4835        (
4836            "try { new Uint8Array(-1); 'no-throw' } catch(e) { 'threw' }",
4837            "threw",
4838        ),
4839        (
4840            "try { new Float64Array(-3); 'no-throw' } catch(e) { 'threw' }",
4841            "threw",
4842        ),
4843        // `typedArray.set(array, offset)`は仕様上`offset + array.length`が
4844        // 対象の長さを超えるとRangeErrorだが、以前は範囲外の書き込みを
4845        // 黙って無視するだけで、`ta.set([1,2,3], hugeOffset)`が例外にならず
4846        // 無害なno-opとして成立してしまっていた(TypedArrayコンストラクタの
4847        // 負lengthバグと同じ「範囲外を黙って受け流す」パターン)。
4848        (
4849            "try { new Uint8Array(3).set([1,2,3], 5); 'no-throw' } catch(e) { 'threw' }",
4850            "threw",
4851        ),
4852        (
4853            "try { new Uint8Array(3).set([1,2], 2); 'no-throw' } catch(e) { 'threw' }",
4854            "threw",
4855        ),
4856        (
4857            // 境界ちょうど(offset + length === target.length)は範囲内なので例外にならない。
4858            "var a=new Uint8Array(3); a.set([9,9], 1); a.join(',')",
4859            "0,9,9",
4860        ),
4861        // Uint8Array.set — offset 付き上書き。
4862        ("var a=new Uint8Array(4); a.set([9,9],1); a.join(',')", "0,9,9,0"),
4863        // Uint8Array.set — 仕様上 array-like(非配列プレーンオブジェクト)も受け付けるべき。
4864        (
4865            "var a=new Uint8Array(3); a.set({0:5,1:6,length:2}); a.join(',')",
4866            "5,6,0",
4867        ),
4868        // Uint8Array.slice — 独立コピーを返す。
4869        ("var a=new Uint8Array([1,2,3,4,5]); var s=a.slice(1,3); s.join(',')+'/'+a.length", "2,3/5"),
4870        // Uint8Array.fill — 破壊的に埋めて this を返す。
4871        ("var a=new Uint8Array(4); a.fill(7,1,3); a.join(',')", "0,7,7,0"),
4872        // Uint8Array — byteLength / BYTES_PER_ELEMENT / buffer.byteLength。
4873        ("var a=new Uint8Array(5); a.byteLength+','+a.BYTES_PER_ELEMENT+','+a.buffer.byteLength", "5,1,5"),
4874        // ArrayBuffer — byteLength と、そこから Uint8Array を構築。
4875        ("var b=new ArrayBuffer(8); new Uint8Array(b).length+','+b.byteLength", "8,8"),
4876        // ArrayBuffer.prototype.resize/transfer/resizable/maxByteLength(ES2024)。
4877        // 以前は丸ごと未対応で、非 resizable なバッファしか作れなかった。
4878        (
4879            "var b=new ArrayBuffer(4,{maxByteLength:16}); b.resizable+','+b.maxByteLength+','+b.byteLength",
4880            "true,16,4",
4881        ),
4882        ("var b=new ArrayBuffer(4); b.resizable+','+b.maxByteLength", "false,4"),
4883        // resize は byteLength をその場で伸縮させる(新規領域はゼロ埋め)。
4884        (
4885            "var b=new ArrayBuffer(4,{maxByteLength:16}); b.resize(8); var u=new Uint8Array(b); b.byteLength+','+u.length",
4886            "8,8",
4887        ),
4888        // maxByteLength を超える resize は RangeError 相当の例外。
4889        (
4890            "var b=new ArrayBuffer(4,{maxByteLength:8}); try { b.resize(9); 'no throw'; } catch(e) { 'threw'; }",
4891            "threw",
4892        ),
4893        // 非 resizable なバッファへの resize は TypeError 相当の例外。
4894        (
4895            "var b=new ArrayBuffer(4); try { b.resize(2); 'no throw'; } catch(e) { 'threw'; }",
4896            "threw",
4897        ),
4898        // transfer は同じ byteLength の新バッファを返し、元のバッファを detach する(byteLength→0)。
4899        // 注: この処理系の ArrayBuffer/TypedArray は実メモリを共有しない独立コピー簡易実装
4900        // (DataView 等と同じ既存の割り切り)のため、TypedArray 経由で書いた値の引き継ぎまでは検証しない。
4901        (
4902            "var b=new ArrayBuffer(4); var b2=b.transfer(); b.byteLength+','+b2.byteLength",
4903            "0,4",
4904        ),
4905        // detach 後の再 transfer/resize は例外。
4906        (
4907            "var b=new ArrayBuffer(4); b.transfer(); try { b.transfer(); 'no throw'; } catch(e) { 'threw'; }",
4908            "threw",
4909        ),
4910        // transferToFixedLength は常に非 resizable な結果を返す。
4911        (
4912            "var b=new ArrayBuffer(4,{maxByteLength:8}); var b2=b.transferToFixedLength(); b2.resizable",
4913            "false",
4914        ),
4915        // `structuredClone(resizableArrayBuffer)`が`resizable`/`maxByteLength`
4916        // を引き継がず非resizableな複製に化けていたバグ(丸ごと未対応
4917        // だった。2026-07-17 発見・実装)。
4918        (
4919            "var b=new ArrayBuffer(4,{maxByteLength:16}); var c=structuredClone(b); \
4920             c.resizable+','+c.maxByteLength+','+c.byteLength",
4921            "true,16,4",
4922        ),
4923        (
4924            "var b=new ArrayBuffer(4,{maxByteLength:16}); var c=structuredClone(b); \
4925             c.resize(8); c.byteLength",
4926            "8",
4927        ),
4928        // Uint8Array.prototype.toBase64/toHex + 静的 fromBase64/fromHex(ES2024/2025)。
4929        ("new Uint8Array([72,101,108,108,111]).toBase64()", "SGVsbG8="),
4930        ("Array.from(Uint8Array.fromBase64('SGVsbG8=')).join(',')", "72,101,108,108,111"),
4931        ("new Uint8Array([222,173,190,239]).toHex()", "deadbeef"),
4932        ("Array.from(Uint8Array.fromHex('deadbeef')).join(',')", "222,173,190,239"),
4933        // 往復(バイト列 -> base64/hex -> バイト列)が元に戻ることを確認。
4934        (
4935            "var a=new Uint8Array([1,2,3,255,0]); Array.from(Uint8Array.fromBase64(a.toBase64())).join(',')",
4936            "1,2,3,255,0",
4937        ),
4938        (
4939            "var a=new Uint8Array([1,2,3,255,0]); Array.from(Uint8Array.fromHex(a.toHex())).join(',')",
4940            "1,2,3,255,0",
4941        ),
4942        // fromHex: 不正な16進文字列(奇数長)は例外を投げる。
4943        (
4944            "try { Uint8Array.fromHex('abc'); 'no-throw' } catch(e) { 'caught:' + e.name }",
4945            "caught:SyntaxError",
4946        ),
4947        // setFromBase64/setFromHex(ES2024/2025)— 既存バッファへインプレース書き込み。
4948        (
4949            "var a=new Uint8Array(5); var r=a.setFromBase64('SGVsbG8='); Array.from(a).join(',')+'|'+r.read+'|'+r.written",
4950            "72,101,108,108,111|8|5",
4951        ),
4952        (
4953            "var a=new Uint8Array(4); var r=a.setFromHex('deadbeef'); Array.from(a).join(',')+'|'+r.read+'|'+r.written",
4954            "222,173,190,239|8|4",
4955        ),
4956        (
4957            "try { new Uint8Array(1).setFromHex('zz'); 'no-throw' } catch(e) { 'caught:' + e.name }",
4958            "caught:SyntaxError",
4959        ),
4960        // TextEncoder — Uint8Array を返し、UTF-8 バイト値が正しい。
4961        ("var e=new TextEncoder().encode('AB'); e.length+','+e[0]+','+e[1]", "2,65,66"),
4962        // TextEncoder→TextDecoder 往復。
4963        ("new TextDecoder().decode(new TextEncoder().encode('hello'))", "hello"),
4964        // `TextEncoder.prototype.encodeInto(source, destination)` が丸ごと
4965        // 未対応だった(`encode()` は既存だが、この性能向けバリアントが欠けていた)。
4966        (
4967            "var buf=new Uint8Array(5); var r=new TextEncoder().encodeInto('Hi', buf); \
4968             r.read+','+r.written+','+buf[0]+','+buf[1]",
4969            "2,2,72,105",
4970        ),
4971        // 宛先バッファが足りない場合はコードポイント境界で打ち切る(マルチバイト
4972        // 文字を途中で分割しない)。
4973        (
4974            "var buf=new Uint8Array(1); var r=new TextEncoder().encodeInto('AB', buf); \
4975             r.read+','+r.written",
4976            "1,1",
4977        ),
4978        // Int8Array — 符号付きラップ(200 は -56、-100 はそのまま)。
4979        ("var a=new Int8Array([200,-100]); a.join(',')", "-56,-100"),
4980        // Uint8ClampedArray — 飽和(クランプ、ラップではない)。
4981        ("var a=new Uint8ClampedArray([300,-50,128.6]); a.join(',')", "255,0,129"),
4982        // ToUint8Clamp は同点を偶数丸め(banker's rounding)する仕様
4983        // (Math.round の四捨五入とは異なる)。
4984        ("var a=new Uint8ClampedArray([0.5,1.5,2.5,3.5]); a.join(',')", "0,2,2,4"),
4985        // Int16Array / Uint16Array — 幅16bitのラップ。
4986        ("var a=new Int16Array([40000]); a[0]", "-25536"),
4987        // 索引代入 `ta[i]=v` はコンストラクタ/`set`/`fill` と違って型変換
4988        // (ラップ/クランプ)が一切効かず生の数値がそのまま入るバグだった。
4989        ("var a=new Int8Array(1); a[0]=200; a[0]", "-56"),
4990        ("var a=new Uint8ClampedArray(1); a[0]=300; a[0]", "255"),
4991        ("var a=new Uint8ClampedArray(1); a[0]=-50; a[0]", "0"),
4992        ("var a=new Int16Array(1); a[0]=40000; a[0]", "-25536"),
4993        ("var a=new Uint16Array([70000]); a[0]", "4464"),
4994        // Int32Array / Uint32Array — 幅32bitのラップ。
4995        ("var a=new Int32Array([4294967295]); a[0]", "-1"),
4996        ("var a=new Uint32Array([-1]); a[0]", "4294967295"),
4997        // Float32Array — 単精度への丸め込み(倍精度のままではない)。
4998        ("var a=new Float32Array([0.1]); a[0] !== 0.1", "true"),
4999        // Float16Array / DataView getFloat16 / setFloat16 (ES2025)。
5000        ("var a=new Float16Array([1.5]); a[0] === 1.5", "true"),
5001        ("var dv=new DataView(new ArrayBuffer(2)); dv.setFloat16(0, 2.5); dv.getFloat16(0)", "2.5"),
5002        // Float64Array — 倍精度はそのまま保持。
5003        ("var a=new Float64Array([0.1]); a[0] === 0.1", "true"),
5004        // 各 TypedArray の BYTES_PER_ELEMENT。
5005        ("new Int16Array(1).BYTES_PER_ELEMENT+','+new Float64Array(1).BYTES_PER_ELEMENT", "2,8"),
5006        // TypedArray.from(静的)と set/slice/fill の型別クロスチェック。
5007        ("Int32Array.from([1,2,3]).join(',')", "1,2,3"),
5008        ("var a=new Uint16Array(3); a.set([1,2],1); a.join(',')", "0,1,2"),
5009        ("var a=new Int8Array([1,2,3,4]); a.slice(1,3).join(',')", "2,3"),
5010        ("var a=new Uint32Array(3); a.fill(5); a.join(',')", "5,5,5"),
5011        // `toSorted`/`toReversed`/`toSpliced`/`with`(ES2023)が TypedArray に対しては
5012        // 常に普通の配列を返すバグだった(`structuredClone(TypedArray)` と同種)。
5013        // 型変換(ラップ)も維持されることを確認する。
5014        (
5015            "var a=new Int8Array([200,3,1]); var b=a.toSorted(); b.join(',')+','+b.BYTES_PER_ELEMENT",
5016            "-56,1,3,1",
5017        ),
5018        (
5019            "var a=new Int8Array([1,2,200]); var b=a.toReversed(); b.join(',')+','+b.BYTES_PER_ELEMENT",
5020            "-56,2,1,1",
5021        ),
5022        (
5023            "var a=new Int8Array([1,2,3]); var b=a.with(1,200); b.join(',')+','+b.BYTES_PER_ELEMENT",
5024            "1,-56,3,1",
5025        ),
5026        // `map`/`filter` も同じ理由で TypedArray に対しては常に普通の配列を返す
5027        // バグだった。callback の戻り値が有効域外でも型ごとの変換(ラップ)が働く。
5028        (
5029            "var a=new Int8Array([1,2,3]); var b=a.map(x => x + 200); b.join(',')+','+b.BYTES_PER_ELEMENT",
5030            "-55,-54,-53,1",
5031        ),
5032        (
5033            "var a=new Int8Array([1,2,3,4]); var b=a.filter(x => x % 2 === 0); b.join(',')+','+b.BYTES_PER_ELEMENT",
5034            "2,4,1",
5035        ),
5036        // DataView(ES2015)が丸ごと未対応で `new DataView(buf)` が
5037        // `DataView is not defined` になっていた。
5038        (
5039            "var dv=new DataView(new ArrayBuffer(4)); dv.setUint8(0,255); dv.getUint8(0)",
5040            "255",
5041        ),
5042        // デフォルトはビッグエンディアン。
5043        (
5044            "var dv=new DataView(new ArrayBuffer(4)); dv.setUint32(0,0x01020304); dv.getUint8(0)+','+dv.getUint8(3)",
5045            "1,4",
5046        ),
5047        // littleEndian=true を渡すとバイト順が反転する。
5048        (
5049            "var dv=new DataView(new ArrayBuffer(4)); dv.setUint32(0,0x01020304,true); dv.getUint8(0)+','+dv.getUint8(3)",
5050            "4,1",
5051        ),
5052        // 符号付き/符号無しの往復(16bit)。
5053        ("var dv=new DataView(new ArrayBuffer(2)); dv.setInt16(0,-1); dv.getUint16(0)", "65535"),
5054        // Float64 の往復(丸めなし)。
5055        ("var dv=new DataView(new ArrayBuffer(8)); dv.setFloat64(0,3.5); dv.getFloat64(0)", "3.5"),
5056        // 範囲外アクセスは例外。
5057        (
5058            "try { new DataView(new ArrayBuffer(1)).getUint32(0); 'no throw'; } catch(e) { 'threw'; }",
5059            "threw",
5060        ),
5061        // Blob(File API の基礎コンテナ)が丸ごと未対応で `new Blob([...])` が
5062        // `Blob is not defined` になっていた。
5063        ("new Blob(['hello', ' ', 'world']).size", "11"),
5064        ("new Blob(['abc'], {type:'text/plain'}).type", "text/plain"),
5065        ("await new Blob(['hello world']).text()", "hello world"),
5066        ("await new Blob(['hello world']).slice(0,5).text()", "hello"),
5067        // `Blob.arrayBuffer()` が返す ArrayBuffer は実バイト列を持ち、そこから作った
5068        // DataView で正しく読み戻せる(`Blob`→`ArrayBuffer`→`DataView` の実用連携)。
5069        (
5070            "var buf = await new Blob(['A']).arrayBuffer(); new DataView(buf).getUint8(0)",
5071            "65",
5072        ),
5073        // `Blob.prototype.bytes()`(ES2024。丸ごと未対応だった。`await blob.
5074        // arrayBuffer()`→`new Uint8Array(...)` の2手間を1メソッドで済ませる。
5075        // 2026-07-15 発見・実装)。
5076        // 注: `instanceof Uint8Array` では検証しない — 調査の結果、この処理系の
5077        // TypedArray コンストラクタ群は `.prototype` を一切持たず `instanceof` が
5078        // 常に `false` を返す既存の別バグと判明(`Array`/`Map` 等 TypedArray 以外の
5079        // 組み込み型も含め instanceof 自体の自己テストがコードベースに1件も無く、
5080        // 広範な既知の未検証領域)。`BYTES_PER_ELEMENT`(Uint8Array 固有の値)で
5081        // 型を検証し、この既存バグを新規追跡項目として TODO.md へ記録した。
5082        (
5083            "var u = await new Blob(['AB']).bytes(); u.BYTES_PER_ELEMENT",
5084            "1",
5085        ),
5086        ("var u = await new Blob(['AB']).bytes(); u[0]+','+u[1]", "65,66"),
5087        // `File`(`Blob` を継承し `name`/`lastModified` を追加する File API の
5088        // 基礎コンストラクタ)が丸ごと未対応で `File is not defined` になっていた。
5089        ("new File(['hi'], 'a.txt').name", "a.txt"),
5090        ("new File(['hi'], 'a.txt', {type:'text/plain'}).type", "text/plain"),
5091        ("new File(['hello'], 'a.txt').size", "5"),
5092        ("await new File(['hello'], 'a.txt').text()", "hello"),
5093        // `Blob` 由来のメソッド(`slice`)もそのまま継承する。
5094        ("await new File(['hello world'], 'a.txt').slice(0,5).text()", "hello"),
5095        // `lastModified` は明示指定を尊重する。
5096        ("new File(['x'], 'a.txt', {lastModified: 12345}).lastModified", "12345"),
5097        // `File.webkitRelativePath`(丸ごと未対応だった。実際のディレクトリ
5098        // 選択UIが無いため常に空文字列の誠実な簡略実装。2026-07-17 発見・
5099        // 実装)。
5100        ("new File(['x'], 'a.txt').webkitRelativePath", ""),
5101        // `structuredClone(File)`が`Blob`分岐に落ちて`name`/`lastModified`
5102        // が失われ、素の`Blob`へ格下げされていたバグ(丸ごと未対応
5103        // だった。2026-07-17 発見・実装)。
5104        (
5105            "var c=structuredClone(new File(['hi'], 'a.txt', {lastModified: 12345})); \
5106             c.name + ',' + c.lastModified",
5107            "a.txt,12345",
5108        ),
5109        (
5110            "await structuredClone(new File(['hello'], 'a.txt')).text()",
5111            "hello",
5112        ),
5113        // 素の`Blob`(`name`無し)は引き続き`name`を持たない`Blob`のまま
5114        // 複製される(回帰確認)。
5115        ("structuredClone(new Blob(['x'])).name", "undefined"),
5116        // `FileReader` が丸ごと未対応だった(`blob.text()` 等の Promise 版は
5117        // 既に対応済みだったが、古典的なイベントベースの `readAsText`+`onload`
5118        // という書き方自体が丸ごと存在しなかった)。
5119        (
5120            "var r=new FileReader(); var out; r.onload=function(e){ out=e.target.result; }; \
5121             r.readAsText(new Blob(['hello'])); out",
5122            "hello",
5123        ),
5124        (
5125            "var r=new FileReader(); r.readAsText(new Blob(['x'])); r.readyState",
5126            "2",
5127        ),
5128        (
5129            "var r=new FileReader(); var buf; r.onload=function(e){ buf=e.target.result; }; \
5130             r.readAsArrayBuffer(new Blob(['A'])); new DataView(buf).getUint8(0)",
5131            "65",
5132        ),
5133        (
5134            "var r=new FileReader(); var url; r.onload=function(e){ url=e.target.result; }; \
5135             r.readAsDataURL(new Blob(['hi'], {type:'text/plain'})); url",
5136            "data:text/plain;base64,aGk=",
5137        ),
5138        // `addEventListener('load', ...)` 経由でも `onload` と同じく発火する
5139        // (`on<type>` 単一リスナ方式を共有しているため)。
5140        (
5141            "var r=new FileReader(); var fired=false; r.addEventListener('load', ()=>fired=true); \
5142             r.readAsText(new Blob(['x'])); fired",
5143            "true",
5144        ),
5145        // `Notification`(丸ごと未対応だった。通知 UI 自体が無いこの OS では
5146        // 実際の表示は行わず JS 側の契約のみ満たす簡略実装)。
5147        ("Notification.permission", "granted"),
5148        ("await Notification.requestPermission()", "granted"),
5149        (
5150            "var n = new Notification('hi', {body: 'world'}); n.title + ':' + n.body",
5151            "hi:world",
5152        ),
5153        ("typeof new Notification('x').close", "function"),
5154        // `data`/`requireInteraction`/`silent`/`dir`/`lang`(丸ごと未対応
5155        // だった。`title`/`body`/`icon`/`tag`は既に対応済みだったが、同じ
5156        // コンストラクタoptionsの残りが漏れていた。2026-07-17 発見・実装)。
5157        (
5158            "var n = new Notification('x', {data: {id: 5}, requireInteraction: true, silent: true}); \
5159             n.data.id + ',' + n.requireInteraction + ',' + n.silent",
5160            "5,true,true",
5161        ),
5162        ("new Notification('x').dir", "auto"),
5163        ("new Notification('x').data", "null"),
5164        ("typeof new Notification('x').removeEventListener", "function"),
5165        // Promise.withResolvers(ES2024)
5166        (
5167            "var {promise, resolve} = Promise.withResolvers(); resolve(5); await promise",
5168            "5",
5169        ),
5170        (
5171            "var {promise, reject} = Promise.withResolvers(); reject('e'); await promise.catch(e => 'caught:' + e)",
5172            "caught:e",
5173        ),
5174        // Promise.try(ES2025): 同期の戻り値/例外/Promise 戻り値をすべて統一的に扱う
5175        ("await Promise.try(() => 42)", "42"),
5176        (
5177            "await Promise.try(() => { throw 'boom' }).catch(e => 'caught:' + e)",
5178            "caught:boom",
5179        ),
5180        ("await Promise.try(() => Promise.resolve('ok'))", "ok"),
5181        ("await Promise.try((a, b) => a + b, 1, 2)", "3"),
5182        // String.prototype.isWellFormed / toWellFormed(ES2024)
5183        ("'abc'.isWellFormed()", "true"),
5184        ("'abc'.toWellFormed()", "abc"),
5185        // using 宣言(Explicit Resource Management、ES2023→ES2026ベースライン)
5186        (
5187            "var log=''; { using r = {[Symbol.dispose]: () => log+='d'}; log+='a' } log+='b'; log",
5188            "adb",
5189        ),
5190        // 複数 using は宣言の逆順で dispose される(LIFO)。
5191        (
5192            "var log=''; { using a = {[Symbol.dispose]: () => log+='A'}; using b = {[Symbol.dispose]: () => log+='B'} } log",
5193            "BA",
5194        ),
5195        // return で早期脱出しても dispose は呼ばれる(戻り値確定後に外側の log へ副作用)。
5196        (
5197            "var log=''; function f(){ { using r = {[Symbol.dispose]: () => log+='d'}; return 'a' } } var res=f(); log+res",
5198            "da",
5199        ),
5200        // await using は Symbol.asyncDispose の戻り値を await する。
5201        (
5202            "async function f(){ var log=''; { await using r = {[Symbol.asyncDispose]: async () => { log+='d' }}; log+='a' } return log } await f()",
5203            "ad",
5204        ),
5205        // Array.fromAsync(ES2024/2025): 同期イテラブル + 各要素の Promise を await。
5206        (
5207            "(await Array.fromAsync([Promise.resolve(1), 2, Promise.resolve(3)])).join(',')",
5208            "1,2,3",
5209        ),
5210        // mapFn 付き。
5211        ("(await Array.fromAsync([1,2,3], x => x * 10)).join(',')", "10,20,30"),
5212        // 複数 using の dispose がいずれも例外を投げると SuppressedError へ集約される
5213        // (LIFO: 後から宣言した b が先に dispose される → error=A(最後に投げた), suppressed=B)。
5214        (
5215            "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",
5216            "SuppressedError:A:B",
5217        ),
5218        // for await...of: 同期イテラブル + 各要素の Promise を await するフォールバック。
5219        (
5220            "async function f(){ var out=''; for await (const x of [Promise.resolve(1), 2, Promise.resolve(3)]) { out += x } return out } await f()",
5221            "123",
5222        ),
5223        // for await...of: Symbol.asyncIterator を実装した独自オブジェクトを手動駆動。
5224        (
5225            "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()",
5226            "123",
5227        ),
5228        // async generator インスタンスは Symbol.asyncIterator を公開すべき
5229        // (Generator.prototype[Symbol.asyncIterator] は自分自身を返す仕様)。
5230        // 以前は generator_method に asyncIterator/iterator の登録が無く、for await が
5231        // 常に eager-drain フォールバックへ落ちていた(値は一致するが例外伝播が違った)。
5232        (
5233            "async function* g(){ yield 1; yield 2; yield 3; } typeof g()[Symbol.asyncIterator]",
5234            "function",
5235        ),
5236        (
5237            "async function* g(){ yield 1; yield 2; yield 3; } var it=g(); it[Symbol.asyncIterator]() === it",
5238            "true",
5239        ),
5240        (
5241            "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()",
5242            "123",
5243        ),
5244        // 通常の(非 async)generator は Symbol.asyncIterator を公開してはいけない
5245        // (for await のフォールバックが正しく同期経路を通ることの確認)。
5246        (
5247            "function* g(){ yield 1; } typeof g()[Symbol.asyncIterator]",
5248            "undefined",
5249        ),
5250        // async generator が途中で例外を投げた場合、for await はそれを正しく伝播する
5251        // (eager-drain フォールバックはこれを黙って握り潰していた)。
5252        (
5253            "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()",
5254            "1:boom",
5255        ),
5256        // 非宣言形式の for-of(x は既存変数): 配列を反復。
5257        ("var x; var out=''; for (x of [1,2,3]) { out += x } out", "123"),
5258        // 非宣言形式は既存の外側変数へ代入する(ループ後もその値が残る)。
5259        ("var x=0; for (x of [1,2,3]) {} x", "3"),
5260        // 非宣言形式の for-in(k は既存変数)。
5261        ("var k; var out=''; for (k in {a:1,b:2}) { out += k } out", "ab"),
5262        // 非宣言形式 + 配列分割代入 for-of。
5263        (
5264            "var a,b; var out=''; for ([a,b] of [[1,2],[3,4]]) { out += a+','+b+';' } out",
5265            "1,2;3,4;",
5266        ),
5267        // 非宣言形式 + オブジェクト分割代入 for-of。
5268        (
5269            "var a,b; var out=''; for ({a,b} of [{a:1,b:2},{a:3,b:4}]) { out += a+','+b+';' } out",
5270            "1,2;3,4;",
5271        ),
5272        // 非宣言形式 + メンバー式 for-of(obj.prop)。
5273        ("var obj={p:0}; for (obj.p of [1,2,3]) {} obj.p", "3"),
5274        // 非宣言形式 + 添字式 for-of(arr[0])。
5275        ("var arr=[0]; for (arr[0] of [7,8,9]) {} arr[0]", "9"),
5276        // New Set Methods(ES2024/2025)。仕様上第一引数は `Set` に限らず任意の
5277        // set-like(iterable)を受け付けるべきだが、以前は `&mut Interp` を持たない
5278        // `iterable_values` にフォールバックしており Generator を渡すと常に空扱いに
5279        // なるバグだった(`iterable_values`/`this_items` 系の横展開監査で見落とし)。
5280        (
5281            "function* g(){yield 3;yield 4;} [...new Set([1,2,3]).union(g())].sort().join(',')",
5282            "1,2,3,4",
5283        ),
5284        (
5285            "function* g(){yield 2;yield 3;} [...new Set([1,2,3]).intersection(g())].sort().join(',')",
5286            "2,3",
5287        ),
5288        ("[...new Set([1,2,3]).union(new Set([3,4,5]))].sort().join(',')", "1,2,3,4,5"),
5289        ("[...new Set([1,2,3]).intersection(new Set([2,3,4]))].sort().join(',')", "2,3"),
5290        ("[...new Set([1,2,3]).difference(new Set([2,3]))].join(',')", "1"),
5291        (
5292            "[...new Set([1,2,3]).symmetricDifference(new Set([2,3,4]))].sort().join(',')",
5293            "1,4",
5294        ),
5295        ("new Set([1,2]).isSubsetOf(new Set([1,2,3]))", "true"),
5296        ("new Set([1,2,3]).isSupersetOf(new Set([1,2]))", "true"),
5297        ("new Set([1,2]).isDisjointFrom(new Set([3,4]))", "true"),
5298        ("new Set([1,2]).isDisjointFrom(new Set([2,3]))", "false"),
5299        // Iterator helpers(ES2025): generator に対する map/filter/take/drop/flatMap/toArray/
5300        // forEach/some/every/find/reduce。無限イテレータには非対応の簡略実装(要素を全展開してから処理)。
5301        ("function* g(){ yield 1; yield 2; yield 3; } g().map(x=>x*2).join(',')", "2,4,6"),
5302        (
5303            "function* g(){ yield 1; yield 2; yield 3; yield 4; } g().filter(x=>x%2===0).join(',')",
5304            "2,4",
5305        ),
5306        (
5307            "function* g(){ yield 1; yield 2; yield 3; yield 4; yield 5; } g().take(2).join(',')",
5308            "1,2",
5309        ),
5310        (
5311            "function* g(){ yield 1; yield 2; yield 3; yield 4; yield 5; } g().drop(3).join(',')",
5312            "4,5",
5313        ),
5314        (
5315            "function* g(){ yield 1; yield 2; } g().flatMap(x=>[x,x*10]).join(',')",
5316            "1,10,2,20",
5317        ),
5318        ("function* g(){ yield 1; yield 2; } g().toArray().join(',')", "1,2"),
5319        (
5320            "function* g(){ yield 1; yield 2; yield 3; } var s=0; g().forEach(x=>s+=x); s",
5321            "6",
5322        ),
5323        ("function* g(){ yield 1; yield 2; yield 3; } g().some(x=>x>2)", "true"),
5324        ("function* g(){ yield 1; yield 2; yield 3; } g().every(x=>x>0)", "true"),
5325        ("function* g(){ yield 1; yield 2; yield 3; } g().find(x=>x>1)", "2"),
5326        (
5327            "function* g(){ yield 1; yield 2; yield 3; } g().reduce((a,x)=>a+x,0)",
5328            "6",
5329        ),
5330        // Iterator.from(ES2025): 任意のイテラブル(配列/Set/Generator)を Iterator helpers
5331        // 一式にアクセスできる形でラップする。map/filter 等は Array.prototype 経由、
5332        // take/drop/toArray は Iterator helpers 経由(Array に無いメソッドのため)。
5333        ("Iterator.from([1,2,3]).map(x=>x*2).join(',')", "2,4,6"),
5334        ("Iterator.from([1,2,3,4,5]).take(2).join(',')", "1,2"),
5335        ("Iterator.from([1,2,3,4,5]).drop(3).join(',')", "4,5"),
5336        ("Iterator.from(new Set([1,2,3])).toArray().join(',')", "1,2,3"),
5337        (
5338            "function* g(){ yield 1; yield 2; yield 3; } Iterator.from(g()).take(2).join(',')",
5339            "1,2",
5340        ),
5341        // Iterator.concat(ES2025 Standard): 複数のイテラブル(配列、Set、Generator等)をシームレスに結合する。
5342        ("Iterator.concat([1,2], new Set([3,4]), [5]).toArray().join(',')", "1,2,3,4,5"),
5343        ("Iterator.concat(['a','b'], ['c']).take(2).toArray().join(',')", "a,b"),
5344        // `instanceof`(ES1 以来の基礎演算子)が `BinaryOp::InstanceOf => Value::Bool(false)`
5345        // という未実装のプレースホルダのまま放置され、常に false を返す重大バグだった。
5346        // OrdinaryHasInstance 相当(`r.prototype` を起点に `l` のプロトタイプ連鎖を辿る)を実装。
5347        ("function Foo(){} new Foo() instanceof Foo", "true"),
5348        ("function Foo(){} function Bar(){} new Foo() instanceof Bar", "false"),
5349        ("class Animal{} class Dog extends Animal{} new Dog() instanceof Animal", "true"),
5350        ("class Animal{} class Dog extends Animal{} new Dog() instanceof Dog", "true"),
5351        ("1 instanceof Object", "false"),
5352        // 組み込みの `Object`/`Array` コンストラクタは(他のビルトインメソッド解決と同様に
5353        // ObjKind による特殊扱いで実装されており)実際の `.prototype` オブジェクトを
5354        // 持たないため、リテラルとの `instanceof` は常に false になる簡略実装の既知の限界。
5355        // ユーザー定義の function/class コンストラクタ(実際に `.prototype` を持つ)が
5356        // 主要なユースケースであり、そちらは上のテストの通り正しく動作する。
5357        ("({}) instanceof Object", "false"),
5358        ("[] instanceof Array", "false"),
5359        // グローバルな `Error` コンストラクタ自体が長期間欠落しており(TypeError/RangeError/
5360        // SyntaxError というサブタイプだけが存在し基底の `Error` が無い状態だった)、
5361        // `new Error("msg")` は「Error is not defined」で ReferenceError になっていた。
5362        // 合わせて各サブタイプの `.prototype` を `Error.prototype` を proto に持つ形へ
5363        // 再構成し、`instanceof` の実装と対で `new TypeError() instanceof Error` 等が
5364        // 正しく true になるようにした。
5365        ("new Error('boom').message", "boom"),
5366        ("new Error('boom').name", "Error"),
5367        ("new Error('boom') instanceof Error", "true"),
5368        // `Error.prototype.stack`(丸ごと未対応だった)。実際のコールフレーム
5369        // 一覧は再現できないため、V8 の1行目と同じ `"name: message"` 形式の
5370        // 簡略実装(詳細は `builtins::build_error_stack` 参照)。
5371        ("new Error('boom').stack", "Error: boom"),
5372        ("new TypeError('x').stack", "TypeError: x"),
5373        ("typeof new Error().stack === 'string' && new Error().stack.length > 0", "true"),
5374        (
5375            "(function(){ try { null.foo; } catch(e) { return typeof e.stack; } })()",
5376            "string",
5377        ),
5378        ("new TypeError('x') instanceof Error", "true"),
5379        ("new TypeError('x') instanceof TypeError", "true"),
5380        ("new RangeError('x') instanceof TypeError", "false"),
5381        ("new Uint8Array(1) instanceof Uint8Array", "true"),
5382        ("new Int8Array(1) instanceof Int8Array", "true"),
5383        ("new Uint8Array(1) instanceof Int8Array", "false"),
5384        ("Object.getPrototypeOf(new Uint8Array(1)) === Uint8Array.prototype", "true"),
5385        ("Uint8Array.prototype.constructor === Uint8Array", "true"),
5386        (
5387            "try { null.x } catch(e) { e instanceof Error }",
5388            "true",
5389        ),
5390        // ユーザー定義のカスタムエラークラス(`class MyError extends Error`)は、`super(msg)`
5391        // が到達するネイティブ `Error` コンストラクタが `this`(サブクラスのインスタンス。
5392        // proto は MyError.prototype → Error.prototype と連鎖)へ直接 message を書き込む
5393        // ことに依存しており、今回の一連の修正で自然に動くようになった実用パターン。
5394        (
5395            "class MyError extends Error { constructor(m){ super(m); this.name='MyError'; } } var e = new MyError('bad'); e.message + '/' + e.name",
5396            "bad/MyError",
5397        ),
5398        (
5399            "class MyError extends Error { constructor(m){ super(m); } } new MyError('x') instanceof Error",
5400            "true",
5401        ),
5402        (
5403            "class MyError extends Error { constructor(m){ super(m); } } new MyError('x') instanceof MyError",
5404            "true",
5405        ),
5406        // **重要**: `Symbol.hasInstance`(ES2015。`class Foo { static
5407        // [Symbol.hasInstance](x){...} }` によるカスタム `instanceof` 判定)が丸ごと
5408        // 未対応で、常にプロトタイプ連鎖の照合に決め打ちされていた。
5409        (
5410            "class Even { static [Symbol.hasInstance](n){ return typeof n === 'number' && n % 2 === 0; } } (4 instanceof Even) + ',' + (3 instanceof Even)",
5411            "true,false",
5412        ),
5413        (
5414            "class AlwaysTrue { static [Symbol.hasInstance](x){ return true; } } ({} instanceof AlwaysTrue)",
5415            "true",
5416        ),
5417        // `Error.isError(value)`(ES2025)が丸ごと欠落していた。`instanceof Error` と違い
5418        // `Symbol.hasInstance`/`Error.prototype` の上書きに影響されず組込み proto を直接見る。
5419        ("Error.isError(new Error('x'))", "true"),
5420        ("Error.isError(new TypeError('x'))", "true"),
5421        (
5422            "class MyError extends Error { constructor(m){ super(m); } } Error.isError(new MyError('x'))",
5423            "true",
5424        ),
5425        ("Error.isError({message:'x'})", "false"),
5426        ("Error.isError(42)", "false"),
5427        ("Error.isError(undefined)", "false"),
5428        // `new Error(msg, {cause})`(ES2022)が丸ごと未対応で、`err.cause` が常に
5429        // `undefined` になっていた(例外を包んで再送出する定番イディオムで使われる)。
5430        ("new Error('wrap', {cause: 'orig'}).cause", "orig"),
5431        ("typeof new Error('x').cause", "undefined"),
5432        ("new TypeError('t', {cause: 42}).cause", "42"),
5433        (
5434            "try { throw new Error('inner') } catch(e) { new Error('outer', {cause: e}).cause.message }",
5435            "inner",
5436        ),
5437        // `RegExp.escape(str)`(ES2025)が丸ごと未対応だった。ユーザー入力を
5438        // `new RegExp(...)` へそのまま渡す前に構文文字を無害化する定番イディオム。
5439        ("RegExp.escape('a.b*c')", "a\\.b\\*c"),
5440        ("new RegExp(RegExp.escape('1+1=2')).test('1+1=2')", "true"),
5441        (
5442            "new RegExp('^' + RegExp.escape('a.b') + '$').test('aXb')",
5443            "false",
5444        ),
5445        // `RegExp.prototype.compile()`(レガシーだが仕様に残るメソッド。丸ごと
5446        // 未対応だった)。`this` を新しいオブジェクトを作らずその場で差し替える。
5447        ("var r=/a/; r.compile('b'); r.test('b')", "true"),
5448        ("var r=/a/i; r.compile('a'); r.flags", ""),
5449        ("var r=/a/; r.compile('a','gi'); r.flags", "gi"),
5450        ("var r=/a/g; r.lastIndex=3; r.compile('b'); r.lastIndex", "0"),
5451        ("var r=/x/; var r2=r.compile('y'); r===r2", "true"),
5452        // `CSS.escape(str)`(CSSOM。丸ごと未対応だった。`CSS` 名前空間自体が
5453        // 存在しなかった)。
5454        ("CSS.escape('a.b')", "a\\.b"),
5455        ("CSS.escape('.foo#bar')", "\\.foo\\#bar"),
5456        ("CSS.escape('123')", "\\31 23"),
5457        ("CSS.escape('-1')", "-\\31 "),
5458        ("CSS.escape('-')", "\\-"),
5459        ("CSS.escape('abc')", "abc"),
5460        // `CSS.supports(conditionText)`/`CSS.supports(property, value)`
5461        // (CSSOM。丸ごと未対応だった。`@supports` の条件式評価をそのまま
5462        // 再利用するため、`not`/`and`/`or` の論理結合も正しく評価される)。
5463        ("CSS.supports('(display: grid)')", "true"),
5464        ("CSS.supports('display', 'grid')", "true"),
5465        ("CSS.supports('not (display: grid)')", "false"),
5466        (
5467            "CSS.supports('(display: grid) and (gap: 1rem)')",
5468            "true",
5469        ),
5470        (
5471            "CSS.supports('not (display: grid) or (gap: 1rem)')",
5472            "true",
5473        ),
5474        // `<audio>`/`<video>` の `.play()`/`.pause()`/`.load()`/`.canPlayType()`
5475        // (HTMLMediaElement。丸ごと未対応だった)。
5476        (
5477            "typeof document.createElement('audio').play().then",
5478            "function",
5479        ),
5480        ("typeof document.createElement('video').pause()", "undefined"),
5481        ("typeof document.createElement('audio').load()", "undefined"),
5482        ("document.createElement('video').canPlayType('video/mp4')", ""),
5483        // ARIA 反映 IDL 属性(`.role`/`.ariaLabel`/`.ariaChecked` 等)が丸ごと
5484        // 未対応だった。
5485        ("document.createElement('div').role", "null"),
5486        (
5487            "var e=document.createElement('div'); e.role='button'; e.getAttribute('role')",
5488            "button",
5489        ),
5490        (
5491            "var e=document.createElement('div'); e.setAttribute('aria-label','x'); e.ariaLabel",
5492            "x",
5493        ),
5494        (
5495            "var e=document.createElement('div'); e.ariaExpanded='true'; \
5496             e.getAttribute('aria-expanded')",
5497            "true",
5498        ),
5499        (
5500            "var e=document.createElement('div'); e.ariaLabel='x'; e.ariaLabel=null; \
5501             e.hasAttribute('aria-label')",
5502            "false",
5503        ),
5504        // 表・リスト系の残りの ARIA 反映 IDL 属性(丸ごと未対応だった。
5505        // 2026-07-15 発見・実装)。
5506        (
5507            "var e=document.createElement('div'); e.ariaColCount='3'; \
5508             e.getAttribute('aria-colcount')",
5509            "3",
5510        ),
5511        (
5512            "var e=document.createElement('div'); e.setAttribute('aria-rowindex','2'); \
5513             e.ariaRowIndex",
5514            "2",
5515        ),
5516        (
5517            "var e=document.createElement('div'); e.ariaSetSize='5'; e.ariaPosInSet='2'; \
5518             e.ariaSetSize + ',' + e.ariaPosInSet",
5519            "5,2",
5520        ),
5521        (
5522            "var e=document.createElement('div'); e.ariaLevel='1'; e.getAttribute('aria-level')",
5523            "1",
5524        ),
5525        // `String.prototype.repeat(count)` が仕様上必須の「負値/+Infinity で RangeError」
5526        // を投げず黙って空文字列に丸めていたバグ。
5527        ("'ab'.repeat(3)", "ababab"),
5528        ("'x'.repeat(0)", ""),
5529        (
5530            "try { 'x'.repeat(-1); 'no-throw' } catch(e) { 'threw' }",
5531            "threw",
5532        ),
5533        (
5534            "try { 'x'.repeat(Infinity); 'no-throw' } catch(e) { 'threw' }",
5535            "threw",
5536        ),
5537        // `eval`/`Function`(ES1 の最基礎機能の2つ)が丸ごと欠落していた
5538        // (`eval is not a function`/`Function is not defined`)。
5539        ("eval('1 + 2 * 3')", "7"),
5540        ("eval(42)", "42"),
5541        ("var x = 10; eval('x = x + 5'); x", "15"),
5542        ("new Function('a', 'b', 'return a + b')(3, 4)", "7"),
5543        ("new Function('return 1 + 1')()", "2"),
5544        (
5545            "typeof new Function('a', 'return a * 2')",
5546            "function",
5547        ),
5548        // Iterator Helpers(ES2025)が丸ごと未対応だった。`arr.values()` 等が返す軽量
5549        // イテレータに `map`/`filter`/`take`/`drop`/`toArray`/`forEach`/`reduce` を追加。
5550        ("[1,2,3].values().map(x => x * 2).toArray().join(',')", "2,4,6"),
5551        ("[1,2,3,4].values().filter(x => x % 2 === 0).toArray().join(',')", "2,4"),
5552        ("[1,2,3,4,5].values().take(2).toArray().join(',')", "1,2"),
5553        ("[1,2,3,4,5].values().drop(3).toArray().join(',')", "4,5"),
5554        (
5555            "[1,2,3].values().map(x => x + 1).filter(x => x > 2).toArray().join(',')",
5556            "3,4",
5557        ),
5558        (
5559            "var s=''; [1,2,3].values().forEach(x => s += x); s",
5560            "123",
5561        ),
5562        ("[1,2,3,4].values().reduce((a,b) => a + b)", "10"),
5563        ("[1,2,3].values().reduce((a,b) => a + b, 10)", "16"),
5564        // `some`/`every`/`find`(同じ ES2025 Iterator Helpers 提案に含まれる残り3つ
5565        // が丸ごと未対応だった。`map`/`filter`/`take`/`drop`/`toArray`/`forEach`/
5566        // `reduce` は既に対応済み)。
5567        ("[1,2,3].values().some(x => x > 2)", "true"),
5568        ("[1,2,3].values().some(x => x > 5)", "false"),
5569        ("[1,2,3].values().every(x => x > 0)", "true"),
5570        ("[1,2,3].values().every(x => x > 1)", "false"),
5571        ("[1,2,3,4].values().find(x => x % 2 === 0)", "2"),
5572        ("typeof [1,2,3].values().find(x => x > 5)", "undefined"),
5573        // `flatMap`(ES2025 Iterator Helpers 提案の最後の1つが丸ごと未対応だった)。
5574        (
5575            "[1,2,3].values().flatMap(x => [x, x * 10]).toArray().join(',')",
5576            "1,10,2,20,3,30",
5577        ),
5578        // `Iterator.prototype.flatMap` のマッパー戻り値が配列以外の iterable(Generator 等)
5579        // でも展開されるべき(以前は実配列以外を無条件で空扱いする同型のバグがあった)。
5580        (
5581            "function* dup(x){ yield x; yield x*10; } [1,2].values().flatMap(x => dup(x)).toArray().join(',')",
5582            "1,10,2,20",
5583        ),
5584        // `Object.getPrototypeOf`/`Object.setPrototypeOf`(ES5/ES6。`Reflect` 版は既に
5585        // あったが、実用上はるかに一般的なこちらの静的メソッドが欠落していた)。
5586        (
5587            "function Foo(){} Object.getPrototypeOf(new Foo()) === Foo.prototype",
5588            "true",
5589        ),
5590        (
5591            "var a={}; var b={x:1}; Object.setPrototypeOf(a,b); a.x",
5592            "1",
5593        ),
5594        // `Object.setPrototypeOf` は循環参照を検証しないため、循環したプロトタイプ
5595        // 連鎖を持つオブジェクトへのプロパティ読み書き/`in`/`for...in` が無限ループ
5596        // (QEMU 起動ハング)になり得たバグ。
5597        (
5598            "var a={}; Object.setPrototypeOf(a,a); a.nope",
5599            "undefined",
5600        ),
5601        (
5602            "var a={}; var b={}; Object.setPrototypeOf(a,b); Object.setPrototypeOf(b,a); a.nope",
5603            "undefined",
5604        ),
5605        (
5606            "var a={}; Object.setPrototypeOf(a,a); ('nope' in a)",
5607            "false",
5608        ),
5609        (
5610            "var a={}; Object.setPrototypeOf(a,a); a.x=1; a.x",
5611            "1",
5612        ),
5613        (
5614            "var a={}; Object.setPrototypeOf(a,a); var ks=[]; for (var k in a) { ks.push(k); } ks.length",
5615            "0",
5616        ),
5617        // `obj.__proto__`(Annex B のレガシーアクセサ。`Object.getPrototypeOf`/
5618        // `setPrototypeOf` は既に対応済みだったが、実務コードで極めて広く使われる
5619        // こちらの糖衣構文自体が丸ごと未対応だった)。
5620        ("var a={}; var b={x:1}; a.__proto__ = b; a.x", "1"),
5621        ("var b={x:1}; var a=Object.create(b); a.__proto__ === b", "true"),
5622        ("({}).__proto__ === null", "true"),
5623        ("var a={}; a.__proto__ = null; a.__proto__", "null"),
5624        // オブジェクトリテラル内の `__proto__: value`(算出キーでない場合)は
5625        // 通常のプロパティではなく [[Prototype]] を設定する特別構文(Annex B.3.1)。
5626        ("var p={x:5}; var o={__proto__: p}; o.x", "5"),
5627        ("var p={x:5}; var o={__proto__: p}; Object.keys(o).includes('__proto__')", "false"),
5628        // 算出キー `{['__proto__']: v}` は通常の own プロパティのまま(特別扱いしない)。
5629        ("var o={['__proto__']: 5}; o.__proto__", "5"),
5630        // `Object.prototype.isPrototypeOf`/`propertyIsEnumerable`。
5631        ("function Foo(){} Foo.prototype.isPrototypeOf(new Foo())", "true"),
5632        ("({}).isPrototypeOf({})", "false"),
5633        ("({x:1}).propertyIsEnumerable('x')", "true"),
5634        ("({x:1}).propertyIsEnumerable('y')", "false"),
5635        // `Function.prototype.call`/`apply`/`bind`(ES3/ES5)が丸ごと欠落していた
5636        // (`Reflect.apply` はあったが、はるかに一般的なこちらのインスタンスメソッドが無かった)。
5637        ("function f(a,b){ return this.x+a+b; } f.call({x:10}, 1, 2)", "13"),
5638        ("function f(a,b){ return this.x+a+b; } f.apply({x:10}, [1,2])", "13"),
5639        // `Function.prototype.apply` の第2引数も `Reflect.apply` と同じく array-like
5640        // 全般(非配列)を受け付けるべきだが、以前は `iterable_values` を使っており
5641        // 素の array-like を渡すと引数が消える同型のバグだった。
5642        (
5643            "function f(a,b){ return this.x+a+b; } f.apply({x:10}, {0:1,1:2,length:2})",
5644            "13",
5645        ),
5646        (
5647            "function f(a,b){ return this.x+a+b; } var g = f.bind({x:10}, 1); g(2)",
5648            "13",
5649        ),
5650        (
5651            "function f(){ return this.x; } var g = f.bind({x:5}); g.call({x:99})",
5652            "5",
5653        ),
5654        // `new.target`(ES6 MetaProperty)が未実装だった。`new` 経由の呼び出しでは
5655        // コンストラクタ自身、通常呼び出しでは undefined になる。
5656        // `new Foo()` はコンストラクタが(オブジェクトではなく)真偽値を return しても
5657        // 仕様どおり無視され `this`(構築済みインスタンス)を返すため、`new.target` の値は
5658        // 外側の変数に代入して確認する。
5659        (
5660            "var r; function Foo(){ r = new.target === Foo; } new Foo(); r",
5661            "true",
5662        ),
5663        ("function Foo(){ return new.target === Foo; } Foo()", "false"),
5664        (
5665            "function Foo(){ return typeof new.target; } Foo()",
5666            "undefined",
5667        ),
5668        (
5669            "class Foo { constructor(){ this.isNew = new.target === Foo; } } new Foo().isNew",
5670            "true",
5671        ),
5672        // アロー関数は `this` と同様に `new.target` もレキシカルに周囲から継承する
5673        // (アロー自身は `new` できず独自のフレームを持たないため)。
5674        (
5675            "var r; function Foo(){ var f = () => { r = new.target === Foo; }; f(); } new Foo(); r",
5676            "true",
5677        ),
5678        // `catch` 節の分割代入パターン(ES2015)。以前は `catch_param` が単なる識別子
5679        // 文字列で、パターン(`{message}` 等)を受け付けられなかった。
5680        (
5681            "try { throw {message:'boom', code:42}; } catch({message, code}) { message + '/' + code; }",
5682            "boom/42",
5683        ),
5684        (
5685            "try { throw [1,2,3]; } catch([a,,c]) { a + '/' + c; }",
5686            "1/3",
5687        ),
5688        ("try { throw 'x'; } catch(e) { e; }", "x"),
5689        // クラスの算出メソッド名 `[expr](){}` が未対応だった(パーサがキーとして
5690        // 識別子/文字列/数値/`[`を含む未知トークンを読み飛ばすだけで、`[Symbol.iterator]`
5691        // のようなよくあるパターンが正しくパースできなかった)。
5692        (
5693            "class C { ['foo' + 'bar'](){ return 42; } } new C().foobar()",
5694            "42",
5695        ),
5696        // 上の算出メソッド名対応と組み合わせ、`for...of` がユーザー定義の
5697        // `[Symbol.iterator]()` を実装したカスタムイテラブル(class)を正しく駆動できる
5698        // ようにした(従来は Array/Set/Map/Generator/文字列以外は「オブジェクト自身の
5699        // プロパティ値」を無条件に列挙するだけで、Symbol.iterator 自体を一切見ていなかった)。
5700        (
5701            "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",
5702            "0123",
5703        ),
5704        (
5705            "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(',')",
5706            "0,1,2",
5707        ),
5708        // `[Symbol.iterator]` 等の偽装 Symbol キーが `Object.keys`/`values`/`entries`/
5709        // `for...in`/`JSON.stringify` に漏れて列挙されてしまうバグの検証
5710        // (`Object.assign`/スプレッド構文は仕様どおり対象のままなので混同しないこと)。
5711        (
5712            "var o={a:1}; o[Symbol.iterator]=function(){}; Object.keys(o).join(',')",
5713            "a",
5714        ),
5715        (
5716            "var o={a:1}; o[Symbol.iterator]=function(){}; Object.values(o).join(',')",
5717            "1",
5718        ),
5719        (
5720            "var o={a:1}; o[Symbol.iterator]=function(){}; JSON.stringify(o)",
5721            "{\"a\":1}",
5722        ),
5723        (
5724            "var o={a:1}; o[Symbol.iterator]=function(){}; var k=''; for (const x in o) { k+=x; } k",
5725            "a",
5726        ),
5727        // `Date` が丸ごと未実装だった(`performance.now()` はあったが実時刻を持たない
5728        // 単純カウンタで、`Date` 自体はグローバルに存在しなかった)。実時刻は NTP 同期済みの
5729        // カーネル壁時計(`kernel::timer::get_unix_time()`)を利用する。
5730        (
5731            "new Date(2024, 0, 15, 10, 30, 45, 500).toISOString()",
5732            "2024-01-15T10:30:45.500Z",
5733        ),
5734        ("new Date(0).toISOString()", "1970-01-01T00:00:00.000Z"),
5735        // `toDateString`/`toTimeString`/`toLocaleDateString`/`toLocaleTimeString` は
5736        // 以前すべて `toString`(フルISO日時文字列)にエイリアスされており、日付のみ/
5737        // 時刻のみを返すべきところに他方の情報が混入していた。
5738        (
5739            "new Date(2024, 0, 15, 10, 30, 45, 500).toDateString()",
5740            "2024-01-15",
5741        ),
5742        (
5743            "new Date(2024, 0, 15, 10, 30, 45, 500).toTimeString()",
5744            "10:30:45",
5745        ),
5746        (
5747            "new Date(2024, 0, 15).toLocaleDateString()",
5748            "2024-01-15",
5749        ),
5750        ("new Date(2024, 0, 15).toLocaleTimeString()", "00:00:00"),
5751        ("new Date(2024, 0, 1).getFullYear()", "2024"),
5752        ("new Date(2024, 5, 15).getMonth()", "5"),
5753        ("new Date(2024, 0, 1).getDay()", "1"), // 2024-01-01 は月曜日
5754        // getTimezoneOffset()(丸ごと未対応だった)。UTC を常にローカルとみなす簡略実装のため常に0。
5755        ("typeof new Date().getTimezoneOffset()", "number"),
5756        ("new Date().getTimezoneOffset()", "0"),
5757        // `Date.prototype.set*` が `setTime` も含めて1つも実装されておらず、getter
5758        // しか無い丸ごと未対応バグだった(`setFullYear`/`setDate` 等の定番書き換え
5759        // イディオムが軒並み `undefined is not a function` になっていた)。
5760        (
5761            "var d=new Date(2024,0,15); d.setFullYear(2025); d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate()",
5762            "2025-1-15",
5763        ),
5764        // `getYear`/`setYear`(Annex B.2.4/B.2.5。2桁年時代の遺産)が丸ごと未対応だった。
5765        ("new Date(2024,0,1).getYear()", "124"),
5766        // `setYear(y)` は `0<=y<=99` なら `1900+y` を、それ以外は `y` をそのまま
5767        // フルイヤーとして使う仕様どおりの奇妙な後方互換ロジック。
5768        ("var d=new Date(2024,0,1); d.setYear(5); d.getFullYear()", "1905"),
5769        ("var d=new Date(2024,0,1); d.setYear(2030); d.getFullYear()", "2030"),
5770        (
5771            "var d=new Date(2024,0,15); d.setMonth(5); d.getMonth()",
5772            "5",
5773        ),
5774        (
5775            "var d=new Date(2024,0,15); d.setDate(d.getDate()+1); d.getDate()",
5776            "16",
5777        ),
5778        // setDate は月境界を仕様どおり繰り上げる。
5779        (
5780            "var d=new Date(2024,0,31); d.setDate(d.getDate()+1); (d.getMonth()+1)+'-'+d.getDate()",
5781            "2-1",
5782        ),
5783        (
5784            "var d=new Date(2024,0,15,10,20,30); d.setHours(23); d.getHours()+':'+d.getMinutes()+':'+d.getSeconds()",
5785            "23:20:30",
5786        ),
5787        (
5788            "var d=new Date(2024,0,15,10,20,30); d.setMinutes(5); d.getMinutes()",
5789            "5",
5790        ),
5791        (
5792            "var d=new Date(2024,0,15,10,20,30); d.setSeconds(59); d.getSeconds()",
5793            "59",
5794        ),
5795        (
5796            "var d=new Date(2024,0,15); d.setMilliseconds(123); d.getMilliseconds()",
5797            "123",
5798        ),
5799        (
5800            "var d=new Date(0); d.setTime(5000); d.getTime()",
5801            "5000",
5802        ),
5803        // set* の戻り値は仕様どおり新しい getTime() と同じ。
5804        (
5805            "var d=new Date(0); var r=d.setFullYear(2000); r === d.getTime()",
5806            "true",
5807        ),
5808        // `Date` は `to_number()` に `ObjKind::DateObj` 特殊扱いを追加したことで、
5809        // `.getTime()` を明示せずとも算術/比較演算がそのまま動く
5810        // (この処理系はオブジェクト全般の `valueOf`/`Symbol.toPrimitive` は汎用サポートしない
5811        // ため、これは `Date` だけの特殊扱い)。
5812        ("new Date(1000) - new Date(0)", "1000"),
5813        ("new Date(0) < new Date(1000)", "true"),
5814        ("+new Date(500)", "500"),
5815        ("typeof Date.now()", "number"),
5816        // `delete`(ES1 以来の基礎演算子)が AST/パーサ/インタプリタのどこにも存在せず、
5817        // 丸ごと未実装だった(`delete` トークン自体はレキサーが認識するのに、パーサが
5818        // どの式としても解釈しない状態)。
5819        ("var o = {x:1}; delete o.x; 'x' in o", "false"),
5820        ("var o = {x:1, y:2}; delete o.x; o.y", "2"),
5821        ("var k='x'; var o = {x:1}; delete o[k]; 'x' in o", "false"),
5822        ("var a=[1,2,3]; delete a[1]; a[1]", "undefined"),
5823        ("var a=[1,2,3]; delete a[1]; a.length", "3"),
5824        ("delete 5", "true"),
5825        // カンマ演算子 — 以前はパーサが先行する式を単に上書きして AST から消してしまい、
5826        // その式の副作用(関数呼び出し・代入等)が一切実行されない重大なバグだった。
5827        ("var s=''; (s+='a', s+='b', s+='c'); s", "abc"),
5828        ("var x=(1,2,3); x", "3"),
5829        (
5830            "var s=''; for (var i=0,j=10; i<3; i++,j--) { s += i+':'+j+' '; } s",
5831            "0:10 1:9 2:8 ",
5832        ),
5833        ("var a; var b; (a=1, b=a+1); a+','+b", "1,2"),
5834        // `generator.throw(err)`(ES2015)が丸ごと未実装だった(`next`/`return` はあったが
5835        // `throw` だけが欠落)。中断中の yield 位置に例外を注入する。
5836        (
5837            "function* g(){ try { yield 1; yield 2; } catch(e) { yield 'caught:'+e; } } var it=g(); it.next(); it.throw('boom').value",
5838            "caught:boom",
5839        ),
5840        (
5841            "function* g(){ yield 1; yield 2; } var it=g(); it.next(); var r; try { it.throw('x'); } catch(e) { r = 'propagated:'+e; } r",
5842            "propagated:x",
5843        ),
5844        // `String.prototype.codePointAt`(ES2015)/`localeCompare`(ES3)が丸ごと未実装だった。
5845        ("'A'.codePointAt(0)", "65"),
5846        ("'ABC'.codePointAt(5)", "undefined"),
5847        ("'a'.localeCompare('b')", "-1"),
5848        ("'b'.localeCompare('a')", "1"),
5849        ("'a'.localeCompare('a')", "0"),
5850        // `String.prototype.substr`(ES3 由来の Annex B 非推奨メソッド)が `slice`/
5851        // `substring` はあったのに欠落していた。古いコードで依然として広く使われる。
5852        ("'Hello World'.substr(6)", "World"),
5853        ("'Hello World'.substr(0, 5)", "Hello"),
5854        ("'Hello World'.substr(-5)", "World"),
5855        ("'Hello World'.substr(-5, 3)", "Wor"),
5856        // `static { ... }`(ES2022 静的初期化ブロック)が未実装だった。パーサはメソッド名
5857        // として識別子/文字列/数値/`[computed]` しか認識せず、`{` が来ると読み飛ばして
5858        // 続行する未実装フォールバックに落ちていた。
5859        (
5860            "class C { static x; static { C.x = 1 + 2; } } C.x",
5861            "3",
5862        ),
5863        (
5864            "class C { static x = 10; static { C.x += 5; } } C.x",
5865            "15",
5866        ),
5867        // 複数の static ブロックは宣言順に実行される。
5868        (
5869            "class C { static log=''; static { C.log+='a'; } static { C.log+='b'; } } C.log",
5870            "ab",
5871        ),
5872        // `Array.prototype.flat(depth)` が `depth` 引数を完全に無視し、常に1段しか
5873        // 展開しないバグだった。`.flat(Infinity)`(深いネスト解除の定番イディオム)も
5874        // 動作していなかった。
5875        ("[1,[2,3]].flat().join(',')", "1,2,3"),
5876        ("[1,[2,[3,[4]]]].flat(2).join(',')", "1,2,3,4"),
5877        ("[1,[2,[3,[4,[5]]]]].flat(Infinity).join(',')", "1,2,3,4,5"),
5878        // `.flat(Infinity)` は循環参照する配列(`a.push(a)`)に対しては祖先追跡
5879        // ガードが無いと無限再帰になり、この no_std 環境ではスタック
5880        // オーバーフロー(クラッシュ/ハング)に直結し得るバグだった(2026-07-13。
5881        // `Value::to_js_string` で修正済みの循環参照系と同種)。クラッシュ/
5882        // ハングせず完走することを確認する(既に祖先に現れた配列はそれ以上
5883        // 展開されずそのまま結果に含まれるため、要素数は打ち切り位置に依存)。
5884        ("var a=[1]; a.push(a); a.flat(Infinity); 'ok'", "ok"),
5885        // depth=0 は展開しない(トップレベルの要素数がそのまま length に残ることで確認)。
5886        ("[1,[2,[3]]].flat(0).length", "2"),
5887        // `Object.freeze()` が明示的な no-op(同一オブジェクトを返すだけ)だった。
5888        // `Object.isFrozen()` 自体も丸ごと欠落していた。
5889        ("var o={x:1}; Object.freeze(o); o.x=2; o.x", "1"),
5890        ("var o={x:1}; Object.freeze(o); o.y=2; 'y' in o", "false"),
5891        ("var o={x:1}; Object.freeze(o); delete o.x; o.x", "1"),
5892        ("var o={x:1}; Object.isFrozen(o)", "false"),
5893        ("var o={x:1}; Object.freeze(o); Object.isFrozen(o)", "true"),
5894        ("Object.isFrozen(5)", "true"),
5895        // `Object.seal`/`isSealed`/`preventExtensions`/`isExtensible` も丸ごと欠落していた。
5896        ("var o={x:1}; Object.seal(o); o.x=2; o.x", "2"),
5897        ("var o={x:1}; Object.seal(o); o.y=2; 'y' in o", "false"),
5898        ("var o={x:1}; Object.seal(o); delete o.x; 'x' in o", "true"),
5899        ("var o={x:1}; Object.isSealed(o)", "false"),
5900        ("var o={x:1}; Object.seal(o); Object.isSealed(o)", "true"),
5901        (
5902            "var o={x:1}; Object.preventExtensions(o); o.y=2; 'y' in o",
5903            "false",
5904        ),
5905        (
5906            "var o={x:1}; Object.preventExtensions(o); o.x=5; delete o.x; 'x' in o",
5907            "false",
5908        ),
5909        ("var o={x:1}; Object.isExtensible(o)", "true"),
5910        (
5911            "var o={x:1}; Object.preventExtensions(o); Object.isExtensible(o)",
5912            "false",
5913        ),
5914        // `Array.isArray` は仕様上 Proxy を透過して target を見る必要があるが、
5915        // 以前は Proxy でラップした配列が false 判定になっていた。
5916        ("Array.isArray(new Proxy([1,2], {}))", "true"),
5917        ("Array.isArray(new Proxy({}, {}))", "false"),
5918        // TypedArray も内部表現は ObjKind::Array を流用するため、`_ta_kind` タグを
5919        // 見ずに判定すると `Array.isArray(new Int8Array(...))` が仕様に反して
5920        // `true` になるバグだった(TypedArray は Array exotic object ではない)。
5921        ("Array.isArray(new Int8Array([1,2]))", "false"),
5922        ("Array.isArray([1,2])", "true"),
5923        // `Object.keys`/`values`/`entries` は Proxy を素通しできず(Proxy 自身は props を
5924        // 持たないため)常に空を返すバグだった。target フォワードのみの Proxy では
5925        // 正しく動くようにした(ownKeys トラップの呼び出しまでは非対応)。
5926        ("Object.keys(new Proxy({a:1,b:2}, {})).sort().join(',')", "a,b"),
5927        ("Object.values(new Proxy({a:1,b:2}, {})).sort().join(',')", "1,2"),
5928        (
5929            "Object.entries(new Proxy({a:1}, {})).map(e=>e[0]+':'+e[1]).join(',')",
5930            "a:1",
5931        ),
5932        // `JSON.stringify` も同じ Proxy 透過漏れで、Proxy を渡すと常に `{}` になっていた。
5933        ("JSON.stringify(new Proxy({a:1,b:2}, {}))", "{\"a\":1,\"b\":2}"),
5934        // `for...of`/スプレッドも同じ Proxy 透過漏れで、target フォワードのみの Proxy が
5935        // 配列/Set/Map を包んでいても何も反復しないバグだった(`iterate_values()` が自身の
5936        // `ObjKind` だけを見て Proxy を Array 等として認識できていなかった)。
5937        (
5938            "var s=''; for (const x of new Proxy([1,2,3], {})) { s += x; } s",
5939            "123",
5940        ),
5941        ("[...new Proxy([1,2,3], {})].join(',')", "1,2,3"),
5942        // `Object.assign` も同じ系統のバグを2つ抱えていた: (1) ソースが Proxy だと
5943        // 何もコピーされない、(2) ソースが配列だと要素が `ObjKind::Array` 側にあり
5944        // `props` には無いため、これも何もコピーされない。
5945        (
5946            "var t = Object.assign({}, new Proxy({a:1,b:2}, {})); t.a+','+t.b",
5947            "1,2",
5948        ),
5949        (
5950            "var t = Object.assign({}, [10,20,30]); t[0]+','+t[1]+','+t[2]",
5951            "10,20,30",
5952        ),
5953        // `Reflect.deleteProperty` は `delete` 演算子と同じ凍結/封印済みオブジェクト保護を
5954        // 適用すべきだが、以前は一切チェックせず常に削除・成功させていた(非対称だった)。
5955        (
5956            "var o={x:1}; Object.freeze(o); Reflect.deleteProperty(o,'x')",
5957            "false",
5958        ),
5959        ("var o={x:1}; Reflect.deleteProperty(o,'x')", "true"),
5960        // `Reflect.set` も同様に、以前は常に `true` を返しており、凍結済みオブジェクトへの
5961        // 書込みが黙殺されていても呼び出し元からは成功したように見えていた。
5962        (
5963            "var o={x:1}; Object.freeze(o); Reflect.set(o,'x',2)",
5964            "false",
5965        ),
5966        ("var o={x:1}; Reflect.set(o,'x',2); o.x", "2"),
5967        // `Array.prototype.includes` は SameValueZero で比較すべきところ `===` を使っており、
5968        // `[NaN].includes(NaN)` が false になるバグだった(`indexOf` は `===` のままで正しい)。
5969        ("[NaN].includes(NaN)", "true"),
5970        ("[NaN].indexOf(NaN)", "-1"),
5971        ("[1,2,NaN].includes(NaN)", "true"),
5972        ("[1,2,3].includes(2)", "true"),
5973        ("[1,2,3].includes(5)", "false"),
5974        // 高階配列メソッドの第2引数 `thisArg` が `map`/`filter`/`forEach`/`find` 系
5975        // すべてで無視されていた(コールバックの `this` を常に `undefined` 決め打ちしていた)。
5976        (
5977            "var ctx={mul:10}; [1,2,3].map(function(x){return x*this.mul;}, ctx).join(',')",
5978            "10,20,30",
5979        ),
5980        (
5981            "var ctx={min:2}; [1,2,3].filter(function(x){return x>this.min;}, ctx).join(',')",
5982            "3",
5983        ),
5984        (
5985            "var ctx={sum:0}; [1,2,3].forEach(function(x){this.sum+=x;}, ctx); ctx.sum",
5986            "6",
5987        ),
5988        (
5989            "var ctx={target:2}; [1,2,3].find(function(x){return x===this.target;}, ctx)",
5990            "2",
5991        ),
5992        (
5993            "var ctx={target:5}; [1,2,3].some(function(x){return x===this.target;}, ctx)",
5994            "false",
5995        ),
5996        (
5997            "var ctx={min:0}; [1,2,3].every(function(x){return x>this.min;}, ctx)",
5998            "true",
5999        ),
6000        // 同じ `thisArg` 無視バグが `Map.prototype.forEach`/`Set.prototype.forEach`/
6001        // `Array.from` の第3引数にもあった。
6002        (
6003            "var ctx={sum:0}; var m=new Map([['a',1],['b',2]]); m.forEach(function(v){this.sum+=v;}, ctx); ctx.sum",
6004            "3",
6005        ),
6006        (
6007            "var ctx={sum:0}; var s=new Set([1,2,3]); s.forEach(function(v){this.sum+=v;}, ctx); ctx.sum",
6008            "6",
6009        ),
6010        (
6011            "var ctx={mul:2}; Array.from([1,2,3], function(x){return x*this.mul;}, ctx).join(',')",
6012            "2,4,6",
6013        ),
6014        // `JSON.stringify` の第3引数 `space`(インデント整形。デバッグ出力の定番
6015        // イディオム `JSON.stringify(obj, null, 2)`)が完全に無視されていた。
6016        (
6017            "JSON.stringify({a:1,b:2}, null, 2)",
6018            "{\n  \"a\": 1,\n  \"b\": 2\n}",
6019        ),
6020        ("JSON.stringify([1,2], null, 2)", "[\n  1,\n  2\n]"),
6021        ("JSON.stringify({}, null, 2)", "{}"),
6022        ("JSON.stringify([], null, 2)", "[]"),
6023        ("JSON.stringify({a:1}, null, '\\t')", "{\n\t\"a\": 1\n}"),
6024        // space 省略時は従来どおりコンパクト出力のまま。
6025        ("JSON.stringify({a:1,b:2})", "{\"a\":1,\"b\":2}"),
6026        // `JSON.stringify` は BigInt を直列化できず本来 TypeError を投げるべきだが、
6027        // 以前は静かに省略していた(`undefined`/関数と同じ扱いになっていたバグ)。
6028        (
6029            "try { JSON.stringify(10n); 'no-throw' } catch(e) { 'caught:' + e.name }",
6030            "caught:Error",
6031        ),
6032        (
6033            "try { JSON.stringify({a:10n}); 'no-throw' } catch(e) { 'caught' }",
6034            "caught",
6035        ),
6036        // `replacer` 引数(キーの絞り込み用配列 / 値差し替え関数)も同様に丸ごと未対応だった。
6037        ("JSON.stringify({a:1,b:2,c:3}, ['a','c'])", "{\"a\":1,\"c\":3}"),
6038        (
6039            "JSON.stringify({a:1,b:2}, function(k,v){ return typeof v === 'number' ? v*10 : v; })",
6040            "{\"a\":10,\"b\":20}",
6041        ),
6042        // replacer 配列はオブジェクトのキー絞り込みのみに適用され、配列要素自体は
6043        // 絞り込まれない(仕様どおり)。
6044        ("JSON.stringify([1,2,3], ['a'])", "[1,2,3]"),
6045        // `JSON.parse` の第2引数 `reviver`(キーをボトムアップで巡る変換関数)も
6046        // 同様に丸ごと未対応だった。
6047        (
6048            "JSON.parse('{\"a\":1,\"b\":2}', function(k,v){ return typeof v === 'number' ? v*10 : v; }).a",
6049            "10",
6050        ),
6051        (
6052            "var r = JSON.parse('{\"a\":1,\"b\":2}', function(k,v){ return k==='b' ? undefined : v; }); 'b' in r",
6053            "false",
6054        ),
6055        (
6056            "JSON.parse('[1,2,3]', function(k,v){ return typeof v === 'number' ? v+1 : v; }).join(',')",
6057            "2,3,4",
6058        ),
6059        ("JSON.parse('{\"a\":1}').a", "1"),
6060        // `String.prototype.replaceAll` が RegExp パターン(`g` フラグ必須)・関数
6061        // リプレーサのどちらも未対応だった(RegExp を渡すと文字列表現 "/pat/flags" として
6062        // 扱われ無変換になっていた)。
6063        ("'aaa'.replaceAll(/a/g, 'b')", "bbb"),
6064        (
6065            "try { 'aaa'.replaceAll(/a/, 'b'); 'no-throw' } catch(e) { 'threw' }",
6066            "threw",
6067        ),
6068        (
6069            "'a1b2'.replaceAll(/[0-9]/g, function(m){ return '['+m+']'; })",
6070            "a[1]b[2]",
6071        ),
6072        ("'aXaXa'.replaceAll('X', function(){ return '-'; })", "a-a-a"),
6073        ("'abc'.replaceAll('x', 'y')", "abc"),
6074        // 同一の `var` 文の中で、前の宣言子を後の初期化子が参照できるか。
6075        // jQuery 1.8.2 の先頭が
6076        // `var ...,h=[],p="1.8.2",d=h.concat,...,w=p.trim,...` という形で、
6077        // ここが順に評価されないと `p` が undefined になり
6078        // 「Cannot read properties of undefined (reading 'trim')」で落ちる。
6079        ("var a1 = 1, b1 = a1 + 1; b1", "2"),
6080        ("var s1 = 'xy', t1 = s1.length; t1", "2"),
6081        ("var h1 = [], p1 = '1.8.2', w1 = p1.trim; typeof w1", "function"),
6082        ("var q1 = [], r1 = q1.push; typeof r1", "function"),
6083        // `String.prototype` が丸ごと無く、プロトタイプから直接読む形が
6084        // undefined になっていた。jQuery 1.8.2 の `o=String.prototype.trim`
6085        // で本体が実行時に落ちていた。
6086        ("typeof String.prototype", "object"),
6087        ("typeof String.prototype.trim", "function"),
6088        ("typeof String.prototype.slice", "function"),
6089        ("String.prototype.trim.call('  x  ')", "x"),
6090        ("String.prototype.toUpperCase.call('ab')", "AB"),
6091        // 実体からの解決は従来どおり。
6092        ("'  y '.trim()", "y"),
6093        // 素のオブジェクト/配列/関数から、プロトタイプ経由で
6094        // メソッドを**値として**読めるか。jQuery は
6095        // `core_toString = class2type.toString`(`class2type = {}`)のように
6096        // 読んでから `.call(...)` する。読めないと
6097        // 「Cannot read properties of undefined (reading 'call')」になる。
6098        ("typeof ({}).toString", "function"),
6099        ("typeof ({}).hasOwnProperty", "function"),
6100        ("typeof [].slice", "function"),
6101        ("typeof [].concat", "function"),
6102        ("typeof (function(){}).call", "function"),
6103        ("typeof (function(){}).apply", "function"),
6104        ("({}).toString.call([])", "[object Array]"),
6105        // `Array.prototype` はオブジェクトとしては在ったがメソッドが空だった。
6106        // jQuery 1.8.2 の
6107        // `j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf`
6108        // で undefined になり、続く `k.call(...)` で落ちていた。
6109        ("typeof Array.prototype.push", "function"),
6110        ("typeof Array.prototype.slice", "function"),
6111        ("typeof Array.prototype.indexOf", "function"),
6112        ("Array.prototype.slice.call([1,2,3], 1).join(',')", "2,3"),
6113        ("Array.prototype.indexOf.call([4,5,6], 5)", "1"),
6114        // `Object.prototype` 側も jQuery が読む 2 つを確かめる。
6115        ("typeof Object.prototype.toString", "function"),
6116        ("typeof Object.prototype.hasOwnProperty", "function"),
6117        // jQuery 1.8.2 の機能検出は、**切り離された**要素へ innerHTML を入れ、
6118        // そこから要素を取り出して `style` を触る。この一連が通らないと
6119        // 「Cannot read properties of undefined (reading 'style')」になる。
6120        ("var d = document.createElement('div'); typeof d", "object"),
6121        ("var d2 = document.createElement('div'); typeof d2.style", "object"),
6122        (
6123            "var d3 = document.createElement('div'); d3.innerHTML = \"<a href='/a'>a</a>\";              d3.getElementsByTagName('a').length",
6124            "1",
6125        ),
6126        (
6127            "var d4 = document.createElement('div'); d4.innerHTML = \"<a href='/a'>a</a>\";              typeof d4.getElementsByTagName('a')[0]",
6128            "object",
6129        ),
6130        (
6131            "var d5 = document.createElement('div'); d5.innerHTML = \"<a href='/a'>a</a>\";              typeof d5.getElementsByTagName('a')[0].style",
6132            "object",
6133        ),
6134        (
6135            "var d6 = document.createElement('div');              d6.innerHTML = '<table></table><a href=\"/a\">a</a>';              d6.getElementsByTagName('*').length > 0",
6136            "true",
6137        ),
6138        (
6139            "var d7 = document.createElement('div'); d7.style.cssText = 'top:1px';              typeof d7.style.cssText",
6140            "string",
6141        ),
6142        (
6143            "var d8 = document.createElement('div'); d8.setAttribute('className','t');              d8.getAttribute('className')",
6144            "t",
6145        ),
6146        // jQuery 1.8.2 の機能検出が実際に入れる HTML。先頭の空白 2 つ・
6147        // 自己閉じの `<link/>`・`<table></table>` が `<a>` の前に来る。
6148        // ここで `<a>` が取れないと `d.style` で落ちる。
6149        // どの要素が原因かを分けて確かめる。
6150        (
6151            "var e1 = document.createElement('div'); e1.innerHTML = '<link/><a href=\"/a\">a</a>';              e1.getElementsByTagName('a').length",
6152            "1",
6153        ),
6154        (
6155            "var e2 = document.createElement('div');              e2.innerHTML = '<table></table><a href=\"/a\">a</a>';              e2.getElementsByTagName('a').length",
6156            "1",
6157        ),
6158        (
6159            "var e3 = document.createElement('div'); e3.innerHTML = '  <a href=\"/a\">a</a>';              e3.getElementsByTagName('a').length",
6160            "1",
6161        ),
6162        (
6163            "var e4 = document.createElement('div');              e4.innerHTML = '<input type=\"checkbox\"/><a href=\"/a\">a</a>';              e4.getElementsByTagName('a').length",
6164            "1",
6165        ),
6166        // jQuery が入れる文字列そのもの。
6167        (
6168            "var e5 = document.createElement('div');              e5.innerHTML = '  <link/><table></table><a href=\"/a\">a</a><input type=\"checkbox\"/>';              e5.getElementsByTagName('a').length",
6169            "1",
6170        ),
6171        (
6172            "var e6 = document.createElement('div');              e6.innerHTML = '  <link/><table></table><a href=\"/a\">a</a><input type=\"checkbox\"/>';              e6.getElementsByTagName('*').length > 0",
6173            "true",
6174        ),
6175        // jQuery は IIFE の中で `e = a.document`(`a = window`)として
6176        // **`window.document` 経由**で触る。グローバルの `document` を
6177        // 直接使う経路とは別なので、そちらも確かめる。
6178        ("typeof window.document", "object"),
6179        // グローバルにあるのに `window.` 経由で読めない漏れ。
6180        // jQuery 1.8.2 は `a.navigator.userAgent`(`a` は window)と書く。
6181        ("typeof window.navigator", "object"),
6182        ("typeof window.navigator.userAgent", "string"),
6183        ("window.navigator === navigator", "true"),
6184        ("typeof window.location", "object"),
6185        ("typeof window.JSON", "object"),
6186        ("typeof window.Math", "object"),
6187        ("typeof window.XMLHttpRequest", "function"),
6188        // `window.x = ...` の後に素の `x` で読めること。
6189        // jQuery 1.8.2 は最後に `a.jQuery = a.$ = p`(`a` は window)で
6190        // 自分を公開するので、これが無いと **jQuery が完走しても
6191        // `$ is not defined`** になる。
6192        ("window.myGlobal1 = 42; myGlobal1", "42"),
6193        ("window.myFn1 = function(){ return 'z'; }; myFn1()", "z"),
6194        ("window.mySelf1 = window; typeof mySelf1.document", "object"),
6195        // スコープの変数が優先される(window 側で影を作らない)。
6196        ("window.shadow1 = 'w'; var shadow1 = 'v'; shadow1", "v"),
6197        // `Object.prototype.toString` はプリミティブの種別を返す。
6198        // 全部 `[object Object]` だと、jQuery の `jQuery.extend(true, ...)` が
6199        // 文字列を「素のオブジェクト」と誤判定して `{}` に置き換える
6200        // (実際に `s.type`("GET")が壊れ `$.getJSON` が落ちていた)。
6201        ("Object.prototype.toString.call('x')", "[object String]"),
6202        ("Object.prototype.toString.call(1)", "[object Number]"),
6203        ("Object.prototype.toString.call(true)", "[object Boolean]"),
6204        ("Object.prototype.toString.call(null)", "[object Null]"),
6205        ("Object.prototype.toString.call(undefined)", "[object Undefined]"),
6206        ("Object.prototype.toString.call({})", "[object Object]"),
6207        ("Object.prototype.toString.call([])", "[object Array]"),
6208        ("Object.prototype.toString.call(function(){})", "[object Function]"),
6209        ("window.document === document", "true"),
6210        ("typeof window.document.createElement", "function"),
6211        (
6212            "var w1 = window.document.createElement('div'); typeof w1.style",
6213            "object",
6214        ),
6215        (
6216            "var w2 = window.document.createElement('div');              w2.innerHTML = '  <link/><table></table><a href=\"/a\">a</a><input type=\"checkbox\"/>';              typeof w2.getElementsByTagName('a')[0]",
6217            "object",
6218        ),
6219        // 関数の引数として渡した window から辿る形(jQuery の IIFE と同じ)。
6220        (
6221            "(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)",
6222            "object",
6223        ),
6224        // `replaceAll`/`matchAll` が独自の `[Symbol.replace]`/`[Symbol.matchAll]` へ
6225        // 委譲していなかった(`replace`/`match`/`search`/`split` は委譲済みだった)。
6226        // 【順序】`g` フラグの検査が**先**、委譲はその後。逆にすると
6227        // 非 g の RegExp が委譲側へ流れて TypeError が出なくなる。
6228        (
6229            "var o = {}; o[Symbol.replace] = function(s, r){ return 'X' + s + r; };              'ab'.replaceAll(o, 'Z')",
6230            "XabZ",
6231        ),
6232        (
6233            "var o = {}; o[Symbol.matchAll] = function(s){ return ['m:' + s]; };              'ab'.matchAll(o)[0]",
6234            "m:ab",
6235        ),
6236        // 委譲を足しても、非 g の RegExp は従来どおり TypeError のまま。
6237        (
6238            "try { 'aaa'.matchAll(/a/); 'no-throw' } catch(e) { 'threw' }",
6239            "threw",
6240        ),
6241        // `String.prototype.split` の第2引数 `limit` が完全に無視されていた。
6242        ("'a,b,c,d'.split(',', 2).join('|')", "a|b"),
6243        ("'a,b,c'.split(',', 0).length", "0"),
6244        ("'a1b2c3'.split(/[0-9]/, 2).join('|')", "a|b"),
6245        ("'a,b,c'.split(',').join('|')", "a|b|c"),
6246        // `matchAll` は `g` フラグ必須(無ければ TypeError)だが、以前は無条件に
6247        // 全件マッチを返していた。
6248        (
6249            "try { 'aa'.matchAll(/a/); 'no-throw' } catch(e) { 'threw' }",
6250            "threw",
6251        ),
6252        (
6253            "[...'a1b2'.matchAll(/[0-9]/g)].map(m=>m[0]).join(',')",
6254            "1,2",
6255        ),
6256        // `structuredClone` は循環参照(自己参照)を扱えず無限再帰(この no_std 環境では
6257        // クラッシュ/ハングになり得る)に陥るバグだった。
6258        (
6259            "var a={x:1}; a.self=a; var c=structuredClone(a); c.self===c",
6260            "true",
6261        ),
6262        ("var a={x:1}; a.self=a; var c=structuredClone(a); c.x", "1"),
6263        // 同一オブジェクトへの複数参照は複製後も共有構造として保たれる(仕様どおり)。
6264        (
6265            "var shared={v:1}; var o={a:shared,b:shared}; var c=structuredClone(o); c.a===c.b",
6266            "true",
6267        ),
6268        // `JSON.stringify` にも同じ「循環参照で無限再帰」バグがあった。仕様どおり
6269        // TypeError を投げるようにした(同一オブジェクトへの非循環な複数参照=
6270        // 構造共有は問題なく直列化できることも確認)。
6271        (
6272            "try { var a={}; a.self=a; JSON.stringify(a); 'no-throw' } catch(e) { 'threw' }",
6273            "threw",
6274        ),
6275        // 深いネスト(30段超)でもスタックオーバーフローせずTypeErrorをスローするかの保護テスト
6276        (
6277            "var cur={}; for(var i=0;i<40;i++){ cur={child:cur}; } try { JSON.stringify(cur); 'no-throw' } catch(e) { 'depth-exceeded' }",
6278            "depth-exceeded",
6279        ),
6280        (
6281            "var shared={v:1}; JSON.stringify({a:shared,b:shared})",
6282            "{\"a\":{\"v\":1},\"b\":{\"v\":1}}",
6283        ),
6284        // `console.log` の表示整形(`display_value`)にも同じ「循環参照で無限再帰」
6285        // バグがあった。`console.log(a)`(`a.self=a`)は特に日常的にありうる操作のため、
6286        // 一番踏みやすいケースだった(クラッシュ/ハングせず完走することを確認)。
6287        (
6288            "var a={}; a.self=a; console.log(a); 'ok'",
6289            "ok",
6290        ),
6291        (
6292            "var a=[1,2]; a.push(a); console.log(a); 'ok'",
6293            "ok",
6294        ),
6295        // `console.log` の表示整形(`display_value_inner`)が `Proxy` を
6296        // `Array`/`Plain` 以外への素通しフォールバックに落としており、
6297        // `[ 1, 2, 3 ]` のような角括弧付き表示にならず素の `"1,2,3"` に
6298        // なっていた(`Value::to_js_string` の Proxy 素通しバグ修正と同時に
6299        // 発見。target へ委譲して同じ表示ロジックへ再帰するよう修正)。
6300        (
6301            "console.log(new Proxy([1,2,3], {})); 'ok'",
6302            "ok",
6303        ),
6304        // `Value::to_js_string`(`Array.prototype.join`/`toString`・テンプレート
6305        // リテラル補間・暗黙の文字列連結が共通で使う変換)にも同じ「循環参照で
6306        // 無限再帰」バグが残っていた(`structuredClone`/`JSON.stringify`/
6307        // `console.log` は既に修正済みだったが、この経路だけ未点検だった)。
6308        // `a.push(a); a.join()` はクラッシュ/ハングせず完走し、循環要素は
6309        // 仕様どおり空文字列として扱われることを確認する。
6310        ("var a=[1]; a.push(a); a.join()", "1,1,"),
6311        ("var a=[1]; a.push(a); String(a); 'ok'", "ok"),
6312        ("var a=[1]; a.push(a); `${a}`; 'ok'", "ok"),
6313        // `Number.prototype.toString(radix)` が `radix` 引数を完全に無視し常に10進表示
6314        // だった(`n.toString(16)` 等の定番イディオムが動かなかった)。
6315        ("(255).toString(16)", "ff"),
6316        ("(8).toString(2)", "1000"),
6317        ("(255).toString()", "255"),
6318        ("(-255).toString(16)", "-ff"),
6319        ("(0).toString(2)", "0"),
6320        (
6321            "try { (1).toString(1); 'no-throw' } catch(e) { 'threw' }",
6322            "threw",
6323        ),
6324        // `parseInt` が `radix` 未指定時に `0x` 接頭辞を自動検出せず、明示的に
6325        // `radix=16` を渡しても `0x` を読み飛ばさないバグだった。
6326        ("parseInt('0xFF')", "255"),
6327        ("parseInt('0xFF', 16)", "255"),
6328        ("parseInt('FF', 16)", "255"),
6329        ("parseInt('10')", "10"),
6330        ("parseInt('-0x10')", "-16"),
6331        ("parseInt('10', 2)", "2"),
6332        // `Number.parseFloat`/`Number.parseInt`(ES2015)は仕様上グローバル
6333        // `parseFloat`/`parseInt` と同一の関数オブジェクトでなければならないが、
6334        // 以前は別々に構築されており `===` が `false` になっていた。
6335        ("Number.parseFloat === parseFloat", "true"),
6336        ("Number.parseInt === parseInt", "true"),
6337        ("Number.parseFloat.name", "parseFloat"),
6338        ("Number.parseInt.name", "parseInt"),
6339        // `JSON.stringify` は `Date` の `toJSON`(ISO 文字列)相当を特殊扱いする。
6340        // 以前は Date インスタンスが(`toJSON` を汎用的にチェックしないため)
6341        // プロパティなしの `{}` として直列化されてしまっていた。
6342        (
6343            "JSON.stringify(new Date(0))",
6344            "\"1970-01-01T00:00:00.000Z\"",
6345        ),
6346        (
6347            "JSON.stringify({d: new Date(0)})",
6348            "{\"d\":\"1970-01-01T00:00:00.000Z\"}",
6349        ),
6350        // 文字列化(テンプレートリテラル補間・暗黙の文字列連結)でも `[object Object]` ではなく
6351        // ISO 形式が出るようにした(`Value::to_js_string()` に `ObjKind::DateObj` 分岐を追加)。
6352        ("'' + new Date(0)", "1970-01-01T00:00:00.000Z"),
6353        ("`date=${new Date(0)}`", "date=1970-01-01T00:00:00.000Z"),
6354        // Object.defineProperty / Property attributes (writable, configurable, enumerable)
6355        (
6356            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); o.x = 2; o.x",
6357            "1",
6358        ),
6359        (
6360            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); delete o.x",
6361            "false",
6362        ),
6363        (
6364            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); Object.keys(o).length",
6365            "0",
6366        ),
6367        (
6368            "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",
6369            "0",
6370        ),
6371        (
6372            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); JSON.stringify(o)",
6373            "{}",
6374        ),
6375        (
6376            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); ({...o}).x",
6377            "undefined",
6378        ),
6379        (
6380            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); Object.assign({}, o).x",
6381            "undefined",
6382        ),
6383        (
6384            "var o={}; Object.defineProperty(o, 'x', {value: 1, writable: false, configurable: false, enumerable: false}); Reflect.deleteProperty(o, 'x')",
6385            "false",
6386        ),
6387        // `DataView.prototype.getBigInt64`/`.getBigUint64`/`.setBigInt64`/
6388        // `.setBigUint64`(ES2020)が丸ごと未対応だった。64bit整数はNumberの
6389        // 安全整数範囲(2^53)を超えうるためBigIntで往復する必要がある。
6390        // 2026-07-18 発見・実装)。
6391        (
6392            "var b=new ArrayBuffer(8); var v=new DataView(b); v.setBigInt64(0, 123456789012345n); v.getBigInt64(0).toString()",
6393            "123456789012345",
6394        ),
6395        (
6396            "var b=new ArrayBuffer(8); var v=new DataView(b); v.setBigInt64(0, -1n); v.getBigInt64(0).toString()",
6397            "-1",
6398        ),
6399        (
6400            "var b=new ArrayBuffer(8); var v=new DataView(b); v.setBigUint64(0, -1n); v.getBigUint64(0).toString()",
6401            "18446744073709551615",
6402        ),
6403        (
6404            "var b=new ArrayBuffer(8); var v=new DataView(b); v.setBigInt64(0, 1n, true); v.getBigInt64(0, true).toString()",
6405            "1",
6406        ),
6407        // `JSON.rawJSON`/`JSON.isRawJSON`(ES2025)が丸ごと未対応だった。
6408        // Numberの精度で表現できない巨大な整数をJSON文字列へ精度損失無しで
6409        // 埋め込むための機能。2026-07-18 発見・実装)。
6410        (
6411            "JSON.stringify({n: JSON.rawJSON('123456789012345678901234567890')})",
6412            "{\"n\":123456789012345678901234567890}",
6413        ),
6414        ("JSON.isRawJSON(JSON.rawJSON('42'))", "true"),
6415        ("JSON.isRawJSON({rawJSON: '42'})", "false"),
6416        ("JSON.isRawJSON(42)", "false"),
6417        (
6418            "(function(){ try { JSON.rawJSON(' 1'); return 'no-throw'; } catch(e) { return e instanceof Error; } })()",
6419            "true",
6420        ),
6421        (
6422            "(function(){ try { JSON.rawJSON('not json'); return 'no-throw'; } catch(e) { return e instanceof Error; } })()",
6423            "true",
6424        ),
6425        // `button.command`/`.commandForElement`(Invoker Commands API)が
6426        // 丸ごと未対応だった。`popoverTargetAction`/`.popoverTargetElement`と
6427        // 全く同じ反映・idref解決パターンで実装(クリック時の実際の
6428        // `CommandEvent`発火/組込み動作は本サイクルでは未対応。
6429        // 2026-07-18 発見・実装)。
6430        (
6431            "var b=document.createElement('button'); b.command='show-modal'; b.command",
6432            "show-modal",
6433        ),
6434        (
6435            "document.createElement('button').commandForElement",
6436            "null",
6437        ),
6438        // `commandForElement`セッターの往復(`b.commandForElement = 要素`→
6439        // `commandfor`属性へidを反映)。この方向はDOMツリーへの接続を
6440        // 要さない(`popoverTargetElement`と同じ、対象要素のidを直接
6441        // 読むだけの実装)ため、`document.getElementById`のアタッチ要件
6442        // (共有テストフィクスチャ`wrap`が本テストスイート内の別テストで
6443        // 未接続化され得る既知の不安定要素)に依存しない自己完結した形に
6444        // している。逆方向(属性→要素解決)は`popoverTargetElement`と
6445        // 全く同じ`get_element_by_id`経路を再利用しているため別途検証済み。
6446        (
6447            "var t=document.createElement('div'); t.setAttribute('id','cfe-target'); var b=document.createElement('button'); b.commandForElement=t; b.getAttribute('commandfor')",
6448            "cfe-target",
6449        ),
6450        // プレーンオブジェクトのプロパティ列挙順序(ECMA-262
6451        // `OrdinaryOwnPropertyKeys`)が丸ごと未対応だった(`Obj.props`が
6452        // `BTreeMap`のため常に文字列の辞書式順序になっていた、
6453        // architecture-level の既知ギャップ)。`BTreeMap`→`IndexMap`移行
6454        // (挿入順保持。2026-07-18完了)に続けて、`Object.keys`/`.values`/
6455        // `.entries`へ「正整数キー→数値昇順」「残り→挿入順」の2段階
6456        // 並べ替え(`spec_key_order`)を配線し、仕様どおりの列挙順序を
6457        // 完成させた。2026-07-21 に `for...in`/`JSON.stringify`/スプレッド構文/
6458        // `Object.assign` 等すべての列挙箇所への同ロジック横展開を完了。
6459        (
6460            "Object.keys({y:1,x:2,a:3}).join(',')",
6461            "y,x,a",
6462        ),
6463        (
6464            "var o={}; delete o.x; o.b=1; o.a=2; o.c=3; delete o.a; o.a=4; Object.keys(o).join(',')",
6465            "b,c,a",
6466        ),
6467        (
6468            "Object.keys({2:'b',1:'a',10:'c'}).join(',')",
6469            "1,2,10",
6470        ),
6471        (
6472            "Object.keys({10:'c',y:1,2:'b',x:2}).join(',')",
6473            "2,10,y,x",
6474        ),
6475        (
6476            "Object.values({10:'c',y:1,2:'b',x:2}).join(',')",
6477            "b,c,1,2",
6478        ),
6479        (
6480            "var res=[]; for(var k in {10:'c',y:1,2:'b',x:2}){ res.push(k); } res.join(',')",
6481            "2,10,y,x",
6482        ),
6483        (
6484            "JSON.stringify({10:'c',y:1,2:'b',x:2})",
6485            "{\"2\":\"b\",\"10\":\"c\",\"y\":1,\"x\":2}",
6486        ),
6487        (
6488            "Object.keys({...{10:'c',y:1,2:'b',x:2}}).join(',')",
6489            "2,10,y,x",
6490        ),
6491        (
6492            "Object.keys(Object.assign({}, {10:'c',y:1,2:'b',x:2})).join(',')",
6493            "2,10,y,x",
6494        ),
6495        (
6496            "Object.entries({2:'b',y:1,1:'a'}).map(e=>e.join(':')).join(',')",
6497            "1:a,2:b,y:1",
6498        ),
6499        // `01`(先頭ゼロ)は仕様上「配列インデックス」ではなく通常の文字列
6500        // キー扱いのため数値昇順の対象外(挿入順のまま)。
6501        (
6502            "Object.keys({'01':'x', 1:'y'}).join(',')",
6503            "1,01",
6504        ),
6505        // ECMAScript Internationalization API (Intl)
6506        (
6507            "typeof Intl",
6508            "object",
6509        ),
6510        (
6511            "Intl.getCanonicalLocales('ja-JP').join(',')",
6512            "ja-JP",
6513        ),
6514        // `ja-JP`も`en-US`と同じ3桁カンマ区切り書式を使う言語のため`"123,456"`が
6515        // 正しい期待値(この処理系はロケール別の書式データベースを持たず全て
6516        // 同じ簡略書式を使うため、ロケール文字列自体は解析結果に影響しない)。
6517        // 元は書式化ロジック実装前の無加工出力`"123456"`をそのまま期待値に
6518        // していた誤り(2026-07-18 発見・修正)。
6519        (
6520            "new Intl.NumberFormat('ja-JP').format(123456)",
6521            "123,456",
6522        ),
6523        (
6524            "new Intl.DateTimeFormat('ja-JP').resolvedOptions().locale",
6525            "ja-JP",
6526        ),
6527        (
6528            "new Intl.Collator().compare('a', 'b')",
6529            "-1",
6530        ),
6531        (
6532            "new Intl.PluralRules().select(1)",
6533            "one",
6534        ),
6535        (
6536            "new Intl.RelativeTimeFormat().format(-1, 'day')",
6537            "1 days ago",
6538        ),
6539        (
6540            "new Intl.ListFormat().format(['a', 'b'])",
6541            "a, b",
6542        ),
6543        (
6544            "new Intl.DisplayNames(['en'], {type: 'language'}).of('ja')",
6545            "ja",
6546        ),
6547        // RegExp u/v flags & Unicode property escapes
6548        (
6549            "new RegExp('a', 'u').unicode",
6550            "true",
6551        ),
6552        (
6553            "new RegExp('a', 'v').unicodeSets",
6554            "true",
6555        ),
6556        (
6557            "/\\p{Letter}/u.test('A')",
6558            "true",
6559        ),
6560        (
6561            "/\\p{Number}/u.test('9')",
6562            "true",
6563        ),
6564        // Generator.prototype.return(v) & try...finally
6565        (
6566            "var finRan = false; function* g() { try { yield 1; } finally { finRan = true; } } var it = g(); it.next(); it.return(42); finRan",
6567            "true",
6568        ),
6569        // `Object.create(null)`/`Object.groupBy()`/`Object.setPrototypeOf(obj,null)`が
6570        // 実際には`Object.prototype`のメソッド(`hasOwnProperty`/`toString`等)を
6571        // 引き続き持ってしまうバグ(`proto: None`だけでは「明示的にnull
6572        // プロトタイプ」なのか「単に通常オブジェクトでチェーンが尽きた」のか
6573        // 区別できず、`get_property`の共通メソッドフォールバックが無条件に
6574        // 適用されていた)。`null_proto`フラグを新設して修正(2026-07-18
6575        // 発見・実装)。
6576        (
6577            "typeof Object.create(null).hasOwnProperty",
6578            "undefined",
6579        ),
6580        (
6581            "typeof Object.create(null).toString",
6582            "undefined",
6583        ),
6584        (
6585            "Object.getPrototypeOf(Object.create(null))",
6586            "null",
6587        ),
6588        // 通常のオブジェクトリテラルは引き続き`Object.prototype`のメソッドを
6589        // 継承する(`null_proto`の導入で既存の通常オブジェクトの挙動を
6590        // 壊していないことの回帰確認)。
6591        (
6592            "typeof {}.hasOwnProperty",
6593            "function",
6594        ),
6595        (
6596            "typeof Object.groupBy([1,2,3], x=>x%2).hasOwnProperty",
6597            "undefined",
6598        ),
6599        (
6600            "var o={}; Object.setPrototypeOf(o, null); typeof o.toString",
6601            "undefined",
6602        ),
6603        // `setPrototypeOf`で実オブジェクトへ戻せば`null_proto`が解除され、
6604        // 継承したメソッドが再び見えることの確認。
6605        (
6606            "var o={}; Object.setPrototypeOf(o, null); Object.setPrototypeOf(o, Object.prototype); typeof o.hasOwnProperty",
6607            "function",
6608        ),
6609        // `crypto.subtle.digest('SHA-256', data)`が丸ごと未対応だった
6610        // (`Float16Array`/`oklch()`と同格の「1ビットの誤りが静かに間違った
6611        // 結果を生み続けるリスク」と判断し従来は着手見送りだったが、SHA-256は
6612        // NIST/RFC公開の既知の正解値で完全一致検証できる純粋なビット演算
6613        // アルゴリズムのため浮動小数点近似とは性質が異なり、リスク評価を
6614        // 見直し実装した。以下はFIPS 180-4/一般に広く知られる既知の正解値
6615        // (Known Answer Test)。2026-07-18 発見・実装)。
6616        (
6617            "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(''))",
6618            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
6619        ),
6620        (
6621            "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(''))",
6622            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
6623        ),
6624        (
6625            "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(''))",
6626            "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
6627        ),
6628        // `algorithm`引数を`{name: "SHA-256"}`形式(実ブラウザでも有効な
6629        // もう一方の呼び出し形式)で渡しても同じ結果になることの確認。
6630        (
6631            "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(''))",
6632            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
6633        ),
6634        // 未対応アルゴリズム名は仕様どおりPromiseのreject(例外)で通知される。
6635        (
6636            "await crypto.subtle.digest('SHA-1', new TextEncoder().encode('x')).then(() => 'resolved', () => 'rejected')",
6637            "rejected",
6638        ),
6639        // `TextDecoder.decode(arrayBuffer)`(TypedArrayビューではなく生の
6640        // `ArrayBuffer`を直接渡す、仕様上有効な呼び出し形式)が常に空文字列を
6641        // 返すバグだった(`crypto.subtle.digest`実装の検証中に発見した
6642        // `new Uint8Array(arrayBuffer)`のゼロ埋めバグと同系統の「生の
6643        // ArrayBufferから実データを読む経路」の欠落)。2026-07-18 発見・実装。
6644        (
6645            "new TextDecoder().decode(new TextEncoder().encode('hello').buffer)",
6646            "hello",
6647        ),
6648        (
6649            "new TextDecoder().decode(new Uint8Array([65,116,109,79,83]).buffer)",
6650            "AtmOS",
6651        ),
6652        // `DataView` を `TextDecoder.decode` や `crypto.subtle.digest` や
6653        // `new Uint8Array(dataView)` に渡した際、`_dv_bytes` が参照されず
6654        // 全ゼロ埋めになるバグを修正(2026-07-21 発見・実装)。
6655        (
6656            "new TextDecoder().decode(new DataView(new TextEncoder().encode('AtmOS').buffer))",
6657            "AtmOS",
6658        ),
6659        (
6660            "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(''))",
6661            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
6662        ),
6663        (
6664            "new Uint8Array(new DataView(new Uint8Array([65,66,67]).buffer)).join('')",
6665            "656667",
6666        ),
6667        // `String.prototype.normalize()` 日本語ひらがな・カタカナの濁点・半濁点の分解(NFD)・合成(NFC)
6668        (
6669            "'が'.normalize('NFD') === 'か\\u3099'",
6670            "true",
6671        ),
6672        (
6673            "'か\\u3099'.normalize('NFC')",
6674            "が",
6675        ),
6676        (
6677            "'パ'.normalize('NFD') === 'ハ\\u309A'",
6678            "true",
6679        ),
6680        (
6681            "'ハ\\u309A'.normalize('NFC')",
6682            "パ",
6683        ),
6684        // `structuredClone` 極端な深いネストの保護(DataCloneError 例外の返却)。
6685        // 元は閾値1000・ネスト1100段でテストしていたが、これ自体が
6686        // ブート時点の64KB初期コアスタックを枯渇させ、QEMU起動が丸ごと
6687        // 無応答になる重大な回帰を引き起こしていた(実測で深さ80段でも
6688        // 無応答、50段は安全と確認)。`deep_clone_value`の閾値を30へ
6689        // 引き下げ、ネスト段数も安全に確認できた範囲内の40へ縮小する
6690        // (2026-07-18 発見・修正)。
6691        (
6692            "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; }",
6693            "DataCloneError",
6694        ),
6695        // `structuredClone` フラットな大配列(要素数>30のオブジェクト配列)が
6696        // 累積オブジェクト数(seen.len())ではなく再帰深さ(depth)で判定され
6697        // 正しく複製されることの検証(seen.len() ガード時代の過剰エラーバグ防止)。
6698        (
6699            "var a = []; for (var i = 0; i < 50; i++) { a.push({v: i}); } var c = structuredClone(a); c.length === 50 && c[49].v === 49",
6700            "true",
6701        ),
6702        // `JSON.parse`(`os_lib::json::parse`のRust側相互再帰パーサ)にも
6703        // `structuredClone`と同種のRustネイティブ再帰スタックオーバーフロー
6704        // リスクがあったため、同じ閾値30の深さガードを新設(2026-07-21)。
6705        // 20階層のネストは閾値内で安全にパースできる。
6706        (
6707            "(function(){ var s = '['.repeat(20) + '0' + ']'.repeat(20); try { JSON.parse(s); return 'ok'; } catch (e) { return 'error'; } })()",
6708            "ok",
6709        ),
6710        // GlobalEventHandlers (onchange / onclick 等) の属性・プロパティ統合テスト
6711        (
6712            "var d=document.createElement('div'); var r='none'; d.onchange=function(){r='ok';}; d.dispatchEvent(new Event('change')); r",
6713            "ok",
6714        ),
6715        // outerHTML セッターのテスト
6716        (
6717            "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",
6718            "B,y",
6719        ),
6720    ];
6721    let mut passed = 0;
6722    for (src, expect) in cases {
6723        let mut rt = JsRuntime::new();
6724        match rt.eval(src) {
6725            Ok(v) if &v.to_js_string() == expect => passed += 1,
6726            Ok(v) => crate::println!(
6727                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
6728                src,
6729                v.to_js_string(),
6730                expect
6731            ),
6732            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
6733        }
6734    }
6735
6736    // setInterval / clearInterval の2段階評価テスト
6737    {
6738        let mut rt = JsRuntime::new();
6739        let _ = rt.eval("var n=0; var id=setInterval(function(){n++;}, 10);");
6740        match rt.eval("n > 0") {
6741            Ok(v) if v.to_js_string() == "true" => passed += 1,
6742            Ok(v) => crate::println!(
6743                "JS_SELFTEST FAIL: `setInterval` => n > 0 is `{}` (want true)",
6744                v.to_js_string()
6745            ),
6746            Err(e) => crate::println!("JS_SELFTEST ERR:  `setInterval` => {}", e),
6747        }
6748    }
6749    {
6750        let mut rt = JsRuntime::new();
6751        let _ = rt.eval("var n=0; var id=setInterval(function(){n++;}, 10); clearInterval(id);");
6752        match rt.eval("n") {
6753            Ok(v) if v.to_js_string() == "0" => passed += 1,
6754            Ok(v) => crate::println!(
6755                "JS_SELFTEST FAIL: `clearInterval` => n is `{}` (want 0)",
6756                v.to_js_string()
6757            ),
6758            Err(e) => crate::println!("JS_SELFTEST ERR:  `clearInterval` => {}", e),
6759        }
6760    }
6761    // `setInterval`の発火回数上限(以前は8回。実サイト www.sugi-lab.net の
6762    // 統計カウンターアニメーション`increment=target/100`が最終値の8%程度で
6763    // 頭打ちになり正しい数値へ到達できないバグだった。2026-07-22発見・修正で
6764    // 200回へ引き上げ)の回帰防止テスト。target=50, increment=0.5として、
6765    // 100回の発火で正確にtargetへ到達し`clearInterval`されることを検証する
6766    // (8回上限のままなら`n`は50に到達せず4のまま止まる)。
6767    {
6768        let mut rt = JsRuntime::new();
6769        let _ = rt.eval(
6770            "var n=0; var target=50; var inc=target/100; \
6771             var id=setInterval(function(){ \
6772                 n+=inc; \
6773                 if (n>=target) { n=target; clearInterval(id); } \
6774             }, 1);",
6775        );
6776        match rt.eval("n") {
6777            Ok(v) if v.to_js_string() == "50" => passed += 1,
6778            Ok(v) => crate::println!(
6779                "JS_SELFTEST FAIL: setInterval 統計カウンター模擬 => n is `{}` (want 50)",
6780                v.to_js_string()
6781            ),
6782            Err(e) => crate::println!("JS_SELFTEST ERR:  setInterval 統計カウンター模擬 => {}", e),
6783        }
6784    }
6785    // `AbortSignal.timeout(ms)`(ES2022)の2段階評価テスト(setTimeout 経由で
6786    // マクロタスクキューが解ける必要があるため setInterval と同じ2段階構成)。
6787    let abort_timeout_total = 1;
6788    {
6789        let mut rt = JsRuntime::new();
6790        let _ = rt.eval(
6791            "var fired=false; var s=AbortSignal.timeout(0); s.addEventListener('abort', function(){fired=true;});",
6792        );
6793        // `s.reason.name`(丸ごと未対応だった。以前は既定の `AbortError` の
6794        // ままで仕様上の `TimeoutError`ではなかったバグ。2026-07-16 発見・
6795        // 実装)も合わせて検証する。
6796        match rt.eval("fired + ',' + s.reason.name") {
6797            Ok(v) if v.to_js_string() == "true,TimeoutError" => passed += 1,
6798            Ok(v) => crate::println!(
6799                "JS_SELFTEST FAIL: `AbortSignal.timeout` => `{}` (want `true,TimeoutError`)",
6800                v.to_js_string()
6801            ),
6802            Err(e) => crate::println!("JS_SELFTEST ERR:  `AbortSignal.timeout` => {}", e),
6803        }
6804    }
6805
6806    // DOM 連携(Phase 3)。小さな DOM を構築してから評価する。
6807    let dom_html = "<div id='out'>0</div><p class='msg'>hi</p><button id='b' class='x'>go</button>";
6808    let dom_cases: &[(&str, &str)] = &[
6809        ("document.getElementById('out').textContent", "0"),
6810        ("var e=document.getElementById('out'); e.textContent='42'; e.textContent", "42"),
6811        ("document.getElementById('out').tagName", "DIV"),
6812        ("document.querySelector('.msg').textContent", "hi"),
6813        ("document.querySelectorAll('div').length", "1"),
6814        ("document.getElementById('b').classList.contains('x')", "true"),
6815        ("var e=document.getElementById('b'); e.classList.add('y'); e.classList.contains('y')", "true"),
6816        ("var e=document.getElementById('b'); e.classList.toggle('x'); e.classList.contains('x')", "false"),
6817        ("var e=document.getElementById('out'); e.style.color='red'; e.style.color", "red"),
6818        ("document.getElementById('missing') === null", "true"),
6819        ("document.getElementById('b').getAttribute('id')", "b"),
6820        // `document.children`/`.childElementCount`(`ParentNode`ミックスイン
6821        // の`Document`側配線漏れ。丸ごと未対応だった。bare `cases`配列では
6822        // `<html>`未構築のため、実際にHTMLフィクスチャがあるこちらで検証。
6823        // 2026-07-17 発見・実装)。
6824        ("document.children.length", "1"),
6825        ("document.children[0] === document.documentElement", "true"),
6826        ("document.childElementCount", "1"),
6827        // addEventListener + クリック発火(ブリッジ経由でディスパッチ)
6828        ("var n=0; document.getElementById('b').addEventListener('click', () => n++); n", "0"),
6829        // 動的ノード(createElement / appendChild / innerHTML / remove)
6830        ("document.createElement('DIV').tagName", "DIV"),
6831        ("var d=document.createElement('div'); d.textContent='hi'; d.textContent", "hi"),
6832        // textContent/innerHTML は WebIDL 上 nullable (DOMString?) につき null/undefined 代入は空文字列になる
6833        ("var d=document.createElement('div'); d.textContent='hi'; d.textContent=null; d.textContent===''", "true"),
6834        ("var d=document.createElement('div'); d.innerHTML='<b>x</b>'; d.innerHTML=undefined; d.innerHTML===''", "true"),
6835        ("var c=document.getElementById('out'); var s=document.createElement('span'); s.textContent='X'; c.appendChild(s); c.textContent", "0X"),
6836        ("var c=document.getElementById('out'); c.innerHTML='<b>Y</b>'; c.textContent", "Y"),
6837        ("var c=document.getElementById('out'); var i=document.createElement('i'); c.appendChild(i); document.querySelector('i') === null", "false"),
6838        ("var c=document.getElementById('out'); c.innerHTML='<span class=z>1</span><span class=z>2</span>'; document.querySelectorAll('.z').length", "2"),
6839        ("var c=document.getElementById('out'); var t=document.createTextNode('TT'); c.appendChild(t); c.textContent", "0TT"),
6840        ("var c=document.getElementById('out'); c.appendChild(document.createElement('em')); document.querySelector('em').remove(); document.querySelector('em') === null", "true"),
6841        // `ariaActiveDescendantElement`(ARIA Element Reflection。
6842        // `aria-activedescendant`のidref文字列ではなく実際のElement参照を
6843        // 返す/設定する。`popoverTargetElement`と同じidref解決パターン。
6844        // 丸ごと未対応だった。既にDOMツリーに接続されたフィクスチャ要素
6845        // (`get_element_by_id`は接続済みノードしか見ない)が必要なため、
6846        // bare `cases`配列ではなくこちらのfixture付き配列で検証。
6847        // 2026-07-18 発見・実装)。
6848        (
6849            "var a=document.createElement('div'); var b=document.getElementById('out'); a.ariaActiveDescendantElement=b; a.getAttribute('aria-activedescendant')+'|'+(a.ariaActiveDescendantElement===b)",
6850            "out|true",
6851        ),
6852        // `ariaLabelledByElements`/`ariaDescribedByElements`等(ARIA Element
6853        // Reflection、複数形)が丸ごと未対応だった。空白区切りidref属性を
6854        // 実際のElement参照の配列として読み書きする。単数形の
6855        // `ariaActiveDescendantElement`と同じ理由で接続済みfixture要素が
6856        // 必要。2026-07-18 発見・実装)。
6857        (
6858            "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)",
6859            "out b|2|true|true",
6860        ),
6861        (
6862            "document.createElement('div').ariaDescribedByElements.length",
6863            "0",
6864        ),
6865        // `parent.moveBefore(movedNode, referenceNode)`(丸ごと未対応
6866        // だった。この処理系には保持すべき「生きた状態」自体が無いため、
6867        // `insertBefore`と同じdetach+挿入の簡略実装で仕様どおり動作する。
6868        // 2026-07-18 発見・実装)。
6869        (
6870            "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",
6871            "0",
6872        ),
6873        (
6874            "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",
6875            "true",
6876        ),
6877        (
6878            "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; }",
6879            "NotFoundError",
6880        ),
6881    ];
6882    let total = cases.len() + dom_cases.len();
6883    for (src, expect) in dom_cases {
6884        let mut rt = JsRuntime::new();
6885        rt.dom
6886            .borrow_mut()
6887            .build_from(&crate::os_lib::dom::parse_html(dom_html));
6888        match rt.eval(src) {
6889            Ok(v) if &v.to_js_string() == expect => passed += 1,
6890            Ok(v) => crate::println!(
6891                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
6892                src,
6893                v.to_js_string(),
6894                expect
6895            ),
6896            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
6897        }
6898    }
6899
6900    // 要素 DOM API(hasAttribute/removeAttribute/matches/closest/element querySelector/ナビ)。
6901    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>";
6902    let dom2_cases: &[(&str, &str)] = &[
6903        ("document.getElementById('t').hasAttribute('class')", "true"),
6904        ("document.getElementById('t').hasAttribute('nope')", "false"),
6905        ("document.getElementById('t').getAttribute('data-k')", "v"),
6906        ("var e=document.getElementById('t'); e.removeAttribute('class'); e.hasAttribute('class')", "false"),
6907        ("document.getElementById('t').matches('.a')", "true"),
6908        ("document.getElementById('t').matches('.z')", "false"),
6909        ("document.getElementById('t').matches('p')", "true"),
6910        ("document.getElementById('t').closest('.container').id", "wrap"),
6911        ("document.getElementById('t').closest('#wrap').id", "wrap"),
6912        ("document.getElementById('t').closest('p').id", "t"),
6913        ("document.getElementById('t').closest('.nope') === null", "true"),
6914        ("document.getElementById('wrap').querySelector('.a').id", "t"),
6915        ("document.getElementById('wrap').querySelector('span').id", "s"),
6916        ("document.getElementById('wrap').querySelectorAll('*').length", "2"),
6917        // コンパウンドセレクタ(タグ+クラス、以前は文字列全体をタグ名として比較しており
6918        // 常に false になっていた)。
6919        ("document.getElementById('t').matches('p.a')", "true"),
6920        ("document.getElementById('t').matches('p.z')", "false"),
6921        ("document.getElementById('t').matches('p.a.b')", "true"),
6922        // 属性セレクタ(以前は丸ごと未対応で常に false になっていた)。
6923        ("document.getElementById('t').matches('[data-k]')", "true"),
6924        ("document.getElementById('t').matches('[data-k=v]')", "true"),
6925        ("document.getElementById('t').matches('[data-k=x]')", "false"),
6926        ("document.getElementById('t').matches('[data-k^=v]')", "true"),
6927        // 結合子(子孫/子、以前は丸ごと未対応で常に false になっていた)。
6928        ("document.getElementById('t').matches('#wrap p')", "true"),
6929        ("document.getElementById('t').matches('#wrap > p')", "true"),
6930        ("document.getElementById('t').matches('#wrap > span')", "false"),
6931        // 一般兄弟結合子(`p ~ span`)。
6932        ("document.getElementById('s').matches('p ~ span')", "true"),
6933        ("document.getElementById('s').matches('span ~ span')", "false"),
6934        // querySelector(All) でも同じコンパウンド/属性セレクタが機能する。
6935        ("document.getElementById('wrap').querySelector('p.a').id", "t"),
6936        ("document.getElementById('wrap').querySelectorAll('[data-k]').length", "1"),
6937        // `:not()` 疑似クラス。
6938        ("document.getElementById('t').matches(':not(span)')", "true"),
6939        ("document.getElementById('t').matches(':not(p)')", "false"),
6940        ("document.getElementById('t').matches('p:not(.z)')", "true"),
6941        ("document.getElementById('t').matches('p:not(.a)')", "false"),
6942        ("document.getElementById('wrap').querySelectorAll(':not(span)').length", "1"),
6943        // `:scope` 疑似クラス(Selectors Level 4。丸ごと未対応だった)。
6944        // `element.querySelector(All)`/`matches`/`closest` における `:scope` は
6945        // 呼び出し元の要素自身を指す。
6946        ("document.getElementById('wrap').querySelectorAll(':scope > p').length", "1"),
6947        ("document.getElementById('wrap').querySelectorAll(':scope > p')[0].id", "t"),
6948        ("document.getElementById('wrap').querySelector(':scope > span').id", "s"),
6949        ("document.getElementById('wrap').querySelectorAll(':scope > .nope').length", "0"),
6950        ("document.getElementById('t').matches(':scope')", "true"),
6951        ("document.getElementById('t').closest(':scope').id", "t"),
6952        // 位置系疑似クラス(`:first-child`/`:last-child`/`:only-child`/`:nth-child()`)。
6953        ("document.getElementById('t').matches(':first-child')", "true"),
6954        ("document.getElementById('t').matches(':last-child')", "false"),
6955        ("document.getElementById('s').matches(':last-child')", "true"),
6956        ("document.getElementById('t').matches(':only-child')", "false"),
6957        ("document.getElementById('t').matches(':nth-child(1)')", "true"),
6958        ("document.getElementById('s').matches(':nth-child(2)')", "true"),
6959        ("document.getElementById('t').matches(':nth-child(odd)')", "true"),
6960        ("document.getElementById('s').matches(':nth-child(odd)')", "false"),
6961        ("document.getElementById('s').matches(':nth-child(2n)')", "true"),
6962        // `:nth-last-child()`(末尾から数える版。丸ごと未対応だった)。
6963        // #t/#s は2要素中1番目/2番目 → 末尾からは2番目/1番目。
6964        ("document.getElementById('s').matches(':nth-last-child(1)')", "true"),
6965        ("document.getElementById('t').matches(':nth-last-child(1)')", "false"),
6966        ("document.getElementById('t').matches(':nth-last-child(2)')", "true"),
6967        ("document.getElementById('s').matches(':nth-last-child(odd)')", "true"),
6968        // `:first-of-type`/`:last-of-type`/`:only-of-type`/`:nth-of-type()`(タグ名が
6969        // 同じ兄弟のみを数える点以外は `:nth-child()` 系と同じ規則)。各テストケースは
6970        // `document.getElementById` の「同一 id が複数あれば最初のものを返す」挙動と
6971        // 衝突しないよう、テストごとに固有の id を使う(以前は全ケースで同じ
6972        // p1/s1/p2/p3 を使い回しており、2回目以降は常に最初に追加した要素を
6973        // 参照してしまっていた)。
6974        (
6975            "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')",
6976            "true",
6977        ),
6978        (
6979            "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')",
6980            "true",
6981        ),
6982        (
6983            "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')",
6984            "false",
6985        ),
6986        (
6987            "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')",
6988            "true",
6989        ),
6990        (
6991            "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')",
6992            "false",
6993        ),
6994        (
6995            "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)')",
6996            "true",
6997        ),
6998        (
6999            "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)')",
7000            "false",
7001        ),
7002        // `:nth-last-of-type()`(末尾から数える版。丸ごと未対応だった)。
7003        // p 要素は a/c/d の3つ → 末尾からは d=1番目, c=2番目, a=3番目。
7004        (
7005            "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)')",
7006            "true",
7007        ),
7008        (
7009            "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)')",
7010            "true",
7011        ),
7012        // `:is()`/`:where()`(コンパウンドセレクタのリストのみ対応。結合子を含む
7013        // 複雑なセレクタは非対応)。
7014        ("document.getElementById('t').matches(':is(.z, .a)')", "true"),
7015        ("document.getElementById('t').matches(':is(.z, .y)')", "false"),
7016        ("document.getElementById('t').matches(':where(span, p.a)')", "true"),
7017        ("document.getElementById('s').matches(':is(span)')", "true"),
7018        ("document.getElementById('t').parentElement.id", "wrap"),
7019        ("document.getElementById('wrap').children.length", "2"),
7020        ("document.getElementById('wrap').childElementCount", "2"),
7021        // `node.hasChildNodes()`(丸ごと未対応だった。2026-07-15 発見・実装)。
7022        ("document.getElementById('wrap').hasChildNodes()", "true"),
7023        ("document.createElement('div').hasChildNodes()", "false"),
7024        // `document.createElementNS(ns, tag)`(丸ごと未対応だった。SVG 要素を
7025        // 動的生成する標準的な定番パターン。2026-07-15 発見・実装)。
7026        // `*AttributeNS` と同じく名前空間自体はモデル化せず非 NS 版へ委譲する
7027        // 簡略実装のため、生成される要素は通常の `createElement` と同じ。
7028        (
7029            "document.createElementNS('http://www.w3.org/2000/svg', 'circle').tagName",
7030            "CIRCLE",
7031        ),
7032        (
7033            "var e=document.createElementNS('http://www.w3.org/2000/svg', 'rect'); \
7034             e.setAttribute('width', '10'); e.getAttribute('width')",
7035            "10",
7036        ),
7037        ("document.getElementById('wrap').firstElementChild.id", "t"),
7038        ("document.getElementById('wrap').lastElementChild.id", "s"),
7039        ("document.getElementById('t').nextElementSibling.id", "s"),
7040        ("document.getElementById('s').previousElementSibling.id", "t"),
7041        ("document.getElementById('wrap').getAttributeNames().join(',')", "id,class"),
7042        // `element.attributes`(DOM 標準の `NamedNodeMap`。丸ごと未対応だった。
7043        // `{name,value}` オブジェクトの配列という簡略表現)。
7044        ("document.getElementById('wrap').attributes.length", "2"),
7045        ("document.getElementById('wrap').attributes[0].name", "id"),
7046        ("document.getElementById('wrap').attributes[0].value", "wrap"),
7047        // `getAttributeNode`/`setAttributeNode`/`removeAttributeNode`
7048        // (丸ごと未対応だった。2026-07-14 発見・実装)。
7049        ("document.getElementById('wrap').getAttributeNode('id').value", "wrap"),
7050        ("document.getElementById('wrap').getAttributeNode('missing')", "null"),
7051        (
7052            "var e=document.getElementById('wrap'); \
7053             e.setAttributeNode({name:'data-x', value:'1'}); e.getAttribute('data-x')",
7054            "1",
7055        ),
7056        (
7057            "var e=document.getElementById('wrap'); var a=e.getAttributeNode('id'); \
7058             e.removeAttributeNode(a); e.hasAttribute('id')",
7059            "false",
7060        ),
7061        // `document.createAttribute(name)`(丸ごと未対応だった。`Attr` ノードを
7062        // 新規に作ってから `setAttributeNode()` で取り付ける定番パターン。
7063        // 2026-07-15 発見・実装)。仕様どおり名前は小文字化される。
7064        ("document.createAttribute('DATA-X').name", "data-x"),
7065        ("document.createAttribute('data-x').value", ""),
7066        ("document.createAttribute('data-x').ownerElement", "null"),
7067        (
7068            "var e=document.getElementById('wrap'); var a=document.createAttribute('data-y'); \
7069             a.value='42'; e.setAttributeNode(a); e.getAttribute('data-y')",
7070            "42",
7071        ),
7072        // Fullscreen API(`element.requestFullscreen()`/`document.exitFullscreen()`/
7073        // `document.fullscreenElement`。丸ごと未対応だった。実際の画面占有切替は
7074        // 行わず状態のみ追跡する簡略実装)。
7075        ("document.fullscreenElement", "null"),
7076        ("document.fullscreenEnabled", "true"),
7077        (
7078            "var e=document.getElementById('wrap'); e.requestFullscreen(); document.fullscreenElement===e",
7079            "true",
7080        ),
7081        (
7082            "var e=document.getElementById('wrap'); e.requestFullscreen(); document.exitFullscreen(); document.fullscreenElement",
7083            "null",
7084        ),
7085        // Pointer Lock API(`element.requestPointerLock()`/`document.exitPointerLock()`/
7086        // `document.pointerLockElement`。丸ごと未対応だった。Fullscreen API と
7087        // 同じ「状態のみ追跡する」簡略実装)。
7088        ("document.pointerLockElement", "null"),
7089        (
7090            "var e=document.getElementById('wrap'); e.requestPointerLock(); document.pointerLockElement===e",
7091            "true",
7092        ),
7093        (
7094            "var e=document.getElementById('wrap'); e.requestPointerLock(); document.exitPointerLock(); document.pointerLockElement",
7095            "null",
7096        ),
7097        // Picture-in-Picture API(`element.requestPictureInPicture()`/
7098        // `document.exitPictureInPicture()`/`document.pictureInPictureElement`。
7099        // 丸ごと未対応だった。Fullscreen/Pointer Lock API と同じ
7100        // 「状態のみ追跡する」簡略実装)。
7101        ("document.pictureInPictureElement", "null"),
7102        ("document.pictureInPictureEnabled", "true"),
7103        (
7104            "var e=document.getElementById('wrap'); e.requestPictureInPicture(); \
7105             document.pictureInPictureElement===e",
7106            "true",
7107        ),
7108        (
7109            "var e=document.getElementById('wrap'); e.requestPictureInPicture(); \
7110             document.exitPictureInPicture(); document.pictureInPictureElement",
7111            "null",
7112        ),
7113        // `document.createTreeWalker(root)`(丸ごと未対応だった。DOM 部分木を
7114        // 走査する定番パターン。`wrap` の子は `#t`(p)/`#s`(span) の2要素)。
7115        (
7116            "document.createTreeWalker(document.getElementById('wrap')).currentNode.id",
7117            "wrap",
7118        ),
7119        (
7120            "var tw=document.createTreeWalker(document.getElementById('wrap')); \
7121             tw.firstChild(); tw.currentNode.id",
7122            "t",
7123        ),
7124        (
7125            "var tw=document.createTreeWalker(document.getElementById('wrap')); \
7126             tw.firstChild(); tw.nextSibling(); tw.currentNode.id",
7127            "s",
7128        ),
7129        (
7130            "var tw=document.createTreeWalker(document.getElementById('wrap')); \
7131             tw.firstChild(); tw.nextSibling(); tw.parentNode(); tw.currentNode.id",
7132            "wrap",
7133        ),
7134        (
7135            "var tw=document.createTreeWalker(document.getElementById('wrap')); \
7136             tw.nextNode(); tw.currentNode.id",
7137            "t",
7138        ),
7139        (
7140            "var tw=document.createTreeWalker(document.getElementById('wrap')); \
7141             var r=tw.parentNode(); r === null && tw.currentNode.id === 'wrap'",
7142            "true",
7143        ),
7144        // `document.createNodeIterator(root)`(丸ごと未対応だった。`TreeWalker` とは
7145        // 異なる「参照ノード+前後どちら側を指しているか」のポインタモデル)。
7146        (
7147            "document.createNodeIterator(document.getElementById('wrap')).nextNode().id",
7148            "wrap",
7149        ),
7150        (
7151            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7152             ni.nextNode(); ni.nextNode().id",
7153            "t",
7154        ),
7155        // `#t` の中身はテキストノード "x" のため pre-order 3件目はそのテキスト
7156        // ノード(`nodeValue` で判定。`#s` に到達するのは5件目)。
7157        (
7158            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7159             ni.nextNode(); ni.nextNode(); ni.nextNode().nodeValue",
7160            "x",
7161        ),
7162        (
7163            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7164             ni.nextNode(); ni.nextNode(); ni.nextNode(); ni.nextNode().id",
7165            "s",
7166        ),
7167        (
7168            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7169             ni.nextNode(); ni.nextNode(); ni.nextNode(); ni.nextNode(); ni.nextNode(); \
7170             ni.nextNode() === null",
7171            "true",
7172        ),
7173        (
7174            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7175             ni.nextNode(); ni.nextNode(); ni.previousNode().id",
7176            "t",
7177        ),
7178        (
7179            "var ni=document.createNodeIterator(document.getElementById('wrap')); \
7180             ni.nextNode(); ni.nextNode(); ni.previousNode(); ni.previousNode().id",
7181            "wrap",
7182        ),
7183        // `element.contentEditable`/`.isContentEditable`/`document.designMode`
7184        // (丸ごと未対応だった。属性値の反映のみを行う簡略実装)。
7185        ("document.getElementById('wrap').contentEditable", "inherit"),
7186        ("document.getElementById('wrap').isContentEditable", "false"),
7187        (
7188            "var e=document.getElementById('wrap'); e.contentEditable='true'; \
7189             e.contentEditable + ',' + e.isContentEditable",
7190            "true,true",
7191        ),
7192        (
7193            "var e=document.getElementById('wrap'); e.contentEditable='plaintext-only'; \
7194             e.contentEditable + ',' + e.isContentEditable",
7195            "plaintext-only,true",
7196        ),
7197        (
7198            "var e=document.getElementById('wrap'); e.contentEditable='bogus'; e.contentEditable",
7199            "inherit",
7200        ),
7201        ("document.designMode", "off"),
7202        // `element.spellcheck`/`.inputMode`/`.enterKeyHint`(`spellcheck`/
7203        // `inputmode`/`enterkeyhint` グローバル属性の JS プロパティ版。丸ごと
7204        // 未対応だった。属性値の反映のみを行う簡略実装。2026-07-15 発見・実装)。
7205        ("document.getElementById('wrap').spellcheck", "true"),
7206        (
7207            "var e=document.getElementById('wrap'); e.spellcheck=false; e.spellcheck",
7208            "false",
7209        ),
7210        ("document.getElementById('wrap').inputMode", ""),
7211        (
7212            "var e=document.getElementById('wrap'); e.inputMode='numeric'; e.inputMode",
7213            "numeric",
7214        ),
7215        (
7216            "var e=document.getElementById('wrap'); e.enterKeyHint='search'; e.enterKeyHint",
7217            "search",
7218        ),
7219        // `element.autocapitalize`/`.popover`(`autocapitalize`/`popover`
7220        // グローバル属性の JS プロパティ版。丸ごと未対応だった。2026-07-15
7221        // 発見・実装。`popover` は `showPopover()`等のメソッド自体は前サイクル
7222        // 実装済みだが、属性読み書き用の IDL プロパティが欠けていた)。
7223        (
7224            "var e=document.getElementById('wrap'); e.autocapitalize='words'; e.autocapitalize",
7225            "words",
7226        ),
7227        ("document.createElement('div').popover", "null"),
7228        (
7229            "var e=document.createElement('div'); e.popover='manual'; e.popover",
7230            "manual",
7231        ),
7232        (
7233            "var e=document.createElement('div'); e.popover='auto'; e.popover=null; e.popover",
7234            "null",
7235        ),
7236        // `button.popoverTargetElement`/`.popoverTargetAction`(丸ごと未対応
7237        // だった。`popovertarget` idref 属性を実際の要素へ解決する IDL 属性。
7238        // 2026-07-15 発見・実装)。
7239        (
7240            "var b=document.createElement('button'); b.setAttribute('popovertarget','wrap'); \
7241             b.popoverTargetElement === document.getElementById('wrap')",
7242            "true",
7243        ),
7244        ("document.createElement('button').popoverTargetElement", "null"),
7245        (
7246            "var b=document.createElement('button'); b.popoverTargetElement = document.getElementById('wrap'); \
7247             b.getAttribute('popovertarget')",
7248            "wrap",
7249        ),
7250        (
7251            "var b=document.createElement('button'); b.popoverTargetAction='hide'; b.popoverTargetAction",
7252            "hide",
7253        ),
7254        // `input.list`(`list` idref 属性から実際の `<datalist>` 要素を解決する
7255        // IDL 属性。丸ごと未対応だった。2026-07-15 発見・実装)。
7256        ("document.createElement('input').list", "null"),
7257        (
7258            "var i=document.createElement('input'); i.setAttribute('list','missing'); i.list",
7259            "null",
7260        ),
7261        (
7262            "var dl=document.createElement('datalist'); dl.id='opts'; \
7263             document.getElementById('wrap').appendChild(dl); \
7264             var i=document.createElement('input'); i.setAttribute('list','opts'); \
7265             i.list === dl",
7266            "true",
7267        ),
7268        (
7269            "var d=document.createElement('div'); d.id='notdl'; \
7270             document.getElementById('wrap').appendChild(d); \
7271             var i=document.createElement('input'); i.setAttribute('list','notdl'); i.list",
7272            "null",
7273        ),
7274        // `a`/`area`/`link.relList`(`rel` 属性のトークン配列版。丸ごと未対応
7275        // だった。読み取り専用の簡略実装。2026-07-15 発見・実装)。
7276        ("document.createElement('a').relList.length", "0"),
7277        (
7278            "var a=document.createElement('a'); a.rel='noopener noreferrer'; \
7279             a.relList.join(',')",
7280            "noopener,noreferrer",
7281        ),
7282        (
7283            "var a=document.createElement('a'); a.rel='noopener noreferrer'; \
7284             a.relList.includes('noreferrer')",
7285            "true",
7286        ),
7287        // `.referrerPolicy`/`.loading`/`.decoding`/`.allow`/`.sandbox`(丸ごと
7288        // 未対応だった。属性値の反映のみを行う簡略実装。2026-07-15 発見・実装)。
7289        (
7290            "var a=document.createElement('a'); a.referrerPolicy='no-referrer'; \
7291             a.referrerPolicy",
7292            "no-referrer",
7293        ),
7294        (
7295            "var i=document.createElement('img'); i.loading='lazy'; i.loading",
7296            "lazy",
7297        ),
7298        (
7299            "var i=document.createElement('img'); i.decoding='async'; i.decoding",
7300            "async",
7301        ),
7302        (
7303            "var f=document.createElement('iframe'); f.allow='camera'; f.allow",
7304            "camera",
7305        ),
7306        ("document.createElement('iframe').sandbox.length", "0"),
7307        (
7308            "var f=document.createElement('iframe'); \
7309             f.setAttribute('sandbox','allow-scripts allow-forms'); \
7310             f.sandbox.join(',')",
7311            "allow-scripts,allow-forms",
7312        ),
7313        // `.crossOrigin`/`.fetchPriority`/`.integrity`/`script.async`/`.defer`/
7314        // `.noModule`(丸ごと未対応だった。属性値の反映のみを行う簡略実装。
7315        // 2026-07-15 発見・実装)。
7316        ("document.createElement('img').crossOrigin", "null"),
7317        (
7318            "var i=document.createElement('img'); i.crossOrigin='anonymous'; i.crossOrigin",
7319            "anonymous",
7320        ),
7321        ("document.createElement('img').fetchPriority", "auto"),
7322        (
7323            "var i=document.createElement('img'); i.fetchPriority='high'; i.fetchPriority",
7324            "high",
7325        ),
7326        (
7327            "var s=document.createElement('script'); s.integrity='sha256-x'; s.integrity",
7328            "sha256-x",
7329        ),
7330        (
7331            "var s=document.createElement('script'); s.async=true; s.async + ',' + \
7332             s.hasAttribute('async')",
7333            "true,true",
7334        ),
7335        (
7336            "var s=document.createElement('script'); s.defer=true; s.defer=false; s.defer",
7337            "false",
7338        ),
7339        (
7340            "var s=document.createElement('script'); s.noModule=true; s.noModule",
7341            "true",
7342        ),
7343        // `.srcset`/`.sizes`/`.media`/`img.useMap`/`.isMap`/`.currentSrc`
7344        // (丸ごと未対応だった。属性値の反映のみを行う簡略実装。2026-07-15
7345        // 発見・実装)。
7346        (
7347            "var i=document.createElement('img'); i.srcset='a.jpg 1x, b.jpg 2x'; i.srcset",
7348            "a.jpg 1x, b.jpg 2x",
7349        ),
7350        (
7351            "var i=document.createElement('img'); i.sizes='(max-width:600px) 480px'; i.sizes",
7352            "(max-width:600px) 480px",
7353        ),
7354        (
7355            "var l=document.createElement('link'); l.media='print'; l.media",
7356            "print",
7357        ),
7358        (
7359            "var i=document.createElement('img'); i.useMap='#m'; i.useMap",
7360            "#m",
7361        ),
7362        ("document.createElement('img').isMap", "false"),
7363        (
7364            "var i=document.createElement('img'); i.isMap=true; i.hasAttribute('ismap')",
7365            "true",
7366        ),
7367        (
7368            "var i=document.createElement('img'); i.src='a.jpg'; i.currentSrc",
7369            "a.jpg",
7370        ),
7371        // `img.complete`/`.naturalWidth`/`.naturalHeight`/`.decode()`が丸ごと
7372        // 未対応だった(2026-07-17 発見・実装。実デコード寸法を追跡する
7373        // 経路が無いため`naturalWidth`/`.naturalHeight`は常に`0`を返す
7374        // 誠実な簡略実装。`complete`は`src`の有無のみで判定)。
7375        ("document.createElement('img').complete", "false"),
7376        (
7377            "var i=document.createElement('img'); i.src='a.jpg'; i.complete",
7378            "true",
7379        ),
7380        (
7381            "var i=document.createElement('img'); i.src='a.jpg'; i.naturalWidth + ',' + i.naturalHeight",
7382            "0,0",
7383        ),
7384        (
7385            "var i=document.createElement('img'); i.src='a.jpg'; \
7386             await i.decode().then(() => 'ok')",
7387            "ok",
7388        ),
7389        (
7390            "var i=document.createElement('img'); \
7391             await i.decode().catch(e => e.name)",
7392            "EncodingError",
7393        ),
7394        // `audio`/`video.autoplay`/`.controls`/`.loop`/`.muted`/`.playsInline`/
7395        // `.preload`/`.poster`/`a.ping`/`.hreflang`(丸ごと未対応だった。属性値の
7396        // 反映のみを行う簡略実装。2026-07-15 発見・実装)。
7397        ("document.createElement('video').autoplay", "false"),
7398        (
7399            "var v=document.createElement('video'); v.autoplay=true; v.autoplay + \
7400             ',' + v.hasAttribute('autoplay')",
7401            "true,true",
7402        ),
7403        (
7404            "var v=document.createElement('video'); v.controls=true; v.controls=false; \
7405             v.controls",
7406            "false",
7407        ),
7408        (
7409            "var v=document.createElement('video'); v.loop=true; v.loop",
7410            "true",
7411        ),
7412        (
7413            "var v=document.createElement('video'); v.muted=true; v.muted",
7414            "true",
7415        ),
7416        (
7417            "var v=document.createElement('video'); v.playsInline=true; \
7418             v.hasAttribute('playsinline')",
7419            "true",
7420        ),
7421        (
7422            "var v=document.createElement('video'); v.preload='auto'; v.preload",
7423            "auto",
7424        ),
7425        (
7426            "var v=document.createElement('video'); v.poster='p.jpg'; v.poster",
7427            "p.jpg",
7428        ),
7429        (
7430            "var a=document.createElement('a'); a.ping='https://x'; a.ping",
7431            "https://x",
7432        ),
7433        (
7434            "var a=document.createElement('a'); a.hreflang='en'; a.hreflang",
7435            "en",
7436        ),
7437        // `audio`/`video.paused`/`.currentTime`/`.duration`/`.volume`/
7438        // `.playbackRate`(丸ごと未対応だった。`.play()`/`.pause()`と同じ
7439        // 状態追跡のみの簡略実装。2026-07-15 発見・実装)。
7440        ("document.createElement('video').paused", "true"),
7441        (
7442            "var v=document.createElement('video'); await v.play(); v.paused",
7443            "false",
7444        ),
7445        (
7446            "var v=document.createElement('video'); await v.play(); v.pause(); v.paused",
7447            "true",
7448        ),
7449        (
7450            "var v=document.createElement('video'); v.currentTime=12.5; v.currentTime",
7451            "12.5",
7452        ),
7453        (
7454            "var v=document.createElement('video'); Number.isNaN(v.duration)",
7455            "true",
7456        ),
7457        ("document.createElement('video').volume", "1"),
7458        (
7459            "var v=document.createElement('video'); v.volume=0.5; v.volume",
7460            "0.5",
7461        ),
7462        (
7463            "var v=document.createElement('video'); v.volume=5; v.volume",
7464            "1",
7465        ),
7466        (
7467            "var v=document.createElement('video'); v.playbackRate=2; v.playbackRate",
7468            "2",
7469        ),
7470        // `.play()`/`.pause()` の `play`/`playing`/`pause` イベント発火(丸ごと
7471        // 未対応だった。既に同じ状態なら発火しない冪等性も含む。2026-07-15
7472        // 発見・実装)。
7473        (
7474            "var v=document.createElement('video'); var log=[]; \
7475             v.addEventListener('play', ()=>log.push('play')); \
7476             v.addEventListener('playing', ()=>log.push('playing')); \
7477             await v.play(); log.join(',')",
7478            "play,playing",
7479        ),
7480        (
7481            "var v=document.createElement('video'); var n=0; \
7482             v.addEventListener('pause', ()=>n++); \
7483             await v.play(); v.pause(); v.pause(); n",
7484            "1",
7485        ),
7486        (
7487            "var v=document.createElement('video'); var n=0; \
7488             v.addEventListener('play', ()=>n++); \
7489             await v.play(); await v.play(); n",
7490            "1",
7491        ),
7492        // `.currentTime`/`.volume`/`.muted`/`.playbackRate` 代入時の
7493        // `seeked`/`volumechange`/`ratechange` イベント発火(丸ごと未対応
7494        // だった。2026-07-15 発見・実装)。
7495        (
7496            "var v=document.createElement('video'); var f=false; \
7497             v.addEventListener('seeked', ()=>f=true); v.currentTime=5; f",
7498            "true",
7499        ),
7500        (
7501            "var v=document.createElement('video'); var f=false; \
7502             v.addEventListener('volumechange', ()=>f=true); v.volume=0.3; f",
7503            "true",
7504        ),
7505        (
7506            "var v=document.createElement('video'); var f=false; \
7507             v.addEventListener('volumechange', ()=>f=true); v.muted=true; f",
7508            "true",
7509        ),
7510        (
7511            "var v=document.createElement('video'); var f=false; \
7512             v.addEventListener('ratechange', ()=>f=true); v.playbackRate=1.5; f",
7513            "true",
7514        ),
7515        // `.readyState`/`.networkState`/`.error`(丸ごと未対応だった。実際の
7516        // ネットワーク層/デコーダが無いため誠実に「何もロードしていない」
7517        // 状態を返す簡略実装。2026-07-15 発見・実装)。
7518        ("document.createElement('video').readyState", "0"),
7519        ("document.createElement('video').networkState", "0"),
7520        (
7521            "var v=document.createElement('video'); v.src='a.mp4'; v.networkState",
7522            "3",
7523        ),
7524        ("document.createElement('video').error", "null"),
7525        // `.buffered`/`.seekable`(`TimeRanges`。丸ごと未対応だった。実際には
7526        // メディアを一切ロードしないため常に空。範囲外アクセスは仕様どおり
7527        // 例外を投げる誠実な簡略実装)。`.defaultPlaybackRate` は単純な数値
7528        // 状態の保持のみ(`.playbackRate`とは独立)。2026-07-15 発見・実装。
7529        ("document.createElement('video').buffered.length", "0"),
7530        ("document.createElement('video').seekable.length", "0"),
7531        (
7532            "try { document.createElement('video').buffered.start(0); 'no-throw' } \
7533             catch(e) { 'threw' }",
7534            "threw",
7535        ),
7536        ("document.createElement('video').defaultPlaybackRate", "1"),
7537        (
7538            "var v=document.createElement('video'); v.defaultPlaybackRate=1.5; \
7539             v.defaultPlaybackRate + ',' + v.playbackRate",
7540            "1.5,1",
7541        ),
7542        // `element.getAnimations()`/`document.getAnimations()`(丸ごと未対応
7543        // だった。`.animate()` の戻り値を一切保持していないため常に空配列を
7544        // 返す誠実な簡略実装。2026-07-15 発見・実装)。
7545        (
7546            "document.createElement('div').getAnimations().length",
7547            "0",
7548        ),
7549        (
7550            "var e=document.createElement('div'); e.animate([], 100); \
7551             e.getAnimations().length",
7552            "0",
7553        ),
7554        ("document.getAnimations().length", "0"),
7555        // `Animation.reverse()`/`.finish()`/`.playbackRate`(丸ごと未対応
7556        // だった。`play`/`pause`/`cancel`と同じno-opの簡略方針。
7557        // 2026-07-17 発見・実装)。
7558        (
7559            "var a=document.createElement('div').animate([],100); \
7560             typeof a.reverse + ',' + typeof a.finish + ',' + a.playbackRate",
7561            "function,function,1",
7562        ),
7563        (
7564            "var a=document.createElement('div').animate([],100); a.reverse(); a.finish(); a.playbackRate=2; a.playbackRate",
7565            "2",
7566        ),
7567        // `script`/`style.nonce`(丸ごと未対応だった。属性値の反映のみを行う
7568        // 簡略実装。2026-07-15 発見・実装)。
7569        (
7570            "var s=document.createElement('script'); s.nonce='abc123'; s.nonce",
7571            "abc123",
7572        ),
7573        // `Selection`(`document.getSelection()`)の残りのメソッド群/
7574        // プロパティが丸ごと未対応だった。テキスト選択機構自体が無いため
7575        // 「常に何も選択していない」誠実な簡略実装。2026-07-15 発見・実装。
7576        (
7577            "var s=document.getSelection(); s.collapse(document.body, 0); \
7578             s.anchorNode + ',' + s.type",
7579            "null,None",
7580        ),
7581        (
7582            "var s=document.getSelection(); s.containsNode(document.body)",
7583            "false",
7584        ),
7585        // `Selection.removeRange(range)`が`addRange`/`removeAllRanges`の隣で
7586        // 漏れていた兄弟ギャップ(丸ごと未対応だった。2026-07-17 発見・
7587        // 実装)。
7588        (
7589            "typeof document.getSelection().removeRange",
7590            "function",
7591        ),
7592        (
7593            "document.getSelection().removeRange(document.createRange())",
7594            "undefined",
7595        ),
7596        (
7597            "try { document.getSelection().getRangeAt(0); 'no-throw' } \
7598             catch(e) { 'threw' }",
7599            "threw",
7600        ),
7601        // `Range`(`document.createRange()`)の残りのメソッド群/プロパティが
7602        // 丸ごと未対応だった。`Selection` と同方針の誠実な簡略実装。
7603        // 2026-07-15 発見・実装。
7604        (
7605            "var r=document.createRange(); r.setStart(document.body, 0); \
7606             r.collapsed + ',' + r.toString()",
7607            "true,",
7608        ),
7609        (
7610            "var r=document.createRange(); var r2=r.cloneRange(); \
7611             r2.collapsed",
7612            "true",
7613        ),
7614        (
7615            "document.createRange().isPointInRange(document.body, 0)",
7616            "false",
7617        ),
7618        (
7619            "document.designMode='on'; document.designMode",
7620            "on",
7621        ),
7622        // `element.hasAttributes()`(丸ごと未対応だった。単一属性の有無を見る
7623        // `hasAttribute(name)` は既に対応済みだったが、こちらが漏れていた)。
7624        ("document.getElementById('wrap').hasAttributes()", "true"),
7625        ("document.createElement('div').hasAttributes()", "false"),
7626        // `node.isSameNode(other)`/`node.isEqualNode(other)` が丸ごと未対応だった。
7627        ("var e=document.getElementById('wrap'); e.isSameNode(e)", "true"),
7628        (
7629            "document.getElementById('wrap').isSameNode(document.getElementById('t'))",
7630            "false",
7631        ),
7632        (
7633            "var a=document.createElement('div'); a.id='x'; var b=document.createElement('div'); b.id='x'; a.isEqualNode(b)",
7634            "true",
7635        ),
7636        (
7637            "var a=document.createElement('div'); a.id='x'; var b=document.createElement('div'); b.id='y'; a.isEqualNode(b)",
7638            "false",
7639        ),
7640        // `node.normalize()` が丸ごと未対応だった。連続する隣接テキストノードを
7641        // 1つに連結し、空のテキストノードを取り除く。
7642        (
7643            "var d=document.createElement('div'); d.appendChild(document.createTextNode('a')); d.appendChild(document.createTextNode('b')); d.normalize(); d.childNodes.length",
7644            "1",
7645        ),
7646        (
7647            "var d=document.createElement('div'); d.appendChild(document.createTextNode('a')); d.appendChild(document.createTextNode('b')); d.normalize(); d.textContent",
7648            "ab",
7649        ),
7650        (
7651            "var d=document.createElement('div'); d.appendChild(document.createTextNode('')); d.normalize(); d.childNodes.length",
7652            "0",
7653        ),
7654        // `document.createDocumentFragment()` が丸ごと未対応だった。挿入すると
7655        // フラグメント自身ではなく中身の子要素群がそのまま移動する(実 DOM の挙動)。
7656        ("typeof document.createDocumentFragment", "function"),
7657        (
7658            "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",
7659            "2,SPAN,EM",
7660        ),
7661        // フラグメント自身は挿入後に空になる(中身だけが移動したことの確認)。
7662        (
7663            "var f=document.createDocumentFragment(); f.appendChild(document.createElement('span')); var d=document.createElement('div'); d.appendChild(f); f.childNodes.length",
7664            "0",
7665        ),
7666        // getElementsByClassName / getElementsByTagName
7667        ("document.getElementsByTagName('p').length", "1"),
7668        ("document.getElementsByClassName('a')[0].id", "t"),
7669        ("document.getElementsByTagName('span')[0].id", "s"),
7670        // 要素インスタンス版の getElementsByClassName/getElementsByTagName
7671        // (`document` 版は対応済みだったがこちらが丸ごと未対応だった)。
7672        (
7673            "document.getElementById('wrap').getElementsByClassName('a')[0].id",
7674            "t",
7675        ),
7676        (
7677            "document.getElementById('wrap').getElementsByTagName('span')[0].id",
7678            "s",
7679        ),
7680        (
7681            "document.getElementById('s').getElementsByClassName('a').length",
7682            "0",
7683        ),
7684        // getElementsByTagNameNS / getAttributeNodeNS(丸ごと未対応だった)。
7685        ("document.getElementsByTagNameNS('*', 'span')[0].id", "s"),
7686        (
7687            "document.getElementById('wrap').getElementsByTagNameNS(null, 'span')[0].id",
7688            "s",
7689        ),
7690        (
7691            "document.getElementById('wrap').getAttributeNodeNS(null, 'id').value",
7692            "wrap",
7693        ),
7694        (
7695            "document.getElementById('wrap').getAttributeNodeNS('http://www.w3.org/1999/xhtml', 'missing') === null",
7696            "true",
7697        ),
7698        // `document.getElementsByName(name)`(丸ごと未対応だった。ラジオボタン
7699        // グループ等、同じ `name` 属性を持つ要素群をまとめて取得する定番
7700        // パターン。2026-07-15 発見・実装)。
7701        (
7702            "var a=document.createElement('input'); a.setAttribute('name','q'); \
7703             var b=document.createElement('input'); b.setAttribute('name','q'); \
7704             var wrap=document.getElementById('wrap'); \
7705             wrap.appendChild(a); wrap.appendChild(b); \
7706             document.getElementsByName('q').length",
7707            "2",
7708        ),
7709        ("document.getElementsByName('no-such-name').length", "0"),
7710        // append / prepend / before / after / replaceWith
7711        ("var w=document.getElementById('wrap'); var n=document.createElement('b'); n.id='ap'; w.append(n); w.lastElementChild.id", "ap"),
7712        ("var w=document.getElementById('wrap'); var n=document.createElement('b'); n.id='pp'; w.prepend(n); w.firstElementChild.id", "pp"),
7713        ("var n=document.createElement('b'); n.id='bf'; document.getElementById('t').before(n); document.getElementById('t').previousElementSibling.id", "bf"),
7714        ("var n=document.createElement('b'); n.id='af'; document.getElementById('t').after(n); document.getElementById('t').nextElementSibling.id", "af"),
7715        ("var n=document.createElement('b'); n.id='rw'; document.getElementById('s').replaceWith(n); document.getElementById('wrap').lastElementChild.id", "rw"),
7716        ("var w=document.getElementById('wrap'); w.append('TXT'); w.textContent.indexOf('TXT') >= 0", "true"),
7717        // `element.replaceChildren(...nodes)`(`innerHTML=''; append(...)` の1行版)
7718        // が丸ごと未対応だった。既存の子は全て消え、渡した内容だけが残る。
7719        (
7720            "var w=document.getElementById('wrap'); var n=document.createElement('b'); n.id='rc'; w.replaceChildren(n); w.children.length + ',' + w.firstElementChild.id",
7721            "1,rc",
7722        ),
7723        (
7724            "var w=document.getElementById('wrap'); w.replaceChildren(); w.children.length",
7725            "0",
7726        ),
7727        // `element.scrollTo(x,y)`/`.scrollBy(x,y)`(オブジェクト引数 `{top}` 形式も
7728        // 含む)が丸ごと未対応だった。既存の `element.scrollTop` setter へ委譲する。
7729        (
7730            "var w=document.getElementById('wrap'); w.scrollTo(0, 50); w.scrollTop",
7731            "50",
7732        ),
7733        (
7734            "var w=document.getElementById('wrap'); w.scrollTo({top: 30}); w.scrollTop",
7735            "30",
7736        ),
7737        (
7738            "var w=document.getElementById('wrap'); w.scrollTop=10; w.scrollBy(0, 5); w.scrollTop",
7739            "15",
7740        ),
7741        // classList 追加(length / value / item / toString)
7742        ("document.getElementById('t').classList.length", "2"),
7743        ("document.getElementById('t').classList.value", "a b"),
7744        ("document.getElementById('t').classList.item(0)", "a"),
7745        ("document.getElementById('t').classList.toString()", "a b"),
7746        // insertAdjacentHTML / insertAdjacentElement / toggleAttribute
7747        ("var w=document.getElementById('wrap'); w.insertAdjacentHTML('beforeend','<i id=iah>z</i>'); document.getElementById('iah').tagName", "I"),
7748        // position 引数は仕様上 ASCII 大文字小文字を無視すべきだが、以前は完全一致
7749        // のみで `"beforeEnd"` 等を渡すと静かに no-op になっていた。
7750        ("var w=document.getElementById('wrap'); w.insertAdjacentHTML('beforeEnd','<b id=iahc>z</b>'); document.getElementById('iahc').tagName", "B"),
7751        ("var t=document.getElementById('t'); t.insertAdjacentHTML('afterend','<u id=iae>q</u>'); t.nextElementSibling.id", "iae"),
7752        ("var t=document.getElementById('t'); var n=document.createElement('b'); n.id='iel'; t.insertAdjacentElement('beforebegin', n); t.previousElementSibling.id", "iel"),
7753        ("var t=document.getElementById('t'); t.toggleAttribute('hidden'); t.hasAttribute('hidden')", "true"),
7754        ("var t=document.getElementById('t'); t.toggleAttribute('hidden'); t.toggleAttribute('hidden'); t.hasAttribute('hidden')", "false"),
7755        ("var t=document.getElementById('t'); t.toggleAttribute('hidden', true); t.hasAttribute('hidden')", "true"),
7756        ("var t=document.getElementById('t'); t.toggleAttribute('hidden', true); t.toggleAttribute('hidden', false); t.hasAttribute('hidden')", "false"),
7757        // <dialog> の show()/showModal()/close()(以前は丸ごと未対応だった)。
7758        ("var d=document.createElement('dialog'); d.open", "false"),
7759        ("var d=document.createElement('dialog'); d.show(); d.open", "true"),
7760        ("var d=document.createElement('dialog'); d.showModal(); d.open", "true"),
7761        // `.show()`と`.showModal()`が同一実装を共有しておりCSS `:modal`用の
7762        // 区別が一切無かった(2026-07-18 発見・実装)。`_modal`は
7763        // `showModal()`のみが書き込む内部専用属性で、`.show()`では
7764        // 付かず、`.close()`/`.requestClose()`でどちらも除去される。
7765        ("var d=document.createElement('dialog'); d.show(); d.hasAttribute('_modal')", "false"),
7766        ("var d=document.createElement('dialog'); d.showModal(); d.hasAttribute('_modal')", "true"),
7767        ("var d=document.createElement('dialog'); d.showModal(); d.close(); d.hasAttribute('_modal')", "false"),
7768        ("var d=document.createElement('dialog'); d.show(); d.close(); d.open", "false"),
7769        ("var d=document.createElement('dialog'); d.show(); d.close('ok'); d.returnValue", "ok"),
7770        (
7771            "var d=document.createElement('dialog'); var closed=false; d.addEventListener('close', function(){ closed=true; }); d.show(); d.close(); closed",
7772            "true",
7773        ),
7774        ("var d=document.createElement('dialog'); d.open=true; d.hasAttribute('open')", "true"),
7775        // Popover API `showPopover()`/`hidePopover()`/`togglePopover(force?)`
7776        // (丸ごと未対応だった。`<dialog>` の `open` 属性と同じ内部専用属性の
7777        // 付け外しのみの簡略実装。2026-07-15 発見・実装)。
7778        ("var e=document.createElement('div'); e.togglePopover()", "true"),
7779        (
7780            "var e=document.createElement('div'); e.togglePopover(); e.togglePopover()",
7781            "false",
7782        ),
7783        (
7784            "var e=document.createElement('div'); e.togglePopover(true); e.togglePopover(true)",
7785            "true",
7786        ),
7787        (
7788            "var e=document.createElement('div'); e.showPopover(); e.togglePopover()",
7789            "false",
7790        ),
7791        // Popover API が状態変化を`toggle`イベントとして発火していな
7792        // かった(`<dialog>.close()`は既に`"close"`イベントを発火して
7793        // いたのに同じ役目のPopover側だけ漏れていた。丸ごと未対応
7794        // だった。2026-07-17 発見・実装)。
7795        (
7796            "var e=document.createElement('div'); var n=0; \
7797             e.addEventListener('toggle', () => n++); e.showPopover(); e.hidePopover(); n",
7798            "2",
7799        ),
7800        (
7801            "var e=document.createElement('div'); var n=0; \
7802             e.addEventListener('toggle', () => n++); e.togglePopover(); n",
7803            "1",
7804        ),
7805        // `toggle`イベントの`oldState`/`newState`(仕様上の`ToggleEvent`の
7806        // 主要プロパティ。丸ごと未対応だった。2026-07-17 発見・実装)。
7807        (
7808            "var e=document.createElement('div'); var s=''; \
7809             e.addEventListener('toggle', ev => s=ev.oldState+'>'+ev.newState); \
7810             e.showPopover(); s",
7811            "closed>open",
7812        ),
7813        (
7814            "var e=document.createElement('div'); var s=''; \
7815             e.addEventListener('toggle', ev => s=ev.oldState+'>'+ev.newState); \
7816             e.showPopover(); e.hidePopover(); s",
7817            "open>closed",
7818        ),
7819        // `beforetoggle`(状態変更「前」に発火する取消可能なイベント。
7820        // `preventDefault()`で開閉を中止できる。丸ごと未対応だった。
7821        // 2026-07-17 発見・実装)。
7822        (
7823            "var e=document.createElement('div'); var n=0; \
7824             e.addEventListener('beforetoggle', () => n++); e.showPopover(); n",
7825            "1",
7826        ),
7827        (
7828            "var e=document.createElement('div'); var toggled=false; \
7829             e.addEventListener('beforetoggle', ev => ev.preventDefault()); \
7830             e.addEventListener('toggle', () => toggled=true); \
7831             e.showPopover(); toggled",
7832            "false",
7833        ),
7834        (
7835            "var e=document.createElement('div'); \
7836             e.addEventListener('beforetoggle', ev => ev.preventDefault()); \
7837             e.showPopover(); e.togglePopover()",
7838            "false",
7839        ),
7840        // `inert` グローバル真偽属性(丸ごと未対応だった)。
7841        ("var t=document.createElement('div'); t.inert", "false"),
7842        ("var t=document.createElement('div'); t.setAttribute('inert',''); t.inert", "true"),
7843        ("var t=document.createElement('div'); t.inert=true; t.hasAttribute('inert')", "true"),
7844        ("var t=document.createElement('div'); t.inert=true; t.inert=false; t.hasAttribute('inert')", "false"),
7845        // <template>.content(丸ごと未対応だった)。
7846        (
7847            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; tpl.content.children.length",
7848            "1",
7849        ),
7850        (
7851            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; tpl.content.firstChild.textContent",
7852            "hi",
7853        ),
7854        // 初回アクセスで子がフラグメント側へ移り、template 自身は空になる。
7855        (
7856            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; var f=tpl.content; tpl.children.length",
7857            "0",
7858        ),
7859        // 2回目以降のアクセスでも同じフラグメントを返す(子を再度奪わない)。
7860        (
7861            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; var f1=tpl.content; var f2=tpl.content; f1===f2",
7862            "true",
7863        ),
7864        // DocumentFragment を appendChild すると中身だけが移動する(実 DOM と同じ挙動)。
7865        (
7866            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; var host=document.createElement('div'); host.appendChild(tpl.content); host.children.length",
7867            "1",
7868        ),
7869        // `document.importNode(externalNode, deep)`/`document.adoptNode(node)`
7870        // (丸ごと未対応だった。`<template>` の中身を取り込む定番パターン。
7871        // 2026-07-15 発見・実装)。`importNode` はコピーなので元の `.content`
7872        // には影響しない。
7873        (
7874            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; \
7875             var imported=document.importNode(tpl.content, true); \
7876             var host=document.createElement('div'); host.appendChild(imported); \
7877             host.children.length + ',' + tpl.content.children.length",
7878            "1,1",
7879        ),
7880        (
7881            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; \
7882             document.importNode(tpl.content, true).firstChild.textContent",
7883            "hi",
7884        ),
7885        (
7886            "var host1=document.createElement('div'); var host2=document.createElement('div'); \
7887             var child=document.createElement('span'); host1.appendChild(child); \
7888             document.adoptNode(child); host2.appendChild(child); \
7889             host1.children.length + ',' + host2.children.length",
7890            "0,1",
7891        ),
7892        // cloneNode(): .content に一度アクセス済みの <template> を複製すると、
7893        // クローン先の .content が元の(同じ)フラグメントを指したまま共有されて
7894        // しまうバグだった(複製後にクローン側の中身を変更しても元に影響しない、
7895        // という cloneNode の基本契約が破られていた)。
7896        (
7897            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; var f=tpl.content; \
7898             var clone=tpl.cloneNode(true); clone.content===tpl.content",
7899            "false",
7900        ),
7901        (
7902            "var tpl=document.createElement('template'); tpl.innerHTML='<p>hi</p>'; var f=tpl.content; \
7903             var clone=tpl.cloneNode(true); clone.content.children.length+','+tpl.content.children.length",
7904            "1,1",
7905        ),
7906        // outerHTML/getAttributeNames() が `_content_frag`(`.content` アクセス済み
7907        // <template> が内部で持つ housekeeping 属性)等の `_` 始まり内部専用属性を
7908        // そのまま漏らしていたバグ。
7909        (
7910            "var tpl=document.createElement('template'); tpl.innerHTML='hi'; var f=tpl.content; tpl.outerHTML",
7911            "<template></template>",
7912        ),
7913        (
7914            "var tpl=document.createElement('template'); tpl.innerHTML='hi'; var f=tpl.content; \
7915             tpl.getAttributeNames().join(',')",
7916            "",
7917        ),
7918        // <progress>/<meter> の .value/.max/.min(丸ごと未対応だった。既定値は
7919        // 仕様どおり value=0/max=1/meter.min=0)。
7920        ("document.createElement('progress').value", "0"),
7921        ("document.createElement('progress').max", "1"),
7922        ("var p=document.createElement('progress'); p.setAttribute('value','30'); p.setAttribute('max','50'); p.value+'/'+p.max", "30/50"),
7923        ("var p=document.createElement('progress'); p.value=40; p.getAttribute('value')", "40"),
7924        ("document.createElement('meter').min", "0"),
7925        ("var m=document.createElement('meter'); m.setAttribute('value','5'); typeof m.value", "number"),
7926        // `input.valueAsNumber`(HTML5。丸ごと未対応だった)。
7927        ("var i=document.createElement('input'); i.type='number'; i.value='42'; i.valueAsNumber", "42"),
7928        ("var i=document.createElement('input'); i.type='text'; i.value='42'; isNaN(i.valueAsNumber)", "true"),
7929        ("var i=document.createElement('input'); i.type='number'; i.valueAsNumber=7; i.value", "7"),
7930        ("var i=document.createElement('input'); i.type='number'; i.value='5'; i.valueAsNumber=NaN; i.value", ""),
7931        // `input.valueAsDate`(HTML5。`type="date"`/`"month"` の丸ごと未対応だった。
7932        // `week`/その他の type は仕様どおり `null`)。
7933        (
7934            "var i=document.createElement('input'); i.type='date'; i.value='2024-03-15'; \
7935             i.valueAsDate.getUTCFullYear()+'-'+(i.valueAsDate.getUTCMonth()+1)+'-'+i.valueAsDate.getUTCDate()",
7936            "2024-3-15",
7937        ),
7938        ("var i=document.createElement('input'); i.type='text'; i.value='2024-03-15'; i.valueAsDate", "null"),
7939        (
7940            "var i=document.createElement('input'); i.type='date'; i.valueAsDate=new Date(Date.UTC(2024,2,15)); i.value",
7941            "2024-03-15",
7942        ),
7943        (
7944            "var i=document.createElement('input'); i.type='month'; i.valueAsDate=new Date(Date.UTC(2024,2,15)); i.value",
7945            "2024-03",
7946        ),
7947        (
7948            "var i=document.createElement('input'); i.type='date'; i.value='x'; i.valueAsDate=null; i.value",
7949            "",
7950        ),
7951        // `input[type=date/month/datetime-local].value`は仕様上、属性値が
7952        // 正しい形式でなければ空文字列を返すべきだが、`color`/`range`と同じ
7953        // バグパターンで不正な生文字列がそのまま漏れていた。
7954        (
7955            "var i=document.createElement('input'); i.type='date'; \
7956             i.setAttribute('value','not-a-date'); i.value",
7957            "",
7958        ),
7959        (
7960            "var i=document.createElement('input'); i.type='date'; \
7961             i.setAttribute('value','2026-07-18'); i.value",
7962            "2026-07-18",
7963        ),
7964        (
7965            "var i=document.createElement('input'); i.type='month'; \
7966             i.setAttribute('value','garbage'); i.value",
7967            "",
7968        ),
7969        (
7970            "var i=document.createElement('input'); i.type='datetime-local'; \
7971             i.setAttribute('value','2026-07-18T14:30'); i.value",
7972            "2026-07-18T14:30",
7973        ),
7974        // getAttributeNS/setAttributeNS/hasAttributeNS/removeAttributeNS(丸ごと
7975        // 未対応だった。名前空間は無視し非NS版と同じ属性ストアへ委譲する簡略実装)。
7976        ("var e=document.createElement('div'); e.setAttributeNS(null,'data-k','v'); e.getAttribute('data-k')", "v"),
7977        ("var e=document.createElement('div'); e.setAttribute('data-k','v'); e.getAttributeNS(null,'data-k')", "v"),
7978        ("var e=document.createElement('div'); e.setAttributeNS(null,'data-k','v'); e.hasAttributeNS(null,'data-k')", "true"),
7979        (
7980            "var e=document.createElement('div'); e.setAttributeNS(null,'data-k','v'); e.removeAttributeNS(null,'data-k'); e.hasAttribute('data-k')",
7981            "false",
7982        ),
7983        // node.getRootNode()(丸ごと未対応だった)。
7984        (
7985            "var p=document.createElement('div'); var c=document.createElement('span'); p.appendChild(c); c.getRootNode()===p",
7986            "true",
7987        ),
7988        // 接続済みツリーでは異なる要素同士でも同じルートを返す。
7989        (
7990            "document.getElementById('t').getRootNode()===document.getElementById('wrap').getRootNode()",
7991            "true",
7992        ),
7993        // 親の無い単独ノードは自分自身を返す。
7994        ("var e=document.createElement('div'); e.getRootNode()===e", "true"),
7995        // lookupNamespaceURI/lookupPrefix/isDefaultNamespace(丸ごと未対応
7996        // だった。2026-07-16 発見・実装。この処理系は XML 名前空間を一切
7997        // モデル化していないため常に「名前空間なし」を返す簡略実装)。
7998        ("var e=document.createElement('div'); e.lookupNamespaceURI('svg')", "null"),
7999        ("var e=document.createElement('div'); e.lookupPrefix('http://www.w3.org/2000/svg')", "null"),
8000        ("var e=document.createElement('div'); e.isDefaultNamespace(null)", "true"),
8001        ("var e=document.createElement('div'); e.isDefaultNamespace('http://www.w3.org/2000/svg')", "false"),
8002        // ノード読みプロパティ(nodeType / nodeName / hidden)
8003        ("document.getElementById('t').nodeType", "1"),
8004        ("document.getElementById('t').nodeName", "P"),
8005        ("document.getElementById('t').hidden", "false"),
8006        ("var e=document.getElementById('t'); e.toggleAttribute('hidden'); e.hidden", "true"),
8007        // dataset(data-* ↔ camelCase)  ※ getBoundingClientRect は別ブロックで rect を注入して検証
8008
8009        ("document.getElementById('t').dataset.k", "v"),
8010        ("var e=document.getElementById('t'); e.dataset.userId='42'; e.getAttribute('data-user-id')", "42"),
8011        ("var e=document.getElementById('t'); e.setAttribute('data-foo-bar','9'); e.dataset.fooBar", "9"),
8012        ("document.getElementById('t').dataset.missing === undefined", "true"),
8013        // `delete element.dataset.foo` は `dataset:` プロキシが `.props` を持たず実データが
8014        // DOM ノード属性側にあるため、何も起きない黙殺バグだった。
8015        (
8016            "var e=document.getElementById('t'); e.dataset.temp='x'; delete e.dataset.temp; e.dataset.temp === undefined",
8017            "true",
8018        ),
8019        (
8020            "var e=document.getElementById('t'); e.dataset.userId='42'; delete e.dataset.userId; e.hasAttribute('data-user-id')",
8021            "false",
8022        ),
8023        // `delete element.style.color` も同じ種類の黙殺バグだった。
8024        (
8025            "var e=document.getElementById('t'); e.style.color='red'; delete e.style.color; e.style.color",
8026            "",
8027        ),
8028        // insertBefore: 新ノードを s の前へ → wrap の子順が t, new, s
8029        ("var w=document.getElementById('wrap'); var n=document.createElement('b'); n.id='n'; w.insertBefore(n, document.getElementById('s')); w.children[1].id", "n"),
8030        ("var w=document.getElementById('wrap'); var n=document.createElement('b'); w.insertBefore(n, null); w.lastElementChild.tagName", "B"),
8031        // replaceChild: s を新ノードに置換
8032        ("var w=document.getElementById('wrap'); var n=document.createElement('i'); n.id='ni'; w.replaceChild(n, document.getElementById('s')); w.lastElementChild.id", "ni"),
8033        ("var w=document.getElementById('wrap'); var n=document.createElement('i'); var old=w.replaceChild(n, document.getElementById('s')); old.id", "s"),
8034        // `insertBefore`/`replaceChild`/`removeChild`は仕様上、参照ノードが
8035        // `this`の子でない場合`NotFoundError`を投げるべきだが、以前は検証が
8036        // 無く黙って末尾へ追加/何もせず成功したかのように振る舞っていた
8037        // (Table DOM/CharacterData/TypedArrayと同じ「不正な参照・範囲の
8038        // 黙殺」バグ族)。
8039        (
8040            "var w=document.getElementById('wrap'); var outsider=document.createElement('div'); \
8041             var n=document.createElement('b'); \
8042             try { w.insertBefore(n, outsider); 'no-throw' } catch(e) { e.name }",
8043            "NotFoundError",
8044        ),
8045        (
8046            "var w=document.getElementById('wrap'); var outsider=document.createElement('div'); \
8047             var n=document.createElement('b'); \
8048             try { w.replaceChild(n, outsider); 'no-throw' } catch(e) { e.name }",
8049            "NotFoundError",
8050        ),
8051        (
8052            "var w=document.getElementById('wrap'); var outsider=document.createElement('div'); \
8053             try { w.removeChild(outsider); 'no-throw' } catch(e) { e.name }",
8054            "NotFoundError",
8055        ),
8056        // cloneNode(deep): テキストごと複製
8057        ("document.getElementById('t').cloneNode(true).textContent", "x"),
8058        ("var c=document.getElementById('t').cloneNode(false); c.tagName + ':' + c.children.length", "P:0"),
8059        ("document.getElementById('t').cloneNode(true).parentElement === null", "true"),
8060        // classList.replace
8061        ("var e=document.getElementById('t'); e.classList.replace('a','z'); e.classList.contains('z') + ',' + e.classList.contains('a')", "true,false"),
8062        ("document.getElementById('t').classList.replace('nope','z')", "false"),
8063        // classList.toggle(name, force) — 第2引数 force が以前は完全に無視されていた。
8064        (
8065            "var e=document.getElementById('t'); e.classList.toggle('force-on', true); e.classList.contains('force-on')",
8066            "true",
8067        ),
8068        (
8069            "var e=document.getElementById('t'); e.classList.add('force-off'); e.classList.toggle('force-off', false); e.classList.contains('force-off')",
8070            "false",
8071        ),
8072        // classList.add/remove の複数引数(可変長引数)対応、classList.supports、webkitMatchesSelector、execCommand
8073        (
8074            "var e=document.createElement('div'); e.classList.add('c1', 'c2', 'c3'); e.className",
8075            "c1 c2 c3",
8076        ),
8077        (
8078            "var e=document.createElement('div'); e.classList.add('c1', 'c2', 'c3'); e.classList.remove('c1', 'c3'); e.className",
8079            "c2",
8080        ),
8081        (
8082            "var e=document.createElement('div'); e.classList.supports('foo')",
8083            "false",
8084        ),
8085        // 元は`document.body.appendChild`/`.remove()`を経由していたが、この
8086        // 自己テスト実行環境(`JsRuntime::new()`直後)には既定でパース済みの
8087        // `<html>`/`<body>`が一切存在しない(`document.body`は常に`null`)
8088        // ため、その`null`への`.appendChild`呼び出しが例外を投げていた
8089        // (2026-07-18 発見・修正)。`#id`セレクタの`matches()`判定は
8090        // ツリーへの接続を要さない(自身の`id`属性のみ見る)ため、
8091        // 接続操作自体が不要と判明し削除。
8092        (
8093            "var e=document.createElement('div'); e.id='wm'; e.webkitMatchesSelector('#wm')",
8094            "true",
8095        ),
8096        (
8097            "document.execCommand('copy')",
8098            "true",
8099        ),
8100        // `classList` の `Symbol.iterator`/`forEach` が丸ごと未対応で、`for...of`/
8101        // スプレッド/`forEach` のいずれも常に何も反復しない黙殺バグだった。
8102        (
8103            "var e=document.createElement('div'); e.className='a b c'; var out=[]; for (const c of e.classList) out.push(c); out.join(',')",
8104            "a,b,c",
8105        ),
8106        (
8107            "var e=document.createElement('div'); e.className='x y'; [...e.classList].join(',')",
8108            "x,y",
8109        ),
8110        (
8111            "var e=document.createElement('div'); e.className='p q'; var out=''; e.classList.forEach(function(c,i){out+=i+':'+c+' '}); out.trim()",
8112            "0:p 1:q",
8113        ),
8114        // `classList.entries()`/`.keys()`/`.values()`(丸ごと未対応だった。`forEach`
8115        // と for-of だけが配線され、明示イテレータ取得メソッド3種は未配線だった。
8116        // 2026-07-15 発見・実装)。
8117        (
8118            "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(',')",
8119            "0:r,1:s",
8120        ),
8121        (
8122            "var e=document.createElement('div'); e.className='r s'; [...e.classList.keys()].join(',')",
8123            "0,1",
8124        ),
8125        (
8126            "var e=document.createElement('div'); e.className='r s'; [...e.classList.values()].join(',')",
8127            "r,s",
8128        ),
8129        // `Array.prototype.with(index, value)`(ES2023)— 範囲外 index は仕様上 RangeError
8130        // だが、以前は黙って無視して元と同じ配列を返していた。
8131        ("[1,2,3].with(1, 'x').join(',')", "1,x,3"),
8132        ("[1,2,3].with(-1, 'x').join(',')", "1,2,x"),
8133        (
8134            "try { [1,2,3].with(5, 'x'); 'no-throw' } catch(e) { 'threw' }",
8135            "threw",
8136        ),
8137        // `element.style.setProperty`/`getPropertyValue`/`removeProperty` が丸ごと
8138        // 欠落していた(直接のプロパティ代入 `el.style.color='red'` はあったが、
8139        // メソッド経由のインラインスタイル読み書きが無く、特に CSS カスタムプロパティ
8140        // `--foo` はこの経路でしか設定できないため影響が大きかった)。
8141        (
8142            "var e=document.getElementById('t'); e.style.setProperty('color','red'); e.style.color",
8143            "red",
8144        ),
8145        (
8146            "var e=document.getElementById('t'); e.style.setProperty('--my-var','10px'); e.style.getPropertyValue('--my-var')",
8147            "10px",
8148        ),
8149        (
8150            "var e=document.getElementById('t'); e.style.setProperty('color','blue'); e.style.removeProperty('color'); e.style.color",
8151            "",
8152        ),
8153        // `style.getPropertyPriority(name)`/`setProperty(name, value, priority)` の
8154        // 第3引数(丸ごと未対応・無視されていた。2026-07-16 発見・実装)。
8155        (
8156            "var e=document.getElementById('t'); e.style.setProperty('color','red','important'); e.style.getPropertyPriority('color')",
8157            "important",
8158        ),
8159        // `!important` 付きで設定した値を `getPropertyValue`/直接プロパティ双方から
8160        // 読んでも、優先度マーカーを含まない値本体のみが返るべき(仕様どおり)。
8161        (
8162            "var e=document.getElementById('t'); e.style.setProperty('color','red','important'); e.style.color",
8163            "red",
8164        ),
8165        (
8166            "var e=document.getElementById('t'); e.style.setProperty('color','red'); e.style.getPropertyPriority('color')",
8167            "",
8168        ),
8169        // `style.length`/`.item(index)`(丸ごと未対応だった。反復イディオム
8170        // `for (let i=0;i<style.length;i++) style.item(i)`。2026-07-17
8171        // 発見・実装)。
8172        (
8173            "var e=document.getElementById('t'); e.style.cssText='color: red; font-size: 12px'; e.style.length",
8174            "2",
8175        ),
8176        (
8177            "var e=document.getElementById('t'); e.style.cssText='color: red; font-size: 12px'; \
8178             e.style.item(0)+','+e.style.item(1)",
8179            "color,font-size",
8180        ),
8181        ("document.getElementById('t').style.length", "0"),
8182        // `style.cssFloat`(`float`プロパティのレガシー別名。丸ごと未対応
8183        // だった。2026-07-17 発見・実装)。
8184        (
8185            "var e=document.getElementById('t'); e.style.cssFloat='left'; e.style.float",
8186            "left",
8187        ),
8188        (
8189            "var e=document.getElementById('t'); e.style.float='right'; e.style.cssFloat",
8190            "right",
8191        ),
8192        // `element.style.cssText`(全プロパティを1つの文字列で読み書きする定番パターン)が
8193        // 丸ごと未対応で、通常のプロパティ名として扱われ常に空文字列になっていた。
8194        (
8195            "var e=document.getElementById('t'); e.style.cssText='color: red; font-size: 12px'; e.style.color+','+e.style.fontSize",
8196            "red,12px",
8197        ),
8198        (
8199            "var e=document.getElementById('t'); e.style.color='blue'; e.style.fontSize='10px'; e.style.cssText",
8200            "color: blue; font-size: 10px",
8201        ),
8202        // innerHTML/outerHTML が丸ごと未対応(innerHTML は textContent と同じ実装を
8203        // 共有しておりマークアップ自体が失われていた)だったバグ。
8204        (
8205            "var e=document.getElementById('t'); e.innerHTML='<b>bold</b>text'; e.innerHTML",
8206            "<b>bold</b>text",
8207        ),
8208        (
8209            "var e=document.getElementById('t'); e.innerHTML='hi'; e.outerHTML",
8210            "<p id=\"t\" class=\"a b\" data-k=\"v\">hi</p>",
8211        ),
8212        // nodeType/nodeName がテキストノードでも常に ELEMENT_NODE(1)/空文字列固定
8213        // だったバグ。childNodes/firstChild/lastChild/nextSibling/previousSibling
8214        // (テキストノードも含む Node レベル走査 API)も丸ごと未対応だった。
8215        (
8216            "var e=document.getElementById('t'); e.innerHTML='a<b>x</b>'; e.childNodes.length+','+e.firstChild.nodeType+','+e.lastChild.nodeType",
8217            "2,3,1",
8218        ),
8219        (
8220            "var e=document.getElementById('t'); e.innerHTML='a<b>x</b>'; e.firstChild.nextSibling.nodeName",
8221            "B",
8222        ),
8223        // `node.nodeValue`(テキストノードは自身のテキスト、要素ノードは仕様どおり
8224        // `null`)が丸ごと未対応だった。代入は要素ノードでは no-op。
8225        (
8226            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.nodeValue",
8227            "hello",
8228        ),
8229        (
8230            "var e=document.getElementById('t'); e.innerHTML='hello'; e.nodeValue === null",
8231            "true",
8232        ),
8233        (
8234            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.nodeValue='bye'; e.textContent",
8235            "bye",
8236        ),
8237        // `CharacterData.data`/`.length`/`.appendData`/`.deleteData`/
8238        // `.insertData`/`.replaceData`/`.substringData`(丸ごと未対応
8239        // だった。`nodeValue`は対応済みだったが単体アクセサ`data`と
8240        // 編集用メソッド群は漏れていた。2026-07-16 発見・実装)。
8241        (
8242            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.data",
8243            "hello",
8244        ),
8245        (
8246            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.length",
8247            "5",
8248        ),
8249        (
8250            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.data='world'; e.textContent",
8251            "world",
8252        ),
8253        (
8254            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.appendData(' world'); e.textContent",
8255            "hello world",
8256        ),
8257        (
8258            "var e=document.getElementById('t'); e.innerHTML='hello world'; e.firstChild.deleteData(5,6); e.textContent",
8259            "hello",
8260        ),
8261        (
8262            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.insertData(5,' world'); e.textContent",
8263            "hello world",
8264        ),
8265        (
8266            "var e=document.getElementById('t'); e.innerHTML='hello world'; e.firstChild.replaceData(6,5,'there'); e.textContent",
8267            "hello there",
8268        ),
8269        (
8270            "var e=document.getElementById('t'); e.innerHTML='hello world'; e.firstChild.substringData(6,5)",
8271            "world",
8272        ),
8273        // `deleteData`/`insertData`/`replaceData`/`substringData`/`splitText`は
8274        // 仕様上`offset`が`length`を超えると`IndexSizeError`を投げるべきだが、
8275        // 以前は`offset`を黙って`length`へクランプしてしまい、範囲外呼び出しが
8276        // 常に無害なno-op/末尾追記として成立していた(Table DOMの
8277        // `insertRow`等と同じ「範囲外インデックスの黙殺」バグ族)。
8278        (
8279            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8280             try { e.firstChild.deleteData(99,1); 'no-throw' } catch(e) { e.name }",
8281            "IndexSizeError",
8282        ),
8283        (
8284            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8285             try { e.firstChild.insertData(99,'x'); 'no-throw' } catch(e) { e.name }",
8286            "IndexSizeError",
8287        ),
8288        (
8289            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8290             try { e.firstChild.replaceData(99,1,'x'); 'no-throw' } catch(e) { e.name }",
8291            "IndexSizeError",
8292        ),
8293        (
8294            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8295             try { e.firstChild.substringData(99,1); 'no-throw' } catch(e) { e.name }",
8296            "IndexSizeError",
8297        ),
8298        (
8299            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8300             try { e.firstChild.splitText(99); 'no-throw' } catch(e) { e.name }",
8301            "IndexSizeError",
8302        ),
8303        (
8304            // offset===lengthはちょうど境界で範囲内(末尾扱い)なので例外にならない。
8305            "var e=document.getElementById('t'); e.innerHTML='hi'; \
8306             e.firstChild.insertData(2,'!'); e.textContent",
8307            "hi!",
8308        ),
8309        // `Text.splitText(offset)`(丸ごと未対応だった。2026-07-16 発見・
8310        // 実装。テキストノードを分割し、後半を新しい兄弟ノードとして
8311        // 挿入する)。
8312        (
8313            "var e=document.getElementById('t'); e.innerHTML='hello world'; \
8314             var t1=e.firstChild; var t2=t1.splitText(5); t1.data+'|'+t2.data",
8315            "hello| world",
8316        ),
8317        (
8318            "var e=document.getElementById('t'); e.innerHTML='hello world'; \
8319             var t1=e.firstChild; t1.splitText(5); e.childNodes.length",
8320            "2",
8321        ),
8322        (
8323            "var e=document.getElementById('t'); e.innerHTML='hello world'; \
8324             var t1=e.firstChild; var t2=t1.splitText(5); t2.nextSibling===null && t2.previousSibling===t1",
8325            "true",
8326        ),
8327        // `Text.wholeText`(丸ごと未対応だった。`splitText`と対をなす読み
8328        // 取りプロパティ。2026-07-16 発見・実装。連続するテキストノード
8329        // 兄弟の`data`を全て連結して返す)。
8330        (
8331            "var e=document.getElementById('t'); e.innerHTML='hello world'; \
8332             var t1=e.firstChild; var t2=t1.splitText(5); t1.wholeText+'|'+t2.wholeText",
8333            "hello world|hello world",
8334        ),
8335        (
8336            "var e=document.getElementById('t'); e.innerHTML='hello'; e.firstChild.wholeText",
8337            "hello",
8338        ),
8339        // HTML5 Table DOM(`table.insertRow`/`.deleteRow`/`.rows`、
8340        // `tr.insertCell`/`.deleteCell`/`.cells`、`.rowIndex`/`.cellIndex`)が
8341        // 丸ごと未対応だった(2026-07-17 発見・実装)。
8342        (
8343            "var t=document.createElement('table'); \
8344             t.insertRow(); t.insertRow(); t.rows.length",
8345            "2",
8346        ),
8347        (
8348            "var t=document.createElement('table'); var r=t.insertRow(); r.insertCell(); r.insertCell(); \
8349             r.cells.length",
8350            "2",
8351        ),
8352        (
8353            "var t=document.createElement('table'); \
8354             var r0=t.insertRow(); var r1=t.insertRow(); var rmid=t.insertRow(1); \
8355             rmid===t.rows[1]",
8356            "true",
8357        ),
8358        (
8359            "var t=document.createElement('table'); t.insertRow(); t.insertRow(); t.deleteRow(0); \
8360             t.rows.length",
8361            "1",
8362        ),
8363        (
8364            "var t=document.createElement('table'); var r=t.insertRow(); \
8365             r.insertCell(); var mid=r.insertCell(); r.insertCell(); \
8366             mid===r.cells[1]",
8367            "true",
8368        ),
8369        (
8370            "var t=document.createElement('table'); var r=t.insertRow(); r.insertCell(); r.insertCell(); \
8371             r.deleteCell(0); r.cells.length",
8372            "1",
8373        ),
8374        (
8375            "var t=document.createElement('table'); t.insertRow(); var r=t.insertRow(); r.rowIndex",
8376            "1",
8377        ),
8378        (
8379            "var t=document.createElement('table'); var r=t.insertRow(); r.insertCell(); var c=r.insertCell(); c.cellIndex",
8380            "1",
8381        ),
8382        // `insertRow`/`deleteRow`/`insertCell`/`deleteCell`は仕様上、範囲外の
8383        // インデックス(`-1`未満、または`insertRow`/`insertCell`なら
8384        // `length`超過、`deleteRow`/`deleteCell`なら`length`以上)で
8385        // `IndexSizeError`を投げるべきだが、以前は範囲外の値をすべて
8386        // 「末尾へ追加」/「末尾を削除」として黙って受け入れてしまっていた。
8387        (
8388            "var t=document.createElement('table'); t.insertRow(); \
8389             try { t.insertRow(5); 'no-throw' } catch(e) { e.name }",
8390            "IndexSizeError",
8391        ),
8392        (
8393            "var t=document.createElement('table'); t.insertRow(); \
8394             try { t.deleteRow(5); 'no-throw' } catch(e) { e.name }",
8395            "IndexSizeError",
8396        ),
8397        (
8398            "var t=document.createElement('table'); var r=t.insertRow(); r.insertCell(); \
8399             try { r.insertCell(5); 'no-throw' } catch(e) { e.name }",
8400            "IndexSizeError",
8401        ),
8402        (
8403            "var t=document.createElement('table'); var r=t.insertRow(); r.insertCell(); \
8404             try { r.deleteCell(5); 'no-throw' } catch(e) { e.name }",
8405            "IndexSizeError",
8406        ),
8407        (
8408            // `insertRow(length)`(末尾追加)と`insertRow(-1)`(同じく末尾追加)は
8409            // 範囲内なので従来どおり例外にならない。
8410            "var t=document.createElement('table'); t.insertRow(); \
8411             t.insertRow(1); t.insertRow(-1); t.rows.length",
8412            "3",
8413        ),
8414        // `tr.sectionRowIndex`(丸ごと未対応だった。`rowIndex`は実装済み
8415        // なのに対になるこちらだけ抜けていた兄弟ギャップ。2026-07-17
8416        // 発見・実装)。`thead`2行+`tbody`3行の表で、`tbody`側2行目
8417        // (テーブル全体では4番目=`rowIndex:3`)の`sectionRowIndex`は
8418        // `tbody`内での位置`1`(0始まり)になる。
8419        (
8420            "var t=document.createElement('table'); \
8421             var h=t.createTHead(); h.insertRow(); h.insertRow(); \
8422             var b=document.createElement('tbody'); t.appendChild(b); \
8423             b.insertRow(); var r=b.insertRow(); b.insertRow(); \
8424             r.rowIndex + ',' + r.sectionRowIndex",
8425            "3,1",
8426        ),
8427        // `HTMLTableSectionElement.insertRow`/`.rows`(丸ごと未対応だった。
8428        // `table.insertRow`は実装済みなのに対になるこちらだけ抜けていた
8429        // 兄弟ギャップ。`tbody`自身が持つ`insertRow`/`.rows`はそのセクション
8430        // 内に限定される。2026-07-17 発見・実装)。
8431        (
8432            "var t=document.createElement('table'); var b=document.createElement('tbody'); \
8433             t.appendChild(b); b.insertRow(); b.insertRow(); b.rows.length",
8434            "2",
8435        ),
8436        // `thead`/`tbody`ラッパー無しで直接`table`に`insertRow`した場合は
8437        // `rowIndex`と`sectionRowIndex`が一致する。
8438        (
8439            "var t=document.createElement('table'); t.insertRow(); var r=t.insertRow(); \
8440             r.rowIndex + ',' + r.sectionRowIndex",
8441            "1,1",
8442        ),
8443        // `table.tHead`/`.tFoot`/`.caption`/`.tBodies`/`.createTHead()`/
8444        // `.createTFoot()`/`.createCaption()`/`.delete*()`(丸ごと未対応
8445        // だった。2026-07-17 発見・実装。「既存要素があれば返す、無ければ
8446        // 新規作成」の冪等な動作)。
8447        ("document.createElement('table').tHead", "null"),
8448        (
8449            "var t=document.createElement('table'); var h=t.createTHead(); h===t.tHead && h.tagName",
8450            "THEAD",
8451        ),
8452        (
8453            "var t=document.createElement('table'); var h1=t.createTHead(); var h2=t.createTHead(); h1===h2",
8454            "true",
8455        ),
8456        (
8457            "var t=document.createElement('table'); t.createTHead(); t.deleteTHead(); t.tHead",
8458            "null",
8459        ),
8460        (
8461            "var t=document.createElement('table'); var f=t.createTFoot(); f===t.tFoot && f.tagName",
8462            "TFOOT",
8463        ),
8464        (
8465            "var t=document.createElement('table'); var c=t.createCaption(); c===t.caption && c.tagName",
8466            "CAPTION",
8467        ),
8468        (
8469            "var t=document.createElement('table'); t.appendChild(document.createElement('tbody')); \
8470             t.appendChild(document.createElement('tbody')); t.tBodies.length",
8471            "2",
8472        ),
8473        // `node.isConnected`(click-outside 判定等で使われる)が丸ごと未対応だった。
8474        (
8475            "document.createElement('div').isConnected",
8476            "false",
8477        ),
8478        (
8479            "document.getElementById('t').isConnected",
8480            "true",
8481        ),
8482        (
8483            "var e=document.getElementById('t'); e.remove(); e.isConnected",
8484            "false",
8485        ),
8486        // `Node.ELEMENT_NODE`/`Node.TEXT_NODE`(`if (node.nodeType === Node.
8487        // ELEMENT_NODE)` という定番パターンで使われる標準定数)が丸ごと未対応
8488        // だった(グローバル `Node` 自体が存在しなかった)。
8489        (
8490            "document.createElement('div').nodeType === Node.ELEMENT_NODE",
8491            "true",
8492        ),
8493        ("Node.TEXT_NODE", "3"),
8494        ("Node.COMMENT_NODE", "8"),
8495        ("var c=document.createComment('test'); c.nodeType", "8"),
8496        ("var c=document.createComment('test'); c.nodeName", "#comment"),
8497        ("var c=document.createComment('test'); c.nodeValue", "test"),
8498        ("var c=document.createComment('test'); c.data", "test"),
8499        ("var c=document.createComment('test'); c.length", "4"),
8500        ("var c=document.createComment('test'); c.nodeValue='new'; c.nodeValue", "new"),
8501        ("var c=document.createComment('test'); c.data='new'; c.data", "new"),
8502        ("var c=document.createComment('test'); c.textContent", "test"),
8503        ("var c=document.createComment('test'); c.textContent='new'; c.textContent", "new"),
8504        (
8505            "var d=document.createElement('div'); var c=document.createComment('foo'); d.appendChild(c); d.innerHTML",
8506            "<!--foo-->",
8507        ),
8508        (
8509            "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",
8510            "1",
8511        ),
8512        (
8513            "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",
8514            "div { color: red; }",
8515        ),
8516        (
8517            "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",
8518            "div",
8519        ),
8520        (
8521            "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",
8522            "2",
8523        ),
8524        (
8525            "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",
8526            "span { color: blue; }",
8527        ),
8528        (
8529            "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",
8530            "1",
8531        ),
8532        (
8533            "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",
8534            "span { color: blue; }",
8535        ),
8536        // `document.title` が常に install 時の空文字列固定で `<title>` 要素の中身/
8537        // 代入結果と一切連動していなかったバグ。ここでは `<head>` 自体が無い状態
8538        // (フレッシュな JsRuntime)での内部プロパティへのフォールバック保存/読み出し
8539        // を確認する(`<head>`/`<title>` 実要素との連動は別ブロックで確認)。
8540        ("document.title = 'Hello'; document.title", "Hello"),
8541        // `element.lang`/`.dir`(グローバル属性の JS プロパティ版)が他の属性直結
8542        // プロパティ群の点検から漏れて丸ごと未対応だった。
8543        (
8544            "var e=document.createElement('p'); e.lang='ja'; e.lang",
8545            "ja",
8546        ),
8547        (
8548            "var e=document.createElement('p'); e.dir='rtl'; e.dir",
8549            "rtl",
8550        ),
8551        (
8552            "var e=document.createElement('p'); e.setAttribute('lang','fr'); e.lang",
8553            "fr",
8554        ),
8555        // `element.title`/`.accessKey`/`.translate`(同じ理由で丸ごと未対応
8556        // だった。未マッチキーは巨大 match の末尾 `_ => Value::Undefined`
8557        // に落ちるだけで汎用フォールバックが無いため常に `undefined` に
8558        // なっていた。2026-07-16 発見・実装)。
8559        (
8560            "var e=document.createElement('p'); e.title='tip'; e.title",
8561            "tip",
8562        ),
8563        (
8564            "var e=document.createElement('p'); e.setAttribute('title','x'); e.title",
8565            "x",
8566        ),
8567        (
8568            "var e=document.createElement('p'); e.accessKey='k'; e.getAttribute('accesskey')",
8569            "k",
8570        ),
8571        // `translate` は既定 `true`、`translate=\"no\"` の時のみ `false`。
8572        ("document.createElement('p').translate", "true"),
8573        (
8574            "var e=document.createElement('p'); e.setAttribute('translate','no'); e.translate",
8575            "false",
8576        ),
8577        (
8578            "var e=document.createElement('p'); e.translate=false; e.getAttribute('translate')",
8579            "no",
8580        ),
8581        // `element.draggable`(丸ごと未対応だった。2026-07-16 発見・実装。
8582        // 明示指定が無ければ既定 `false` の簡略実装)。
8583        ("document.createElement('div').draggable", "false"),
8584        (
8585            "var e=document.createElement('div'); e.draggable=true; e.getAttribute('draggable')",
8586            "true",
8587        ),
8588        (
8589            "var e=document.createElement('div'); e.setAttribute('draggable','true'); e.draggable",
8590            "true",
8591        ),
8592        // `element.slot`(named slot 割り当て名。丸ごと未対応だった。
8593        // 2026-07-16 発見・実装)。
8594        (
8595            "var e=document.createElement('div'); e.slot='header'; e.getAttribute('slot')",
8596            "header",
8597        ),
8598        // DOM コンストラクタ階層 (Node -> Element -> HTMLElement -> HTMLXxxElement)
8599        // および `instanceof` / `.constructor` 連携の自己テスト
8600        (
8601            "document.createElement('div') instanceof HTMLDivElement",
8602            "true",
8603        ),
8604        (
8605            "document.createElement('div') instanceof HTMLElement",
8606            "true",
8607        ),
8608        (
8609            "document.createElement('div') instanceof Element",
8610            "true",
8611        ),
8612        (
8613            "document.createElement('div') instanceof Node",
8614            "true",
8615        ),
8616        (
8617            "document.createElement('div') instanceof HTMLInputElement",
8618            "false",
8619        ),
8620        (
8621            "document.createElement('input') instanceof HTMLInputElement",
8622            "true",
8623        ),
8624        (
8625            "document.createElement('input') instanceof HTMLElement",
8626            "true",
8627        ),
8628        (
8629            "document.createElement('form') instanceof HTMLFormElement",
8630            "true",
8631        ),
8632        (
8633            "document.createElement('div').constructor === HTMLDivElement",
8634            "true",
8635        ),
8636        (
8637            "try { new Node(); false; } catch(e) { e instanceof TypeError }",
8638            "true",
8639        ),
8640        (
8641            "try { new HTMLElement(); false; } catch(e) { e instanceof TypeError }",
8642            "true",
8643        ),
8644        (
8645            "var e=document.createElement('div'); e.setAttribute('slot','footer'); e.slot",
8646            "footer",
8647        ),
8648        // `element.tabIndex`(`tabindex` 属性の数値プロパティ版)も同じ理由で
8649        // 丸ごと未対応だった。未指定時の既定値は `-1`。
8650        (
8651            "document.createElement('div').tabIndex",
8652            "-1",
8653        ),
8654        (
8655            "var e=document.createElement('div'); e.tabIndex=3; e.tabIndex",
8656            "3",
8657        ),
8658        (
8659            "var e=document.createElement('div'); e.tabIndex=2; e.getAttribute('tabindex')",
8660            "2",
8661        ),
8662        // `element.hidden = true/false`(要素の表示/非表示切替の定番イディオム)の
8663        // setter が丸ごと未対応だった(getter は既に対応済み)。
8664        (
8665            "var e=document.createElement('div'); e.hidden=true; e.hidden",
8666            "true",
8667        ),
8668        (
8669            "var e=document.createElement('div'); e.hidden=true; e.hasAttribute('hidden')",
8670            "true",
8671        ),
8672        (
8673            "var e=document.createElement('div'); e.hidden=true; e.hidden=false; e.hidden",
8674            "false",
8675        ),
8676        // `form.noValidate`(`novalidate` 属性の camelCase プロパティ版)が丸ごと
8677        // 未対応だった。
8678        (
8679            "var f=document.createElement('form'); f.noValidate=true; f.noValidate",
8680            "true",
8681        ),
8682        (
8683            "var f=document.createElement('form'); f.noValidate=true; f.hasAttribute('novalidate')",
8684            "true",
8685        ),
8686        (
8687            "document.createElement('form').noValidate",
8688            "false",
8689        ),
8690        // `form.elements`(全フォームコントロールを走査する定番イディオム)が丸ごと
8691        // 未対応だった。
8692        (
8693            "var f=document.createElement('form'); var i=document.createElement('input'); f.appendChild(i); f.elements.length",
8694            "1",
8695        ),
8696        (
8697            "var f=document.createElement('form'); var i=document.createElement('input'); i.name='x'; f.appendChild(i); f.elements[0].name",
8698            "x",
8699        ),
8700        // `form.length`(`form.elements.length`と同じ値を返すIDL属性の数値
8701        // ショートハンド。`select.length`の対になる`<form>`側だが丸ごと
8702        // 未対応だった)。
8703        (
8704            "var f=document.createElement('form'); var i=document.createElement('input'); \
8705             var s=document.createElement('select'); f.appendChild(i); f.appendChild(s); \
8706             f.length + ',' + f.elements.length",
8707            "2,2",
8708        ),
8709        (
8710            "document.createElement('form').length",
8711            "0",
8712        ),
8713        // `fieldset.elements`(`HTMLFieldSetElement`のIDLプロパティ。丸ごと
8714        // 未対応だった。`form.elements`と異なり純粋なDOM子孫のみが対象。
8715        // 2026-07-17 発見・実装)。
8716        (
8717            "var fs=document.createElement('fieldset'); var i=document.createElement('input'); \
8718             fs.appendChild(i); fs.elements.length",
8719            "1",
8720        ),
8721        (
8722            "document.createElement('fieldset').elements.length",
8723            "0",
8724        ),
8725        // `fieldset.type`(仕様上、属性値に関わらず常に固定文字列
8726        // `"fieldset"`を返す読み取り専用IDLプロパティ。丸ごと未対応
8727        // だった。2026-07-17 発見・実装)。
8728        ("document.createElement('fieldset').type", "fieldset"),
8729        (
8730            "var fs=document.createElement('fieldset'); fs.setAttribute('type','x'); fs.type",
8731            "fieldset",
8732        ),
8733        // `select.type`(仕様上、`multiple`属性の有無で`"select-multiple"`/
8734        // `"select-one"`のいずれか固定文字列を返す読み取り専用IDL
8735        // プロパティ。`fieldset.type`と同じ理由で丸ごと未対応だった。
8736        // 2026-07-17 発見・実装)。
8737        ("document.createElement('select').type", "select-one"),
8738        (
8739            "var s=document.createElement('select'); s.multiple=true; s.type",
8740            "select-multiple",
8741        ),
8742        // `button.type`(`type`属性が無いか不正な値なら既定`"submit"`に
8743        // フォールバックする。`fieldset.type`/`select.type`と同じ理由で
8744        // 丸ごと未対応だった。`if (btn.type === 'submit')`という定番
8745        // チェックが常に偽になる実害があった。2026-07-17 発見・実装)。
8746        ("document.createElement('button').type", "submit"),
8747        // `textarea.type`(仕様上、属性値に関わらず常に固定文字列
8748        // `"textarea"`を返す読み取り専用IDLプロパティ。`fieldset.type`と
8749        // 同じ理由で丸ごと未対応だった。2026-07-17 発見・実装)。
8750        ("document.createElement('textarea').type", "textarea"),
8751        (
8752            "var b=document.createElement('button'); b.setAttribute('type','reset'); b.type",
8753            "reset",
8754        ),
8755        (
8756            "var b=document.createElement('button'); b.setAttribute('type','button'); b.type",
8757            "button",
8758        ),
8759        (
8760            "var b=document.createElement('button'); b.setAttribute('type','bogus'); b.type",
8761            "submit",
8762        ),
8763        // `input.type`(`type`属性が無いか既知の列挙値以外なら既定
8764        // `"text"`にフォールバックする。`fieldset.type`/`select.type`/
8765        // `button.type`と同じ理由で丸ごと未対応だった。全入力系IDL
8766        // プロパティの中で最も広く参照される`.type`が常に空文字列に
8767        // なっていた実害の大きいバグ。2026-07-17 発見・実装)。
8768        ("document.createElement('input').type", "text"),
8769        (
8770            "var i=document.createElement('input'); i.setAttribute('type','checkbox'); i.type",
8771            "checkbox",
8772        ),
8773        (
8774            "var i=document.createElement('input'); i.setAttribute('type','DATE'); i.type",
8775            "date",
8776        ),
8777        (
8778            "var i=document.createElement('input'); i.setAttribute('type','bogus'); i.type",
8779            "text",
8780        ),
8781        // `form.elements`が`form="formId"`属性によるフォーム外関連付け
8782        // (HTML5仕様の「form owner」)を反映していなかった兄弟ギャップの
8783        // 自己テストは、この配列が`(&str, &str)`(HTML fixtureを持たない)
8784        // 型のため3要素タプルでは追加できない。別途、独自のHTML fixtureを
8785        // 持つ`fd2_cases`(本ファイル内。`FormData`のform owner対応テストと
8786        // 同じfixtureを再利用)に追加してある。
8787        // `input.form`(自身が属する `<form>` への逆参照)が丸ごと未対応だった。
8788        (
8789            "var f=document.createElement('form'); f.id='ff'; var i=document.createElement('input'); f.appendChild(i); i.form.id",
8790            "ff",
8791        ),
8792        (
8793            "document.createElement('input').form === null",
8794            "true",
8795        ),
8796        // `input.labels`(`for` 属性の明示的な関連付けと、`<label>` 子孫の暗黙の
8797        // 関連付けの両方)が丸ごと未対応だった。
8798        (
8799            "var i=document.createElement('input'); i.id='u'; var l=document.createElement('label'); l.setAttribute('for','u'); i.labels.length",
8800            "1",
8801        ),
8802        (
8803            "var l=document.createElement('label'); var i=document.createElement('input'); l.appendChild(i); i.labels.length",
8804            "1",
8805        ),
8806        (
8807            "document.createElement('input').labels.length",
8808            "0",
8809        ),
8810        // `label.control`(`labels` の逆方向)が丸ごと未対応だった。
8811        (
8812            "var i=document.createElement('input'); i.id='v'; var l=document.createElement('label'); l.setAttribute('for','v'); l.control.id",
8813            "v",
8814        ),
8815        (
8816            "var l=document.createElement('label'); var i=document.createElement('input'); i.name='y'; l.appendChild(i); l.control.name",
8817            "y",
8818        ),
8819        (
8820            "document.createElement('label').control === null",
8821            "true",
8822        ),
8823        // `node.contains(other)`(click-outside 判定等で広く使われる)が丸ごと
8824        // 未対応だった。
8825        (
8826            "var w=document.getElementById('wrap'); var t=document.getElementById('t'); w.contains(t)+','+t.contains(w)+','+t.contains(t)",
8827            "true,false,true",
8828        ),
8829        // `Node.compareDocumentPosition(other)` が丸ごと未対応だった(`contains()` はあったが
8830        // 兄弟同士の前後関係や祖先/子孫の判定を1回のビットマスクで得る標準APIが欠落)。
8831        (
8832            "var t=document.getElementById('t'); t.compareDocumentPosition(t)",
8833            "0",
8834        ),
8835        (
8836            "var w=document.getElementById('wrap'); var t=document.getElementById('t'); t.compareDocumentPosition(w)",
8837            "10",
8838        ),
8839        (
8840            "var w=document.getElementById('wrap'); var t=document.getElementById('t'); w.compareDocumentPosition(t)",
8841            "20",
8842        ),
8843        (
8844            "var t=document.getElementById('t'); var s=document.getElementById('s'); t.compareDocumentPosition(s)",
8845            "4",
8846        ),
8847        (
8848            "var t=document.getElementById('t'); var s=document.getElementById('s'); s.compareDocumentPosition(t)",
8849            "2",
8850        ),
8851        // `element.click()` が単なる no-op で、実際のクリック(登録済みリスナの発火)を
8852        // 一切引き起こさなかったバグ(プログラム的クリックはテストコードで広く使われる
8853        // 定番パターン)。
8854        (
8855            "var e=document.getElementById('t'); var clicked=false; e.addEventListener('click', function(){ clicked=true; }); e.click(); clicked",
8856            "true",
8857        ),
8858        // `document.scrollingElement`(HTML5。丸ごと未対応だった。標準準拠
8859        // モードでは `documentElement`(`<html>`)と同じ要素を返す仕様)。
8860        ("document.scrollingElement.tagName", "HTML"),
8861        ("document.scrollingElement === document.documentElement", "true"),
8862        // `document.activeElement`/`element.focus()`/`.blur()`(HTML5。丸ごと
8863        // 未対応だった。以前は `activeElement` が常に `null` 固定、
8864        // `.focus()`/`.blur()` 自体が丸ごと存在しなかった)。
8865        (
8866            "document.getElementById('t').focus(); document.activeElement.id",
8867            "t",
8868        ),
8869        (
8870            "document.getElementById('t').focus(); document.getElementById('t') === document.activeElement",
8871            "true",
8872        ),
8873        (
8874            "document.getElementById('t').focus(); document.getElementById('s').focus(); document.activeElement.id",
8875            "s",
8876        ),
8877        (
8878            "document.getElementById('t').focus(); document.getElementById('t').blur(); \
8879             document.getElementById('t') === document.activeElement",
8880            "false",
8881        ),
8882        (
8883            "var log=''; document.getElementById('t').addEventListener('focus', ()=>log+='f'); \
8884             document.getElementById('t').addEventListener('blur', ()=>log+='b'); \
8885             document.getElementById('t').focus(); document.getElementById('s').focus(); log",
8886            "fb",
8887        ),
8888        (
8889            "var n=0; document.getElementById('t').addEventListener('focus', ()=>n++); \
8890             document.getElementById('t').focus(); document.getElementById('t').focus(); n",
8891            "1",
8892        ),
8893    ];
8894    for (src, expect) in dom2_cases {
8895        let mut rt = JsRuntime::new();
8896        rt.dom
8897            .borrow_mut()
8898            .build_from(&crate::os_lib::dom::parse_html(dom2_html));
8899        match rt.eval(src) {
8900            Ok(v) if &v.to_js_string() == expect => passed += 1,
8901            Ok(v) => crate::println!(
8902                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
8903                src,
8904                v.to_js_string(),
8905                expect
8906            ),
8907            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
8908        }
8909    }
8910    // ===== DOM イベントシステム(Phase 3+)=====
8911    // addEventListener / removeEventListener / dispatchEvent と Event オブジェクトの
8912    // 伝播(capture→target→bubble)、preventDefault / stopPropagation /
8913    // stopImmediatePropagation / once / target・currentTarget を JS 層から検証する。
8914    // dispatchEvent はホストの dispatch_key/dispatch_mouse と同じ dispatch_event_in_interp を
8915    // 通るため、ここで伝播ロジック全体を決定論的にカバーできる。
8916    let ev_html = "<div id='outer'><div id='mid'><button id='btn'>go</button></div></div>";
8917    let event_cases: &[(&str, &str)] = &[
8918        // 基本: addEventListener したリスナが dispatchEvent で発火する。
8919        ("var n=0; var b=document.getElementById('btn'); b.addEventListener('click', function(){ n++; }); b.dispatchEvent({type:'click'}); n", "1"),
8920        // 同一要素に複数リスナ → 登録順に全て発火。
8921        ("var s=''; var b=document.getElementById('btn'); b.addEventListener('click', ()=>s+='a'); b.addEventListener('click', ()=>s+='b'); b.dispatchEvent({type:'click'}); s", "ab"),
8922        // removeEventListener で解除したリスナは発火しない。
8923        ("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"),
8924        // バブリング: button で発火 → 祖先 mid / outer の click リスナも発火。
8925        ("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"),
8926        // event.target は発火元、currentTarget は処理中の要素(バブリング中は祖先)。
8927        ("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"),
8928        // stopPropagation: button のリスナで止めると祖先には伝わらない。
8929        ("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"),
8930        // stopImmediatePropagation: 同一要素の後続リスナも止める。
8931        ("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"),
8932        // preventDefault → dispatchEvent は false を返す(defaultPrevented)。
8933        ("var b=document.getElementById('btn'); b.addEventListener('click', e=>e.preventDefault()); b.dispatchEvent({type:'click'})", "false"),
8934        // preventDefault しなければ dispatchEvent は true を返す。
8935        ("var b=document.getElementById('btn'); b.addEventListener('click', ()=>{}); b.dispatchEvent({type:'click'})", "true"),
8936        // e.defaultPrevented フラグが preventDefault 後に true。
8937        ("var d=false; var b=document.getElementById('btn'); b.addEventListener('click', function(e){ e.preventDefault(); d=e.defaultPrevented; }); b.dispatchEvent({type:'click'}); d", "true"),
8938        // once: true のリスナは 1 回だけ発火し、2 回目は無視される。
8939        ("var n=0; var b=document.getElementById('btn'); b.addEventListener('click', ()=>n++, {once:true}); b.dispatchEvent({type:'click'}); b.dispatchEvent({type:'click'}); n", "1"),
8940        // capture フェーズ: 祖先の capture リスナは target より先に発火。
8941        ("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"),
8942        // event.type が正しく渡る。
8943        ("var ty=''; var b=document.getElementById('btn'); b.addEventListener('custom', function(e){ ty=e.type; }); b.dispatchEvent({type:'custom'}); ty", "custom"),
8944        // カスタムプロパティ(detail 等)がリスナ側へ転送される。
8945        ("var d=0; var b=document.getElementById('btn'); b.addEventListener('ping', function(e){ d=e.detail; }); b.dispatchEvent({type:'ping', detail:7}); d", "7"),
8946        // 異なるイベント型のリスナは発火しない。
8947        ("var n=0; var b=document.getElementById('btn'); b.addEventListener('mouseover', ()=>n++); b.dispatchEvent({type:'click'}); n", "0"),
8948        // バブリングフラグ未指定でも、本エンジンは委譲(イベント委譲)を成立させるため
8949        // 祖先のリスナも発火する設計(blur/focus を親で受ける UI を許容)。
8950        ("var s=''; document.getElementById('outer').addEventListener('foo', ()=>s+='O'); document.getElementById('btn').addEventListener('foo', ()=>s+='B'); document.getElementById('btn').dispatchEvent({type:'foo'}); s", "BO"),
8951        // `document.addEventListener('click', fn)` によるイベント委譲パターンが
8952        // 丸ごと未対応だった(`document` は `self.dom.nodes` の一員ではないため
8953        // 祖先チェーンに一切含まれず、`click`等の汎用種別は登録すら黙って
8954        // 捨てられていた。2026-07-16 発見・実装)。要素からバブリングした
8955        // クリックが `document` レベルのリスナまで届くことを確認。
8956        ("var s=''; document.addEventListener('click', ()=>s+='D'); document.getElementById('btn').addEventListener('click', ()=>s+='B'); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); s", "BD"),
8957        // `stopPropagation()` すれば document レベルへは届かない。
8958        ("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"),
8959        // document.removeEventListener('click', fn) で解除できる。
8960        ("var n=0; var f=()=>n++; document.addEventListener('click', f); document.removeEventListener('click', f); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); n", "0"),
8961        // document 側でも e.target は発火元要素、currentTarget は document 自身。
8962        ("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"),
8963        // `document.dispatchEvent(new Event('click'))` で document 自身から直接
8964        // 合成発火することもできる(実DOM要素を経由しない経路)。
8965        ("var n=0; document.addEventListener('click', ()=>n++); document.dispatchEvent(new Event('click')); n", "1"),
8966        // `document.addEventListener(type, fn, {capture:true})` がキャプチャ
8967        // フェーズを一切扱えないバグ(丸ごと未対応だった。`document` の汎用
8968        // リスナは `event_target_add_event_listener` を再利用しており、
8969        // `capture` オプションは元々「DOM ツリーを持たないクラスでは無意味」
8970        // として即座に破棄されていた。`document` に対して再利用したことで
8971        // 意味が変わり、キャプチャ登録が黙ってバブル扱いになっていた。
8972        // 2026-07-16 発見・実装)。document の capture リスナは祖先の
8973        // capture リスナより先(最も外側)に発火する。
8974        ("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"),
8975        // capture 登録は bubble フェーズでは発火しない(両方登録すれば両方発火)。
8976        ("var s=''; document.addEventListener('click', ()=>s+='C', true); document.addEventListener('click', ()=>s+='B'); document.getElementById('btn').dispatchEvent({type:'click', bubbles:true}); s", "CB"),
8977    ];
8978    let event_total = event_cases.len();
8979    for (src, expect) in event_cases {
8980        let mut rt = JsRuntime::new();
8981        rt.dom
8982            .borrow_mut()
8983            .build_from(&crate::os_lib::dom::parse_html(ev_html));
8984        match rt.eval(src) {
8985            Ok(v) if &v.to_js_string() == expect => passed += 1,
8986            Ok(v) => crate::println!(
8987                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
8988                src,
8989                v.to_js_string(),
8990                expect
8991            ),
8992            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
8993        }
8994    }
8995
8996    // FormData(form): <form> 配下のコントロールから値を収集(DOM 必須)。
8997    let fd_form_total = 4;
8998    {
8999        let fd_html = "<form id='f'>\
9000            <input name='user' value='alice'>\
9001            <input name='nope'>\
9002            <input type='checkbox' name='agree' value='yes' checked>\
9003            <input type='checkbox' name='news' value='1'>\
9004            <input type='radio' name='plan' value='free'>\
9005            <input type='radio' name='plan' value='pro' checked>\
9006            <input type='submit' name='btn' value='go'>\
9007            <select name='color' value='green'><option value='red'>R</option><option value='green'>G</option></select>\
9008            <textarea name='bio'>hello</textarea>\
9009        </form>";
9010        let fd_cases: &[(&str, &str)] = &[
9011            // user=alice, agree=yes(checked), plan=pro(checked radio), color=green, bio=hello。
9012            // nope は name はあるが value 空 → 空値で含まれる。news/free は未チェックで除外。submit は除外。
9013            (
9014                "new FormData(document.getElementById('f')).get('user')",
9015                "alice",
9016            ),
9017            (
9018                "new FormData(document.getElementById('f')).get('agree')",
9019                "yes",
9020            ),
9021            (
9022                "new FormData(document.getElementById('f')).get('plan')",
9023                "pro",
9024            ),
9025            (
9026                "var f=new FormData(document.getElementById('f')); f.has('news')+','+f.has('btn')",
9027                "false,false",
9028            ),
9029        ];
9030        for (src, expect) in fd_cases {
9031            let mut rt = JsRuntime::new();
9032            rt.dom
9033                .borrow_mut()
9034                .build_from(&crate::os_lib::dom::parse_html(fd_html));
9035            match rt.eval(src) {
9036                Ok(v) if &v.to_js_string() == expect => passed += 1,
9037                Ok(v) => crate::println!(
9038                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9039                    src,
9040                    v.to_js_string(),
9041                    expect
9042                ),
9043                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9044            }
9045        }
9046    }
9047    let fd2_form_total = 5;
9048    {
9049        // `collect_form_data`(FormData収集の実体)が`form="formId"`属性による
9050        // フォーム外関連付け(HTML5仕様の「form owner」。`element.form`/
9051        // `checkValidity()`は既に対応済み)を反映していなかった兄弟ギャップ。
9052        // 併せて`<fieldset disabled>`祖先による暗黙の無効化(`is_disabled`)も
9053        // 送信データ収集側では未反映だったため同時に修正した。
9054        let fd2_html = "<form id='f6'>\
9055            <input name='inside' value='a'>\
9056            <fieldset disabled><input name='hidden_by_fieldset' value='b'></fieldset>\
9057        </form>\
9058        <input name='outside' form='f6' value='c'>\
9059        <input name='not_associated' value='d'>";
9060        let fd2_cases: &[(&str, &str)] = &[
9061            (
9062                "new FormData(document.getElementById('f6')).get('inside')",
9063                "a",
9064            ),
9065            (
9066                "new FormData(document.getElementById('f6')).get('outside')",
9067                "c",
9068            ),
9069            (
9070                "var f=new FormData(document.getElementById('f6')); f.has('hidden_by_fieldset')",
9071                "false",
9072            ),
9073            (
9074                "var f=new FormData(document.getElementById('f6')); f.has('not_associated')",
9075                "false",
9076            ),
9077            // `form.elements`も同じ`form_associated_controls`を再利用するため、
9078            // フォーム外の`outside`(`form='f6'`)を含む3件(inside/
9079            // hidden_by_fieldset/outside。`not_associated`は含まれない)になる。
9080            (
9081                "document.getElementById('f6').elements.length",
9082                "3",
9083            ),
9084        ];
9085        for (src, expect) in fd2_cases {
9086            let mut rt = JsRuntime::new();
9087            rt.dom
9088                .borrow_mut()
9089                .build_from(&crate::os_lib::dom::parse_html(fd2_html));
9090            match rt.eval(src) {
9091                Ok(v) if &v.to_js_string() == expect => passed += 1,
9092                Ok(v) => crate::println!(
9093                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9094                    src,
9095                    v.to_js_string(),
9096                    expect
9097                ),
9098                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9099            }
9100        }
9101    }
9102    // `<select multiple>` — 選択された option 1つにつき1エントリを送信する仕様が
9103    // FormData 収集で丸ごと未対応だったバグ(複数選択リストボックスという定番 UI
9104    // パターン)。`selectedOptions`(丸ごと未対応だった)も合わせて確認する。
9105    let select_multiple_total = 8;
9106    {
9107        let sm_html = "<form id='f2'>\
9108            <select name='tags' multiple>\
9109                <option value='a' selected>A</option>\
9110                <option value='b'>B</option>\
9111                <option value='c' selected>C</option>\
9112            </select>\
9113        </form>";
9114        let sm_cases: &[(&str, &str)] = &[
9115            (
9116                "new FormData(document.getElementById('f2')).getAll('tags').join(',')",
9117                "a,c",
9118            ),
9119            (
9120                "document.querySelector('select').selectedOptions.length",
9121                "2",
9122            ),
9123            (
9124                "Array.from(document.querySelector('select').selectedOptions).map(o => o.value).join(',')",
9125                "a,c",
9126            ),
9127            // `select.options`(動的にドロップダウンを構築する定番イディオム)が
9128            // 丸ごと未対応だった。
9129            (
9130                "document.querySelector('select').options.length",
9131                "3",
9132            ),
9133            (
9134                "Array.from(document.querySelector('select').options).map(o => o.value).join(',')",
9135                "a,b,c",
9136            ),
9137            // `select.options.add`/`.remove`/`.namedItem`(`HTMLOptionsCollection`。
9138            // 丸ごと未対応だった)。
9139            (
9140                "var s=document.querySelector('select'); \
9141                 var o=new Option('D','d'); o.id='optd'; s.options.add(o); \
9142                 s.options.length + ':' + s.options[3].value",
9143                "4:d",
9144            ),
9145            (
9146                "var s=document.querySelector('select'); s.options.remove(1); \
9147                 Array.from(s.options).map(o => o.value).join(',')",
9148                "a,c",
9149            ),
9150            (
9151                "document.querySelector('select').options.namedItem('nope')",
9152                "null",
9153            ),
9154            (
9155                "var s=document.querySelector('select'); s.options.item(0).value",
9156                "a",
9157            ),
9158            (
9159                "var s=document.querySelector('select'); s.options.selectedIndex=1; s.selectedIndex",
9160                "1",
9161            ),
9162            (
9163                "var s=document.querySelector('select'); s.selectedIndex=2; s.options.selectedIndex",
9164                "2",
9165            ),
9166        ];
9167        for (src, expect) in sm_cases {
9168            let mut rt = JsRuntime::new();
9169            rt.dom
9170                .borrow_mut()
9171                .build_from(&crate::os_lib::dom::parse_html(sm_html));
9172            match rt.eval(src) {
9173                Ok(v) if &v.to_js_string() == expect => passed += 1,
9174                Ok(v) => crate::println!(
9175                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9176                    src,
9177                    v.to_js_string(),
9178                    expect
9179                ),
9180                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9181            }
9182        }
9183    }
9184    // `<select>`配下`<optgroup>`でグループ化された`<option>`が丸ごと見えなく
9185    // なっていたバグ(`select.options`/`selectedOptions`/フォーム送信の
9186    // どちらも直接の子要素しか見ておらず、`<optgroup>`という非常によく
9187    // 使われるカテゴリ分けパターンを完全に見落としていた。2026-07-17
9188    // 発見・実装)。
9189    // 【2026-07-24】以前`optgroup_total`が固定値`5`だったが、実際の`og_cases`
9190    // 配列は4件しか無く、`total`側だけ1件多く数える「集計ずれ」の一例だった
9191    // (`pass == total`の一言判定のみでは個別FAIL行が一切出ないままカテゴリ
9192    // 丸ごとFAILになり原因不明だった)。`.len()`から動的に求めることで
9193    // 今後同じズレが再発しないようにする。
9194    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>";
9195    let og_cases: &[(&str, &str)] = &[
9196        (
9197            "document.querySelector('select').options.length",
9198            "3",
9199        ),
9200        (
9201            "Array.from(document.querySelector('select').options).map(o => o.value).join(',')",
9202            "lemon,lime,straw",
9203        ),
9204        (
9205            "document.querySelector('select').selectedOptions.length",
9206            "1",
9207        ),
9208        (
9209            "document.querySelector('select').value",
9210            "lime",
9211        ),
9212    ];
9213    let optgroup_total = og_cases.len();
9214    {
9215        for (src, expect) in og_cases {
9216            let mut rt = JsRuntime::new();
9217            rt.dom
9218                .borrow_mut()
9219                .build_from(&crate::os_lib::dom::parse_html(og_html));
9220            match rt.eval(src) {
9221                Ok(v) if &v.to_js_string() == expect => passed += 1,
9222                Ok(v) => crate::println!(
9223                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9224                    src,
9225                    v.to_js_string(),
9226                    expect
9227                ),
9228                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9229            }
9230        }
9231    }
9232    // `document.title` が実際の `<head><title>` 要素と連動すること(既存 `<title>` の
9233    // 更新、無い場合は `<head>` へ新規作成)を、実 DOM 構造を持つフィクスチャで確認する。
9234    let doc_title_total = 3;
9235    {
9236        let title_cases: &[(&str, &str, &str)] = &[
9237            // 既存の <title> があれば、その中身が更新される。
9238            (
9239                "<html><head><title>old</title></head><body></body></html>",
9240                "document.title",
9241                "old",
9242            ),
9243            (
9244                "<html><head><title>old</title></head><body></body></html>",
9245                "document.title='new'; document.querySelector('title').textContent",
9246                "new",
9247            ),
9248            // <head> はあるが <title> が無い場合、新規作成されて連動する。
9249            (
9250                "<html><head></head><body></body></html>",
9251                "document.title='created'; document.querySelector('title').textContent",
9252                "created",
9253            ),
9254        ];
9255        for (html, src, expect) in title_cases {
9256            let mut rt = JsRuntime::new();
9257            rt.dom
9258                .borrow_mut()
9259                .build_from(&crate::os_lib::dom::parse_html(html));
9260            match rt.eval(src) {
9261                Ok(v) if &v.to_js_string() == expect => passed += 1,
9262                Ok(v) => crate::println!(
9263                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9264                    src,
9265                    v.to_js_string(),
9266                    expect
9267                ),
9268                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9269            }
9270        }
9271    }
9272    // `document.forms`/`.images`/`.scripts`(文書全体をタグ横断で集約する定番の
9273    // HTMLCollection 群)が丸ごと未対応だった。
9274    let doc_collections_total = 8;
9275    {
9276        let coll_html = "<html><body>\
9277            <form id='f1'></form><form id='f2'></form>\
9278            <img src='a.png'><img src='b.png'><img src='c.png'>\
9279            <script>1</script>\
9280            <a href='x.html'>x</a><a name='top'>anchor</a>\
9281            <area href='y.html' alt='y'>\
9282            <embed src='z.swf'>\
9283        </body></html>";
9284        let coll_cases: &[(&str, &str)] = &[
9285            ("document.forms.length", "2"),
9286            ("document.images.length", "3"),
9287            ("document.scripts.length", "1"),
9288            // `document.links`(href 付きの a/area のみ。`name` のみの a は含まない)が
9289            // 丸ごと未対応だった。
9290            ("document.links.length", "2"),
9291            // `document.anchors`(`name` 属性付きの a のみ)も丸ごと未対応だった。
9292            ("document.anchors.length", "1"),
9293            // `document.embeds`/`.plugins`(仕様上同じ集合を指すエイリアス)/
9294            // `document.applets`(Java アプレット廃止に伴い常に空固定のレガシー
9295            // スタブ)が丸ごと未対応だった。2026-07-16 発見・実装。
9296            ("document.embeds.length", "1"),
9297            ("document.plugins.length", "1"),
9298            ("document.applets.length", "0"),
9299        ];
9300        for (src, expect) in coll_cases {
9301            let mut rt = JsRuntime::new();
9302            rt.dom
9303                .borrow_mut()
9304                .build_from(&crate::os_lib::dom::parse_html(coll_html));
9305            match rt.eval(src) {
9306                Ok(v) if &v.to_js_string() == expect => passed += 1,
9307                Ok(v) => crate::println!(
9308                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9309                    src,
9310                    v.to_js_string(),
9311                    expect
9312                ),
9313                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9314            }
9315        }
9316    }
9317    // getBoundingClientRect / offset* (レンダラが set_rect で注入する矩形を JS から読む)。
9318    let rect_total = 15;
9319    {
9320        let rect_checks: &[(&str, &str)] = &[
9321            (
9322                "document.getElementById('t').getBoundingClientRect().width",
9323                "30",
9324            ),
9325            (
9326                "document.getElementById('t').getBoundingClientRect().height",
9327                "40",
9328            ),
9329            (
9330                "document.getElementById('t').getBoundingClientRect().left",
9331                "10",
9332            ),
9333            (
9334                "document.getElementById('t').getBoundingClientRect().bottom",
9335                "60",
9336            ),
9337            ("document.getElementById('t').offsetWidth", "30"),
9338            ("document.getElementById('t').offsetTop", "20"),
9339            // clientWidth = 30 (width) - 5 (left border) - 3 (right border) = 22
9340            ("document.getElementById('t').clientWidth", "22"),
9341            // clientHeight = 40 (height) - 2 (top border) - 4 (bottom border) = 34
9342            ("document.getElementById('t').clientHeight", "34"),
9343            // clientLeft = 5 (left border)
9344            ("document.getElementById('t').clientLeft", "5"),
9345            // clientTop = 2 (top border)
9346            ("document.getElementById('t').clientTop", "2"),
9347            // `element.getClientRects()`(丸ごと未対応だった。テキスト行フラグメント
9348            // モデルが無いこの処理系では要素1つにつき `getBoundingClientRect()` と
9349            // 同じ矩形を1個だけ持つ配列として返す簡略実装)。
9350            ("document.getElementById('t').getClientRects().length", "1"),
9351            (
9352                "document.getElementById('t').getClientRects()[0].width",
9353                "30",
9354            ),
9355            // rect が未登録(レンダラ未描画)の要素は仕様どおり空配列。
9356            ("document.getElementById('s').getClientRects().length", "0"),
9357            // `element.offsetParent`(丸ごと未対応だった。祖先に position が
9358            // static 以外の要素も `<body>` も無ければ仕様どおり `null`)。
9359            (
9360                "document.getElementById('t').offsetParent",
9361                "null",
9362            ),
9363            (
9364                "document.getElementById('wrap').style.position='relative'; \
9365                 document.getElementById('t').offsetParent.id",
9366                "wrap",
9367            ),
9368        ];
9369        for (src, expect) in rect_checks {
9370            let mut rt = JsRuntime::new();
9371            rt.dom
9372                .borrow_mut()
9373                .build_from(&crate::os_lib::dom::parse_html(dom2_html));
9374            // レンダラ相当: #t の矩形 (x=10,y=20,w=30,h=40) を注入。
9375            let idx = rt.dom.borrow().get_element_by_id("t");
9376            if let Some(i) = idx {
9377                rt.dom.borrow_mut().set_rect(i, 10, 20, 30, 40);
9378                rt.dom.borrow_mut().set_border_widths(i, 2, 3, 4, 5); // top=2, right=3, bottom=4, left=5
9379            }
9380            match rt.eval(src) {
9381                Ok(v) if &v.to_js_string() == expect => passed += 1,
9382                Ok(v) => crate::println!(
9383                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9384                    src,
9385                    v.to_js_string(),
9386                    expect
9387                ),
9388                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9389            }
9390        }
9391    }
9392
9393    // `document.elementFromPoint(x,y)`/`.elementsFromPoint(x,y)`(座標ヒットテスト)
9394    // が丸ごと未対応だった。`rects`(レンダラが `set_rect` で注入)から座標を含む
9395    // 矩形を集め、最も面積が小さい(=最も深くネストした)要素を返す簡略実装。
9396    let point_test_total = 4;
9397    {
9398        let point_checks: &[(&str, &str)] = &[
9399            ("document.elementFromPoint(20, 15).id", "t"),
9400            ("document.elementFromPoint(5, 5).id", "wrap"),
9401            ("document.elementFromPoint(500, 500)", "null"),
9402            (
9403                "document.elementsFromPoint(20, 15).map(e => e.id).join(',')",
9404                "t,wrap",
9405            ),
9406        ];
9407        for (src, expect) in point_checks {
9408            let mut rt = JsRuntime::new();
9409            rt.dom
9410                .borrow_mut()
9411                .build_from(&crate::os_lib::dom::parse_html(dom2_html));
9412            // レンダラ相当: 'wrap'(外側、大きい矩形)に 't'(内側、小さい矩形)が
9413            // ネストした状態を注入。
9414            let wrap_idx = rt.dom.borrow().get_element_by_id("wrap");
9415            if let Some(i) = wrap_idx {
9416                rt.dom.borrow_mut().set_rect(i, 0, 0, 100, 100);
9417            }
9418            let t_idx = rt.dom.borrow().get_element_by_id("t");
9419            if let Some(i) = t_idx {
9420                rt.dom.borrow_mut().set_rect(i, 10, 10, 30, 20);
9421            }
9422            match rt.eval(src) {
9423                Ok(v) if &v.to_js_string() == expect => passed += 1,
9424                Ok(v) => crate::println!(
9425                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9426                    src,
9427                    v.to_js_string(),
9428                    expect
9429                ),
9430                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9431            }
9432        }
9433    }
9434
9435    // getComputedStyle(set_computed_style で注入した算出スタイルを camelCase/kebab/getPropertyValue で読む)。
9436    let computed_total = 4;
9437    {
9438        let cs_checks: &[(&str, &str)] = &[
9439            ("getComputedStyle(document.getElementById('t')).color", "red"),
9440            ("getComputedStyle(document.getElementById('t')).backgroundColor", "blue"),
9441            ("getComputedStyle(document.getElementById('t')).getPropertyValue('background-color')", "blue"),
9442            ("window.getComputedStyle(document.getElementById('t')).fontSize", "16px"),
9443        ];
9444        for (src, expect) in cs_checks {
9445            let mut rt = JsRuntime::new();
9446            rt.dom
9447                .borrow_mut()
9448                .build_from(&crate::os_lib::dom::parse_html(dom2_html));
9449            let idx = rt.dom.borrow().get_element_by_id("t");
9450            if let Some(i) = idx {
9451                let mut d = rt.dom.borrow_mut();
9452                d.set_computed_style(i, "color", "red");
9453                d.set_computed_style(i, "background-color", "blue");
9454                d.set_computed_style(i, "font-size", "16px");
9455            }
9456            match rt.eval(src) {
9457                Ok(v) if &v.to_js_string() == expect => passed += 1,
9458                Ok(v) => crate::println!(
9459                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9460                    src,
9461                    v.to_js_string(),
9462                    expect
9463                ),
9464                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9465            }
9466        }
9467    }
9468
9469    // `element.checkVisibility()`(DOM 標準。丸ごと未対応だった)。
9470    let check_visibility_total = 4;
9471    {
9472        let cv_checks: &[(&str, &str, &[(&str, &str, &str)])] = &[
9473            (
9474                "document.getElementById('t').checkVisibility()",
9475                "true",
9476                &[],
9477            ),
9478            (
9479                "document.getElementById('t').checkVisibility()",
9480                "false",
9481                &[("t", "display", "none")],
9482            ),
9483            (
9484                "document.getElementById('t').checkVisibility()",
9485                "false",
9486                &[("wrap", "display", "none")],
9487            ),
9488            (
9489                "document.getElementById('t').checkVisibility()",
9490                "false",
9491                &[("t", "visibility", "hidden")],
9492            ),
9493        ];
9494        for (src, expect, styles) in cv_checks {
9495            let mut rt = JsRuntime::new();
9496            rt.dom
9497                .borrow_mut()
9498                .build_from(&crate::os_lib::dom::parse_html(dom2_html));
9499            for (id, prop, val) in *styles {
9500                let idx = rt.dom.borrow().get_element_by_id(id);
9501                if let Some(i) = idx {
9502                    rt.dom.borrow_mut().set_computed_style(i, prop, val);
9503                }
9504            }
9505            match rt.eval(src) {
9506                Ok(v) if &v.to_js_string() == expect => passed += 1,
9507                Ok(v) => crate::println!(
9508                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
9509                    src,
9510                    v.to_js_string(),
9511                    expect
9512                ),
9513                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
9514            }
9515        }
9516    }
9517
9518    // クリックディスパッチの統合確認(リスナ登録→dispatch_click→カウンタ増加)。
9519    {
9520        let mut rt = JsRuntime::new();
9521        rt.dom
9522            .borrow_mut()
9523            .build_from(&crate::os_lib::dom::parse_html(dom_html));
9524        let _ = rt.eval("var clicks=0; document.getElementById('b').addEventListener('click', () => { clicks++; document.getElementById('out').textContent = 'clicked ' + clicks; });");
9525        let bidx = rt.dom.borrow().get_element_by_id("b");
9526        if let Some(i) = bidx {
9527            rt.dispatch_click(i);
9528            rt.dispatch_click(i);
9529        }
9530        match rt.eval("clicks + ':' + document.getElementById('out').textContent") {
9531            Ok(v) if v.to_js_string() == "2:clicked 2" => passed += 1,
9532            Ok(v) => crate::println!(
9533                "JS_SELFTEST FAIL: click dispatch => `{}` (want `2:clicked 2`)",
9534                v.to_js_string()
9535            ),
9536            Err(e) => crate::println!("JS_SELFTEST ERR:  click dispatch => {}", e),
9537        }
9538    }
9539
9540    // Event: バブリング / target・currentTarget / stopPropagation / preventDefault / stopImmediate。
9541    let event_html = "<div id='par'><button id='btn'>go</button></div>";
9542    let event_checks: &[(&str, &str, &str)] = &[
9543        // 委譲: par のリスナが子 btn のクリックで発火。target=btn, currentTarget=par。
9544        ("var L=''; document.getElementById('par').addEventListener('click', function(e){ L += 'par('+e.target.id+','+e.currentTarget.id+')'; });",
9545         "L", "par(btn,par)"),
9546        // バブリング順: target(btn) → 祖先(par)。
9547        ("var L=''; document.getElementById('btn').addEventListener('click', function(){ L+='btn'; }); document.getElementById('par').addEventListener('click', function(){ L+='-par'; });",
9548         "L", "btn-par"),
9549        // stopPropagation で祖先に伝播しない。
9550        ("var L=''; document.getElementById('btn').addEventListener('click', function(e){ L+='btn'; e.stopPropagation(); }); document.getElementById('par').addEventListener('click', function(){ L+='-par'; });",
9551         "L", "btn"),
9552        // preventDefault → defaultPrevented。
9553        ("var L=''; document.getElementById('btn').addEventListener('click', function(e){ e.preventDefault(); L += e.defaultPrevented; });",
9554         "L", "true"),
9555        // stopImmediatePropagation で同一ノードの後続リスナも止まる。
9556        ("var L=''; document.getElementById('btn').addEventListener('click', function(e){ L+='1'; e.stopImmediatePropagation(); }); document.getElementById('btn').addEventListener('click', function(){ L+='2'; });",
9557         "L", "1"),
9558        // キャプチャ→ターゲット→バブルの順。par(capture) → btn → par(bubble)。
9559        ("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'; });",
9560         "L", "cap-tgt-bub"),
9561        // eventPhase: capture=1, at target=2, bubble=3。
9562        ("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; });",
9563         "P", "123"),
9564        // once: クリックで一度発火する(複数回 dispatch のテストは専用ブロックで実施)。
9565        ("var N=0; document.getElementById('btn').addEventListener('click', function(){ N++; }, {once:true}); N",
9566         "N", "1"),
9567        // removeEventListener: 登録解除すると発火しない。
9568        ("var L=''; function h(){ L+='x'; } document.getElementById('btn').addEventListener('click', h); document.getElementById('btn').removeEventListener('click', h);",
9569         "L", ""),
9570        // composedPath: target→祖先(btn, par, ...)。先頭2件の id を確認。
9571        ("var C=''; document.getElementById('btn').addEventListener('click', function(e){ var p=e.composedPath(); C=p[0].id+','+p[1].id; });",
9572         "C", "btn,par"),
9573    ];
9574    for (setup, read, expect) in event_checks {
9575        let mut rt = JsRuntime::new();
9576        rt.dom
9577            .borrow_mut()
9578            .build_from(&crate::os_lib::dom::parse_html(event_html));
9579        let _ = rt.eval(setup);
9580        let bidx = rt.dom.borrow().get_element_by_id("btn");
9581        if let Some(i) = bidx {
9582            rt.dispatch_click(i);
9583        }
9584        match rt.eval(read) {
9585            Ok(v) if &v.to_js_string() == expect => passed += 1,
9586            Ok(v) => crate::println!(
9587                "JS_SELFTEST FAIL: event `{}` => `{}` (want `{}`)",
9588                read,
9589                v.to_js_string(),
9590                expect
9591            ),
9592            Err(e) => crate::println!("JS_SELFTEST ERR:  event `{}` => {}", read, e),
9593        }
9594    }
9595
9596    // dispatchEvent: 手動で Event を発火し、リスナが受け取りバブリングする
9597    // (`bubbles: true` を明示。仕様上 `new Event('foo')` の既定は `bubbles: false`
9598    // で、以前は `bubbles` を一切見ず常に無条件で祖先まで伝播していたバグが
9599    // あったため、明示的に `true` を指定してバブリングの意図を検証する)。
9600    {
9601        let mut rt = JsRuntime::new();
9602        rt.dom
9603            .borrow_mut()
9604            .build_from(&crate::os_lib::dom::parse_html(event_html));
9605        let _ = rt.eval(
9606            "var L=''; \
9607             document.getElementById('btn').addEventListener('foo', function(e){ L+='btn:'+e.type; }); \
9608             document.getElementById('par').addEventListener('foo', function(){ L+='-par'; }); \
9609             document.getElementById('btn').dispatchEvent(new Event('foo', {bubbles:true}));",
9610        );
9611        match rt.eval("L") {
9612            Ok(v) if v.to_js_string() == "btn:foo-par" => passed += 1,
9613            Ok(v) => crate::println!(
9614                "JS_SELFTEST FAIL: dispatchEvent => `{}` (want `btn:foo-par`)",
9615                v.to_js_string()
9616            ),
9617            Err(e) => crate::println!("JS_SELFTEST ERR:  dispatchEvent => {}", e),
9618        }
9619    }
9620
9621    // dispatchEvent: `bubbles: false`(既定)なら祖先へ伝播しない。以前は
9622    // `bubbles` を一切見ず常に無条件で祖先まで伝播してしまう仕様違反バグだった。
9623    {
9624        let mut rt = JsRuntime::new();
9625        rt.dom
9626            .borrow_mut()
9627            .build_from(&crate::os_lib::dom::parse_html(event_html));
9628        let _ = rt.eval(
9629            "var L=''; \
9630             document.getElementById('btn').addEventListener('foo', function(e){ L+='btn:'+e.type; }); \
9631             document.getElementById('par').addEventListener('foo', function(){ L+='-par'; }); \
9632             document.getElementById('btn').dispatchEvent(new Event('foo'));",
9633        );
9634        match rt.eval("L") {
9635            Ok(v) if v.to_js_string() == "btn:foo" => passed += 1,
9636            Ok(v) => crate::println!(
9637                "JS_SELFTEST FAIL: dispatchEvent non-bubbling => `{}` (want `btn:foo`)",
9638                v.to_js_string()
9639            ),
9640            Err(e) => crate::println!("JS_SELFTEST ERR:  dispatchEvent non-bubbling => {}", e),
9641        }
9642    }
9643
9644    // dispatchEvent: CustomEvent の `bubbles` オプションが正しく反映される
9645    // (以前は常に `false` 決め打ちで、`{bubbles:true}` を渡しても無視されていた)。
9646    {
9647        let mut rt = JsRuntime::new();
9648        rt.dom
9649            .borrow_mut()
9650            .build_from(&crate::os_lib::dom::parse_html(event_html));
9651        let _ = rt.eval(
9652            "var L=''; \
9653             document.getElementById('btn').addEventListener('foo', function(e){ L+='btn:'+e.type; }); \
9654             document.getElementById('par').addEventListener('foo', function(){ L+='-par'; }); \
9655             document.getElementById('btn').dispatchEvent(new CustomEvent('foo', {bubbles:true}));",
9656        );
9657        match rt.eval("L") {
9658            Ok(v) if v.to_js_string() == "btn:foo-par" => passed += 1,
9659            Ok(v) => crate::println!(
9660                "JS_SELFTEST FAIL: CustomEvent bubbles => `{}` (want `btn:foo-par`)",
9661                v.to_js_string()
9662            ),
9663            Err(e) => crate::println!("JS_SELFTEST ERR:  CustomEvent bubbles => {}", e),
9664        }
9665    }
9666
9667    // once: 複数回 dispatch しても 1 回だけ発火。
9668    {
9669        let mut rt = JsRuntime::new();
9670        rt.dom
9671            .borrow_mut()
9672            .build_from(&crate::os_lib::dom::parse_html(event_html));
9673        let _ = rt.eval(
9674            "var N=0; document.getElementById('btn').addEventListener('foo', function(){ N++; }, {once:true}); \
9675             document.getElementById('btn').dispatchEvent(new Event('foo')); \
9676             document.getElementById('btn').dispatchEvent(new Event('foo')); \
9677             document.getElementById('btn').dispatchEvent(new Event('foo'));",
9678        );
9679        match rt.eval("N") {
9680            Ok(v) if v.to_js_string() == "1" => passed += 1,
9681            Ok(v) => crate::println!(
9682                "JS_SELFTEST FAIL: once dispatch => `{}` (want `1`)",
9683                v.to_js_string()
9684            ),
9685            Err(e) => crate::println!("JS_SELFTEST ERR:  once dispatch => {}", e),
9686        }
9687    }
9688
9689    // dispatchEvent 戻り値: preventDefault されると false(`cancelable:true` の場合のみ。
9690    // 以前は `cancelable` が一切読まれておらず preventDefault() が常に効いていた
9691    // バグの修正に伴い、このテストが元々検証したかった「妨害可能なイベントで
9692    // preventDefault() が効く」という意図を保つため `new Event('foo', {cancelable:
9693    // true})` に明示する)。
9694    {
9695        let mut rt = JsRuntime::new();
9696        rt.dom
9697            .borrow_mut()
9698            .build_from(&crate::os_lib::dom::parse_html(event_html));
9699        let _ = rt.eval(
9700            "document.getElementById('btn').addEventListener('foo', function(e){ e.preventDefault(); }); \
9701             var R = document.getElementById('btn').dispatchEvent(new Event('foo', {cancelable:true}));",
9702        );
9703        match rt.eval("R") {
9704            Ok(v) if v.to_js_string() == "false" => passed += 1,
9705            Ok(v) => crate::println!(
9706                "JS_SELFTEST FAIL: dispatchEvent return => `{}` (want `false`)",
9707                v.to_js_string()
9708            ),
9709            Err(e) => crate::println!("JS_SELFTEST ERR:  dispatchEvent return => {}", e),
9710        }
9711    }
9712
9713    // keydown / input / focus / blur / change / submit / mouse(座標含む) イベント。
9714    // change イベント: input/select に change リスナを登録し dispatch_event で発火。
9715    let change_html = "<form><input id='cb' type='checkbox'><select id='sel'><option value='a'>A</option><option value='b'>B</option></select></form>";
9716    let mut change_passed = 0usize;
9717    {
9718        let mut rt = JsRuntime::new();
9719        rt.dom
9720            .borrow_mut()
9721            .build_from(&crate::os_lib::dom::parse_html(change_html));
9722        let _ = rt.eval("var c=0; document.getElementById('cb').addEventListener('change', function(){ c++; });");
9723        let cidx = rt.dom.borrow().get_element_by_id("cb");
9724        if let Some(i) = cidx {
9725            rt.dispatch_event(i, "change");
9726            rt.dispatch_event(i, "change");
9727        }
9728        match rt.eval("c") {
9729            Ok(v) if v.to_js_string() == "2" => change_passed += 1,
9730            Ok(v) => crate::println!(
9731                "JS_SELFTEST FAIL: change count => `{}` (want `2`)",
9732                v.to_js_string()
9733            ),
9734            Err(e) => crate::println!("JS_SELFTEST ERR:  change count => {}", e),
9735        }
9736    }
9737    {
9738        // change イベントの target が発火要素であること。
9739        let mut rt = JsRuntime::new();
9740        rt.dom
9741            .borrow_mut()
9742            .build_from(&crate::os_lib::dom::parse_html(change_html));
9743        let _ = rt.eval("var t=''; document.getElementById('sel').addEventListener('change', function(e){ t=e.target.id; });");
9744        let sidx = rt.dom.borrow().get_element_by_id("sel");
9745        if let Some(i) = sidx {
9746            rt.dispatch_event(i, "change");
9747        }
9748        match rt.eval("t") {
9749            Ok(v) if v.to_js_string() == "sel" => change_passed += 1,
9750            Ok(v) => crate::println!(
9751                "JS_SELFTEST FAIL: change target => `{}` (want `sel`)",
9752                v.to_js_string()
9753            ),
9754            Err(e) => crate::println!("JS_SELFTEST ERR:  change target => {}", e),
9755        }
9756    }
9757    passed += change_passed;
9758
9759    let key_event_total = 13;
9760    {
9761        let kev_html = "<div id='par'><input id='inp'></div>";
9762        // 1. keydown が e.type/e.key 付きで発火。
9763        {
9764            let mut rt = JsRuntime::new();
9765            rt.dom
9766                .borrow_mut()
9767                .build_from(&crate::os_lib::dom::parse_html(kev_html));
9768            let _ = rt.eval("var L=''; document.getElementById('inp').addEventListener('keydown', function(e){ L = e.type + ':' + e.key; });");
9769            let idx = rt.dom.borrow().get_element_by_id("inp");
9770            if let Some(i) = idx {
9771                rt.dispatch_key(i, "keydown", "x");
9772            }
9773            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "keydown:x" {
9774                passed += 1;
9775            } else {
9776                crate::println!("JS_SELFTEST FAIL: keydown e.key");
9777            }
9778        }
9779        // 2. keydown が祖先へバブリング(委譲)。
9780        {
9781            let mut rt = JsRuntime::new();
9782            rt.dom
9783                .borrow_mut()
9784                .build_from(&crate::os_lib::dom::parse_html(kev_html));
9785            let _ = rt.eval("var L=''; document.getElementById('par').addEventListener('keydown', function(e){ L = 'par:' + e.target.id; });");
9786            let idx = rt.dom.borrow().get_element_by_id("inp");
9787            if let Some(i) = idx {
9788                rt.dispatch_key(i, "keydown", "a");
9789            }
9790            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "par:inp" {
9791                passed += 1;
9792            } else {
9793                crate::println!("JS_SELFTEST FAIL: keydown bubbling");
9794            }
9795        }
9796        // 3. keydown の preventDefault が dispatch_key の戻り値 .1 に反映。
9797        {
9798            let mut rt = JsRuntime::new();
9799            rt.dom
9800                .borrow_mut()
9801                .build_from(&crate::os_lib::dom::parse_html(kev_html));
9802            let _ = rt.eval("document.getElementById('inp').addEventListener('keydown', function(e){ e.preventDefault(); });");
9803            let idx = rt.dom.borrow().get_element_by_id("inp");
9804            let prevented = idx
9805                .map(|i| rt.dispatch_key(i, "keydown", "a").1)
9806                .unwrap_or(false);
9807            if prevented {
9808                passed += 1;
9809            } else {
9810                crate::println!("JS_SELFTEST FAIL: keydown preventDefault");
9811            }
9812        }
9813        // 4. input イベントが発火。
9814        {
9815            let mut rt = JsRuntime::new();
9816            rt.dom
9817                .borrow_mut()
9818                .build_from(&crate::os_lib::dom::parse_html(kev_html));
9819            let _ = rt.eval("var N=0; document.getElementById('inp').addEventListener('input', function(){ N++; });");
9820            let idx = rt.dom.borrow().get_element_by_id("inp");
9821            if let Some(i) = idx {
9822                rt.dispatch_event(i, "input");
9823            }
9824            if rt.eval("N").map(|v| v.to_js_string()).unwrap_or_default() == "1" {
9825                passed += 1;
9826            } else {
9827                crate::println!("JS_SELFTEST FAIL: input event");
9828            }
9829        }
9830        // 5. focus イベントが発火(e.type)。
9831        {
9832            let mut rt = JsRuntime::new();
9833            rt.dom
9834                .borrow_mut()
9835                .build_from(&crate::os_lib::dom::parse_html(kev_html));
9836            let _ = rt.eval("var L=''; document.getElementById('inp').addEventListener('focus', function(e){ L = e.type; });");
9837            let idx = rt.dom.borrow().get_element_by_id("inp");
9838            if let Some(i) = idx {
9839                rt.dispatch_event(i, "focus");
9840            }
9841            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "focus" {
9842                passed += 1;
9843            } else {
9844                crate::println!("JS_SELFTEST FAIL: focus event");
9845            }
9846        }
9847        // 6. blur イベントが祖先へバブリング。
9848        {
9849            let mut rt = JsRuntime::new();
9850            rt.dom
9851                .borrow_mut()
9852                .build_from(&crate::os_lib::dom::parse_html(kev_html));
9853            let _ = rt.eval("var L=''; document.getElementById('par').addEventListener('blur', function(e){ L = 'blur:' + e.target.id; });");
9854            let idx = rt.dom.borrow().get_element_by_id("inp");
9855            if let Some(i) = idx {
9856                rt.dispatch_event(i, "blur");
9857            }
9858            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "blur:inp" {
9859                passed += 1;
9860            } else {
9861                crate::println!("JS_SELFTEST FAIL: blur bubbling");
9862            }
9863        }
9864        // 7. change イベントが発火。
9865        {
9866            let mut rt = JsRuntime::new();
9867            rt.dom
9868                .borrow_mut()
9869                .build_from(&crate::os_lib::dom::parse_html(kev_html));
9870            let _ = rt.eval("var L=''; document.getElementById('inp').addEventListener('change', function(e){ L = e.type; });");
9871            let idx = rt.dom.borrow().get_element_by_id("inp");
9872            if let Some(i) = idx {
9873                rt.dispatch_event(i, "change");
9874            }
9875            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "change" {
9876                passed += 1;
9877            } else {
9878                crate::println!("JS_SELFTEST FAIL: change event");
9879            }
9880        }
9881        // 8/9. submit イベントの発火と preventDefault(送信キャンセル)。
9882        let form_html = "<form id='f'><input id='inp2'></form>";
9883        {
9884            let mut rt = JsRuntime::new();
9885            rt.dom
9886                .borrow_mut()
9887                .build_from(&crate::os_lib::dom::parse_html(form_html));
9888            let _ = rt.eval("var S=0; document.getElementById('f').addEventListener('submit', function(){ S++; });");
9889            let idx = rt.dom.borrow().get_element_by_id("f");
9890            if let Some(i) = idx {
9891                rt.dispatch_event_with(i, "submit", &[]);
9892            }
9893            if rt.eval("S").map(|v| v.to_js_string()).unwrap_or_default() == "1" {
9894                passed += 1;
9895            } else {
9896                crate::println!("JS_SELFTEST FAIL: submit event");
9897            }
9898        }
9899        {
9900            let mut rt = JsRuntime::new();
9901            rt.dom
9902                .borrow_mut()
9903                .build_from(&crate::os_lib::dom::parse_html(form_html));
9904            let _ = rt.eval("document.getElementById('f').addEventListener('submit', function(e){ e.preventDefault(); });");
9905            let idx = rt.dom.borrow().get_element_by_id("f");
9906            let prevented = idx
9907                .map(|i| rt.dispatch_event_with(i, "submit", &[]).1)
9908                .unwrap_or(false);
9909            if prevented {
9910                passed += 1;
9911            } else {
9912                crate::println!("JS_SELFTEST FAIL: submit preventDefault");
9913            }
9914        }
9915        // 10. mouseover が発火(e.type)。
9916        {
9917            let mut rt = JsRuntime::new();
9918            rt.dom
9919                .borrow_mut()
9920                .build_from(&crate::os_lib::dom::parse_html(kev_html));
9921            let _ = rt.eval("var L=''; document.getElementById('inp').addEventListener('mouseover', function(e){ L = e.type; });");
9922            let idx = rt.dom.borrow().get_element_by_id("inp");
9923            if let Some(i) = idx {
9924                rt.dispatch_event(i, "mouseover");
9925            }
9926            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "mouseover" {
9927                passed += 1;
9928            } else {
9929                crate::println!("JS_SELFTEST FAIL: mouseover event");
9930            }
9931        }
9932        // 11. mouseout が祖先へバブリング。
9933        {
9934            let mut rt = JsRuntime::new();
9935            rt.dom
9936                .borrow_mut()
9937                .build_from(&crate::os_lib::dom::parse_html(kev_html));
9938            let _ = rt.eval("var L=''; document.getElementById('par').addEventListener('mouseout', function(e){ L = 'out:' + e.target.id; });");
9939            let idx = rt.dom.borrow().get_element_by_id("inp");
9940            if let Some(i) = idx {
9941                rt.dispatch_event(i, "mouseout");
9942            }
9943            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "out:inp" {
9944                passed += 1;
9945            } else {
9946                crate::println!("JS_SELFTEST FAIL: mouseout bubbling");
9947            }
9948        }
9949        // 12. mousemove が発火(回数カウント)。
9950        {
9951            let mut rt = JsRuntime::new();
9952            rt.dom
9953                .borrow_mut()
9954                .build_from(&crate::os_lib::dom::parse_html(kev_html));
9955            let _ = rt.eval("var N=0; document.getElementById('inp').addEventListener('mousemove', function(){ N++; });");
9956            let idx = rt.dom.borrow().get_element_by_id("inp");
9957            if let Some(i) = idx {
9958                rt.dispatch_event(i, "mousemove");
9959                rt.dispatch_event(i, "mousemove");
9960            }
9961            if rt.eval("N").map(|v| v.to_js_string()).unwrap_or_default() == "2" {
9962                passed += 1;
9963            } else {
9964                crate::println!("JS_SELFTEST FAIL: mousemove event");
9965            }
9966        }
9967        // 13. dispatch_mouse が clientX/clientY を付与。
9968        {
9969            let mut rt = JsRuntime::new();
9970            rt.dom
9971                .borrow_mut()
9972                .build_from(&crate::os_lib::dom::parse_html(kev_html));
9973            let _ = rt.eval("var L=''; document.getElementById('inp').addEventListener('click', function(e){ L = e.clientX + ',' + e.clientY; });");
9974            let idx = rt.dom.borrow().get_element_by_id("inp");
9975            if let Some(i) = idx {
9976                rt.dispatch_mouse(i, "click", 42, 17);
9977            }
9978            if rt.eval("L").map(|v| v.to_js_string()).unwrap_or_default() == "42,17" {
9979                passed += 1;
9980            } else {
9981                crate::println!("JS_SELFTEST FAIL: mouse clientX/clientY");
9982            }
9983        }
9984    }
9985
9986    // window/document.location(set_page_url 後に各成分を検証)。
9987    let location_cases: &[(&str, &str)] = &[
9988        ("location.href", "https://example.com/foo/bar?q=1#h"),
9989        ("location.protocol", "https:"),
9990        ("location.host", "example.com"),
9991        ("location.hostname", "example.com"),
9992        ("location.pathname", "/foo/bar"),
9993        ("location.search", "?q=1"),
9994        ("location.hash", "#h"),
9995        ("location.origin", "https://example.com"),
9996        ("window.location.href", "https://example.com/foo/bar?q=1#h"),
9997        ("document.location.pathname", "/foo/bar"),
9998        ("location.toString()", "https://example.com/foo/bar?q=1#h"),
9999        ("window.location === document.location", "true"),
10000        // `location.protocol`/`.host`/`.hostname`/`.port`への代入は仕様上
10001        // 再ナビゲーションを要求すべきだが、`href`/`hash`/`pathname`/`search`
10002        // だけがナビゲーション要求(`_pending_location`)に変換されており、
10003        // この4つは常に黙って見かけ上の`.props`値だけ更新され、実際には
10004        // どこへも「移動しない」バグだった。
10005        // `location_cases`は1つの`rt`を全ケースで使い回す(前のケースの代入が
10006        // 後続へ持ち越される)ため、各ケースの先頭で`protocol`/`host`を
10007        // 明示的に既知の状態へリセットしてから検証し、実行順に依存しない
10008        // ようにする。
10009        (
10010            "location.protocol='http:'; location._pending_location",
10011            "http://example.com/foo/bar?q=1",
10012        ),
10013        (
10014            "location.protocol='https:'; location.host='example.com'; \
10015             location.hostname='other.com'; location._pending_location",
10016            "https://other.com/foo/bar?q=1",
10017        ),
10018        (
10019            "location.host='example.com'; location.port='8080'; location._pending_location",
10020            "https://example.com:8080/foo/bar?q=1",
10021        ),
10022        (
10023            "location.host='new.com:9090'; location._pending_location",
10024            "https://new.com:9090/foo/bar?q=1",
10025        ),
10026        (
10027            "location.host='new.com:9090'; location.hostname + ',' + location.port",
10028            "new.com,9090",
10029        ),
10030        // `<a>.href`/`<img>.src`/`<form>.action` は仕様上、生の属性文字列ではなく
10031        // ページURL基準で解決した絶対URLを返す必要があるが、以前はこの解決が
10032        // 一切行われず相対文字列がそのまま返るバグだった。
10033        (
10034            "var a=document.createElement('a'); a.setAttribute('href','/p3'); a.href",
10035            "https://example.com/p3",
10036        ),
10037        (
10038            "var i=document.createElement('img'); i.setAttribute('src','x.png'); i.src",
10039            "https://example.com/foo/x.png",
10040        ),
10041        (
10042            "var a=document.createElement('a'); a.setAttribute('href','https://o.com/z'); a.href",
10043            "https://o.com/z",
10044        ),
10045        // `document.URL`/`document.documentURI`/`node.baseURI`(DOM 標準。丸ごと
10046        // 未対応だった。`<base>` タグ非対応のため常に `location.href` と同値)。
10047        ("document.URL", "https://example.com/foo/bar?q=1#h"),
10048        ("document.documentURI", "https://example.com/foo/bar?q=1#h"),
10049        (
10050            "document.createElement('div').baseURI",
10051            "https://example.com/foo/bar?q=1#h",
10052        ),
10053        // `document.readyState`(HTML5。丸ごと未対応だった。この処理系は常に
10054        // "complete" を返す簡略実装)。
10055        ("document.readyState", "complete"),
10056        // `document.cookie`(丸ごと未対応だった。`expires`/`path` 等の属性は
10057        // 無視する簡略実装で `name=value` のみ反映する)。
10058        (
10059            "document.cookie='ck_a=1'; document.cookie='ck_b=2; path=/'; document.cookie",
10060            "ck_a=1; ck_b=2",
10061        ),
10062        // `cookieStore`(Cookie Store API。丸ごと未対応だった。`document.cookie`
10063        // と同じ実データを共有する非同期版。他のテストと Cookie 名が衝突しない
10064        // よう `cs_` 接頭辞を使う)。
10065        (
10066            "await cookieStore.set('cs_x', '1'); (await cookieStore.get('cs_x')).value",
10067            "1",
10068        ),
10069        ("(await cookieStore.get('cs_missing'))", "null"),
10070        (
10071            "await cookieStore.set('cs_y', '2'); \
10072             (await cookieStore.getAll('cs_y')).map(c => c.name+'='+c.value).join(',')",
10073            "cs_y=2",
10074        ),
10075        (
10076            "await cookieStore.set('cs_z', '3'); await cookieStore.delete('cs_z'); \
10077             (await cookieStore.get('cs_z'))",
10078            "null",
10079        ),
10080        (
10081            "await cookieStore.set({name:'cs_w', value:'4'}); (await cookieStore.get('cs_w')).value",
10082            "4",
10083        ),
10084        // Page Visibility API(`document.hidden`/`.visibilityState`)が丸ごと
10085        // 未対応だった。タブ/バックグラウンド化の概念が無いため常に「表示中」。
10086        ("document.hidden", "false"),
10087        ("document.visibilityState", "visible"),
10088    ];
10089    {
10090        let mut rt = JsRuntime::new();
10091        rt.set_page_url("https://example.com/foo/bar?q=1#h");
10092        for (src, expect) in location_cases {
10093            match rt.eval(src) {
10094                Ok(v) if &v.to_js_string() == expect => passed += 1,
10095                Ok(v) => crate::println!(
10096                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
10097                    src,
10098                    v.to_js_string(),
10099                    expect
10100                ),
10101                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
10102            }
10103        }
10104    }
10105
10106    // history.pushState / replaceState(location 書換え + state/length)。
10107    let history_cases: &[(&str, &str)] = &[
10108        (
10109            "history.pushState({a:1},'','/p2'); location.pathname",
10110            "/p2",
10111        ),
10112        (
10113            "history.pushState({},'','/p2'); location.href",
10114            "https://example.com/p2",
10115        ),
10116        ("history.pushState({a:9},'','/p2'); history.state.a", "9"),
10117        (
10118            "var n=history.length; history.pushState({},'','/x'); history.length - n",
10119            "1",
10120        ),
10121        (
10122            "var n=history.length; history.replaceState({},'','/y'); history.length - n",
10123            "0",
10124        ),
10125        (
10126            "history.replaceState({},'','about.html'); location.pathname",
10127            "/foo/about.html",
10128        ),
10129        ("typeof history.pushState", "function"),
10130        ("typeof history.back", "function"),
10131    ];
10132    for (src, expect) in history_cases {
10133        let mut rt = JsRuntime::new();
10134        rt.set_page_url("https://example.com/foo/bar");
10135        match rt.eval(src) {
10136            Ok(v) if &v.to_js_string() == expect => passed += 1,
10137            Ok(v) => crate::println!(
10138                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
10139                src,
10140                v.to_js_string(),
10141                expect
10142            ),
10143            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
10144        }
10145    }
10146
10147    // 相対URL解決(resolve_url 純関数。fetch/XHR の base 基準解決)。
10148    let resolve_cases: &[(&str, &str, &str)] = &[
10149        ("http://h.com/a/b.html", "/api", "http://h.com/api"),
10150        ("http://h.com/a/b.html", "x.json", "http://h.com/a/x.json"),
10151        ("http://h.com/a/", "x.json", "http://h.com/a/x.json"),
10152        (
10153            "http://h.com/a/b.html",
10154            "https://o.com/z",
10155            "https://o.com/z",
10156        ),
10157        ("http://h.com/a/b.html", "data:,hi", "data:,hi"),
10158        ("https://h.com/", "/p/q", "https://h.com/p/q"),
10159        ("http://h.com", "y", "http://h.com/y"),
10160        ("", "/api", "/api"),
10161        // `.`/`..` セグメント解決(以前は文字列結合のみで未解決のまま残っていた)。
10162        ("https://h.com/a/b/c", "../style.css", "https://h.com/a/style.css"),
10163        ("https://h.com/a/b/c", "./style.css", "https://h.com/a/b/style.css"),
10164        ("https://h.com/a/b/c/d", "../../x", "https://h.com/a/x"),
10165    ];
10166    let mut rpassed = 0;
10167    for (base, url, expect) in resolve_cases {
10168        let got = builtins::resolve_url(base, url);
10169        if &got == expect {
10170            rpassed += 1;
10171        } else {
10172            crate::println!(
10173                "JS_SELFTEST FAIL: resolve_url({:?},{:?}) => `{}` (want `{}`)",
10174                base,
10175                url,
10176                got,
10177                expect
10178            );
10179        }
10180    }
10181
10182    // HTML5 制約バリデーション(checkValidity / setCustomValidity / validity)。
10183    let validity_cases: &[(&str, &str, &str)] = &[
10184        // (html, script, expect)
10185        ("<input id='a' required value=''>", "document.getElementById('a').checkValidity()", "false"),
10186        ("<input id='a' required value='x'>", "document.getElementById('a').checkValidity()", "true"),
10187        ("<input id='a' value='hi'>", "document.getElementById('a').checkValidity()", "true"),
10188        ("<input id='a' type='email' value='nope'>", "document.getElementById('a').checkValidity()", "false"),
10189        ("<input id='a' type='email' value='x@y.com'>", "document.getElementById('a').checkValidity()", "true"),
10190        ("<input id='a' type='number' min='5' value='3'>", "document.getElementById('a').checkValidity()", "false"),
10191        ("<input id='a' type='number' min='5' value='9'>", "document.getElementById('a').checkValidity()", "true"),
10192        ("<input id='a' minlength='3' value='ab'>", "document.getElementById('a').checkValidity()", "false"),
10193        ("<input id='a' maxlength='2' value='abc'>", "document.getElementById('a').checkValidity()", "false"),
10194        ("<input id='a' value='ok'>", "var e=document.getElementById('a'); e.setCustomValidity('bad'); e.checkValidity()", "false"),
10195        ("<input id='a' value='ok'>", "var e=document.getElementById('a'); e.setCustomValidity('bad'); e.setCustomValidity(''); e.checkValidity()", "true"),
10196        ("<input id='a' required value=''>", "document.getElementById('a').validity.valueMissing", "true"),
10197        ("<input id='a' required value='x'>", "document.getElementById('a').validity.valid", "true"),
10198        ("<input id='a' value='hi'>", "document.getElementById('a').willValidate", "true"),
10199        ("<input id='a' type='submit'>", "document.getElementById('a').willValidate", "false"),
10200        // `pattern` 属性が以前はメタ文字を含む複雑なパターンを「誤検知回避のため」
10201        // 無条件で通過させる簡易近似だった。本物の RegExp エンジンで `^(?:pattern)$`
10202        // として正しく検証するよう修正(`\d+`/`[0-9]{3}` 等、通常の JS RegExp 構文)。
10203        ("<input id='a' pattern='[0-9]{3}' value='123'>", "document.getElementById('a').checkValidity()", "true"),
10204        ("<input id='a' pattern='[0-9]{3}' value='ab'>", "document.getElementById('a').checkValidity()", "false"),
10205        ("<input id='a' pattern='\\d+' value='123'>", "document.getElementById('a').checkValidity()", "true"),
10206        ("<input id='a' pattern='\\d+' value='abc'>", "document.getElementById('a').checkValidity()", "false"),
10207        // 空値は非 required なら pattern チェック自体を適用しない(仕様どおり)。
10208        ("<input id='a' pattern='[0-9]{3}' value=''>", "document.getElementById('a').checkValidity()", "true"),
10209        // `stepMismatch`(HTML5 制約検証。min/max はあったが step が丸ごと未対応だった)。
10210        ("<input id='a' type='number' step='2' value='4'>", "document.getElementById('a').checkValidity()", "true"),
10211        ("<input id='a' type='number' step='2' value='3'>", "document.getElementById('a').checkValidity()", "false"),
10212        // step の基準点は min(無ければ 0)。min=1, step=2 なら 1,3,5,... が有効。
10213        ("<input id='a' type='number' min='1' step='2' value='5'>", "document.getElementById('a').checkValidity()", "true"),
10214        ("<input id='a' type='number' min='1' step='2' value='4'>", "document.getElementById('a').checkValidity()", "false"),
10215        // `step=\"any\"` は制約なし。
10216        ("<input id='a' type='number' step='any' value='3.14159'>", "document.getElementById('a').checkValidity()", "true"),
10217        // `validity` オブジェクトが `valid`/`valueMissing`/`customError` の3つしか
10218        // 公開しておらず、`patternMismatch`/`typeMismatch`/`rangeOverflow`/
10219        // `rangeUnderflow`/`stepMismatch`/`tooLong`/`tooShort`/`badInput` という
10220        // 特定の制約を狙い撃ちする定番パターンが常に `undefined` になっていた。
10221        ("<input id='a' pattern='[0-9]{3}' value='ab'>", "document.getElementById('a').validity.patternMismatch", "true"),
10222        ("<input id='a' pattern='[0-9]{3}' value='123'>", "document.getElementById('a').validity.patternMismatch", "false"),
10223        ("<input id='a' type='email' value='nope'>", "document.getElementById('a').validity.typeMismatch", "true"),
10224        ("<input id='a' type='number' min='5' value='3'>", "document.getElementById('a').validity.rangeUnderflow", "true"),
10225        ("<input id='a' type='number' max='5' value='9'>", "document.getElementById('a').validity.rangeOverflow", "true"),
10226        ("<input id='a' type='number' step='2' value='3'>", "document.getElementById('a').validity.stepMismatch", "true"),
10227        ("<input id='a' maxlength='2' value='abc'>", "document.getElementById('a').validity.tooLong", "true"),
10228        ("<input id='a' minlength='3' value='ab'>", "document.getElementById('a').validity.tooShort", "true"),
10229        ("<input id='a' type='number' value='abc'>", "document.getElementById('a').validity.badInput", "true"),
10230        // **重要**: `<form>.checkValidity()` が子孫コントロールを一切見ず、`<form>`
10231        // タグ自身に対する検証(常に無条件で true)を返すだけだったバグ。
10232        (
10233            "<form id='f3'><input required value=''></form>",
10234            "document.getElementById('f3').checkValidity()",
10235            "false",
10236        ),
10237        (
10238            "<form id='f3'><input required value='x'></form>",
10239            "document.getElementById('f3').checkValidity()",
10240            "true",
10241        ),
10242        (
10243            "<form id='f3'><input required value='x'><input type='email' value='nope'></form>",
10244            "document.getElementById('f3').checkValidity()",
10245            "false",
10246        ),
10247        // `checkValidity()`/`.validationMessage`/`.validity`が`get_attr(idx,"value")`
10248        // だけを見ており、`value`属性を持たない`<select>`(選択状態は子の
10249        // `<option selected>`が持つ)や、初期値がテキストノードで表現される
10250        // `<textarea>`では、実際には値があっても常に空文字列=未入力として
10251        // 誤って`required`違反になっていたバグ。`.value`ゲッターは既に正しい
10252        // フォールバックを持っていたが、制約バリデーション側に伝わっていなかった。
10253        (
10254            "<select id='a' required><option value='x' selected>X</option></select>",
10255            "document.getElementById('a').checkValidity()",
10256            "true",
10257        ),
10258        (
10259            "<select id='a' required><option value='x' selected>X</option></select>",
10260            "document.getElementById('a').validity.valueMissing",
10261            "false",
10262        ),
10263        (
10264            "<textarea id='a' required>hello</textarea>",
10265            "document.getElementById('a').checkValidity()",
10266            "true",
10267        ),
10268        (
10269            "<textarea id='a' required></textarea>",
10270            "document.getElementById('a').checkValidity()",
10271            "false",
10272        ),
10273        (
10274            "<form id='f4'><select required><option value='x' selected>X</option></select></form>",
10275            "document.getElementById('f4').checkValidity()",
10276            "true",
10277        ),
10278        // `required`のcheckbox/radioは「値」でなく「チェック状態」で判定すべき
10279        // (HTML5仕様)だが、以前は他のinputと同じ「value属性が空か」で判定して
10280        // いたため、value属性を持たないcheckedなチェックボックスが常にrequired
10281        // 違反(valueが空文字列扱い)、逆にvalue属性だけ設定した未チェックの
10282        // チェックボックスが常に妥当、というチェック状態と無関係な結果になっていた。
10283        (
10284            "<input id='a' type='checkbox' required>",
10285            "document.getElementById('a').checkValidity()",
10286            "false",
10287        ),
10288        (
10289            "<input id='a' type='checkbox' required checked>",
10290            "document.getElementById('a').checkValidity()",
10291            "true",
10292        ),
10293        (
10294            "<input id='a' type='checkbox' value='yes' required>",
10295            "document.getElementById('a').validity.valueMissing",
10296            "true",
10297        ),
10298        (
10299            "<input id='a' type='checkbox' required checked>",
10300            "document.getElementById('a').validity.valueMissing",
10301            "false",
10302        ),
10303        // radioはグループ内のいずれかがcheckedであれば充足(自身がcheckedで
10304        // なくても良い)。
10305        (
10306            "<input id='a' type='radio' name='g' required><input type='radio' name='g' checked>",
10307            "document.getElementById('a').checkValidity()",
10308            "true",
10309        ),
10310        (
10311            "<input id='a' type='radio' name='g' required><input type='radio' name='g'>",
10312            "document.getElementById('a').checkValidity()",
10313            "false",
10314        ),
10315        // `type="range"`は`type="number"`と同じmin/max/step制約検証規則を持つ
10316        // (`.stepUp()`/`.valueAsNumber`は既に両者を同列に扱っていたが、
10317        // `validity.rangeOverflow`/`.rangeUnderflow`/`.stepMismatch`だけ
10318        // `number`型限定になっており、range型では常にfalse固定だった)。
10319        (
10320            "<input id='a' type='range' min='0' max='10' value='20'>",
10321            "document.getElementById('a').validity.rangeOverflow",
10322            "true",
10323        ),
10324        (
10325            "<input id='a' type='range' min='0' max='10' value='-5'>",
10326            "document.getElementById('a').validity.rangeUnderflow",
10327            "true",
10328        ),
10329        (
10330            "<input id='a' type='range' min='0' max='10' value='5'>",
10331            "document.getElementById('a').checkValidity()",
10332            "true",
10333        ),
10334        (
10335            "<input id='a' type='range' min='0' max='10' step='2' value='3'>",
10336            "document.getElementById('a').validity.stepMismatch",
10337            "true",
10338        ),
10339        // `type="date"`/`type="month"`の形式チェックが丸ごと未対応だった
10340        // (`input.valueAsDate`は既存の`input_value_as_date_ms()`で妥当性を
10341        // 判定していたが、`validity.typeMismatch`には配線されておらず、
10342        // 不正な日付文字列を代入しても常に`typeMismatch: false`だった)。
10343        (
10344            "<input id='a' type='date' value='2026-07-17'>",
10345            "document.getElementById('a').validity.typeMismatch",
10346            "false",
10347        ),
10348        (
10349            "<input id='a' type='date' value='not-a-date'>",
10350            "document.getElementById('a').validity.typeMismatch",
10351            "true",
10352        ),
10353        (
10354            "<input id='a' type='month' value='2026-07'>",
10355            "document.getElementById('a').checkValidity()",
10356            "true",
10357        ),
10358        (
10359            "<input id='a' type='date' value='2026-13-99'>",
10360            "document.getElementById('a').checkValidity()",
10361            "false",
10362        ),
10363        // `<input type="email" multiple>`はカンマ区切りで複数アドレスを入力できる
10364        // 仕様だが、以前は`is_valid_email(value)`を値全体にそのまま適用しており、
10365        // 2件目以降のアドレスやカンマを含む正当な値が常に不正判定されていた。
10366        (
10367            "<input id='a' type='email' multiple value='a@b.com,c@d.com'>",
10368            "document.getElementById('a').checkValidity()",
10369            "true",
10370        ),
10371        (
10372            "<input id='a' type='email' multiple value='a@b.com, c@d.com'>",
10373            "document.getElementById('a').checkValidity()",
10374            "true",
10375        ),
10376        (
10377            "<input id='a' type='email' multiple value='a@b.com,nope'>",
10378            "document.getElementById('a').checkValidity()",
10379            "false",
10380        ),
10381        (
10382            // `multiple`が無ければ従来どおりカンマ区切りは単一アドレスとして不正。
10383            "<input id='a' type='email' value='a@b.com,c@d.com'>",
10384            "document.getElementById('a').checkValidity()",
10385            "false",
10386        ),
10387        // `type="date"`/`type="month"`の`min`/`max`範囲制約(`type="number"`と同様に
10388        // 対応可能な仕様なのに丸ごと欠けていた)。
10389        (
10390            "<input id='a' type='date' min='2026-01-01' max='2026-12-31' value='2025-12-31'>",
10391            "document.getElementById('a').validity.rangeUnderflow",
10392            "true",
10393        ),
10394        (
10395            "<input id='a' type='date' min='2026-01-01' max='2026-12-31' value='2027-01-01'>",
10396            "document.getElementById('a').validity.rangeOverflow",
10397            "true",
10398        ),
10399        (
10400            "<input id='a' type='date' min='2026-01-01' max='2026-12-31' value='2026-06-15'>",
10401            "document.getElementById('a').checkValidity()",
10402            "true",
10403        ),
10404        (
10405            "<input id='a' type='month' min='2026-03' max='2026-09' value='2026-01'>",
10406            "document.getElementById('a').checkValidity()",
10407            "false",
10408        ),
10409        // `<fieldset disabled>`の子孫コントロールは自身に`disabled`属性が無くても
10410        // 暗黙に無効化される(HTML5仕様)。以前は要素自身の`disabled`属性しか
10411        // 見ておらず、`.disabled`が常に`false`・制約バリデーションも無効化
10412        // されずに常に対象内のままだった。
10413        (
10414            "<fieldset disabled><input id='a' required></fieldset>",
10415            "document.getElementById('a').disabled",
10416            "true",
10417        ),
10418        (
10419            "<fieldset disabled><input id='a' required></fieldset>",
10420            "document.getElementById('a').checkValidity()",
10421            "true",
10422        ),
10423        (
10424            "<fieldset><input id='a' required></fieldset>",
10425            "document.getElementById('a').disabled",
10426            "false",
10427        ),
10428        (
10429            // 無効化されている以上、値が実際には空でも valueMissing にはならない
10430            // (disabled要素は制約バリデーションの対象外という仕様どおり)。
10431            "<fieldset disabled><input id='a' required value=''></fieldset>",
10432            "document.getElementById('a').validity.valueMissing",
10433            "false",
10434        ),
10435        // `form="formId"`属性(HTML5仕様の「form owner」)による、`<form>`の外に
10436        // 置かれたコントロールとの明示的な関連付けが丸ごと未対応だった。
10437        // `element.form`は祖先探索(`closest_tag`)しか見ておらず、
10438        // `<form>.checkValidity()`もフォームの子孫しか走査していなかったため、
10439        // フォーム外配置のコントロールが両方から漏れていた。
10440        (
10441            "<form id='f5'></form><input id='a' form='f5'>",
10442            "document.getElementById('a').form === document.getElementById('f5')",
10443            "true",
10444        ),
10445        (
10446            "<form id='f5'></form><input id='a' form='f5' required>",
10447            "document.getElementById('f5').checkValidity()",
10448            "false",
10449        ),
10450        (
10451            "<form id='f5'></form><input id='a' form='f5' required value='x'>",
10452            "document.getElementById('f5').checkValidity()",
10453            "true",
10454        ),
10455        (
10456            // `form`属性が無い通常の input はこれまでどおり祖先探索のみ。
10457            "<div><input id='a'></div>",
10458            "document.getElementById('a').form",
10459            "null",
10460        ),
10461        // `type="datetime-local"`の`validity.typeMismatch`/`min`/`max`が丸ごと
10462        // 未対応だった。`input_value_as_date_ms()`は既存の`parse_iso_date`が
10463        // `YYYY-MM-DDTHH:MM`形式を既にサポートしているため、`date`/`month`と
10464        // 同じ扱いに含めるだけで対応できた。
10465        (
10466            "<input id='a' type='datetime-local' value='2026-07-17T14:30'>",
10467            "document.getElementById('a').validity.typeMismatch",
10468            "false",
10469        ),
10470        (
10471            "<input id='a' type='datetime-local' value='not-a-datetime'>",
10472            "document.getElementById('a').checkValidity()",
10473            "false",
10474        ),
10475        (
10476            "<input id='a' type='datetime-local' min='2026-01-01T00:00' max='2026-12-31T23:59' value='2025-06-01T00:00'>",
10477            "document.getElementById('a').validity.rangeUnderflow",
10478            "true",
10479        ),
10480        // `<details name="...">`排他グループの初期状態正規化(HTML5。
10481        // 丸ごと未対応だった)。同じ`name`を持つ複数の`<details open>`が
10482        // HTML記述時点で並んでいた場合、文書順で最後の1つだけが開いた
10483        // ままで残り、それより前のものは`build_from`(パース直後)の
10484        // 時点で自動的に閉じられる必要がある。2026-07-18 発見・実装)。
10485        (
10486            "<details id='a' name='g' open></details><details id='b' name='g' open></details>",
10487            "document.getElementById('a').open+','+document.getElementById('b').open",
10488            "false,true",
10489        ),
10490        (
10491            "<details id='a' name='g' open></details><details id='b' open></details>",
10492            "document.getElementById('a').open+','+document.getElementById('b').open",
10493            "true,true",
10494        ),
10495        // `HTMLCollection` / `HTMLFormControlsCollection` の `item(index)` / `namedItem(name)`
10496        // および `classList.value` ゲッター/セッター。
10497        (
10498            "<div id='wrap'><p id='t' name='foo'></p><span id='s'></span></div>",
10499            "document.getElementById('wrap').children.item(0).id + ',' + document.getElementById('wrap').children.namedItem('s').tagName + ',' + document.getElementById('wrap').children.namedItem('foo').id",
10500            "t,SPAN,t",
10501        ),
10502        (
10503            "<div id='wrap'><span></span></div>",
10504            "String(document.getElementById('wrap').children.item(99))",
10505            "null",
10506        ),
10507        (
10508            "<form id='f'><input id='i' name='user'></form>",
10509            "document.getElementById('f').elements.item(0).id + ',' + document.getElementById('f').elements.namedItem('user').id",
10510            "i,i",
10511        ),
10512        (
10513            "<form id='f'><input type='radio' name='r' value='v1'><input type='radio' name='r' value='v2' checked></form>",
10514            "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",
10515            "v2,2,v1,true",
10516        ),
10517        (
10518            "<div id='d' class='a b'></div>",
10519            "var d=document.getElementById('d'); var v=d.classList.value; d.classList.value='c d'; v+','+d.className",
10520            "a b,c d",
10521        ),
10522    ];
10523    for (html, src, expect) in validity_cases {
10524        let mut rt = JsRuntime::new();
10525        rt.dom
10526            .borrow_mut()
10527            .build_from(&crate::os_lib::dom::parse_html(html));
10528        match rt.eval(src) {
10529            Ok(v) if &v.to_js_string() == expect => passed += 1,
10530            Ok(v) => crate::println!(
10531                "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
10532                src,
10533                v.to_js_string(),
10534                expect
10535            ),
10536            Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
10537        }
10538    }
10539
10540    // ES modules(import/export): 複数モジュールを define_module で登録し、
10541    // import 側スクリプトを評価して名前解決・default・namespace・再エクスポートを検証。
10542    let module_total = 9;
10543    {
10544        // 単純な named export を持つ算術モジュール。
10545        let math_src = "export const PI = 3; export function sq(x){ return x*x; } \
10546                        const e = 2; export { e as E };";
10547        // default export と named export の混在。
10548        let greet_src = "export default function(n){ return 'hi ' + n; } \
10549                         export const lang = 'ja';";
10550        // 別モジュールからの再エクスポート(集約モジュール)。
10551        let index_src = "export { PI, sq } from 'math'; export * from 'math';";
10552        // 循環 import: a は b を import し、b は a を import する。
10553        let cyc_a = "import { bVal } from 'cycb'; export const aVal = 10; \
10554                     export function getB(){ return bVal; }";
10555        let cyc_b = "import { aVal } from 'cyca'; export const bVal = 20;";
10556
10557        let module_checks: &[(&str, &str)] = &[
10558            // named import + 関数呼び出し。
10559            ("import { PI, sq } from 'math'; sq(PI)", "9"),
10560            // as による別名 export を import。
10561            ("import { E } from 'math'; E", "2"),
10562            // default import。
10563            ("import greet from 'greet'; greet('bob')", "hi bob"),
10564            // default + named を同時 import。
10565            ("import greet, { lang } from 'greet'; greet(lang)", "hi ja"),
10566            // namespace import。
10567            ("import * as M from 'math'; M.sq(4) + M.PI", "19"),
10568            // 再エクスポート(named)。
10569            ("import { PI } from 'index'; PI", "3"),
10570            // 再エクスポート(export *)経由の関数。
10571            ("import { sq } from 'index'; sq(5)", "25"),
10572            // 循環 import: a 経由で b の値を取得。
10573            ("import { getB } from 'cyca'; getB()", "20"),
10574            // 副作用のみ import(エラーにならず undefined 評価が通ること)。
10575            ("import 'math'; 42", "42"),
10576        ];
10577        for (src, expect) in module_checks {
10578            let mut rt = JsRuntime::new();
10579            rt.define_module("math", math_src);
10580            rt.define_module("greet", greet_src);
10581            rt.define_module("index", index_src);
10582            rt.define_module("cyca", cyc_a);
10583            rt.define_module("cycb", cyc_b);
10584            match rt.eval(src) {
10585                Ok(v) if &v.to_js_string() == expect => passed += 1,
10586                Ok(v) => crate::println!(
10587                    "JS_SELFTEST FAIL: `{}` => `{}` (want `{}`)",
10588                    src,
10589                    v.to_js_string(),
10590                    expect
10591                ),
10592                Err(e) => crate::println!("JS_SELFTEST ERR:  `{}` => {}", src, e),
10593            }
10594        }
10595    }
10596
10597    // `window.scrollTo(x, y)`/`window.scrollBy(x, y)`(丸ごと未対応だった。
10598    // 以前は`scrollTo`が孤立した`SCROLL_Y` staticへ書くだけ、`scrollBy`は
10599    // `dom_noop`のままで、どちらも`window.scrollY`や実際の描画位置に一切
10600    // 反映されない死んだ経路だった。2026-07-16 発見・実装)。実際の視覚的
10601    // 反映は`web_engine/render.rs`の`draw()`側で行われヘッドレスJS自己テスト
10602    // からは駆動できないため、ここではJS→`DomBridge`への配線(`window_
10603    // scroll_to`/`window_scroll_by`が正しい保留値を書き込むこと)のみを
10604    // 直接Rustレベルで検証する。
10605    let scroll_bridge_total = 3;
10606    {
10607        let mut rt = JsRuntime::new();
10608        let _ = rt.eval("window.scrollTo(0, 120);");
10609        let y = rt.dom.borrow().pending_scroll_abs_y;
10610        if y == Some(120) {
10611            passed += 1;
10612        } else {
10613            crate::println!(
10614                "JS_SELFTEST FAIL: window.scrollTo pending_scroll_abs_y => `{:?}` (want `Some(120)`)",
10615                y
10616            );
10617        }
10618    }
10619    {
10620        let mut rt = JsRuntime::new();
10621        let _ = rt.eval("window.scrollBy(0, 30); window.scrollBy(0, 15);");
10622        let dy = rt.dom.borrow().pending_scroll_by_y;
10623        if dy == Some(45) {
10624            passed += 1;
10625        } else {
10626            crate::println!(
10627                "JS_SELFTEST FAIL: window.scrollBy pending_scroll_by_y (累積) => `{:?}` (want `Some(45)`)",
10628                dy
10629            );
10630        }
10631    }
10632    // `element.scrollIntoView()`が丸ごとno-opだった(実サイト www.sugi-lab.net の
10633    // ナビゲーションリンクの`target.scrollIntoView({behavior:'smooth',
10634    // block:'start'})`が完全に無効化されていた。2026-07-22発見・修正)。
10635    // `window.scrollTo`と同じ`pending_scroll_abs_y`機構を再利用しているかを
10636    // 直接Rustレベルで検証する(対象要素のY座標250px、scrollY=0の状態から
10637    // `scrollIntoView()`を呼ぶと、絶対Y座標250へスクロールする保留値が
10638    // 設定されるはず)。
10639    {
10640        let mut rt = JsRuntime::new();
10641        rt.dom
10642            .borrow_mut()
10643            .build_from(&crate::os_lib::dom::parse_html("<div id='t'></div>"));
10644        let idx = rt.dom.borrow().get_element_by_id("t");
10645        if let Some(i) = idx {
10646            rt.dom.borrow_mut().set_rect(i, 0, 250, 100, 50);
10647        }
10648        let _ = rt.eval("document.getElementById('t').scrollIntoView();");
10649        let y = rt.dom.borrow().pending_scroll_abs_y;
10650        if y == Some(250) {
10651            passed += 1;
10652        } else {
10653            crate::println!(
10654                "JS_SELFTEST FAIL: element.scrollIntoView pending_scroll_abs_y => `{:?}` (want `Some(250)`)",
10655                y
10656            );
10657        }
10658    }
10659
10660    let dom_parser_cases: &[(&str, &str)] = &[
10661        (
10662            "var p = new DOMParser(); var doc = p.parseFromString('<div id=\"dp\">hello</div>', 'text/html'); doc.getElementById('dp').textContent",
10663            "hello",
10664        ),
10665    ];
10666    let mut dom_parser_passed = 0;
10667    for (code, expect) in dom_parser_cases {
10668        let mut rt = JsRuntime::new();
10669        let res = rt.eval(code);
10670        let got = res.map(|v| v.to_js_string()).unwrap_or_else(|e| alloc::format!("ERR: {:?}", e));
10671        if &got == expect {
10672            dom_parser_passed += 1;
10673        } else {
10674            crate::println!(
10675                "JS_SELFTEST FAIL: DOMParser code {:?} => `{}` (want `{}`)",
10676                code,
10677                got,
10678                expect
10679            );
10680        }
10681    }
10682
10683    // DOMMatrix / DOMPoint / URLPattern / Iterator.concat / Iterator.from 自己テスト
10684    let geom_web_cases = [
10685        ("const m = new DOMMatrix([1,0,0,1,10,20]); m.a === 1 && m.e === 10", "true"),
10686        ("const m = new DOMMatrix(); m.translateSelf(10, 20); m.e === 10 && m.f === 20", "true"),
10687        ("const m = new DOMMatrix(); m.scaleSelf(2, 3); m.a === 2 && m.d === 3", "true"),
10688        ("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"),
10689        ("const m2 = DOMMatrix.fromMatrix({a: 2, d: 3, e: 5, f: 10}); m2.a === 2 && m2.e === 5", "true"),
10690        ("const p3 = new DOMPoint(1, 2).matrixTransform(new DOMMatrix([2,0,0,3,10,20])); p3.x === 12 && p3.y === 26", "true"),
10691        ("const pat = new URLPattern({ pathname: '/api/*' }); pat.test('/api/users')", "true"),
10692        ("const res = new URLPattern({ pathname: '/users/:id' }).exec('https://example.com/users/42'); res.pathname.groups.id", "42"),
10693        ("const it = Iterator.concat([1, 2], [3, 4]); it.toArray().join(',')", "1,2,3,4"),
10694        ("const it = Iterator.from([10, 20, 30]); it.take(2).toArray().join(',')", "10,20"),
10695        ("new Intl.DurationFormat('en', {style:'long'}).format({hours:1, minutes:30})", "1 hour, 30 minutes"),
10696        ("new Intl.DurationFormat('en').resolvedOptions().locale", "en-US"),
10697        ("Iterator.range(1, 5).toArray().join(',')", "1,2,3,4"),
10698        ("Iterator.range(0, 10, 2).toArray().join(',')", "0,2,4,6,8"),
10699        ("Iterator.range(5, 1, -1).toArray().join(',')", "5,4,3,2"),
10700    ];
10701    let mut geom_web_passed = 0;
10702    for (code, expect) in geom_web_cases {
10703        let mut rt = JsRuntime::new();
10704        let res = rt.eval(code);
10705        let got = res.map(|v| v.to_js_string()).unwrap_or_else(|e| alloc::format!("ERR: {:?}", e));
10706        if &got == expect {
10707            geom_web_passed += 1;
10708        } else {
10709            crate::println!(
10710                "JS_SELFTEST FAIL: Geom/Web API code {:?} => `{}` (want `{}`)",
10711                code,
10712                got,
10713                expect
10714            );
10715        }
10716    }
10717
10718    let template_html = r#"<div><template id="tmpl"><span>Hello</span></template></div>"#;
10719    let template_cases = [
10720        ("document.createElement('template') instanceof HTMLTemplateElement", "true"),
10721        ("const t = document.createElement('template'); t.content.nodeType === 11", "true"),
10722        ("const t = document.getElementById('tmpl'); t.content.childNodes.length", "1"),
10723        ("const t = document.getElementById('tmpl'); t.content.children[0].tagName", "SPAN"),
10724        ("const t = document.getElementById('tmpl'); const f = t.content.cloneNode(true); f.nodeType === 11 && f.children[0].textContent === 'Hello'", "true"),
10725        ("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"),
10726        ("const u = new URL('https://www.sugi-lab.net/?a=1'); u.searchParams.set('b', '2'); u.search === '?a=1&b=2'", "true"),
10727        ("const div = document.createElement('div'); div.setAttribute('id', 'd1'); const c = div.cloneNode(true); c.id === 'd1' && c !== div", "true"),
10728        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx !== null && ctx.canvas === cv", "true"),
10729        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.measureText('test').width > 0", "true"),
10730        ("const cv = document.createElement('canvas'); cv.toDataURL().startsWith('data:image/png;base64,')", "true"),
10731        ("const cv = document.createElement('canvas'); const ctx = cv.getContext('2d'); ctx.lineWidth = 10; ctx.reset(); ctx.lineWidth === 1", "true"),
10732        ("const p = new Path2D('M10 10 H 90 V 90 H 10 Z'); typeof p.moveTo === 'function' && typeof p.roundRect === 'function'", "true"),
10733        ("const div = document.createElement('div'); div.getRootNode() === div", "true"),
10734        ("const div = document.getElementById('tmpl'); div.getRootNode() === document", "true"),
10735        ("const el = document.createElement('div'); document.adoptNode(el) === el", "true"),
10736        ("const el = document.createElement('div'); document.importNode(el, true) !== null", "true"),
10737        ("ArrayBuffer.isView(new Uint8Array(5)) && !ArrayBuffer.isView(new ArrayBuffer(5))", "true"),
10738        ("Math.clamp(15, 0, 10) === 10 && Math.clamp(-5, 0, 10) === 0 && Math.clamp(5, 0, 10) === 5", "true"),
10739        ("const io = new IntersectionObserver(()=>{}); typeof io.takeRecords === 'function' && io.takeRecords().length === 0", "true"),
10740    ];
10741    let mut template_passed = 0;
10742    for (src, expect) in template_cases {
10743        let mut rt = JsRuntime::new();
10744        rt.dom
10745            .borrow_mut()
10746            .build_from(&crate::os_lib::dom::parse_html(template_html));
10747        match rt.eval(src) {
10748            Ok(v) if &v.to_js_string() == expect => template_passed += 1,
10749            Ok(v) => crate::println!(
10750                "JS_SELFTEST FAIL: Template test `{}` => `{}` (want `{}`)",
10751                src,
10752                v.to_js_string(),
10753                expect
10754            ),
10755            Err(e) => crate::println!("JS_SELFTEST ERR:  Template test `{}` => {}", src, e),
10756        }
10757    }
10758
10759    (
10760        passed + rpassed + dom_parser_passed + geom_web_passed + template_passed,
10761        total
10762            + scroll_bridge_total
10763            + dom2_cases.len()
10764            + event_total
10765            + fd_form_total
10766            + fd2_form_total
10767            + select_multiple_total
10768            + optgroup_total
10769            + doc_title_total
10770            + doc_collections_total
10771            + rect_total
10772            + point_test_total
10773            + computed_total
10774            + check_visibility_total
10775            + event_checks.len()
10776            + 8  // click dispatch(1) + dispatchEvent bubbling/non-bubbling/CustomEvent/once/return(5) + change(2)
10777            + 3  // setInterval(1) + clearInterval(1) + setInterval stats counter(1)
10778            + abort_timeout_total
10779            + key_event_total
10780            + location_cases.len()
10781            + history_cases.len()
10782            + resolve_cases.len()
10783            + validity_cases.len()
10784            + dom_parser_cases.len()
10785            + geom_web_cases.len()
10786            + template_cases.len()
10787            + module_total,
10788    )
10789}
10790
10791
10792
10793