Skip to main content

atmos/os_lib/js/interp/
calls.rs

1// 分割: interp.rs の `impl Interp` から機械的に移動(2026-07-16 リファクタ フェーズ5)。
2// ロジック不変。メソッド可視性のみ pub(crate) へ昇格(別モジュールの impl Interp
3// からの self 呼び出しのため)。impl ブロックは型 Interp に自動で結合する。
4use super::*;
5
6/// 診断用の関数名(`name`、関数定義の名前、`a.b=function(){}` の `~b`、無ければ `<anonymous>`)。
7pub(crate) fn func_diag_name(func: &Value) -> String {
8    match func {
9        Value::Object(o) => {
10            let b = o.borrow();
11            b.props
12                .get("name")
13                .map(|v| v.to_js_string())
14                .filter(|n| !n.is_empty())
15                .or_else(|| match &b.kind {
16                    ObjKind::Function(fd) if !fd.name.is_empty() => Some(fd.name.clone()),
17                    _ => None,
18                })
19                .or_else(|| b.props.get("_diag_name").map(|v| alloc::format!("~{}", v.to_js_string())))
20                .unwrap_or_else(|| String::from("<anonymous>"))
21        }
22        _ => String::from("<anonymous>"),
23    }
24}
25
26impl Interp {
27    pub(crate) fn eval_call(
28        &mut self,
29        callee: &Expression,
30        arguments: &[Expression],
31        optional: bool,
32        scope: &Rc<RefCell<Scope>>,
33        this: &Value,
34    ) -> EvalResult {
35        // super(...): 基底クラスのコンストラクタを現在の this で呼ぶ。
36        if matches!(callee, Expression::Super) {
37            let sc = self.super_ctor(scope)?;
38            let args = self.eval_arguments(arguments, scope, this)?;
39            return self.call_value(&sc, this.clone(), &args);
40        }
41        // super.method(...): 基底プロトタイプのメソッドを現在の this で呼ぶ。
42        if let Expression::Member {
43            object, property, ..
44        } = callee
45        {
46            if matches!(**object, Expression::Super) {
47                let sp = self.super_proto(scope)?;
48                let f = self.get_property(&sp, property)?;
49                let args = self.eval_arguments(arguments, scope, this)?;
50                return self.call_value(&f, this.clone(), &args);
51            }
52        }
53        // this の決定: メンバ/添字呼出なら receiver。オブジェクト側のオプショナルチェイン
54        // (`a?.b.c()` 等)の短絡は `resolve_callee` 内で検知される。
55        let (func, call_this, broke) = self.resolve_callee(callee, scope, this)?;
56        if broke {
57            return Ok(Value::Undefined);
58        }
59        if optional && matches!(func, Value::Undefined | Value::Null) {
60            return Ok(Value::Undefined);
61        }
62        let args = self.eval_arguments(arguments, scope, this)?;
63        // 【2026-10-16】呼べない値なら、呼び出し式を添えた TypeError にする(V8 と同じ
64        // `a.b is not a function`)。従来は `call_value_inner` の「not a function: object」
65        // だけで、どの呼び出しか分からず実サイトの JS_ERR を追えなかった。
66        let callable = match &func {
67            Value::Object(o) => o.borrow().is_callable(),
68            _ => false,
69        };
70        if !callable {
71            let desc = super::properties::describe_expr(callee);
72            return Err(self.type_error(alloc::format!("{} is not a function", desc)));
73        }
74        self.call_value(&func, call_this, &args)
75    }
76
77    /// `Call` の callee を this 束縛込みで解決する(`super`/`super.method` は呼出元
78    /// `eval_call` で先に処理済みのためここには来ない)。戻り値は
79    /// `(関数, call_this, 途中で ?. 短絡したか)`。`object`/`callee` 側は
80    /// `eval_chain_object` を再帰的に使うため、`a?.b.c()` のように途中で短絡した場合は
81    /// 以降のプロパティアクセス/引数評価/呼出を一切行わずチェーン全体が `undefined` になる
82    /// (ECMA-262 の OptionalChain セマンティクス。以前は最初の `?.` の直近1段しか
83    /// 短絡せず、後続のアクセスで `TypeError` になっていた)。
84    pub(crate) fn resolve_callee(
85        &mut self,
86        callee: &Expression,
87        scope: &Rc<RefCell<Scope>>,
88        this: &Value,
89    ) -> Result<(Value, Value, bool), Value> {
90        match callee {
91            Expression::Member {
92                object,
93                property,
94                optional: mopt,
95            } if !matches!(**object, Expression::Super) => {
96                let (obj, broke) = self.eval_chain_object(object, scope, this)?;
97                if broke {
98                    return Ok((Value::Undefined, Value::Undefined, true));
99                }
100                if *mopt && matches!(obj, Value::Undefined | Value::Null) {
101                    return Ok((Value::Undefined, Value::Undefined, true));
102                }
103                let f = self.get_property_of_expr(&obj, property, object)?;
104                Ok((f, obj, false))
105            }
106            Expression::Index {
107                object,
108                index,
109                optional: iopt,
110            } => {
111                let (obj, broke) = self.eval_chain_object(object, scope, this)?;
112                if broke {
113                    return Ok((Value::Undefined, Value::Undefined, true));
114                }
115                if *iopt && matches!(obj, Value::Undefined | Value::Null) {
116                    return Ok((Value::Undefined, Value::Undefined, true));
117                }
118                let key = self.eval(index, scope, this)?;
119                let f = self.get_property(&obj, &to_property_key(&key))?;
120                Ok((f, obj, false))
121            }
122            _ => {
123                let (f, broke) = self.eval_chain_object(callee, scope, this)?;
124                Ok((f, Value::Undefined, broke))
125            }
126        }
127    }
128
129    /// `Member`/`Index`/`Call` の「object/callee」位置を短絡込みで評価する。戻り値の
130    /// `bool` は「このサブチェーンのどこかで `?.` が短絡したか」。短絡時、値は常に
131    /// `Value::Undefined`。グローバルな可変フラグではなく戻り値だけで伝播するため、
132    /// 無関係な式の評価に短絡状態が漏れることが無い。`super`/`super.method` を含む
133    /// `Call` は短絡セマンティクス対象外のため既存の `eval_call` にそのまま委譲する。
134    pub(crate) fn eval_chain_object(
135        &mut self,
136        expr: &Expression,
137        scope: &Rc<RefCell<Scope>>,
138        this: &Value,
139    ) -> Result<(Value, bool), Value> {
140        match expr {
141            Expression::Member {
142                object,
143                property,
144                optional,
145            } => {
146                if matches!(**object, Expression::Super) {
147                    let sp = self.super_proto(scope)?;
148                    return Ok((self.get_property(&sp, property)?, false));
149                }
150                let (obj, broke) = self.eval_chain_object(object, scope, this)?;
151                if broke {
152                    return Ok((Value::Undefined, true));
153                }
154                if *optional && matches!(obj, Value::Undefined | Value::Null) {
155                    return Ok((Value::Undefined, true));
156                }
157                Ok((self.get_property(&obj, property)?, false))
158            }
159            Expression::Index {
160                object,
161                index,
162                optional,
163            } => {
164                let (obj, broke) = self.eval_chain_object(object, scope, this)?;
165                if broke {
166                    return Ok((Value::Undefined, true));
167                }
168                if *optional && matches!(obj, Value::Undefined | Value::Null) {
169                    return Ok((Value::Undefined, true));
170                }
171                let key = self.eval(index, scope, this)?;
172                Ok((self.get_property(&obj, &to_property_key(&key))?, false))
173            }
174            Expression::Call {
175                callee,
176                arguments,
177                optional,
178            } => {
179                let involves_super = matches!(**callee, Expression::Super)
180                    || matches!(&**callee, Expression::Member { object, .. } if matches!(**object, Expression::Super));
181                if involves_super {
182                    return Ok((self.eval(expr, scope, this)?, false));
183                }
184                let (func, call_this, broke) = self.resolve_callee(callee, scope, this)?;
185                if broke {
186                    return Ok((Value::Undefined, true));
187                }
188                if *optional && matches!(func, Value::Undefined | Value::Null) {
189                    return Ok((Value::Undefined, true));
190                }
191                let args = self.eval_arguments(arguments, scope, this)?;
192                Ok((self.call_value(&func, call_this, &args)?, false))
193            }
194            _ => Ok((self.eval(expr, scope, this)?, false)),
195        }
196    }
197
198    /// 呼出引数を評価し、スプレッド `...x` を展開した Vec を返す。
199    pub(crate) fn eval_arguments(
200        &mut self,
201        arguments: &[Expression],
202        scope: &Rc<RefCell<Scope>>,
203        this: &Value,
204    ) -> Result<Vec<Value>, Value> {
205        let mut args = Vec::with_capacity(arguments.len());
206        for a in arguments {
207            if let Expression::Spread(inner) = a {
208                let v = self.eval(inner, scope, this)?;
209                let items = self.iter_to_vec(&v);
210                args.extend(items);
211            } else {
212                args.push(self.eval(a, scope, this)?);
213            }
214        }
215        Ok(args)
216    }
217
218    pub(crate) fn eval_new(
219        &mut self,
220        callee: &Expression,
221        arguments: &[Expression],
222        scope: &Rc<RefCell<Scope>>,
223        this: &Value,
224    ) -> EvalResult {
225        let func = self.eval(callee, scope, this)?;
226        let args = self.eval_arguments(arguments, scope, this)?;
227        // Proxy: construct トラップがあれば呼ぶ。無ければ target を new する。
228        let proxy = match &func {
229            Value::Object(o) => match &o.borrow().kind {
230                ObjKind::Proxy { target, handler } => Some((target.clone(), handler.clone())),
231                _ => None,
232            },
233            _ => None,
234        };
235        if let Some((target, handler)) = proxy {
236            let trap = handler.borrow().props.get("construct").cloned();
237            if let Some(trap) =
238                trap.filter(|t| matches!(t, Value::Object(f) if f.borrow().is_callable()))
239            {
240                let arg_arr = Value::Object(Obj::array(args.clone()));
241                let targs = [
242                    Value::Object(target),
243                    arg_arr,
244                    Value::Object(handler.clone()),
245                ];
246                return self.call_value(&trap, Value::Object(handler), &targs);
247            }
248            return self.construct_object(&Value::Object(target), &args);
249        }
250        self.construct_object(&func, &args)
251    }
252
253    /// `new` の本体: インスタンスを生成しコンストラクタを呼ぶ。
254    pub fn construct_value(&mut self, func: &Value, args: &[Value]) -> EvalResult {
255        self.construct_object(func, args)
256    }
257
258    pub(crate) fn construct_object(&mut self, func: &Value, args: &[Value]) -> EvalResult {
259        self.construct_with_target(func, args, func)
260    }
261
262    /// `new` / `Reflect.construct(func, args, new_target)`。インスタンスの原型は
263    /// `new_target.prototype`(仕様の OrdinaryCreateFromConstructor)。
264    pub(crate) fn construct_with_target(&mut self, func: &Value, args: &[Value], new_target: &Value) -> EvalResult {
265        // 束縛済み関数の `new` は、束縛した this を無視し、束縛引数を前に足して元の関数を
266        // `new` する(仕様の [[Construct]])。【2026-10-17】従来は束縛関数自身(prototype
267        // 無し)からインスタンスを作り、元の関数を束縛 this(null)で呼んでいたため、
268        // ES5 の定番 `new (Function.prototype.bind.apply(C, [null].concat(args)))` が
269        // メソッドの無いオブジェクトを返していた(YouTube の DI コンテナ)。
270        if let Value::Object(fo) = func {
271            let bound = match &fo.borrow().kind {
272                ObjKind::Bound { target, bound_args, .. } => Some((target.clone(), bound_args.clone())),
273                _ => None,
274            };
275            if let Some((target, mut all)) = bound {
276                all.extend_from_slice(args);
277                let nt = match (new_target, func) {
278                    (Value::Object(a), Value::Object(b)) if Rc::ptr_eq(a, b) => target.clone(),
279                    _ => new_target.clone(),
280                };
281                return self.construct_with_target(&target, &all, &nt);
282            }
283        }
284        // カスタム要素のクラス(`HTMLElement` を継承)なら、this は DOM 要素のハンドル。
285        if let Some(proto) = self.element_class_proto(new_target) {
286            let (idx, fresh) = self.ce_construct_target(new_target, proto)?;
287            let inst = Value::Object(Obj::dom(idx));
288            if fresh {
289                self.ce_stack.push((idx, new_target.clone()));
290            }
291            self.pending_new_target = Some(new_target.clone());
292            let ret = self.call_value(func, inst.clone(), args);
293            if fresh {
294                self.ce_stack.pop();
295            }
296            return match ret? {
297                r @ Value::Object(_) => Ok(r),
298                _ => Ok(inst),
299            };
300        }
301        // インスタンスを生成し、コンストラクタの prototype を proto に設定(メソッド解決用)。
302        let inst_obj = Obj::plain();
303        if let Value::Object(fo) = new_target {
304            if let Some(Value::Object(p)) = fo.borrow().props.get("prototype") {
305                inst_obj.borrow_mut().proto = Some(p.clone());
306            }
307        }
308        let inst = Value::Object(inst_obj);
309        // `new.target` はこの呼び出し先関数(コンストラクタ自身)。`call_value_inner` が
310        // 消費してスタックへ push する(`super(...)` は construct_object を介さず直接
311        // call_value を呼ぶため、基底クラスのコンストラクタ内では `new.target` が
312        // undefined になる既知の簡略化。実用上最も一般的な「コンストラクタ先頭での
313        // `new.target` チェック」は正しく動作する)。
314        self.pending_new_target = Some(new_target.clone());
315        let ret = self.call_value(func, inst.clone(), args)?;
316        match ret {
317            Value::Object(_) => Ok(ret),
318            _ => Ok(inst),
319        }
320    }
321
322    /// 関数値を呼び出す。
323    pub fn call_value(&mut self, func: &Value, this: Value, args: &[Value]) -> EvalResult {
324        self.depth += 1;
325        // 呼び出しが深すぎる(回数・ネイティブスタックの使用量)なら、ブラウザと同じく捕捉可能な
326        // RangeError を投げる。【2026-10-17】従来は回数上限 400 でスクリプト全体を打ち切っており、
327        // スタック使用量も見ていなかった(起動スレッドの 64 KiB スタックがあふれて BSS を壊した)。
328        let sp = super::current_sp();
329        if sp < self.stack_limit.0.saturating_sub(super::JS_STACK_MARGIN) || sp > self.stack_limit.1 {
330            self.stack_limit = super::js_stack_window();
331        }
332        let used = self.stack_limit.1.saturating_sub(sp);
333        if self.depth > self.max_depth || sp < self.stack_limit.0 {
334            static N: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
335            if N.fetch_add(1, core::sync::atomic::Ordering::Relaxed) < 5 {
336                let top: Vec<&str> = self.call_names.iter().rev().take(16).map(|s| s.as_str()).collect();
337                crate::warn!(
338                    "[JSPROF] call stack exceeded thread={:?} depth={} stack={}KB (steps={}) top={:?}",
339                    crate::kernel::scheduler::get_current_process_name(),
340                    self.depth,
341                    used / 1024,
342                    self.steps,
343                    top
344                );
345            }
346            self.depth -= 1;
347            return Err(self.range_error("Maximum call stack size exceeded"));
348        }
349        // 例外の発生箇所を示すため、呼び出し中の関数名を積む(深さ上限と同じく有限)。
350        let name = func_diag_name(func);
351        self.call_names.push(name);
352        self.tmp_bodies.push(match func { Value::Object(o) => match &o.borrow().kind { ObjKind::Function(fd) => Some(fd.body.clone()), _ => None }, _ => None }); // TMPAPPLY
353        // 循環ゴミの回収(実行の途中でも安全。`cycle_gc` 参照)。
354        if crate::os_lib::js::cycle_gc::should_collect() {
355            let _ = crate::os_lib::js::cycle_gc::maybe_collect();
356        }
357        let result = self.call_value_inner(func, this, args);
358        self.process_ce_reactions();
359        self.call_names.pop();
360        self.tmp_bodies.pop(); // TMPAPPLY
361        self.depth -= 1;
362        result
363    }
364
365    /// イベントリスナ・タイマコールバック・オブザーバ通知など、
366    /// **呼び出し元へ例外を返せない場所**からの関数呼び出し。
367    ///
368    /// 【2026-07-28】これらは従来 `let _ = self.call_value(...)` と書かれており、
369    /// ハンドラ内で投げられた例外はコンソールにもログにも現れず完全に消えていた
370    /// (「ボタンを押しても何も起きない」だけが症状として残る)。ブラウザ同様、
371    /// 捕捉されなかった例外は必ず報告する。
372    pub fn call_listener(
373        &mut self,
374        func: &Value,
375        this: Value,
376        args: &[Value],
377        context: &str,
378    ) -> bool {
379        match self.call_value(func, this, args) {
380            Ok(_) => true,
381            Err(thrown) => {
382                self.report_uncaught(context, &thrown);
383                false
384            }
385        }
386    }
387
388    /// 捕捉されなかった例外を、ページのコンソール出力とシリアルログの両方へ出す。
389    pub fn report_uncaught(&mut self, context: &str, thrown: &Value) {
390        let mut repr = thrown.to_js_string();
391        // 【2026-10-18】Error 以外のオブジェクト(`[object Object]`)は中身が分からないので、
392        // プロパティを数段まで添える(YouTube の `_.oj` は原因を `args[0]` に入れる)。
393        if repr == "[object Object]" {
394            repr = alloc::format!("[object Object] {}", describe_thrown(thrown, 3));
395        }
396        let line = crate::os_lib::js::uncaught::format_uncaught(context, &repr);
397        crate::warn!("[JS] {}", line);
398        self.out.push_str(&line);
399        self.out.push('\n');
400    }
401
402    /// element.dispatchEvent から呼ぶ Interp 内ディスパッチ。JsRuntime 版と同じ
403    /// capture→target→bubble の3フェーズ・once・stopPropagation を再現するが、
404    /// リスナは self.call_value で同一 Interp 上で実行する。戻り値: (発火したか, defaultPrevented)。
405    pub fn dispatch_event_in_interp(
406        &mut self,
407        node_idx: usize,
408        event_type: &str,
409        extra: &[(String, Value)],
410    ) -> (bool, bool) {
411        // 伝播パス(target→祖先)。
412        let path = {
413            let dom = self.dom.borrow();
414            let mut path = alloc::vec![node_idx];
415            let mut cur = node_idx;
416            let mut guard = 0;
417            while let Some(p) = dom.nodes.get(cur).and_then(|n| n.parent) {
418                path.push(p);
419                cur = p;
420                guard += 1;
421                if guard > dom.nodes.len() {
422                    break;
423                }
424            }
425            path
426        };
427        let any = path
428            .iter()
429            .any(|&n| self.dom.borrow().has_listener_on(n, event_type));
430        // `document.addEventListener('click', fn)` のみが登録され、経路上の
431        // どの DOM ノードにも直接のリスナが無いケース(要素側リスナ0件の
432        // 純粋なイベント委譲)を早期 return で取りこぼさないよう、document
433        // 側の汎用リスナ(`_listeners`)の有無も合わせて確認する(2026-07-16
434        // 発見・実装。以前は `any` が false ならここで即座に `(false, false)`
435        // を返しており、後段の document リスナ発火コードへ一切到達しなかった)。
436        let doc_has_listener = {
437            let doc_opt = self.global.borrow().vars.get("document").cloned();
438            doc_opt
439                .and_then(|d| super::super::builtins::event_target_listeners(&d))
440                .and_then(|listeners| listeners.borrow().props.get(event_type).cloned())
441                .is_some_and(|arr| match arr {
442                    Value::Object(o) => matches!(&o.borrow().kind, ObjKind::Array(items) if !items.is_empty()),
443                    _ => false,
444                })
445        };
446        let on_attr_name = alloc::format!("on{}", event_type);
447        // 【2026-07-23 発見・修正】`Obj::dom(n)`は呼び出す度に新規生成される使い捨ての
448        // ラッパーオブジェクトのため、`element.onchange = fn`で書き込まれた関数値は
449        // `dom_set_property`が書いた先の`Obj::dom(n)`インスタンスが破棄された瞬間に
450        // 失われ、ここで`obj_prop`経由で新規生成した別インスタンスを読んでも常に
451        // 見つからなかった(=ハンドラ関数値が丸ごと発火しない静かなバグ)。
452        // `DomBridge::on_handlers`(`listeners`と同じくDOM構造体側の永続ストレージ)
453        // から読む。
454        let has_on_handler = path.iter().any(|&n| {
455            if self.dom.borrow().get_on_handler(n, &on_attr_name).is_some() {
456                return true;
457            }
458            self.dom.borrow().get_attr(n, &on_attr_name).is_some()
459        });
460        if !any && !doc_has_listener && !has_on_handler {
461            return (false, false);
462        }
463
464        // `bubbles` を `extra` から読む(`Event`/`CustomEvent` から転送された実値。
465        // 無ければ内部発火イベント向けの既定値として `true` を使う)。以前はここを
466        // 一切見ずバブリング段階を常に無条件で全祖先まで辿っていたため、
467        // `new CustomEvent('x', {bubbles: false})` を dispatch しても祖先の
468        // リスナーが発火してしまう仕様違反バグだった(capture 段階は仕様上
469        // `bubbles` に関わらず常に祖先を辿るため、そちらは変更しない)。
470        let bubbles = extra
471            .iter()
472            .find(|(k, _)| k == "bubbles")
473            .map(|(_, v)| v.truthy())
474            .unwrap_or(true);
475        let composed: Vec<Value> = path.iter().map(|&n| Value::Object(Obj::dom(n))).collect();
476        let target = Value::Object(Obj::dom(node_idx));
477        let ev = Obj::plain();
478        {
479            let mut e = ev.borrow_mut();
480            e.props.insert(String::from("type"), Value::str(event_type));
481            e.props.insert(String::from("target"), target.clone());
482            e.props.insert(String::from("bubbles"), Value::Bool(true));
483            // `cancelable` が一切読まれておらず、`{cancelable: false}` で構築した
484            // イベントでも `preventDefault()` が常に効いてしまう仕様違反バグだった。
485            // 内部発火(click/submit 等)は `extra` が空のまま呼ばれ続けており、
486            // それらは「妨害可能」という既存の挙動に依存しているため、`bubbles`
487            // と同じく既定値は `true`(後段の `extra` ループが明示指定時のみ上書き)。
488            e.props.insert(String::from("cancelable"), Value::Bool(true));
489            // `isTrusted` が丸ごと未対応だった。仕様上、スクリプトから発火した
490            // イベント(`dispatchEvent()`・内部シミュレートの `.click()` 等)は
491            // 常に `false`(実ユーザー入力由来のイベントのみ `true`。この処理系に
492            // その区別自体が無いため常に `false` で問題ない)。読み取り専用の
493            // 定数のため `extra` での上書きは考慮しない。
494            e.props.insert(String::from("isTrusted"), Value::Bool(false));
495            e.props
496                .insert(String::from("eventPhase"), Value::Number(0.0));
497            e.props
498                .insert(String::from("defaultPrevented"), Value::Bool(false));
499            // `timeStamp` が丸ごと未対応で常に `undefined` だった(このエンジン
500            // 全体に存在しない機能だった。`EventTarget` 経路にも同時に追加済み)。
501            e.props.insert(
502                String::from("timeStamp"),
503                Value::Number(super::super::builtins::next_perf_timestamp()),
504            );
505            // レガシー DOM Level 0 の `returnValue`/`cancelBubble`/`srcElement`
506            // が丸ごと未対応だった。`srcElement` は `target` と同じ値の別名。
507            e.props.insert(String::from("returnValue"), Value::Bool(true));
508            e.props.insert(String::from("cancelBubble"), Value::Bool(false));
509            e.props.insert(String::from("srcElement"), target.clone());
510            e.props.insert(
511                String::from("_composedPath"),
512                Value::Object(Obj::array(composed)),
513            );
514            for (k, v) in extra {
515                e.props.insert(k.clone(), v.clone());
516            }
517            e.props.insert(
518                String::from("preventDefault"),
519                Value::Object(Obj::native("preventDefault", |_, this, _| {
520                    if let Value::Object(o) = &this {
521                        let cancelable = o
522                            .borrow()
523                            .props
524                            .get("cancelable")
525                            .map(|v| v.truthy())
526                            .unwrap_or(true);
527                        if cancelable {
528                            let mut b = o.borrow_mut();
529                            b.props
530                                .insert(String::from("defaultPrevented"), Value::Bool(true));
531                            // レガシー DOM Level 0 の `event.returnValue`
532                            // (`preventDefault()` と等価の意味を持つべきだが
533                            // 丸ごと未対応で常に `undefined` だった)を追従させる。
534                            b.props.insert(String::from("returnValue"), Value::Bool(false));
535                        }
536                    }
537                    Ok(Value::Undefined)
538                })),
539            );
540            e.props.insert(
541                String::from("stopPropagation"),
542                Value::Object(Obj::native("stopPropagation", |_, this, _| {
543                    if let Value::Object(o) = &this {
544                        let mut b = o.borrow_mut();
545                        b.props
546                            .insert(String::from("_stop"), Value::Bool(true));
547                        // レガシー DOM Level 0 の `event.cancelBubble`(同じく
548                        // 丸ごと未対応だった)を追従させる。
549                        b.props.insert(String::from("cancelBubble"), Value::Bool(true));
550                    }
551                    Ok(Value::Undefined)
552                })),
553            );
554            e.props.insert(
555                String::from("stopImmediatePropagation"),
556                Value::Object(Obj::native("stopImmediatePropagation", |_, this, _| {
557                    if let Value::Object(o) = &this {
558                        let mut b = o.borrow_mut();
559                        b.props.insert(String::from("_stop"), Value::Bool(true));
560                        b.props
561                            .insert(String::from("_stopImmediate"), Value::Bool(true));
562                        b.props.insert(String::from("cancelBubble"), Value::Bool(true));
563                    }
564                    Ok(Value::Undefined)
565                })),
566            );
567            e.props.insert(
568                String::from("composedPath"),
569                Value::Object(Obj::native("composedPath", |_, this, _| {
570                    if let Value::Object(o) = &this {
571                        if let Some(p) = o.borrow().props.get("_composedPath") {
572                            return Ok(p.clone());
573                        }
574                    }
575                    Ok(Value::Object(Obj::array(Vec::new())))
576                })),
577            );
578        }
579
580        let mut steps: Vec<(usize, bool, f64)> = Vec::new();
581        for &n in path.iter().skip(1).rev() {
582            steps.push((n, true, 1.0));
583        }
584        steps.push((node_idx, true, 2.0));
585        steps.push((node_idx, false, 2.0));
586        if bubbles {
587            for &n in path.iter().skip(1) {
588                steps.push((n, false, 3.0));
589            }
590        }
591
592        let mut fired = false;
593        let mut to_remove: Vec<u64> = Vec::new();
594        let mut propagation_stopped = false;
595        // `document`のキャプチャフェーズリスナ(`{capture:true}`)を、DOMノードの
596        // キャプチャフェーズ(`path`を祖先→target方向に辿る`steps`の`want_
597        // capture=true`群)より先に発火する。`document`は`self.dom.nodes`の
598        // 一員ではなく`path`祖先チェーンに含まれないため、キャプチャ方向では
599        // 最も外側の仮想的な祖先として扱う(仕様上キャプチャフェーズは`bubbles`
600        // に関わらず常に発火する。2026-07-16 発見・実装。詳細は
601        // `fire_document_listeners`参照)。
602        let (doc_cap_fired, doc_cap_stopped) = self.fire_document_listeners(&ev, event_type, true);
603        if doc_cap_fired {
604            fired = true;
605        }
606        if doc_cap_stopped {
607            propagation_stopped = true;
608        }
609        if !propagation_stopped {
610            'outer: for (n, want_capture, phase) in steps {
611                let listeners = self
612                    .dom
613                    .borrow()
614                    .listeners_phase(n, event_type, want_capture);
615                let on_attr_name = alloc::format!("on{}", event_type);
616                let node_this = Value::Object(Obj::dom(n));
617                // 【2026-10-18】`on<type>` のハンドラにも currentTarget/eventPhase を渡す(従来は
618                // addEventListener のリスナの直前でしか設定せず、onclick では undefined だった)。
619                {
620                    let mut e = ev.borrow_mut();
621                    e.props.insert(String::from("currentTarget"), Value::Object(Obj::dom(n)));
622                    e.props.insert(String::from("eventPhase"), Value::Number(phase));
623                }
624                if !want_capture {
625                    // 上の`has_on_handler`と同じ理由で、使い捨ての`Obj::dom(n)`ではなく
626                    // 永続ストレージ`DomBridge::on_handlers`から読む。
627                    let on_handler = self
628                        .dom
629                        .borrow()
630                        .get_on_handler(n, &on_attr_name)
631                        .filter(|v| !matches!(v, Value::Undefined | Value::Null))
632                        .or_else(|| self.dom.borrow().get_attr(n, &on_attr_name).map(Value::str));
633                    if let Some(h) = on_handler {
634                        match h {
635                            Value::Object(_) => {
636                                let ctx = alloc::format!("on{} handler", event_type);
637                                self.call_listener(
638                                    &h,
639                                    node_this.clone(),
640                                    &[Value::Object(ev.clone())],
641                                    &ctx,
642                                );
643                                fired = true;
644                            }
645                            Value::Str(code) if !code.is_empty() => {
646                                if let Err(thrown) = self.eval_source(&code) {
647                                    let ctx = alloc::format!("on{} attribute", event_type);
648                                    self.report_uncaught(&ctx, &thrown);
649                                }
650                                fired = true;
651                            }
652                            _ => {}
653                        }
654                    }
655                }
656                if listeners.is_empty() {
657                    continue;
658                }
659                {
660                    let mut e = ev.borrow_mut();
661                    e.props
662                        .insert(String::from("currentTarget"), Value::Object(Obj::dom(n)));
663                    e.props
664                        .insert(String::from("eventPhase"), Value::Number(phase));
665                }
666                let node_this = Value::Object(Obj::dom(n));
667                for (func, once, id) in listeners {
668                    let ctx = alloc::format!("{} listener", event_type);
669                    self.call_listener(
670                        &func,
671                        node_this.clone(),
672                        &[Value::Object(ev.clone())],
673                        &ctx,
674                    );
675                    fired = true;
676                    if once {
677                        to_remove.push(id);
678                    }
679                    if ev
680                        .borrow()
681                        .props
682                        .get("_stopImmediate")
683                        .map(|v| v.truthy())
684                        .unwrap_or(false)
685                    {
686                        propagation_stopped = true;
687                        break 'outer;
688                    }
689                }
690                if ev
691                    .borrow()
692                    .props
693                    .get("_stop")
694                    .map(|v| v.truthy())
695                    .unwrap_or(false)
696                {
697                    propagation_stopped = true;
698                    break 'outer;
699                }
700            }
701        }
702        if !to_remove.is_empty() {
703            let mut dom = self.dom.borrow_mut();
704            for id in to_remove {
705                dom.remove_listener_by_id(id);
706            }
707        }
708        // `document.addEventListener('click', fn)` 等の汎用イベント委譲パターン
709        // (丸ごと未対応だった。`document` は `self.dom.nodes` の一員ではないため
710        // 上記の祖先チェーン `path` に一切含まれず、`document`自身の`_listeners`
711        // (`document_add_event_listener`が書き込む先)は従来一切読まれていな
712        // かった。実DOMイベントのバブリングがトップまで到達し `stopPropagation()`
713        // で止められていなければ、最後に `document` を仮想的な最終祖先として
714        // 扱い、対応する(非capture)汎用リスナを発火する。2026-07-16
715        // 発見・実装。capture版は関数冒頭の`fire_document_listeners(...,
716        // true)`呼び出し参照)。
717        if bubbles && !propagation_stopped {
718            let (doc_bub_fired, _) = self.fire_document_listeners(&ev, event_type, false);
719            if doc_bub_fired {
720                fired = true;
721            }
722        }
723        let default_prevented = ev
724            .borrow()
725            .props
726            .get("defaultPrevented")
727            .map(|v| v.truthy())
728            .unwrap_or(false);
729        (fired, default_prevented)
730    }
731
732    /// `document`の`_listeners[event_type]`のうち`capture`フラグが`want_capture`
733    /// と一致するリスナを発火する。`document`は`self.dom.nodes`の一員では
734    /// なくDOMの祖先チェーン`path`に含まれないため、キャプチャ方向では
735    /// 最も外側、バブル方向では最後の仮想的な祖先として`dispatch_event_
736    /// in_interp`の冒頭(キャプチャ)と末尾(バブル)から呼ばれる
737    /// (2026-07-16新設。`event_target_dispatch_event`の発火ループと同型の
738    /// ロジックだが、`document`用にターゲット/フェーズ番号を差し替えている)。
739    /// 戻り値は `(1件でも発火したか, stopPropagation/stopImmediatePropagation
740    /// で打ち切られたか)`。
741    fn fire_document_listeners(
742        &mut self,
743        ev: &super::super::value::ObjRef,
744        event_type: &str,
745        want_capture: bool,
746    ) -> (bool, bool) {
747        let mut fired = false;
748        // `self.global.borrow()`の一時`Ref`を`if let`の被走査式に直接置くと、
749        // 一時値ライフタイム延長でブロック終端まで生き続け、ブロック内の
750        // `self.call_value`(`&mut self`要求)と借用が衝突してビルドが通ら
751        // なくなる。ブロックで包んで明示的にドロップさせる。
752        let doc_opt = {
753            let g = self.global.borrow();
754            g.vars.get("document").cloned()
755        };
756        let Some(Value::Object(doc)) = doc_opt else {
757            return (false, false);
758        };
759        let doc_val = Value::Object(doc);
760        let Some(listeners) = super::super::builtins::event_target_listeners(&doc_val) else {
761            return (false, false);
762        };
763        let cbs: alloc::vec::Vec<Value> = match listeners.borrow().props.get(event_type) {
764            Some(Value::Object(arr)) => match &arr.borrow().kind {
765                ObjKind::Array(items) => items.clone(),
766                _ => alloc::vec::Vec::new(),
767            },
768            _ => alloc::vec::Vec::new(),
769        };
770        let matching: alloc::vec::Vec<Value> = cbs
771            .into_iter()
772            .filter(|entry| super::super::builtins::event_target_listener_capture(entry) == want_capture)
773            .collect();
774        if matching.is_empty() {
775            return (false, false);
776        }
777        {
778            let mut e = ev.borrow_mut();
779            e.props.insert(String::from("currentTarget"), doc_val.clone());
780            e.props
781                .insert(String::from("eventPhase"), Value::Number(if want_capture { 1.0 } else { 3.0 }));
782        }
783        for entry in matching {
784            if super::super::builtins::event_target_listener_aborted(&entry) {
785                continue;
786            }
787            let cb = super::super::builtins::event_target_listener_cb(&entry);
788            let once = super::super::builtins::event_target_listener_once(&entry);
789            let ctx = alloc::format!("{} listener (document)", event_type);
790            self.call_listener(&cb, doc_val.clone(), &[Value::Object(ev.clone())], &ctx);
791            fired = true;
792            if once {
793                if let Some(Value::Object(arr)) = listeners.borrow().props.get(event_type).cloned() {
794                    if let ObjKind::Array(items) = &mut arr.borrow_mut().kind {
795                        items.retain(|v| match (&super::super::builtins::event_target_listener_cb(v), &cb) {
796                            (Value::Object(x), Value::Object(y)) => !Rc::ptr_eq(x, y),
797                            _ => true,
798                        });
799                    }
800                }
801            }
802            if ev
803                .borrow()
804                .props
805                .get("_stopImmediate")
806                .map(|v| v.truthy())
807                .unwrap_or(false)
808            {
809                return (fired, true);
810            }
811        }
812        let stopped = ev.borrow().props.get("_stop").map(|v| v.truthy()).unwrap_or(false);
813        (fired, stopped)
814    }
815
816    pub(crate) fn call_value_inner(&mut self, func: &Value, this: Value, args: &[Value]) -> EvalResult {
817        let obj = match func {
818            Value::Object(o) => o.clone(),
819            // 【2026-09-05】何が関数でなかったのかを出す。
820            // 「not a function」だけでは、どの呼び出しか分からない。
821            _ => {
822                let d = alloc::format!("{}", func.to_js_string());
823                // 【一時】直前のプロパティ参照の並びを添える。
824                // `$` 自体なのか `.ready` なのかを区別するため。
825                return Err(self.throw(alloc::format!("not a function: {}", d)));
826            }
827        };
828        // Proxy: apply トラップがあれば呼ぶ。無ければ target を呼ぶ。
829        let proxy = match &obj.borrow().kind {
830            ObjKind::Proxy { target, handler } => Some((target.clone(), handler.clone())),
831            _ => None,
832        };
833        if let Some((target, handler)) = proxy {
834            let trap = handler.borrow().props.get("apply").cloned();
835            if let Some(trap) =
836                trap.filter(|t| matches!(t, Value::Object(f) if f.borrow().is_callable()))
837            {
838                let arg_arr = Value::Object(Obj::array(args.to_vec()));
839                let targs = [Value::Object(target), this, arg_arr];
840                return self.call_value(&trap, Value::Object(handler), &targs);
841            }
842            return self.call_value(&Value::Object(target), this, args);
843        }
844        // `Function.prototype.bind()` が返す束縛済み関数: `bound_this` を this に固定し、
845        // 束縛済み引数 `bound_args` の後ろに実引数 `args` を連結して `target` を呼ぶ
846        // (呼び出し側が渡す `this` は spec 通り無視する)。
847        let bound = match &obj.borrow().kind {
848            ObjKind::Bound {
849                target,
850                bound_this,
851                bound_args,
852            } => Some((target.clone(), bound_this.clone(), bound_args.clone())),
853            _ => None,
854        };
855        if let Some((target, bound_this, bound_args)) = bound {
856            let mut combined = bound_args;
857            combined.extend_from_slice(args);
858            return self.call_value(&target, bound_this, &combined);
859        }
860        // kind を取り出す(借用を跨がないようクローン)。
861        let kind = {
862            let b = obj.borrow();
863            match &b.kind {
864                ObjKind::Function(fd) => CallKind::User(fd.clone()),
865                ObjKind::Native { func, .. } => CallKind::Native(*func),
866                ObjKind::Resolver { state, reject } => CallKind::Resolver(state.clone(), *reject),
867                // 呼べないオブジェクトの種別を出す(配列なのか素の
868                // オブジェクトなのかで原因の見当が変わる)。
869                other => {
870                    let kind_name = match other {
871                        ObjKind::Array(_) => "array",
872                        ObjKind::Plain => "object",
873                        ObjKind::DomElement(_) => "dom element",
874                        ObjKind::Host(_) => "host object",
875                        _ => "other",
876                    };
877                    return Err(self.throw(alloc::format!(
878                        "not a function: {} (呼べない種別)",
879                        kind_name
880                    )));
881                }
882            }
883        };
884        match kind {
885            CallKind::Native(f) => f(self, this, args),
886            CallKind::Resolver(state, reject) => {
887                let arg = args.first().cloned().unwrap_or(Value::Undefined);
888                if reject {
889                    self.promise_reject(&state, arg);
890                } else {
891                    self.promise_resolve(&state, arg);
892                }
893                Ok(Value::Undefined)
894            }
895            CallKind::User(fd) => {
896                // `new.target`: アロー関数以外の呼び出し毎に push/pop する(アローは
897                // `this` と同様に周囲のスコープからレキシカルに継承するため対象外)。
898                // `construct_object()` が `new` 経由の呼び出し直前に `pending_new_target`
899                // をセットする。早期 return(`?`)を挟んでもスタックの対応が崩れないよう、
900                // 本体の実行を別メソッドへ切り出し、push/pop はこの呼び出しの外側で行う。
901                let nt = self.pending_new_target.take().unwrap_or(Value::Undefined);
902                let pushed = !fd.is_arrow;
903                if pushed {
904                    self.new_target_stack.push(nt);
905                }
906                let result = self.call_user_function(fd, this, args, func.clone());
907                if pushed {
908                    self.new_target_stack.pop();
909                }
910                result
911            }
912        }
913    }
914
915    pub(crate) fn call_user_function(
916        &mut self,
917        fd: FunctionData,
918        this: Value,
919        args: &[Value],
920        callee: Value,
921    ) -> EvalResult {
922        // 非 strict の関数では this が undefined/null ならグローバルオブジェクト(window)。
923        // 【2026-10-17】従来は常に undefined のままで、YouTube の player(base.js)の
924        // `(function(g){var window=this;…})(_yt_player)` で window が undefined になっていた。
925        let this = if !fd.is_arrow && !fd.body.strict && matches!(this, Value::Undefined | Value::Null) {
926            self.global.borrow().vars.get("window").cloned().unwrap_or(Value::Undefined)
927        } else {
928            this
929        };
930        {
931                let call_scope = Scope::child(fd.closure.clone());
932                for (i, p) in fd.params.iter().enumerate() {
933                    if p.is_rest {
934                        // レスト引数: 残余を配列束縛。
935                        let rest_args: Vec<Value> =
936                            args.get(i..).map(|s| s.to_vec()).unwrap_or_default();
937                        self.bind_pattern(
938                            &p.pattern,
939                            Value::Object(Obj::array(rest_args)),
940                            &call_scope,
941                            &this,
942                        )?;
943                        break;
944                    }
945                    let mut v = args.get(i).cloned().unwrap_or(Value::Undefined);
946                    // デフォルト引数: 実引数が undefined のとき評価。
947                    if matches!(v, Value::Undefined) {
948                        if let Some(def) = &p.default {
949                            v = self.eval(def, &call_scope, &this)?;
950                        }
951                    }
952                    self.bind_pattern(&p.pattern, v, &call_scope, &this)?;
953                }
954                let use_this = if fd.is_arrow {
955                    fd.bound_this
956                        .as_ref()
957                        .map(|b| (**b).clone())
958                        .unwrap_or(Value::Undefined)
959                } else {
960                    this
961                };
962                if fd.is_generator {
963                    // generator は遅延評価: 本体は実行せず、関数定義と実引数/this を
964                    // GenState に保存して Generator オブジェクトを返す。実際の本体実行は
965                    // .next()/for-of/spread での resume 時に replay 方式で行う。
966                    return Ok(Value::Object(Obj::generator(GenState {
967                        func: fd.clone(),
968                        args: args.to_vec(),
969                        this: use_this,
970                        sent: Vec::new(),
971                        started: false,
972                        done: false,
973                        returned: Value::Undefined,
974                    })));
975                }
976                let stmts = fd.body.stmts();
977                // arguments 配列。`.callee`(Annex B。無名関数の自己再帰呼び出し
978                // イディオム `arguments.callee` で今も使われる)が丸ごと未対応
979                // だったため、呼び出された関数自身への参照を追加する。
980                // 【2026-10-19】アロー関数は自身の `arguments` を持たず(外側のスコープから継承)、
981                // また通常の関数でも本体が `arguments` / `eval` を参照しない(98% 以上)なら
982                // 確保しない。従来は全呼び出しで無条件に確保して `callee` を入れていたため、
983                // バンドル最外の即時関数(`kevlar_base` 等)の `call_scope` に `arguments.callee` が
984                // 残り、内側の全クロージャから最外の関数と構文木が生きたまま握られていた。
985                if !fd.is_arrow && fd.body.uses_arguments(&stmts) {
986                    let arguments_obj = Obj::array(args.to_vec());
987                    arguments_obj.borrow_mut().props.insert("callee".into(), callee);
988                    scope_declare(&call_scope, "arguments", Value::Object(arguments_obj));
989                }
990                if fd.is_async {
991                    // async 関数は同期実行し、結果/例外を Promise に包んで返す
992                    // (await はマイクロタスクを駆動して解決まで進める)。
993                    let outcome = self.exec_statements(&stmts, &call_scope, &use_this);
994                    let pstate = Rc::new(RefCell::new(PromiseState::pending()));
995                    match outcome {
996                        Ok(Completion::Return(v)) => self.promise_resolve(&pstate, v),
997                        Ok(_) => self.promise_resolve(&pstate, Value::Undefined),
998                        Err(e) => {
999                            if self.aborted {
1000                                return Err(e);
1001                            }
1002                            self.promise_reject(&pstate, e);
1003                        }
1004                    }
1005                    return Ok(Value::Object(Obj::promise(pstate)));
1006                }
1007                match self.exec_statements(&stmts, &call_scope, &use_this)? {
1008                    Completion::Return(v) => Ok(v),
1009                    _ => Ok(Value::Undefined),
1010                }
1011        }
1012    }
1013
1014    // ============ プロパティアクセス ============
1015
1016}
1017
1018/// 捕捉されなかった例外値の簡易な中身表示(`depth` 段まで。配列は先頭 4 要素)。
1019pub(crate) fn describe_thrown(v: &Value, depth: u32) -> String {
1020    match v {
1021        Value::Object(o) => {
1022            if depth == 0 {
1023                return String::from("{…}");
1024            }
1025            let b = o.borrow();
1026            if let ObjKind::Array(items) = &b.kind {
1027                let parts: Vec<String> = items.iter().take(4).map(|x| describe_thrown(x, depth - 1)).collect();
1028                return alloc::format!("[{}]", parts.join(", "));
1029            }
1030            let parts: Vec<String> = b
1031                .props
1032                .iter()
1033                .take(8)
1034                .map(|(k, x)| alloc::format!("{}={}", k, describe_thrown(x, depth - 1)))
1035                .collect();
1036            alloc::format!("{{{}}}", parts.join(", "))
1037        }
1038        other => other.to_js_string().chars().take(300).collect(),
1039    }
1040}