Skip to main content

atmos/os_lib/js/builtins/
events.rs

1// 分割: builtins.rs より機械的に移動(2026-07-16 リファクタ フェーズ2)。
2// ロジック不変。可視性のみ pub(crate) へ昇格し、親が pub(crate) use で再エクスポート。
3use super::*;
4
5// ===== Event / CustomEvent =====
6/// new Event(type, {bubbles, cancelable}) → 最小限の Event オブジェクト。
7/// `event.preventDefault()`(`new Event()`/`new CustomEvent()` で手動構築した
8/// イベント向け)。以前は無条件 no-op で `defaultPrevented` を一切追跡せず、
9/// `EventTarget.dispatchEvent()` の戻り値(`!defaultPrevented`)が常に `true`に
10/// なってしまうバグだった。仕様どおり `cancelable: false` の場合は何もしない。
11pub(crate) fn event_prevent_default(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
12    if let Value::Object(o) = &this {
13        let cancelable = matches!(o.borrow().props.get("cancelable"), Some(Value::Bool(true)));
14        if cancelable {
15            let mut b = o.borrow_mut();
16            b.props.insert("defaultPrevented".into(), Value::Bool(true));
17            // レガシー DOM Level 0 の `event.returnValue`(`preventDefault()` と
18            // 等価の意味を持つべきだが丸ごと未対応で常に `undefined` だった)を
19            // 追従させる(DOM 要素側の `interp.rs` にも同時に追加済み)。
20            b.props.insert("returnValue".into(), Value::Bool(false));
21        }
22    }
23    Ok(Value::Undefined)
24}
25/// `event.stopPropagation()`(`EventTarget` 経路用)。DOM ツリーが無いため
26/// 伝播そのものを止める効果は無い(`dom_noop` と同じ no-op)が、レガシー
27/// `event.cancelBubble` が丸ごと未対応で追従していなかったバグの修正として
28/// フラグだけは立てる。
29pub(crate) fn event_target_stop_propagation(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
30    if let Value::Object(o) = &this {
31        o.borrow_mut().props.insert("cancelBubble".into(), Value::Bool(true));
32    }
33    Ok(Value::Undefined)
34}
35pub(crate) fn event_stop_immediate_propagation(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
36    if let Value::Object(o) = &this {
37        let mut b = o.borrow_mut();
38        b.props.insert("_stopImmediate".into(), Value::Bool(true));
39        b.props.insert("cancelBubble".into(), Value::Bool(true));
40    }
41    Ok(Value::Undefined)
42}
43/// `event.composedPath()`(`EventTarget` 経路用)。DOM ツリーが無いこのクラスでは
44/// `dispatchEvent` 中のみ `target` プロパティが設定されるため、それを利用して
45/// 「発火中は `[target]`、発火前は `[]`」という簡略化した経路を返す。
46pub(crate) fn event_target_composed_path(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
47    if let Value::Object(o) = &this {
48        if let Some(t) = o.borrow().props.get("target").cloned() {
49            if !matches!(t, Value::Undefined) {
50                return Ok(Value::Object(Obj::array(alloc::vec![t])));
51            }
52        }
53    }
54    Ok(Value::Object(Obj::array(alloc::vec::Vec::new())))
55}
56pub(crate) fn event_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
57    let event_type = arg(a, 0).to_js_string();
58    let options = arg(a, 1);
59    let (bubbles, cancelable, composed) = if let Value::Object(o) = &options {
60        let b = o.borrow();
61        (
62            b.props.get("bubbles").map(|v| v.truthy()).unwrap_or(false),
63            b.props
64                .get("cancelable")
65                .map(|v| v.truthy())
66                .unwrap_or(false),
67            b.props.get("composed").map(|v| v.truthy()).unwrap_or(false),
68        )
69    } else {
70        (false, false, false)
71    };
72    let ev = Obj::plain();
73    {
74        let mut b = ev.borrow_mut();
75        b.props.insert("type".into(), Value::str(&event_type));
76        b.props.insert("bubbles".into(), Value::Bool(bubbles));
77        b.props.insert("cancelable".into(), Value::Bool(cancelable));
78        // `composed`(丸ごと未対応だった。2026-07-16 発見。Shadow DOM 境界を
79        // 越えて伝播するかのフラグだが、この処理系に Shadow DOM 自体が
80        // 無いため実際の伝播挙動には影響しない読み取り専用の反映のみ)。
81        b.props.insert("composed".into(), Value::Bool(composed));
82        b.props.insert("defaultPrevented".into(), Value::Bool(false));
83        // `isTrusted` が丸ごと未対応だった。スクリプトから構築・発火した
84        // イベントは仕様上常に `false`。
85        b.props.insert("isTrusted".into(), Value::Bool(false));
86        // `currentTarget`/`eventPhase` が丸ごと未設定で常に `undefined` だった
87        // (DOM 要素側は既に対応済みだった非対称性)。仕様どおり発火前は
88        // `currentTarget: null` / `eventPhase: NONE(0)`。
89        b.props.insert("currentTarget".into(), Value::Null);
90        b.props.insert("eventPhase".into(), Value::Number(0.0));
91        // `timeStamp` が DOM 要素側・`EventTarget` 側どちらにも丸ごと未対応で
92        // 常に `undefined` だった(このエンジン全体に存在しない機能だった)。
93        // 実クロックが無いためリアルタイム時刻ではなく `performance.now()` と
94        // 同じ単調増加カウンタを流用する(イベント生成時点の値で固定)。
95        b.props.insert("timeStamp".into(), Value::Number(next_perf_timestamp()));
96        // レガシー DOM Level 0 の `returnValue`/`cancelBubble` が丸ごと未対応
97        // だった。`preventDefault()`/`stopPropagation()` 呼び出し時にそれぞれ
98        // 追従して更新される(詳細はそちらの実装コメント参照)。
99        b.props.insert("returnValue".into(), Value::Bool(true));
100        b.props.insert("cancelBubble".into(), Value::Bool(false));
101        b.props
102            .insert("preventDefault".into(), nv("preventDefault", event_prevent_default));
103        b.props.insert(
104            "stopPropagation".into(),
105            nv("stopPropagation", event_target_stop_propagation),
106        );
107        // `stopImmediatePropagation` が丸ごと未対応で、呼び出すと「関数ではない」
108        // という TypeError になっていた(`stopPropagation` は伝播先が無い
109        // `EventTarget` では no-op で正しいが、`stopImmediatePropagation` は
110        // 同一ターゲット上の残りのリスナーを止める効果を持つべきで no-op では
111        // 済まない)。`_stopImmediate` フラグを立て、`event_target_dispatch_event`
112        // 側のリスナー呼び出しループがこれを見て残りをスキップする。
113        b.props.insert(
114            "stopImmediatePropagation".into(),
115            nv("stopImmediatePropagation", event_stop_immediate_propagation),
116        );
117        // `composedPath()` が丸ごと未対応で、呼び出すと「関数ではない」という
118        // TypeError になっていた(DOM 要素側の dispatch_event_with/
119        // dispatch_event_in_interp は既に持っていた非対称性)。この処理系の
120        // `EventTarget` には DOM ツリーが無いため、簡略化して「dispatchEvent
121        // 中は [target] を返し、発火前は空配列」とする。
122        b.props.insert(
123            "composedPath".into(),
124            nv("composedPath", event_target_composed_path),
125        );
126    }
127    Ok(Value::Object(ev))
128}
129
130/// `new KeyboardEvent(type, options)`(丸ごと未対応だった。`el.dispatchEvent
131/// (new KeyboardEvent('keydown', {key:'a', code:'KeyA'}))` というキーボード
132/// 操作シミュレーションの定番パターン)。基底の `Event` プロパティは
133/// `event_ctor` を再利用し、`key`/`code`/`keyCode`(レガシー)/`which`
134/// (レガシー)/`location`/`repeat`/修飾キー4種を追加する。
135pub(crate) fn keyboard_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
136    let ev = event_ctor(it, t, a)?;
137    let options = arg(a, 1);
138    if let Value::Object(o) = &ev {
139        let mut b = o.borrow_mut();
140        b.props.insert("key".into(), obj_prop(&options, "key").unwrap_or_else(|| Value::str("")));
141        b.props.insert("code".into(), obj_prop(&options, "code").unwrap_or_else(|| Value::str("")));
142        b.props
143            .insert("keyCode".into(), obj_prop(&options, "keyCode").unwrap_or(Value::Number(0.0)));
144        b.props
145            .insert("which".into(), obj_prop(&options, "which").unwrap_or(Value::Number(0.0)));
146        b.props
147            .insert("location".into(), obj_prop(&options, "location").unwrap_or(Value::Number(0.0)));
148        b.props.insert(
149            "repeat".into(),
150            Value::Bool(obj_prop(&options, "repeat").map(|v| v.truthy()).unwrap_or(false)),
151        );
152        for k in ["ctrlKey", "shiftKey", "altKey", "metaKey"] {
153            b.props.insert(
154                k.into(),
155                Value::Bool(obj_prop(&options, k).map(|v| v.truthy()).unwrap_or(false)),
156            );
157        }
158    }
159    Ok(ev)
160}
161/// `new MouseEvent(type, options)`(丸ごと未対応だった。`el.dispatchEvent(new
162/// MouseEvent('click', {clientX, clientY}))` というポインタ操作シミュレー
163/// ションの定番パターン)。基底の `Event` プロパティは `event_ctor` を
164/// 再利用し、座標系4種/`button`/`buttons`/修飾キー4種/`relatedTarget` を
165/// 追加する。
166pub(crate) fn mouse_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
167    let ev = event_ctor(it, t, a)?;
168    let options = arg(a, 1);
169    if let Value::Object(o) = &ev {
170        let mut b = o.borrow_mut();
171        for k in ["clientX", "clientY", "screenX", "screenY", "movementX", "movementY"] {
172            b.props
173                .insert(k.into(), obj_prop(&options, k).unwrap_or(Value::Number(0.0)));
174        }
175        b.props.insert("button".into(), obj_prop(&options, "button").unwrap_or(Value::Number(0.0)));
176        b.props
177            .insert("buttons".into(), obj_prop(&options, "buttons").unwrap_or(Value::Number(0.0)));
178        b.props
179            .insert("relatedTarget".into(), obj_prop(&options, "relatedTarget").unwrap_or(Value::Null));
180        for k in ["ctrlKey", "shiftKey", "altKey", "metaKey"] {
181            b.props.insert(
182                k.into(),
183                Value::Bool(obj_prop(&options, k).map(|v| v.truthy()).unwrap_or(false)),
184            );
185        }
186    }
187    Ok(ev)
188}
189/// `new WheelEvent(type, options)`(丸ごと未対応だった。`el.dispatchEvent(new
190/// WheelEvent('wheel', {deltaY}))` というホイール操作シミュレーションの
191/// 定番パターン)。仕様どおり `MouseEvent` を継承するため `mouse_event_ctor`
192/// をそのまま土台にし、`deltaX`/`deltaY`/`deltaZ`/`deltaMode` を追加する。
193pub(crate) fn wheel_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
194    let ev = mouse_event_ctor(it, t, a)?;
195    let options = arg(a, 1);
196    if let Value::Object(o) = &ev {
197        let mut b = o.borrow_mut();
198        for k in ["deltaX", "deltaY", "deltaZ"] {
199            b.props
200                .insert(k.into(), obj_prop(&options, k).unwrap_or(Value::Number(0.0)));
201        }
202        b.props
203            .insert("deltaMode".into(), obj_prop(&options, "deltaMode").unwrap_or(Value::Number(0.0)));
204    }
205    Ok(ev)
206}
207/// `new Touch(options)`(Touch Events。丸ごと未対応だった)。この処理系の
208/// USB HID スタックはマウス/キーボードのみでタッチスクリーン入力自体を
209/// 生成しないが、`new TouchEvent(...)` でタッチ操作をシミュレートする
210/// テストコードから使われる定番パターンのため、コンストラクタ自体は
211/// 対応する(実際のタッチ入力イベント発火経路は無い)。
212pub(crate) fn touch_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
213    let options = arg(a, 0);
214    let o = Obj::plain();
215    {
216        let mut b = o.borrow_mut();
217        b.props.insert(
218            "identifier".into(),
219            obj_prop(&options, "identifier").unwrap_or(Value::Number(0.0)),
220        );
221        b.props
222            .insert("target".into(), obj_prop(&options, "target").unwrap_or(Value::Null));
223        for k in ["clientX", "clientY", "pageX", "pageY", "screenX", "screenY"] {
224            b.props.insert(k.into(), obj_prop(&options, k).unwrap_or(Value::Number(0.0)));
225        }
226        b.props
227            .insert("radiusX".into(), obj_prop(&options, "radiusX").unwrap_or(Value::Number(25.0)));
228        b.props
229            .insert("radiusY".into(), obj_prop(&options, "radiusY").unwrap_or(Value::Number(25.0)));
230        b.props.insert(
231            "rotationAngle".into(),
232            obj_prop(&options, "rotationAngle").unwrap_or(Value::Number(0.0)),
233        );
234        b.props
235            .insert("force".into(), obj_prop(&options, "force").unwrap_or(Value::Number(1.0)));
236    }
237    Ok(Value::Object(o))
238}
239/// `new TouchEvent(type, options)`(丸ごと未対応だった。`touches`/
240/// `targetTouches`/`changedTouches` は仕様上 `TouchList` だが、この
241/// 処理系は他のイベント同様プレーンな配列で簡略表現する。基底の
242/// `Event` プロパティは `event_ctor` を再利用する。
243pub(crate) fn touch_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
244    let ev = event_ctor(it, t, a)?;
245    let options = arg(a, 1);
246    if let Value::Object(o) = &ev {
247        let mut b = o.borrow_mut();
248        for k in ["touches", "targetTouches", "changedTouches"] {
249            b.props.insert(
250                k.into(),
251                obj_prop(&options, k).unwrap_or_else(|| Value::Object(Obj::array(alloc::vec![]))),
252            );
253        }
254        for k in ["ctrlKey", "shiftKey", "altKey", "metaKey"] {
255            b.props.insert(
256                k.into(),
257                Value::Bool(obj_prop(&options, k).map(|v| v.truthy()).unwrap_or(false)),
258            );
259        }
260    }
261    Ok(ev)
262}
263/// `new FocusEvent(type, options)`(丸ごと未対応だった。`el.dispatchEvent(new
264/// FocusEvent('focusout', {relatedTarget}))` というフォーカス移動
265/// シミュレーションの定番パターン)。基底の `Event` プロパティは
266/// `event_ctor` を再利用し、`relatedTarget` のみ追加する。
267pub(crate) fn focus_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
268    let ev = event_ctor(it, t, a)?;
269    let options = arg(a, 1);
270    if let Value::Object(o) = &ev {
271        o.borrow_mut()
272            .props
273            .insert("relatedTarget".into(), obj_prop(&options, "relatedTarget").unwrap_or(Value::Null));
274    }
275    Ok(ev)
276}
277/// `new PointerEvent(type, options)`(丸ごと未対応だった。`el.dispatchEvent
278/// (new PointerEvent('pointerdown', {pointerId, pointerType:'touch'}))`
279/// というポインタ入力シミュレーションの定番パターン)。仕様どおり
280/// `MouseEvent` を継承するため `mouse_event_ctor` をそのまま土台にし、
281/// `pointerId`/`width`/`height`/`pressure`/`tangentialPressure`/`tiltX`/
282/// `tiltY`/`twist`/`pointerType`/`isPrimary` を追加する。
283pub(crate) fn pointer_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
284    let ev = mouse_event_ctor(it, t, a)?;
285    let options = arg(a, 1);
286    if let Value::Object(o) = &ev {
287        let mut b = o.borrow_mut();
288        b.props
289            .insert("pointerId".into(), obj_prop(&options, "pointerId").unwrap_or(Value::Number(0.0)));
290        b.props.insert("width".into(), obj_prop(&options, "width").unwrap_or(Value::Number(1.0)));
291        b.props.insert("height".into(), obj_prop(&options, "height").unwrap_or(Value::Number(1.0)));
292        for k in ["pressure", "tangentialPressure", "tiltX", "tiltY", "twist"] {
293            b.props
294                .insert(k.into(), obj_prop(&options, k).unwrap_or(Value::Number(0.0)));
295        }
296        b.props
297            .insert("pointerType".into(), obj_prop(&options, "pointerType").unwrap_or_else(|| Value::str("")));
298        b.props.insert(
299            "isPrimary".into(),
300            Value::Bool(obj_prop(&options, "isPrimary").map(|v| v.truthy()).unwrap_or(false)),
301        );
302    }
303    Ok(ev)
304}
305/// `new InputEvent(type, options)`(丸ごと未対応だった。`el.dispatchEvent(new
306/// InputEvent('input', {data:'a', inputType:'insertText'}))` という入力
307/// シミュレーションの定番パターン)。基底の `Event` プロパティは
308/// `event_ctor` を再利用し、`data`/`inputType`/`isComposing` を追加する。
309pub(crate) fn input_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
310    let ev = event_ctor(it, t, a)?;
311    let options = arg(a, 1);
312    if let Value::Object(o) = &ev {
313        let mut b = o.borrow_mut();
314        b.props.insert("data".into(), obj_prop(&options, "data").unwrap_or(Value::Null));
315        b.props
316            .insert("inputType".into(), obj_prop(&options, "inputType").unwrap_or_else(|| Value::str("")));
317        b.props.insert(
318            "isComposing".into(),
319            Value::Bool(obj_prop(&options, "isComposing").map(|v| v.truthy()).unwrap_or(false)),
320        );
321    }
322    Ok(ev)
323}
324/// `new ProgressEvent(type, options)`(丸ごと未対応だった。`xhr.
325/// dispatchEvent(new ProgressEvent('progress', {loaded, total}))`/
326/// `fetch` 進捗イベントのシミュレーション定番パターン)。基底の `Event`
327/// プロパティは `event_ctor` を再利用し、`lengthComputable`/`loaded`/
328/// `total` を追加する。
329pub(crate) fn progress_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
330    let ev = event_ctor(it, t, a)?;
331    let options = arg(a, 1);
332    if let Value::Object(o) = &ev {
333        let mut b = o.borrow_mut();
334        b.props.insert(
335            "lengthComputable".into(),
336            Value::Bool(obj_prop(&options, "lengthComputable").map(|v| v.truthy()).unwrap_or(false)),
337        );
338        b.props.insert("loaded".into(), obj_prop(&options, "loaded").unwrap_or(Value::Number(0.0)));
339        b.props.insert("total".into(), obj_prop(&options, "total").unwrap_or(Value::Number(0.0)));
340    }
341    Ok(ev)
342}
343/// `new StorageEvent(type, options)`(丸ごと未対応だった。`window.
344/// dispatchEvent(new StorageEvent('storage', {key, newValue, oldValue}))`
345/// というクロスタブ同期シミュレーションの定番パターン。この処理系には
346/// 実際の複数タブ間 `localStorage` 同期機構は無いため、コンストラクタ経由の
347/// 手動発火のみをサポートする簡略実装)。基底の `Event` プロパティは
348/// `event_ctor` を再利用し、`key`/`oldValue`/`newValue`/`url`/`storageArea`
349/// を追加する。
350pub(crate) fn storage_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
351    let ev = event_ctor(it, t, a)?;
352    let options = arg(a, 1);
353    if let Value::Object(o) = &ev {
354        let mut b = o.borrow_mut();
355        b.props.insert("key".into(), obj_prop(&options, "key").unwrap_or(Value::Null));
356        b.props.insert("oldValue".into(), obj_prop(&options, "oldValue").unwrap_or(Value::Null));
357        b.props.insert("newValue".into(), obj_prop(&options, "newValue").unwrap_or(Value::Null));
358        b.props.insert("url".into(), obj_prop(&options, "url").unwrap_or_else(|| Value::str("")));
359        b.props
360            .insert("storageArea".into(), obj_prop(&options, "storageArea").unwrap_or(Value::Null));
361    }
362    Ok(ev)
363}
364/// `new PopStateEvent(type, options)`(丸ごと未対応だった。2026-07-17
365/// 発見。実ナビゲーション経由の`popstate`発火(`interp.rs`の`fire_
366/// popstate`)は既に`state`プロパティ付きで正しく動いていたが、
367/// `window.dispatchEvent(new PopStateEvent('popstate', {state}))`という
368/// 手動シミュレーションの定番パターン向けの専用コンストラクタ自体が
369/// 無かった)。基底の `Event` プロパティは `event_ctor` を再利用する。
370pub(crate) fn pop_state_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
371    let ev = event_ctor(it, t, a)?;
372    let options = arg(a, 1);
373    if let Value::Object(o) = &ev {
374        let mut b = o.borrow_mut();
375        b.props.insert("state".into(), obj_prop(&options, "state").unwrap_or(Value::Null));
376    }
377    Ok(ev)
378}
379/// `new HashChangeEvent(type, options)`(丸ごと未対応だった。
380/// `window.addEventListener('hashchange', ...)` 自体は既に登録経路が
381/// あったが、`oldURL`/`newURL` を持つ専用イベントコンストラクタが無く
382/// `window.dispatchEvent(new HashChangeEvent('hashchange', {oldURL,
383/// newURL}))` というシミュレーションの定番パターンが使えなかった)。
384/// 基底の `Event` プロパティは `event_ctor` を再利用する。
385pub(crate) fn hash_change_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
386    let ev = event_ctor(it, t, a)?;
387    let options = arg(a, 1);
388    if let Value::Object(o) = &ev {
389        let mut b = o.borrow_mut();
390        b.props
391            .insert("oldURL".into(), obj_prop(&options, "oldURL").unwrap_or_else(|| Value::str("")));
392        b.props
393            .insert("newURL".into(), obj_prop(&options, "newURL").unwrap_or_else(|| Value::str("")));
394    }
395    Ok(ev)
396}
397/// `new PageTransitionEvent(type, options)`(丸ごと未対応だった。
398/// `window.addEventListener('pageshow'/'pagehide', ...)` を BFCache
399/// シミュレーションでテストする定番パターン向け)。
400pub(crate) fn page_transition_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
401    let ev = event_ctor(it, t, a)?;
402    let options = arg(a, 1);
403    if let Value::Object(o) = &ev {
404        o.borrow_mut().props.insert(
405            "persisted".into(),
406            Value::Bool(obj_prop(&options, "persisted").map(|v| v.truthy()).unwrap_or(false)),
407        );
408    }
409    Ok(ev)
410}
411/// `new CompositionEvent(type, options)`(丸ごと未対応だった。IME 変換中の
412/// `el.dispatchEvent(new CompositionEvent('compositionupdate', {data}))`
413/// というシミュレーションの定番パターン)。基底の `Event` プロパティは
414/// `event_ctor` を再利用し、`data` のみ追加する。
415pub(crate) fn composition_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
416    let ev = event_ctor(it, t, a)?;
417    let options = arg(a, 1);
418    if let Value::Object(o) = &ev {
419        o.borrow_mut()
420            .props
421            .insert("data".into(), obj_prop(&options, "data").unwrap_or_else(|| Value::str("")));
422    }
423    Ok(ev)
424}
425/// `new ClipboardEvent(type, options)`(丸ごと未対応だった。`el.dispatchEvent
426/// (new ClipboardEvent('paste', {clipboardData}))` というコピー&ペースト
427/// シミュレーションの定番パターン)。基底の `Event` プロパティは
428/// `event_ctor` を再利用し、`clipboardData` のみ追加する(本物の
429/// `DataTransfer` 実装はこの処理系に無いため、渡された値をそのまま透過する
430/// 簡略実装)。
431pub(crate) fn clipboard_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
432    let ev = event_ctor(it, t, a)?;
433    let options = arg(a, 1);
434    if let Value::Object(o) = &ev {
435        o.borrow_mut()
436            .props
437            .insert("clipboardData".into(), obj_prop(&options, "clipboardData").unwrap_or(Value::Null));
438    }
439    Ok(ev)
440}
441/// `new AnimationEvent(type, options)`(丸ごと未対応だった。CSS アニメーション
442/// 完了シミュレーションの `el.dispatchEvent(new AnimationEvent
443/// ('animationend', {animationName}))` という定番パターン)。基底の
444/// `Event` プロパティは `event_ctor` を再利用し、`animationName`/
445/// `elapsedTime`/`pseudoElement` を追加する。
446pub(crate) fn animation_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
447    let ev = event_ctor(it, t, a)?;
448    let options = arg(a, 1);
449    if let Value::Object(o) = &ev {
450        let mut b = o.borrow_mut();
451        b.props.insert(
452            "animationName".into(),
453            obj_prop(&options, "animationName").unwrap_or_else(|| Value::str("")),
454        );
455        b.props
456            .insert("elapsedTime".into(), obj_prop(&options, "elapsedTime").unwrap_or(Value::Number(0.0)));
457        b.props.insert(
458            "pseudoElement".into(),
459            obj_prop(&options, "pseudoElement").unwrap_or_else(|| Value::str("")),
460        );
461    }
462    Ok(ev)
463}
464/// `new TransitionEvent(type, options)`(丸ごと未対応だった。CSS トランジション
465/// 完了シミュレーションの定番パターン)。基底の `Event` プロパティは
466/// `event_ctor` を再利用し、`propertyName`/`elapsedTime`/`pseudoElement`
467/// を追加する。
468pub(crate) fn transition_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
469    let ev = event_ctor(it, t, a)?;
470    let options = arg(a, 1);
471    if let Value::Object(o) = &ev {
472        let mut b = o.borrow_mut();
473        b.props.insert(
474            "propertyName".into(),
475            obj_prop(&options, "propertyName").unwrap_or_else(|| Value::str("")),
476        );
477        b.props
478            .insert("elapsedTime".into(), obj_prop(&options, "elapsedTime").unwrap_or(Value::Number(0.0)));
479        b.props.insert(
480            "pseudoElement".into(),
481            obj_prop(&options, "pseudoElement").unwrap_or_else(|| Value::str("")),
482        );
483    }
484    Ok(ev)
485}
486/// `new DragEvent(type, options)`(丸ごと未対応だった。`el.dispatchEvent(new
487/// DragEvent('drop', {dataTransfer}))` というドラッグ&ドロップシミュレー
488/// ションの定番パターン)。仕様どおり `MouseEvent` を継承するため
489/// `mouse_event_ctor` をそのまま土台にし、`dataTransfer` を追加する(本物の
490/// `DataTransfer` 実装はこの処理系に無いため、渡された値をそのまま透過する
491/// 簡略実装)。
492pub(crate) fn drag_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
493    let ev = mouse_event_ctor(it, t, a)?;
494    let options = arg(a, 1);
495    if let Value::Object(o) = &ev {
496        o.borrow_mut()
497            .props
498            .insert("dataTransfer".into(), obj_prop(&options, "dataTransfer").unwrap_or(Value::Null));
499    }
500    Ok(ev)
501}
502/// `new SubmitEvent(type, options)`(丸ごと未対応だった。`form.
503/// dispatchEvent(new SubmitEvent('submit', {submitter}))` という「どの
504/// ボタンで送信されたか」を伝えるシミュレーションの定番パターン)。基底の
505/// `Event` プロパティは `event_ctor` を再利用し、`submitter` のみ追加する。
506pub(crate) fn submit_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
507    let ev = event_ctor(it, t, a)?;
508    let options = arg(a, 1);
509    if let Value::Object(o) = &ev {
510        o.borrow_mut()
511            .props
512            .insert("submitter".into(), obj_prop(&options, "submitter").unwrap_or(Value::Null));
513    }
514    Ok(ev)
515}
516/// `new MessageEvent(type, options)`(丸ごと未対応だった。`window.
517/// postMessage()`/`reportError()` は内部でこの形の Event を素朴な
518/// `Obj::plain()` で組み立てていたが、ユーザーコードから同じ形を
519/// `dispatchEvent` シミュレーション用に直接構築するコンストラクタ自体が
520/// 存在しなかった)。基底の `Event` プロパティは `event_ctor` を再利用し、
521/// `data`/`origin`/`lastEventId`/`source`/`ports` を追加する。
522pub(crate) fn message_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
523    let ev = event_ctor(it, t, a)?;
524    let options = arg(a, 1);
525    if let Value::Object(o) = &ev {
526        let mut b = o.borrow_mut();
527        b.props.insert("data".into(), obj_prop(&options, "data").unwrap_or(Value::Null));
528        b.props.insert("origin".into(), obj_prop(&options, "origin").unwrap_or_else(|| Value::str("")));
529        b.props.insert(
530            "lastEventId".into(),
531            obj_prop(&options, "lastEventId").unwrap_or_else(|| Value::str("")),
532        );
533        b.props.insert("source".into(), obj_prop(&options, "source").unwrap_or(Value::Null));
534        b.props.insert(
535            "ports".into(),
536            obj_prop(&options, "ports").unwrap_or_else(|| Value::Object(Obj::array(alloc::vec::Vec::new()))),
537        );
538    }
539    Ok(ev)
540}
541/// `new ErrorEvent(type, options)`(丸ごと未対応だった。`reportError()` は
542/// 内部でこの形の Event を素朴な `Obj::plain()` で組み立てていたが、
543/// ユーザーコードから直接構築するコンストラクタ自体が存在しなかった)。
544/// 基底の `Event` プロパティは `event_ctor` を再利用し、
545/// `message`/`filename`/`lineno`/`colno`/`error` を追加する。
546pub(crate) fn error_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
547    let ev = event_ctor(it, t, a)?;
548    let options = arg(a, 1);
549    if let Value::Object(o) = &ev {
550        let mut b = o.borrow_mut();
551        b.props
552            .insert("message".into(), obj_prop(&options, "message").unwrap_or_else(|| Value::str("")));
553        b.props
554            .insert("filename".into(), obj_prop(&options, "filename").unwrap_or_else(|| Value::str("")));
555        b.props.insert("lineno".into(), obj_prop(&options, "lineno").unwrap_or(Value::Number(0.0)));
556        b.props.insert("colno".into(), obj_prop(&options, "colno").unwrap_or(Value::Number(0.0)));
557        b.props.insert("error".into(), obj_prop(&options, "error").unwrap_or(Value::Null));
558    }
559    Ok(ev)
560}
561
562/// `new CustomEvent(type, {detail, bubbles, cancelable})`。以前は `options.bubbles`
563/// を一切読まず常に `false` を決め打ちしていたため、`new CustomEvent('x',
564/// {bubbles: true})` が矛盾した `bubbles === false` なイベントになるバグだった
565/// (`event_ctor` は `bubbles` を正しく読んでいたのに、こちらだけ漏れていた)。
566pub(crate) fn custom_event_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
567    let event_type = arg(a, 0).to_js_string();
568    let options = arg(a, 1);
569    let (detail, bubbles, cancelable, composed) = if let Value::Object(o) = &options {
570        let b = o.borrow();
571        (
572            b.props.get("detail").cloned().unwrap_or(Value::Undefined),
573            b.props.get("bubbles").map(|v| v.truthy()).unwrap_or(false),
574            b.props.get("cancelable").map(|v| v.truthy()).unwrap_or(false),
575            b.props.get("composed").map(|v| v.truthy()).unwrap_or(false),
576        )
577    } else {
578        (Value::Undefined, false, false, false)
579    };
580    let ev = Obj::plain();
581    {
582        let mut b = ev.borrow_mut();
583        b.props.insert("type".into(), Value::str(&event_type));
584        b.props.insert("detail".into(), detail);
585        b.props.insert("bubbles".into(), Value::Bool(bubbles));
586        b.props.insert("cancelable".into(), Value::Bool(cancelable));
587        // `composed`(`event_ctor` と同じ丸ごと未対応バグ。2026-07-16 発見)。
588        b.props.insert("composed".into(), Value::Bool(composed));
589        b.props.insert("defaultPrevented".into(), Value::Bool(false));
590        // `isTrusted` が丸ごと未対応だった。スクリプトから構築・発火した
591        // イベントは仕様上常に `false`。
592        b.props.insert("isTrusted".into(), Value::Bool(false));
593        // `event_ctor` と同じ `currentTarget`/`eventPhase` 欠落バグ(詳細はそちら参照)。
594        b.props.insert("currentTarget".into(), Value::Null);
595        b.props.insert("eventPhase".into(), Value::Number(0.0));
596        // `event_ctor` と同じ `timeStamp` 欠落バグ(詳細はそちら参照)。
597        b.props.insert("timeStamp".into(), Value::Number(next_perf_timestamp()));
598        // `event_ctor` と同じ `returnValue`/`cancelBubble` 欠落バグ(詳細はそちら参照)。
599        b.props.insert("returnValue".into(), Value::Bool(true));
600        b.props.insert("cancelBubble".into(), Value::Bool(false));
601        b.props
602            .insert("preventDefault".into(), nv("preventDefault", event_prevent_default));
603        b.props.insert(
604            "stopPropagation".into(),
605            nv("stopPropagation", event_target_stop_propagation),
606        );
607        // `stopImmediatePropagation` が丸ごと未対応で、呼び出すと「関数ではない」
608        // という TypeError になっていた(`stopPropagation` は伝播先が無い
609        // `EventTarget` では no-op で正しいが、`stopImmediatePropagation` は
610        // 同一ターゲット上の残りのリスナーを止める効果を持つべきで no-op では
611        // 済まない)。`_stopImmediate` フラグを立て、`event_target_dispatch_event`
612        // 側のリスナー呼び出しループがこれを見て残りをスキップする。
613        b.props.insert(
614            "stopImmediatePropagation".into(),
615            nv("stopImmediatePropagation", event_stop_immediate_propagation),
616        );
617        // `event_ctor` と同じ `composedPath()` 欠落バグ(詳細はそちら参照)。
618        b.props.insert(
619            "composedPath".into(),
620            nv("composedPath", event_target_composed_path),
621        );
622    }
623    Ok(Value::Object(ev))
624}
625
626// ===== EventTarget(丸ごと未対応だった。DOM 要素以外で独自のイベント発行/購読の
627// 基盤として使う定番パターン `class MyEmitter extends EventTarget {}` /
628// `new EventTarget()`)=====
629pub(crate) fn event_target_ctor(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
630    let o = Obj::plain();
631    {
632        let mut b = o.borrow_mut();
633        b.props.insert("_listeners".into(), Value::Object(Obj::plain()));
634        b.props.insert(
635            "addEventListener".into(),
636            nv("addEventListener", event_target_add_event_listener),
637        );
638        b.props.insert(
639            "removeEventListener".into(),
640            nv("removeEventListener", event_target_remove_event_listener),
641        );
642        b.props
643            .insert("dispatchEvent".into(), nv("dispatchEvent", event_target_dispatch_event));
644    }
645    Ok(Value::Object(o))
646}
647pub(crate) fn event_target_listeners(this: &Value) -> Option<super::super::value::ObjRef> {
648    if let Value::Object(o) = this {
649        if let Some(Value::Object(l)) = o.borrow().props.get("_listeners") {
650            return Some(l.clone());
651        }
652    }
653    None
654}
655/// リスナ格納エントリ(`[cb, once]` の2要素配列)から実際のコールバックを取り出す。
656pub(crate) fn event_target_listener_cb(entry: &Value) -> Value {
657    if let Value::Object(o) = entry {
658        if let ObjKind::Array(items) = &o.borrow().kind {
659            if let Some(cb) = items.first() {
660                return cb.clone();
661            }
662        }
663    }
664    entry.clone()
665}
666/// リスナ格納エントリの `once` フラグを取り出す。
667pub(crate) fn event_target_listener_once(entry: &Value) -> bool {
668    if let Value::Object(o) = entry {
669        if let ObjKind::Array(items) = &o.borrow().kind {
670            if let Some(v) = items.get(1) {
671                return v.truthy();
672            }
673        }
674    }
675    false
676}
677/// リスナ格納エントリの `{signal}` が abort 済みかどうか(`addEventListener`
678/// の `{signal}` オプション。丸ごと未対応だった。2026-07-14 発見・実装)。
679pub(crate) fn event_target_listener_aborted(entry: &Value) -> bool {
680    if let Value::Object(o) = entry {
681        if let ObjKind::Array(items) = &o.borrow().kind {
682            if let Some(sig) = items.get(2) {
683                return super::super::dom_bridge::is_signal_aborted(sig);
684            }
685        }
686    }
687    false
688}
689/// リスナ格納エントリの `capture` フラグを取り出す(4要素目。`document`
690/// のキャプチャフェーズ発火判定用に2026-07-16新設。他の`EventTarget`風
691/// オブジェクト(XHR/AbortSignal等)は3要素のまま登録されることがあり、
692/// その場合は `get(3)` が `None` になり `false` へ安全にフォールバックする)。
693pub(crate) fn event_target_listener_capture(entry: &Value) -> bool {
694    if let Value::Object(o) = entry {
695        if let ObjKind::Array(items) = &o.borrow().kind {
696            if let Some(v) = items.get(3) {
697                return v.truthy();
698            }
699        }
700    }
701    false
702}
703/// `target.addEventListener(type, fn, options)`。他の `EventTarget` 風オブジェクト
704/// (XHR/AbortSignal 等)と違い、こちらは仕様どおり型ごとに複数リスナを保持する
705/// (同じ関数参照の重複登録は無視する)。第3引数の `{once}` は保持され、
706/// `dispatchEvent` 側で初回呼び出し後に自動解除する。`capture` は元々
707/// 「DOM ツリーを持たないこのクラスでは意味を持たないため無視する」設計
708/// だったが、`document.addEventListener`(`window_api.rs`の`document_add_
709/// event_listener`)がこの実装を再利用するようになった(2026-07-16)ため、
710/// `document`に対してはcaptureフェーズでの発火判定に使う必要が生じ、
711/// エントリの4要素目として保持するよう拡張した(既存の`XHR`/`AbortSignal`
712/// 等の呼び出し元は4要素目を単に読まないため無害)。
713pub(crate) fn event_target_add_event_listener(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
714    { let t = arg(a, 0).to_js_string(); if t == "readystatechange" || t == "load" { let k = match &this { Value::Object(o) => alloc::format!("{:?}", core::mem::discriminant(&o.borrow().kind)), _ => String::from("?") }; crate::warn!("[TMPRS] generic add {} this={} stack={:?}", t, k, _it.call_names.iter().rev().take(5).collect::<Vec<_>>()); } } // TMPRS
715    let Some(listeners) = event_target_listeners(&this) else { return Ok(Value::Undefined) };
716    let etype = arg(a, 0).to_js_string();
717    let cb = arg(a, 1);
718    let (capture, once) = parse_listener_opts(&arg(a, 2));
719    let signal = parse_listener_signal(&arg(a, 2));
720    if signal.as_ref().is_some_and(super::super::dom_bridge::is_signal_aborted) {
721        return Ok(Value::Undefined);
722    }
723    let entry = Value::Object(Obj::array(alloc::vec![
724        cb.clone(),
725        Value::Bool(once),
726        signal.unwrap_or(Value::Undefined),
727        Value::Bool(capture)
728    ]));
729    let existing = listeners.borrow().props.get(&etype).cloned();
730    match existing {
731        Some(Value::Object(arr)) => {
732            if let ObjKind::Array(items) = &mut arr.borrow_mut().kind {
733                // 重複登録の判定は仕様どおり (listener, capture) の組で行う。
734                // `capture` を見ずに `cb` だけで重複判定していたため、同じ
735                // 関数を `{capture:true}`/`{capture:false}` で2回登録する
736                // (capture/bubble 両フェーズで拾いたい定番パターン)と、
737                // 2回目の登録が誤って「重複」扱いされ黙って無視されていた
738                // バグ(`document`のcapture対応〔2026-07-16〕で`capture`を
739                // 保持するようになったことで顕在化・同日発見・修正)。
740                let dup = items.iter().any(|v| {
741                    match (&event_target_listener_cb(v), &cb) {
742                        (Value::Object(x), Value::Object(y)) => {
743                            Rc::ptr_eq(x, y) && event_target_listener_capture(v) == capture
744                        }
745                        _ => false,
746                    }
747                });
748                if !dup {
749                    items.push(entry);
750                }
751            }
752        }
753        _ => {
754            listeners
755                .borrow_mut()
756                .props
757                .insert(etype, Value::Object(Obj::array(alloc::vec![entry])));
758        }
759    }
760    Ok(Value::Undefined)
761}
762/// `target.removeEventListener(type, fn, options)`。仕様どおり `(listener,
763/// capture)` の組が一致するエントリのみを取り除く。以前は `capture` を
764/// 一切見ず `cb` のみで判定していたため、同じ関数を `{capture:true}`/
765/// `{capture:false}` で2回登録した場合に `removeEventListener('type', fn)`
766/// (`capture` 省略=既定 `false`)を呼ぶと両方まとめて消えてしまい、
767/// capture 側だけ残す意図が達成できないバグだった(`document`のcapture
768/// 対応〔2026-07-16〕で`capture`をエントリへ保持するようになったことで
769/// 顕在化・同日発見・修正)。
770pub(crate) fn event_target_remove_event_listener(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
771    let Some(listeners) = event_target_listeners(&this) else { return Ok(Value::Undefined) };
772    let etype = arg(a, 0).to_js_string();
773    let cb = arg(a, 1);
774    let (capture, _once) = parse_listener_opts(&arg(a, 2));
775    if let Some(Value::Object(arr)) = listeners.borrow().props.get(&etype).cloned() {
776        if let ObjKind::Array(items) = &mut arr.borrow_mut().kind {
777            items.retain(|v| match (&event_target_listener_cb(v), &cb) {
778                (Value::Object(x), Value::Object(y)) => {
779                    !(Rc::ptr_eq(x, y) && event_target_listener_capture(v) == capture)
780                }
781                _ => true,
782            });
783        }
784    }
785    Ok(Value::Undefined)
786}
787/// `target.dispatchEvent(event)`。仕様どおり `!event.defaultPrevented` を返す。
788pub(crate) fn event_target_dispatch_event(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
789    let ev = arg(a, 0);
790    if let Some(listeners) = event_target_listeners(&this) {
791        let etype = if let Value::Object(eo) = &ev {
792            eo.borrow().props.get("type").map(|v| v.to_js_string()).unwrap_or_default()
793        } else {
794            String::new()
795        };
796        let cbs = match listeners.borrow().props.get(&etype) {
797            Some(Value::Object(arr)) => match &arr.borrow().kind {
798                ObjKind::Array(items) => items.clone(),
799                _ => alloc::vec::Vec::new(),
800            },
801            _ => alloc::vec::Vec::new(),
802        };
803        if let Value::Object(eo) = &ev {
804            let mut e = eo.borrow_mut();
805            e.props.insert("target".into(), this.clone());
806            // レガシー DOM Level 0 の `srcElement`(`target` の別名)が丸ごと
807            // 未対応だった。
808            e.props.insert("srcElement".into(), this.clone());
809            // `currentTarget`/`eventPhase` が丸ごと未設定だったバグの修正。
810            // `EventTarget` は DOM ツリーを持たず伝播フェーズが無いため、
811            // リスナー呼び出し中は常に AT_TARGET(2) とする。
812            e.props.insert("currentTarget".into(), this.clone());
813            e.props.insert("eventPhase".into(), Value::Number(2.0));
814        }
815        for entry in cbs {
816            if event_target_listener_aborted(&entry) {
817                continue;
818            }
819            let cb = event_target_listener_cb(&entry);
820            let once = event_target_listener_once(&entry);
821            let ctx = alloc::format!("{} listener (EventTarget)", etype);
822            it.call_listener(&cb, this.clone(), core::slice::from_ref(&ev), &ctx);
823            // `{once: true}` で登録されたリスナーは初回呼び出し後に自動解除する
824            // (以前は addEventListener の第3引数自体を読んでおらず、once が
825            // 無視されて毎回発火し続けていた)。
826            if once {
827                if let Some(Value::Object(arr)) = listeners.borrow().props.get(&etype).cloned() {
828                    if let ObjKind::Array(items) = &mut arr.borrow_mut().kind {
829                        items.retain(|v| match (&event_target_listener_cb(v), &cb) {
830                            (Value::Object(x), Value::Object(y)) => !Rc::ptr_eq(x, y),
831                            _ => true,
832                        });
833                    }
834                }
835            }
836            // `stopImmediatePropagation()` が呼ばれたら残りのリスナーを止める
837            // (以前はこのメソッド自体が存在せず、呼び出すと TypeError になり
838            // 実質チェックしようがなかった)。
839            let stopped = if let Value::Object(eo) = &ev {
840                matches!(eo.borrow().props.get("_stopImmediate"), Some(Value::Bool(true)))
841            } else {
842                false
843            };
844            if stopped {
845                break;
846            }
847        }
848        // 発火完了後は仕様どおり `currentTarget: null` / `eventPhase: NONE(0)`
849        // へ戻す(`target` はそのまま残す:発火後の `event.target` 参照用)。
850        if let Value::Object(eo) = &ev {
851            let mut e = eo.borrow_mut();
852            e.props.insert("currentTarget".into(), Value::Null);
853            e.props.insert("eventPhase".into(), Value::Number(0.0));
854        }
855    }
856    let default_prevented = if let Value::Object(eo) = &ev {
857        matches!(eo.borrow().props.get("defaultPrevented"), Some(Value::Bool(true)))
858    } else {
859        false
860    };
861    Ok(Value::Bool(!default_prevented))
862}
863
864// ===== MessageChannel/MessagePort(丸ごと未対応だった。`new MessageChannel()`
865// の `.port1`/`.port2` はそれぞれ EventTarget を土台にした MessagePort で、
866// 一方に `postMessage(data)` すると他方へ `message` イベントとして同期配送
867// される。Worker 間通信の標準的な確立手段)=====
868pub(crate) fn message_channel_ctor(it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
869    let port1 = message_port_new(it)?;
870    let port2 = message_port_new(it)?;
871    if let (Value::Object(p1), Value::Object(p2)) = (&port1, &port2) {
872        p1.borrow_mut().props.insert("_peer".into(), port2.clone());
873        p2.borrow_mut().props.insert("_peer".into(), port1.clone());
874    }
875    let ch = Obj::plain();
876    {
877        let mut b = ch.borrow_mut();
878        b.props.insert("port1".into(), port1);
879        b.props.insert("port2".into(), port2);
880    }
881    Ok(Value::Object(ch))
882}
883pub(crate) fn message_port_new(it: &mut Interp) -> Result<Value, Value> {
884    let port = event_target_ctor(it, Value::Undefined, &[])?;
885    if let Value::Object(o) = &port {
886        let mut b = o.borrow_mut();
887        b.props
888            .insert("postMessage".into(), nv("postMessage", message_port_post_message));
889        b.props.insert("start".into(), nv("start", dom_noop));
890        b.props.insert("close".into(), nv("close", message_port_close));
891    }
892    Ok(port)
893}
894pub(crate) fn message_port_is_closed(port: &Value) -> bool {
895    matches!(port, Value::Object(o) if o.borrow().props.get("_closed").map(|v| v.truthy()).unwrap_or(false))
896}
897/// `port.postMessage(data)`。`structuredClone` と同じアルゴリズムでデータを
898/// 複製してから相手側ポートへ同期配送する(`close()` 済みのポートからは
899/// 送信しても無視する)。
900pub(crate) fn message_port_post_message(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
901    if message_port_is_closed(&this) {
902        return Ok(Value::Undefined);
903    }
904    let data = structured_clone(it, Value::Undefined, a)?;
905    let peer = if let Value::Object(o) = &this {
906        o.borrow().props.get("_peer").cloned()
907    } else {
908        None
909    };
910    if let Some(peer) = peer {
911        message_port_deliver(it, &peer, data);
912    }
913    Ok(Value::Undefined)
914}
915/// 相手ポートへ `message` イベントを配送する。`port.onmessage = fn`(単一
916/// リスナ方式)と `port.addEventListener('message', fn)`(複数リスナ方式)
917/// の両方をサポートする(他の EventTarget 風オブジェクトと同じ二重経路)。
918pub(crate) fn message_port_deliver(it: &mut Interp, port: &Value, data: Value) {
919    if message_port_is_closed(port) {
920        return;
921    }
922    let ev = Obj::plain();
923    ev.borrow_mut().props.insert("type".into(), Value::str("message"));
924    ev.borrow_mut().props.insert("data".into(), data);
925    let onmessage = if let Value::Object(o) = port {
926        o.borrow().props.get("onmessage").cloned()
927    } else {
928        None
929    };
930    if let Some(f) = onmessage.and_then(callable_or_none) {
931        it.call_listener(
932            &f,
933            port.clone(),
934            &[Value::Object(ev.clone())],
935            "MessagePort onmessage",
936        );
937    }
938    let _ = event_target_dispatch_event(it, port.clone(), &[Value::Object(ev)]);
939}
940pub(crate) fn message_port_close(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
941    if let Value::Object(o) = &this {
942        o.borrow_mut().props.insert("_closed".into(), Value::Bool(true));
943    }
944    Ok(Value::Undefined)
945}
946
947// ===== BroadcastChannel(丸ごと未対応だった。`new BroadcastChannel(name)` で
948// 同名チャネルを持つ他インスタンス全てへブロードキャストする。`MessagePort` と
949// 同じ配送ロジック(`message_port_deliver`/`message_port_close`)を再利用し、
950// 同名インスタンス一覧はグローバル隠しプロパティ `__bc_registry` の
951// `{name: [channel, ...]}` で管理する)=====
952pub(crate) fn bc_registry(it: &mut Interp) -> super::super::value::ObjRef {
953    let existing = it.global.borrow().vars.get("__bc_registry").cloned();
954    if let Some(Value::Object(o)) = existing {
955        return o;
956    }
957    let reg = Obj::plain();
958    it.global
959        .borrow_mut()
960        .vars
961        .insert("__bc_registry".into(), Value::Object(reg.clone()));
962    reg
963}
964pub(crate) fn broadcast_channel_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
965    let name = arg(a, 0).to_js_string();
966    let base = event_target_ctor(it, Value::Undefined, &[])?;
967    if let Value::Object(o) = &base {
968        let mut b = o.borrow_mut();
969        b.props.insert("name".into(), Value::str(&name));
970        b.props
971            .insert("postMessage".into(), nv("postMessage", broadcast_channel_post_message));
972        b.props.insert("close".into(), nv("close", message_port_close));
973    }
974    let reg = bc_registry(it);
975    let existing = reg.borrow().props.get(&name).cloned();
976    match existing {
977        Some(Value::Object(arr)) => {
978            if let ObjKind::Array(items) = &mut arr.borrow_mut().kind {
979                items.push(base.clone());
980            }
981        }
982        _ => {
983            reg.borrow_mut()
984                .props
985                .insert(name, Value::Object(Obj::array(alloc::vec![base.clone()])));
986        }
987    }
988    Ok(base)
989}
990/// `channel.postMessage(data)`。仕様どおり送信元自身へは配送しない
991/// (同名の他インスタンス全てへ `message_port_deliver` で配送する)。
992pub(crate) fn broadcast_channel_post_message(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
993    if message_port_is_closed(&this) {
994        return Ok(Value::Undefined);
995    }
996    let name = if let Value::Object(o) = &this {
997        o.borrow().props.get("name").map(|v| v.to_js_string()).unwrap_or_default()
998    } else {
999        String::new()
1000    };
1001    let data = structured_clone(it, Value::Undefined, a)?;
1002    let reg = bc_registry(it);
1003    let peers: Vec<Value> = match reg.borrow().props.get(&name) {
1004        Some(Value::Object(arr)) => match &arr.borrow().kind {
1005            ObjKind::Array(items) => items.clone(),
1006            _ => alloc::vec::Vec::new(),
1007        },
1008        _ => alloc::vec::Vec::new(),
1009    };
1010    for peer in peers {
1011        let is_self = matches!((&peer, &this), (Value::Object(p), Value::Object(t)) if Rc::ptr_eq(p, t));
1012        if is_self {
1013            continue;
1014        }
1015        message_port_deliver(it, &peer, data.clone());
1016    }
1017    Ok(Value::Undefined)
1018}
1019
1020/// `new CommandEvent(type, options)`(Invoker Commands API / WHATWG HTML Standard。
1021/// `<button command="..." commandfor="...">` のアクション時に発火するイベント。
1022/// `command`/`source` をプロパティとして保持する)。
1023pub(crate) fn command_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1024    let ev = event_ctor(it, t, a)?;
1025    let options = arg(a, 1);
1026    if let Value::Object(o) = &ev {
1027        let mut b = o.borrow_mut();
1028        b.props
1029            .insert("command".into(), obj_prop(&options, "command").unwrap_or_else(|| Value::str("")));
1030        b.props
1031            .insert("source".into(), obj_prop(&options, "source").unwrap_or(Value::Null));
1032    }
1033    Ok(ev)
1034}
1035
1036/// `new ToggleEvent(type, options)`(WHATWG HTML Standard。`<details>` や `popover` 等の
1037/// トグル状態で発火するイベント。`oldState`/`newState` をプロパティとして保持する)。
1038pub(crate) fn toggle_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1039    let ev = event_ctor(it, t, a)?;
1040    let options = arg(a, 1);
1041    if let Value::Object(o) = &ev {
1042        let mut b = o.borrow_mut();
1043        b.props.insert(
1044            "oldState".into(),
1045            obj_prop(&options, "oldState").unwrap_or_else(|| Value::str("closed")),
1046        );
1047        b.props.insert(
1048            "newState".into(),
1049            obj_prop(&options, "newState").unwrap_or_else(|| Value::str("open")),
1050        );
1051    }
1052    Ok(ev)
1053}
1054
1055/// `new FormDataEvent(type, options)`(WHATWG HTML Standard。`<form>` 発火の `formdata`
1056/// イベント向け。`formData` をプロパティとして保持する)。
1057pub(crate) fn form_data_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1058    let ev = event_ctor(it, t, a)?;
1059    let options = arg(a, 1);
1060    if let Value::Object(o) = &ev {
1061        let mut b = o.borrow_mut();
1062        b.props
1063            .insert("formData".into(), obj_prop(&options, "formData").unwrap_or(Value::Null));
1064    }
1065    Ok(ev)
1066}
1067
1068
1069/// `document.createEvent(interfaceName)`(DOM Level 2)。種類が空のイベントを返し、
1070/// `initEvent(type, bubbles, cancelable)`(CustomEvent は `initCustomEvent(..., detail)`)で初期化する。
1071/// 【2026-10-17】丸ごと未対応で `document.createEvent is not a function` になっていた
1072/// (YouTube の視聴ページで発生)。
1073pub(crate) fn document_create_event(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1074    let iface = arg(a, 0).to_js_string();
1075    let ev = event_ctor(it, Value::Undefined, &[Value::str("")])?;
1076    if let Value::Object(o) = &ev {
1077        let mut b = o.borrow_mut();
1078        b.props.insert("initEvent".into(), nv("initEvent", event_init_event));
1079        b.props.insert("initCustomEvent".into(), nv("initCustomEvent", event_init_event));
1080        b.props.insert("initUIEvent".into(), nv("initUIEvent", event_init_event));
1081        b.props.insert("initMouseEvent".into(), nv("initMouseEvent", event_init_event));
1082        b.props.insert("initKeyboardEvent".into(), nv("initKeyboardEvent", event_init_event));
1083        if iface.eq_ignore_ascii_case("CustomEvent") {
1084            b.props.insert("detail".into(), Value::Null);
1085        }
1086    }
1087    Ok(ev)
1088}
1089
1090/// `event.initEvent(type, bubbles, cancelable[, detail])`。
1091pub(crate) fn event_init_event(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1092    if let Value::Object(o) = &this {
1093        let mut b = o.borrow_mut();
1094        b.props.insert("type".into(), Value::str(arg(a, 0).to_js_string()));
1095        b.props.insert("bubbles".into(), Value::Bool(arg(a, 1).truthy()));
1096        b.props.insert("cancelable".into(), Value::Bool(arg(a, 2).truthy()));
1097        if a.len() > 3 && b.props.contains_key("detail") {
1098            b.props.insert("detail".into(), arg(a, 3));
1099        }
1100    }
1101    Ok(Value::Undefined)
1102}
1103
1104/// `EventTarget.prototype` の addEventListener/removeEventListener/dispatchEvent(`this` を問わない版)。
1105///
1106/// 【2026-10-19】ShadyDOM(YouTube の視聴ページの Polymer)は、これらを `EventTarget.prototype`
1107/// から `__shady_native_*` として写し、`window`・`document`・要素に対して `.call` で使う。
1108/// 従来は `EventTarget` が原型を持たず「window.__shady_native_addEventListener is not a function」
1109/// で止まっていた。DOM ノードはそのノードの組込みメソッド、自前の同名メソッドを持つ
1110/// オブジェクト(window 等)はそれ、どちらでもなければ `new EventTarget()` の実装へ回す。
1111fn event_target_proto_call(it: &mut Interp, this: Value, a: &[Value], name: &str) -> Result<Value, Value> {
1112    { let t = arg(a, 0).to_js_string(); if t == "readystatechange" { let k = match &this { Value::Object(o) => alloc::format!("{:?} own={}", core::mem::discriminant(&o.borrow().kind), o.borrow().props.contains_key(name)), _ => String::from("?") }; crate::warn!("[TMPRS] proto_call {} {} this={}", name, t, k); } } // TMPRS
1113    if let Some(idx) = this_dom_idx(&this) {
1114        // 組込みだけを引く(昇格したカスタム要素では get_property がこの関数自身を返す)。
1115        let f = it.dom_builtin_get(idx, name);
1116        return it.call_value(&f, this, a);
1117    }
1118    if let Value::Object(o) = &this {
1119        let own = o.borrow().props.get(name).cloned();
1120        if let Some(f @ Value::Object(_)) = own {
1121            return it.call_value(&f, this, a);
1122        }
1123    }
1124    match name {
1125        "addEventListener" => event_target_add_event_listener(it, this, a),
1126        "removeEventListener" => event_target_remove_event_listener(it, this, a),
1127        _ => event_target_dispatch_event(it, this, a),
1128    }
1129}
1130pub(crate) fn event_target_proto_add(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1131    event_target_proto_call(it, this, a, "addEventListener")
1132}
1133pub(crate) fn event_target_proto_remove(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1134    event_target_proto_call(it, this, a, "removeEventListener")
1135}
1136pub(crate) fn event_target_proto_dispatch(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1137    event_target_proto_call(it, this, a, "dispatchEvent")
1138}