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 Some(listeners) = event_target_listeners(&this) else { return Ok(Value::Undefined) };
715    let etype = arg(a, 0).to_js_string();
716    let cb = arg(a, 1);
717    let (capture, once) = parse_listener_opts(&arg(a, 2));
718    let signal = parse_listener_signal(&arg(a, 2));
719    if signal.as_ref().is_some_and(super::super::dom_bridge::is_signal_aborted) {
720        return Ok(Value::Undefined);
721    }
722    let entry = Value::Object(Obj::array(alloc::vec![
723        cb.clone(),
724        Value::Bool(once),
725        signal.unwrap_or(Value::Undefined),
726        Value::Bool(capture)
727    ]));
728    let existing = listeners.borrow().props.get(&etype).cloned();
729    match existing {
730        Some(Value::Object(arr)) => {
731            if let ObjKind::Array(items) = &mut arr.borrow_mut().kind {
732                // 重複登録の判定は仕様どおり (listener, capture) の組で行う。
733                // `capture` を見ずに `cb` だけで重複判定していたため、同じ
734                // 関数を `{capture:true}`/`{capture:false}` で2回登録する
735                // (capture/bubble 両フェーズで拾いたい定番パターン)と、
736                // 2回目の登録が誤って「重複」扱いされ黙って無視されていた
737                // バグ(`document`のcapture対応〔2026-07-16〕で`capture`を
738                // 保持するようになったことで顕在化・同日発見・修正)。
739                let dup = items.iter().any(|v| {
740                    match (&event_target_listener_cb(v), &cb) {
741                        (Value::Object(x), Value::Object(y)) => {
742                            Rc::ptr_eq(x, y) && event_target_listener_capture(v) == capture
743                        }
744                        _ => false,
745                    }
746                });
747                if !dup {
748                    items.push(entry);
749                }
750            }
751        }
752        _ => {
753            listeners
754                .borrow_mut()
755                .props
756                .insert(etype, Value::Object(Obj::array(alloc::vec![entry])));
757        }
758    }
759    Ok(Value::Undefined)
760}
761/// `target.removeEventListener(type, fn, options)`。仕様どおり `(listener,
762/// capture)` の組が一致するエントリのみを取り除く。以前は `capture` を
763/// 一切見ず `cb` のみで判定していたため、同じ関数を `{capture:true}`/
764/// `{capture:false}` で2回登録した場合に `removeEventListener('type', fn)`
765/// (`capture` 省略=既定 `false`)を呼ぶと両方まとめて消えてしまい、
766/// capture 側だけ残す意図が達成できないバグだった(`document`のcapture
767/// 対応〔2026-07-16〕で`capture`をエントリへ保持するようになったことで
768/// 顕在化・同日発見・修正)。
769pub(crate) fn event_target_remove_event_listener(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
770    let Some(listeners) = event_target_listeners(&this) else { return Ok(Value::Undefined) };
771    let etype = arg(a, 0).to_js_string();
772    let cb = arg(a, 1);
773    let (capture, _once) = parse_listener_opts(&arg(a, 2));
774    if let Some(Value::Object(arr)) = listeners.borrow().props.get(&etype).cloned() {
775        if let ObjKind::Array(items) = &mut arr.borrow_mut().kind {
776            items.retain(|v| match (&event_target_listener_cb(v), &cb) {
777                (Value::Object(x), Value::Object(y)) => {
778                    !(Rc::ptr_eq(x, y) && event_target_listener_capture(v) == capture)
779                }
780                _ => true,
781            });
782        }
783    }
784    Ok(Value::Undefined)
785}
786/// `target.dispatchEvent(event)`。仕様どおり `!event.defaultPrevented` を返す。
787pub(crate) fn event_target_dispatch_event(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
788    let ev = arg(a, 0);
789    if let Some(listeners) = event_target_listeners(&this) {
790        let etype = if let Value::Object(eo) = &ev {
791            eo.borrow().props.get("type").map(|v| v.to_js_string()).unwrap_or_default()
792        } else {
793            String::new()
794        };
795        let cbs = match listeners.borrow().props.get(&etype) {
796            Some(Value::Object(arr)) => match &arr.borrow().kind {
797                ObjKind::Array(items) => items.clone(),
798                _ => alloc::vec::Vec::new(),
799            },
800            _ => alloc::vec::Vec::new(),
801        };
802        if let Value::Object(eo) = &ev {
803            let mut e = eo.borrow_mut();
804            e.props.insert("target".into(), this.clone());
805            // レガシー DOM Level 0 の `srcElement`(`target` の別名)が丸ごと
806            // 未対応だった。
807            e.props.insert("srcElement".into(), this.clone());
808            // `currentTarget`/`eventPhase` が丸ごと未設定だったバグの修正。
809            // `EventTarget` は DOM ツリーを持たず伝播フェーズが無いため、
810            // リスナー呼び出し中は常に AT_TARGET(2) とする。
811            e.props.insert("currentTarget".into(), this.clone());
812            e.props.insert("eventPhase".into(), Value::Number(2.0));
813        }
814        for entry in cbs {
815            if event_target_listener_aborted(&entry) {
816                continue;
817            }
818            let cb = event_target_listener_cb(&entry);
819            let once = event_target_listener_once(&entry);
820            let ctx = alloc::format!("{} listener (EventTarget)", etype);
821            it.call_listener(&cb, this.clone(), core::slice::from_ref(&ev), &ctx);
822            // `{once: true}` で登録されたリスナーは初回呼び出し後に自動解除する
823            // (以前は addEventListener の第3引数自体を読んでおらず、once が
824            // 無視されて毎回発火し続けていた)。
825            if once {
826                if let Some(Value::Object(arr)) = listeners.borrow().props.get(&etype).cloned() {
827                    if let ObjKind::Array(items) = &mut arr.borrow_mut().kind {
828                        items.retain(|v| match (&event_target_listener_cb(v), &cb) {
829                            (Value::Object(x), Value::Object(y)) => !Rc::ptr_eq(x, y),
830                            _ => true,
831                        });
832                    }
833                }
834            }
835            // `stopImmediatePropagation()` が呼ばれたら残りのリスナーを止める
836            // (以前はこのメソッド自体が存在せず、呼び出すと TypeError になり
837            // 実質チェックしようがなかった)。
838            let stopped = if let Value::Object(eo) = &ev {
839                matches!(eo.borrow().props.get("_stopImmediate"), Some(Value::Bool(true)))
840            } else {
841                false
842            };
843            if stopped {
844                break;
845            }
846        }
847        // 発火完了後は仕様どおり `currentTarget: null` / `eventPhase: NONE(0)`
848        // へ戻す(`target` はそのまま残す:発火後の `event.target` 参照用)。
849        if let Value::Object(eo) = &ev {
850            let mut e = eo.borrow_mut();
851            e.props.insert("currentTarget".into(), Value::Null);
852            e.props.insert("eventPhase".into(), Value::Number(0.0));
853        }
854    }
855    let default_prevented = if let Value::Object(eo) = &ev {
856        matches!(eo.borrow().props.get("defaultPrevented"), Some(Value::Bool(true)))
857    } else {
858        false
859    };
860    Ok(Value::Bool(!default_prevented))
861}
862
863// ===== MessageChannel/MessagePort(丸ごと未対応だった。`new MessageChannel()`
864// の `.port1`/`.port2` はそれぞれ EventTarget を土台にした MessagePort で、
865// 一方に `postMessage(data)` すると他方へ `message` イベントとして同期配送
866// される。Worker 間通信の標準的な確立手段)=====
867pub(crate) fn message_channel_ctor(it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
868    let port1 = message_port_new(it)?;
869    let port2 = message_port_new(it)?;
870    if let (Value::Object(p1), Value::Object(p2)) = (&port1, &port2) {
871        p1.borrow_mut().props.insert("_peer".into(), port2.clone());
872        p2.borrow_mut().props.insert("_peer".into(), port1.clone());
873    }
874    let ch = Obj::plain();
875    {
876        let mut b = ch.borrow_mut();
877        b.props.insert("port1".into(), port1);
878        b.props.insert("port2".into(), port2);
879    }
880    Ok(Value::Object(ch))
881}
882pub(crate) fn message_port_new(it: &mut Interp) -> Result<Value, Value> {
883    let port = event_target_ctor(it, Value::Undefined, &[])?;
884    if let Value::Object(o) = &port {
885        let mut b = o.borrow_mut();
886        b.props
887            .insert("postMessage".into(), nv("postMessage", message_port_post_message));
888        b.props.insert("start".into(), nv("start", dom_noop));
889        b.props.insert("close".into(), nv("close", message_port_close));
890    }
891    Ok(port)
892}
893pub(crate) fn message_port_is_closed(port: &Value) -> bool {
894    matches!(port, Value::Object(o) if o.borrow().props.get("_closed").map(|v| v.truthy()).unwrap_or(false))
895}
896/// `port.postMessage(data)`。`structuredClone` と同じアルゴリズムでデータを
897/// 複製してから相手側ポートへ同期配送する(`close()` 済みのポートからは
898/// 送信しても無視する)。
899pub(crate) fn message_port_post_message(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
900    if message_port_is_closed(&this) {
901        return Ok(Value::Undefined);
902    }
903    let data = structured_clone(it, Value::Undefined, a)?;
904    let peer = if let Value::Object(o) = &this {
905        o.borrow().props.get("_peer").cloned()
906    } else {
907        None
908    };
909    if let Some(peer) = peer {
910        message_port_deliver(it, &peer, data);
911    }
912    Ok(Value::Undefined)
913}
914/// 相手ポートへ `message` イベントを配送する。`port.onmessage = fn`(単一
915/// リスナ方式)と `port.addEventListener('message', fn)`(複数リスナ方式)
916/// の両方をサポートする(他の EventTarget 風オブジェクトと同じ二重経路)。
917pub(crate) fn message_port_deliver(it: &mut Interp, port: &Value, data: Value) {
918    if message_port_is_closed(port) {
919        return;
920    }
921    let ev = Obj::plain();
922    ev.borrow_mut().props.insert("type".into(), Value::str("message"));
923    ev.borrow_mut().props.insert("data".into(), data);
924    let onmessage = if let Value::Object(o) = port {
925        o.borrow().props.get("onmessage").cloned()
926    } else {
927        None
928    };
929    if let Some(f) = onmessage.and_then(callable_or_none) {
930        it.call_listener(
931            &f,
932            port.clone(),
933            &[Value::Object(ev.clone())],
934            "MessagePort onmessage",
935        );
936    }
937    let _ = event_target_dispatch_event(it, port.clone(), &[Value::Object(ev)]);
938}
939pub(crate) fn message_port_close(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
940    if let Value::Object(o) = &this {
941        o.borrow_mut().props.insert("_closed".into(), Value::Bool(true));
942    }
943    Ok(Value::Undefined)
944}
945
946// ===== BroadcastChannel(丸ごと未対応だった。`new BroadcastChannel(name)` で
947// 同名チャネルを持つ他インスタンス全てへブロードキャストする。`MessagePort` と
948// 同じ配送ロジック(`message_port_deliver`/`message_port_close`)を再利用し、
949// 同名インスタンス一覧はグローバル隠しプロパティ `__bc_registry` の
950// `{name: [channel, ...]}` で管理する)=====
951pub(crate) fn bc_registry(it: &mut Interp) -> super::super::value::ObjRef {
952    let existing = it.global.borrow().vars.get("__bc_registry").cloned();
953    if let Some(Value::Object(o)) = existing {
954        return o;
955    }
956    let reg = Obj::plain();
957    it.global
958        .borrow_mut()
959        .vars
960        .insert("__bc_registry".into(), Value::Object(reg.clone()));
961    reg
962}
963pub(crate) fn broadcast_channel_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
964    let name = arg(a, 0).to_js_string();
965    let base = event_target_ctor(it, Value::Undefined, &[])?;
966    if let Value::Object(o) = &base {
967        let mut b = o.borrow_mut();
968        b.props.insert("name".into(), Value::str(&name));
969        b.props
970            .insert("postMessage".into(), nv("postMessage", broadcast_channel_post_message));
971        b.props.insert("close".into(), nv("close", message_port_close));
972    }
973    let reg = bc_registry(it);
974    let existing = reg.borrow().props.get(&name).cloned();
975    match existing {
976        Some(Value::Object(arr)) => {
977            if let ObjKind::Array(items) = &mut arr.borrow_mut().kind {
978                items.push(base.clone());
979            }
980        }
981        _ => {
982            reg.borrow_mut()
983                .props
984                .insert(name, Value::Object(Obj::array(alloc::vec![base.clone()])));
985        }
986    }
987    Ok(base)
988}
989/// `channel.postMessage(data)`。仕様どおり送信元自身へは配送しない
990/// (同名の他インスタンス全てへ `message_port_deliver` で配送する)。
991pub(crate) fn broadcast_channel_post_message(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
992    if message_port_is_closed(&this) {
993        return Ok(Value::Undefined);
994    }
995    let name = if let Value::Object(o) = &this {
996        o.borrow().props.get("name").map(|v| v.to_js_string()).unwrap_or_default()
997    } else {
998        String::new()
999    };
1000    let data = structured_clone(it, Value::Undefined, a)?;
1001    let reg = bc_registry(it);
1002    let peers: Vec<Value> = match reg.borrow().props.get(&name) {
1003        Some(Value::Object(arr)) => match &arr.borrow().kind {
1004            ObjKind::Array(items) => items.clone(),
1005            _ => alloc::vec::Vec::new(),
1006        },
1007        _ => alloc::vec::Vec::new(),
1008    };
1009    for peer in peers {
1010        let is_self = matches!((&peer, &this), (Value::Object(p), Value::Object(t)) if Rc::ptr_eq(p, t));
1011        if is_self {
1012            continue;
1013        }
1014        message_port_deliver(it, &peer, data.clone());
1015    }
1016    Ok(Value::Undefined)
1017}
1018
1019/// `new CommandEvent(type, options)`(Invoker Commands API / WHATWG HTML Standard。
1020/// `<button command="..." commandfor="...">` のアクション時に発火するイベント。
1021/// `command`/`source` をプロパティとして保持する)。
1022pub(crate) fn command_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1023    let ev = event_ctor(it, t, a)?;
1024    let options = arg(a, 1);
1025    if let Value::Object(o) = &ev {
1026        let mut b = o.borrow_mut();
1027        b.props
1028            .insert("command".into(), obj_prop(&options, "command").unwrap_or_else(|| Value::str("")));
1029        b.props
1030            .insert("source".into(), obj_prop(&options, "source").unwrap_or(Value::Null));
1031    }
1032    Ok(ev)
1033}
1034
1035/// `new ToggleEvent(type, options)`(WHATWG HTML Standard。`<details>` や `popover` 等の
1036/// トグル状態で発火するイベント。`oldState`/`newState` をプロパティとして保持する)。
1037pub(crate) fn toggle_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1038    let ev = event_ctor(it, t, a)?;
1039    let options = arg(a, 1);
1040    if let Value::Object(o) = &ev {
1041        let mut b = o.borrow_mut();
1042        b.props.insert(
1043            "oldState".into(),
1044            obj_prop(&options, "oldState").unwrap_or_else(|| Value::str("closed")),
1045        );
1046        b.props.insert(
1047            "newState".into(),
1048            obj_prop(&options, "newState").unwrap_or_else(|| Value::str("open")),
1049        );
1050    }
1051    Ok(ev)
1052}
1053
1054/// `new FormDataEvent(type, options)`(WHATWG HTML Standard。`<form>` 発火の `formdata`
1055/// イベント向け。`formData` をプロパティとして保持する)。
1056pub(crate) fn form_data_event_ctor(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1057    let ev = event_ctor(it, t, a)?;
1058    let options = arg(a, 1);
1059    if let Value::Object(o) = &ev {
1060        let mut b = o.borrow_mut();
1061        b.props
1062            .insert("formData".into(), obj_prop(&options, "formData").unwrap_or(Value::Null));
1063    }
1064    Ok(ev)
1065}
1066