Skip to main content

atmos/os_lib/js/interp/
operators.rs

1// 分割: interp.rs の `impl Interp` から機械的に移動(2026-07-16 リファクタ フェーズ5)。
2// ロジック不変。メソッド可視性のみ pub(crate) へ昇格(別モジュールの impl Interp
3// からの self 呼び出しのため)。impl ブロックは型 Interp に自動で結合する。
4use super::*;
5
6impl Interp {
7    pub(crate) fn eval_unary(
8        &mut self,
9        op: &UnaryOp,
10        expr: &Expression,
11        scope: &Rc<RefCell<Scope>>,
12        this: &Value,
13    ) -> EvalResult {
14        // typeof は未定義識別子でも例外を出さない。
15        if let UnaryOp::TypeOf = op {
16            if let Expression::Identifier(name) = expr {
17                // 識別子の評価(`eval_expr.rs`)と同じく、スコープに無ければ `window` の
18                // プロパティも見る(2026-10-16。`window.$ = jQuery` の後の `typeof $` が
19                // "undefined" になっていた)。
20                let on_window = || match scope_get(scope, "window") {
21                    Some(Value::Object(w)) => w.borrow().props.contains_key(name.as_str()),
22                    _ => false,
23                };
24                if scope_get(scope, name).is_none() && !on_window() {
25                    return Ok(Value::str("undefined"));
26                }
27            }
28        }
29        // delete はオペランドの「値」ではなく参照(プロパティの持ち主+キー)を必要とするため、
30        // 他の単項演算子と違って `expr` を先に評価してはいけない。
31        if let UnaryOp::Delete = op {
32            return self.eval_delete(expr, scope, this);
33        }
34        let v = self.eval(expr, scope, this)?;
35        Ok(match op {
36            UnaryOp::Neg => match &v {
37                Value::BigInt(b) => Value::bigint(b.neg()),
38                _ => Value::Number(-v.to_number()),
39            },
40            UnaryOp::Pos => {
41                // 単項 + は BigInt に対しては TypeError(ECMAScript 仕様)。
42                if matches!(v, Value::BigInt(_)) {
43                    return Err(self.throw("Cannot convert a BigInt to a number"));
44                }
45                Value::Number(v.to_number())
46            }
47            UnaryOp::Not => Value::Bool(!v.truthy()),
48            UnaryOp::BitNot => match &v {
49                Value::BigInt(b) => Value::bigint(b.bitnot()),
50                _ => Value::Number(!(to_i32(v.to_number())) as f64),
51            },
52            UnaryOp::TypeOf => Value::str(v.type_of()),
53            UnaryOp::Void => Value::Undefined,
54            // 上の早期 return で必ず処理済み(このアームには到達しない)。
55            UnaryOp::Delete => Value::Bool(true),
56        })
57    }
58
59    /// `delete expr` の実装。`expr` がプロパティアクセス(`obj.x`/`obj[x]`)なら
60    /// 実際にプロパティを取り除いて `true` を返す。それ以外(変数・リテラル等)は
61    /// 仕様上ほぼ no-op のため副作用なく `true` を返す(configurable:false 相当の
62    /// 「削除できず false」ケースはこの処理系にはプロパティ属性の概念が無いため非対応)。
63    pub(crate) fn eval_delete(
64        &mut self,
65        expr: &Expression,
66        scope: &Rc<RefCell<Scope>>,
67        this: &Value,
68    ) -> EvalResult {
69        match expr {
70            Expression::Member {
71                object, property, ..
72            } => {
73                let obj = self.eval(object, scope, this)?;
74                self.delete_property(&obj, property)
75            }
76            Expression::Index { object, index, .. } => {
77                let obj = self.eval(object, scope, this)?;
78                let key = self.eval(index, scope, this)?;
79                let k = to_property_key(&key);
80                self.delete_property(&obj, &k)
81            }
82            _ => Ok(Value::Bool(true)),
83        }
84    }
85
86    /// `delete obj.prop`/`delete obj[key]` の共通実装(`Member`/`Index` 両方から使う)。
87    /// 以前は `Member` 経由でしか `dataset`/`style`/`storage` Host プロキシの特別扱いが
88    /// 効かず、`delete element.style['color']` のような `Index` 経由の等価な書き方だと
89    /// 何も削除されないバグがあった。
90    pub(crate) fn delete_property(&mut self, obj: &Value, property: &str) -> EvalResult {
91        if let Value::Object(o) = obj {
92            // `Proxy`: `deleteProperty` トラップがあれば呼ぶ。無ければ target にフォワード
93            // (`get`/`set`/`has` と同じ「target フォワードのみ」方針)。以前はここが
94            // 未対応で、`delete proxyInstance.prop` がトラップ呼び出しはおろか target への
95            // フォワードすら行わず、常に何も削除しないまま無条件に `true` を返す
96            // サイレントバグだった。
97            let proxy = match &o.borrow().kind {
98                ObjKind::Proxy { target, handler } => Some((target.clone(), handler.clone())),
99                _ => None,
100            };
101            if let Some((target, handler)) = proxy {
102                let trap = handler.borrow().props.get("deleteProperty").cloned();
103                if let Some(trap) =
104                    trap.filter(|t| matches!(t, Value::Object(f) if f.borrow().is_callable()))
105                {
106                    let args = [Value::Object(target), Value::str(property)];
107                    let res = self.call_value(&trap, Value::Object(handler), &args)?;
108                    return Ok(Value::Bool(res.truthy()));
109                }
110                return self.delete_property(&Value::Object(target), property);
111            }
112            // `delete element.dataset.foo`(データ属性削除)は `dataset:` Host
113            // プロキシが `.props` を持たず、実データは DOM ノードの属性側にあるため、
114            // 汎用の props.remove では何も起きない黙殺バグだった。
115            let dataset_idx = match &o.borrow().kind {
116                ObjKind::Host(t) if t.starts_with("dataset:") => {
117                    t.strip_prefix("dataset:").and_then(|s| s.parse::<usize>().ok())
118                }
119                _ => None,
120            };
121            if let Some(idx) = dataset_idx {
122                let attr = camel_to_data_attr(property);
123                self.dom.borrow_mut().remove_attr(idx, &attr);
124                return Ok(Value::Bool(true));
125            }
126            // `delete element.style.color` も同じ種類のバグ(`style:` Host
127            // プロキシ自身は `.props` を持たず、実データは DOM ノードのスタイル
128            // 表側にある)。`set_style` の空文字列は削除として扱われる仕様を再利用する。
129            let style_idx = match &o.borrow().kind {
130                ObjKind::Host(t) if t.starts_with("style:") => {
131                    t.strip_prefix("style:").and_then(|s| s.parse::<usize>().ok())
132                }
133                _ => None,
134            };
135            if let Some(idx) = style_idx {
136                self.dom.borrow_mut().set_style(idx, property, "");
137                return Ok(Value::Bool(true));
138            }
139            // `delete localStorage.foo`/`delete sessionStorage.foo` も同じ種類の
140            // バグ(`storage:` Host プロキシ自身は `.props` を持たない)。
141            let storage_tag = match &o.borrow().kind {
142                ObjKind::Host(t) if t.starts_with("storage:") => Some(t.clone()),
143                _ => None,
144            };
145            if let Some(tag) = storage_tag {
146                super::super::builtins::storage_remove_prop(&tag, property);
147                return Ok(Value::Bool(true));
148            }
149            // 個別プロパティ単位の configurable 属性チェック。false の場合は削除不可 (false)。
150            if let Some(attr) = o.borrow().attrs.get(property) {
151                if !attr.configurable {
152                    return Ok(Value::Bool(false));
153                }
154            }
155            // 凍結・封印済みオブジェクトはプロパティを削除できない。
156            if o.borrow().frozen || o.borrow().sealed {
157                return Ok(Value::Bool(false));
158            }
159            let is_array = matches!(&o.borrow().kind, ObjKind::Array(_));
160            if is_array {
161                // 配列要素の delete は詰め直さず「穴」を残す(仕様どおり)。
162                // この処理系は疎配列を表現できないため undefined で近似する
163                // (`[1,,3]` の穴あきリテラルと同じ扱い)。
164                if let Ok(idx) = property.parse::<usize>() {
165                    if let ObjKind::Array(items) = &mut o.borrow_mut().kind {
166                        if idx < items.len() {
167                            items[idx] = Value::Undefined;
168                        }
169                    }
170                }
171            } else {
172                let mut b = o.borrow_mut();
173                if let Some(attr) = b.attrs.get(property) {
174                    if !attr.configurable {
175                        return Ok(Value::Bool(false));
176                    }
177                }
178                b.props.shift_remove(property);
179                b.accessors.shift_remove(property);
180                b.attrs.shift_remove(property);
181            }
182        }
183        Ok(Value::Bool(true))
184    }
185
186    pub(crate) fn eval_update(
187        &mut self,
188        op: &str,
189        prefix: bool,
190        target: &Expression,
191        scope: &Rc<RefCell<Scope>>,
192        this: &Value,
193    ) -> EvalResult {
194        // 左辺のオブジェクトとキーは 1 回だけ評価する(`a[i++]++` で i を 2 度進めていた)。
195        let lref: Option<(Value, String)> = match target {
196            Expression::Member { object, property, .. } if !matches!(**object, Expression::Super) => {
197                Some((self.eval(object, scope, this)?, property.clone()))
198            }
199            Expression::Index { object, index, .. } if !matches!(**object, Expression::Super) => {
200                let o = self.eval(object, scope, this)?;
201                let k = self.eval(index, scope, this)?;
202                Some((o, to_property_key(&k)))
203            }
204            _ => None,
205        };
206        let old = match &lref {
207            Some((o, k)) => self.get_property(o, k)?,
208            None => self.eval(target, scope, this)?,
209        }
210        .to_number();
211        let new = if op == "++" { old + 1.0 } else { old - 1.0 };
212        match &lref {
213            Some((o, k)) => {
214                self.set_property(o, k, Value::Number(new));
215            }
216            None => self.assign_to(target, Value::Number(new), scope, this)?,
217        }
218        Ok(Value::Number(if prefix { new } else { old }))
219    }
220
221    pub(crate) fn eval_assign(
222        &mut self,
223        op: &str,
224        target: &Expression,
225        value: &Expression,
226        scope: &Rc<RefCell<Scope>>,
227        this: &Value,
228    ) -> EvalResult {
229        // `&&=`/`||=`/`??=`(ES2021 論理代入演算子)は短絡評価が仕様上の要件で、
230        // 条件次第では右辺を一切評価してはいけない(例: `obj.cache ??= expensive()` は
231        // `cache` が既に非 null/undefined なら `expensive()` を呼んではならない)。
232        // 以前は関数の先頭で `value` を無条件に評価してから分岐していたため、右辺に
233        // 副作用がある場合に常に実行されてしまう仕様違反のバグだった。他の複合代入
234        // (`+=` 等)はそのような短絡要件が無いため、先に分岐して別経路で処理する。
235        // 左辺の参照(オブジェクトとキー)は右辺より先に、1 回だけ評価する(ES 仕様の評価順)。
236        // 【2026-10-20】従来は右辺を先に評価し、その後で左辺のオブジェクト式を評価していた。
237        // BotGuard(www.google.com/js/th)の `((v=(VJ(…ul(true,24,S1)…)),…),v).WH=S1.kR()` は
238        // 左辺の評価中の `ul(…)` が `S1.kR` を作るので、右辺が先だと「not a function」で落ちた。
239        // 複合代入(`a[i++] += 1`)でも左辺を 2 回評価していた。
240        let lref: Option<(Value, String)> = match target {
241            Expression::Member { object, property, .. } if !matches!(**object, Expression::Super) => {
242                Some((self.eval(object, scope, this)?, property.clone()))
243            }
244            Expression::Index { object, index, .. } if !matches!(**object, Expression::Super) => {
245                let o = self.eval(object, scope, this)?;
246                let k = self.eval(index, scope, this)?;
247                Some((o, to_property_key(&k)))
248            }
249            _ => None,
250        };
251        match op {
252            "&&=" | "||=" | "??=" => {
253                let cur = match &lref {
254                    Some((o, k)) => self.get_property(o, k)?,
255                    None => self.eval(target, scope, this)?,
256                };
257                let assign = match op {
258                    "&&=" => cur.truthy(),
259                    "||=" => !cur.truthy(),
260                    _ => matches!(cur, Value::Undefined | Value::Null),
261                };
262                return if assign {
263                    let v = self.eval(value, scope, this)?;
264                    match &lref {
265                        Some((o, k)) => {
266                            self.set_property(o, k, v.clone());
267                        }
268                        None => self.assign_to(target, v.clone(), scope, this)?,
269                    }
270                    Ok(v)
271                } else {
272                    Ok(cur)
273                };
274            }
275            _ => {}
276        }
277        // 複合代入の現在値も右辺より先に読む。
278        let cur = if op == "=" {
279            None
280        } else {
281            Some(match &lref {
282                Some((o, k)) => self.get_property(o, k)?,
283                None => self.eval(target, scope, this)?,
284            })
285        };
286        let rhs = self.eval(value, scope, this)?;
287        if op == "=" {
288            match target {
289                // NamedEvaluation(ES2015): `x = function(){}` は x を名前にする。
290                Expression::Identifier(n) => super::infer_function_name(&rhs, n),
291                // `a.b = function(){}` は仕様上無名のまま。診断([JSPROF]・スタック)用にだけ
292                // 隠しプロパティで名前を持たせる(ミニファイされたコードの関数を特定するため)。
293                Expression::Member { property, .. } => {
294                    if let Value::Object(o) = &rhs {
295                        let anon = matches!(&o.borrow().kind, ObjKind::Function(fd) if fd.name.is_empty());
296                        if anon {
297                            o.borrow_mut().props.insert("_diag_name".into(), Value::str(property.as_str()));
298                        }
299                    }
300                }
301                _ => {}
302            }
303        }
304        let final_val = if let Some(cur) = cur {
305            let bop = match op {
306                "+=" => BinaryOp::Add,
307                "-=" => BinaryOp::Sub,
308                "*=" => BinaryOp::Mul,
309                "/=" => BinaryOp::Div,
310                "%=" => BinaryOp::Mod,
311                "**=" => BinaryOp::Pow,
312                "&=" => BinaryOp::BitAnd,
313                "|=" => BinaryOp::BitOr,
314                "^=" => BinaryOp::BitXor,
315                "<<=" => BinaryOp::Shl,
316                ">>=" => BinaryOp::Shr,
317                ">>>=" => BinaryOp::UShr,
318                _ => BinaryOp::Add,
319            };
320            self.eval_binary(&bop, cur, rhs)?
321        } else {
322            rhs
323        };
324        match &lref {
325            Some((o, k)) => {
326                self.set_property(o, k, final_val.clone());
327            }
328            None => self.assign_to(target, final_val.clone(), scope, this)?,
329        }
330        Ok(final_val)
331    }
332
333    /// 代入先(識別子・メンバ・添字)へ値を書き込む。
334    pub(crate) fn assign_to(
335        &mut self,
336        target: &Expression,
337        val: Value,
338        scope: &Rc<RefCell<Scope>>,
339        this: &Value,
340    ) -> Result<(), Value> {
341        match target {
342            Expression::Identifier(name) => {
343                if !scope_assign(scope, name, val.clone()) {
344                    // sloppy mode: 未宣言はグローバルに作る。
345                    let mut s = scope.clone();
346                    loop {
347                        let p = s.borrow().parent.clone();
348                        match p {
349                            Some(pp) => s = pp,
350                            None => break,
351                        }
352                    }
353                    scope_declare(&s, name, val);
354                }
355                Ok(())
356            }
357            Expression::Member {
358                object, property, ..
359            } => {
360                let obj = self.eval(object, scope, this)?;
361                self.set_property(&obj, property, val);
362                Ok(())
363            }
364            Expression::Index { object, index, .. } => {
365                let obj = self.eval(object, scope, this)?;
366                let key = self.eval(index, scope, this)?;
367                self.set_property(&obj, &to_property_key(&key), val);
368                Ok(())
369            }
370            _ => Ok(()),
371        }
372    }
373
374    /// ToPrimitive(ECMA-262 7.1.1)。原型の連鎖に利用者定義の `Symbol.toPrimitive`・`toString`・
375    /// `valueOf` があるときだけ呼ぶ(無ければ値をそのまま返し、従来の組込みの変換に任せる)。
376    /// `hint` は "string"・"number"・"default"。
377    /// 【2026-10-20】`String(obj)`・`obj + ''`・テンプレート文字列・XHR の URL が利用者定義の
378    /// `toString` を呼ばず `[object Object]` になっていた(YouTube の TrustedResourceUrl 相当の
379    /// URL オブジェクトで `GET /[object Object]` を送っていた)。
380    pub(crate) fn to_primitive(&mut self, v: Value, hint: &str) -> EvalResult {
381        let Value::Object(o) = &v else { return Ok(v) };
382        if !matches!(o.borrow().kind, ObjKind::Plain | ObjKind::Function(_)) {
383            return Ok(v);
384        }
385        // 利用者定義のメソッドが連鎖にあるか。
386        let mut has = [false; 3];
387        let names = ["Symbol(Symbol.toPrimitive)", "toString", "valueOf"];
388        let mut cur = Some(o.clone());
389        let mut guard = 0;
390        while let Some(c) = cur {
391            {
392                let b = c.borrow();
393                for (i, n) in names.iter().enumerate() {
394                    if b.props.contains_key(*n) || b.accessors.contains_key(*n) {
395                        has[i] = true;
396                    }
397                }
398            }
399            guard += 1;
400            if guard > 64 {
401                break;
402            }
403            cur = c.borrow().proto.clone();
404        }
405        if !has.iter().any(|&h| h) {
406            return Ok(v);
407        }
408        let is_callable = |f: &Value| matches!(f, Value::Object(x) if x.borrow().is_callable());
409        if has[0] {
410            let f = self.get_property(&v, names[0])?;
411            if is_callable(&f) {
412                let r = self.call_value(&f, v.clone(), &[Value::str(hint)])?;
413                if !matches!(r, Value::Object(_)) {
414                    return Ok(r);
415                }
416                return Err(self.type_error("Cannot convert object to primitive value"));
417            }
418        }
419        let order: [&str; 2] = if hint == "string" { ["toString", "valueOf"] } else { ["valueOf", "toString"] };
420        for name in order {
421            let f = self.get_property(&v, name)?;
422            if is_callable(&f) {
423                let r = self.call_value(&f, v.clone(), &[])?;
424                if !matches!(r, Value::Object(_)) {
425                    return Ok(r);
426                }
427            }
428        }
429        Ok(v)
430    }
431
432    /// ToString(利用者定義の toString を考慮)。
433    pub(crate) fn to_string_value(&mut self, v: Value) -> Result<String, Value> {
434        Ok(self.to_primitive(v, "string")?.to_js_string())
435    }
436
437    pub fn eval_binary(&mut self, op: &BinaryOp, l: Value, r: Value) -> EvalResult {
438        // 加算はオブジェクトを先にプリミティブへ(利用者定義の valueOf/toString)。
439        let (l, r) = if matches!(op, BinaryOp::Add) && (matches!(l, Value::Object(_)) || matches!(r, Value::Object(_))) {
440            (self.to_primitive(l, "default")?, self.to_primitive(r, "default")?)
441        } else {
442            (l, r)
443        };
444        // どちらかが BigInt のときは専用処理(混在の算術は TypeError)。
445        if matches!(l, Value::BigInt(_)) || matches!(r, Value::BigInt(_)) {
446            return self.eval_binary_bigint(op, l, r);
447        }
448        Ok(match op {
449            BinaryOp::Add => {
450                // どちらかが文字列/オブジェクトなら連結。
451                let l_str = matches!(l, Value::Str(_) | Value::Object(_));
452                let r_str = matches!(r, Value::Str(_) | Value::Object(_));
453                if l_str || r_str {
454                    Value::str(format!("{}{}", l.to_js_string(), r.to_js_string()))
455                } else {
456                    Value::Number(l.to_number() + r.to_number())
457                }
458            }
459            BinaryOp::Sub => Value::Number(l.to_number() - r.to_number()),
460            BinaryOp::Mul => Value::Number(l.to_number() * r.to_number()),
461            BinaryOp::Div => Value::Number(l.to_number() / r.to_number()),
462            BinaryOp::Mod => {
463                let a = l.to_number();
464                let b = r.to_number();
465                Value::Number(if b == 0.0 { f64::NAN } else { a % b })
466            }
467            BinaryOp::Pow => Value::Number(powf(l.to_number(), r.to_number())),
468            BinaryOp::Eq => Value::Bool(loose_eq(&l, &r)),
469            BinaryOp::NotEq => Value::Bool(!loose_eq(&l, &r)),
470            BinaryOp::StrictEq => Value::Bool(l.strict_eq(&r)),
471            BinaryOp::StrictNotEq => Value::Bool(!l.strict_eq(&r)),
472            BinaryOp::Lt => cmp(&l, &r, |o| o == core::cmp::Ordering::Less),
473            BinaryOp::Gt => cmp(&l, &r, |o| o == core::cmp::Ordering::Greater),
474            BinaryOp::LtEq => cmp(&l, &r, |o| o != core::cmp::Ordering::Greater),
475            BinaryOp::GtEq => cmp(&l, &r, |o| o != core::cmp::Ordering::Less),
476            BinaryOp::BitAnd => {
477                Value::Number((to_i32(l.to_number()) & to_i32(r.to_number())) as f64)
478            }
479            BinaryOp::BitOr => {
480                Value::Number((to_i32(l.to_number()) | to_i32(r.to_number())) as f64)
481            }
482            BinaryOp::BitXor => {
483                Value::Number((to_i32(l.to_number()) ^ to_i32(r.to_number())) as f64)
484            }
485            BinaryOp::Shl => Value::Number(
486                (to_i32(l.to_number()).wrapping_shl(to_u32(r.to_number()) & 31)) as f64,
487            ),
488            BinaryOp::Shr => Value::Number(
489                (to_i32(l.to_number()).wrapping_shr(to_u32(r.to_number()) & 31)) as f64,
490            ),
491            BinaryOp::UShr => Value::Number(
492                ((to_u32(l.to_number())).wrapping_shr(to_u32(r.to_number()) & 31)) as f64,
493            ),
494            BinaryOp::In => {
495                let key = l.to_js_string();
496                match &r {
497                    Value::Object(o) => {
498                        // Proxy: has トラップがあれば呼ぶ。無ければ target にフォワード。
499                        let proxy = match &o.borrow().kind {
500                            ObjKind::Proxy { target, handler } => {
501                                Some((target.clone(), handler.clone()))
502                            }
503                            _ => None,
504                        };
505                        if let Some((target, handler)) = proxy {
506                            let trap = handler.borrow().props.get("has").cloned();
507                            if let Some(trap) = trap.filter(
508                                |t| matches!(t, Value::Object(f) if f.borrow().is_callable()),
509                            ) {
510                                let args = [Value::Object(target), Value::str(&key)];
511                                let res = self.call_value(&trap, Value::Object(handler), &args)?;
512                                return Ok(Value::Bool(res.truthy()));
513                            }
514                            return self.eval_binary(&BinaryOp::In, l, Value::Object(target));
515                        }
516                        if self.has_property_chain(o, &key) {
517                            return Ok(Value::Bool(true));
518                        }
519                        // DOM ノードのプロパティ(`nodeType`・`tagName` 等)は実体に持たず読み取り時に
520                        // 解決するので、読めるかで判定する。【2026-10-17】`"nodeType" in el` が false に
521                        // なり、ノード判定(Closure の `_.ht`)が要素を弾いていた(YouTube の埋め込み)。
522                        let is_dom = matches!(o.borrow().kind, ObjKind::DomElement(_));
523                        if is_dom {
524                            let v = self.get_property(&r, &key)?;
525                            return Ok(Value::Bool(!matches!(v, Value::Undefined)));
526                        }
527                        Value::Bool(false)
528                    }
529                    _ => Value::Bool(false),
530                }
531            }
532            BinaryOp::InstanceOf => self.instance_of(&l, &r)?,
533        })
534    }
535
536    /// DOM ノードのタグ名または種別から、動的に対応する `HTMLXxxElement.prototype`
537    /// (または `HTMLElement.prototype` / `Element.prototype` / `Node.prototype`)を解決する。
538    pub(crate) fn get_dom_node_proto(&self, node_idx: usize) -> Option<ObjRef> {
539        // 昇格したカスタム要素はそのクラスの prototype。
540        if let Some(p) = self.dom.borrow().node_protos.get(&node_idx) {
541            return Some(p.clone());
542        }
543        let tag_or_text = {
544            let dom = self.dom.borrow();
545            dom.nodes.get(node_idx).map(|n| (n.tag.to_lowercase(), n.is_text))
546        }?;
547
548        let (tag, is_text) = tag_or_text;
549        // 【2026-10-20】テキスト・コメント・DocumentFragment も種別ごとの原型にする
550        // (`x instanceof DocumentFragment` が偽で、YouTube のライフサイクルの包みが
551        // フラグメント挿入の分岐に入らず、ytd-page-manager が接続されなかった)。
552        let cls_name = if is_text {
553            "Text"
554        } else {
555            match tag.as_str() {
556                "#comment" => "Comment",
557                "#fragment" | "#document-fragment" => "DocumentFragment",
558                "video" => "HTMLVideoElement",
559                "audio" => "HTMLAudioElement",
560                "input" => "HTMLInputElement",
561                "form" => "HTMLFormElement",
562                "a" => "HTMLAnchorElement",
563                "img" => "HTMLImageElement",
564                "button" => "HTMLButtonElement",
565                "select" => "HTMLSelectElement",
566                "option" => "HTMLOptionElement",
567                "textarea" => "HTMLTextAreaElement",
568                "div" => "HTMLDivElement",
569                "span" => "HTMLSpanElement",
570                "script" => "HTMLScriptElement",
571                "style" => "HTMLStyleElement",
572                "iframe" => "HTMLIFrameElement",
573                "canvas" => "HTMLCanvasElement",
574                "p" => "HTMLParagraphElement",
575                "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => "HTMLHeadingElement",
576                "table" => "HTMLTableElement",
577                "tr" => "HTMLTableRowElement",
578                "td" | "th" => "HTMLTableCellElement",
579                "ul" | "ol" => "HTMLUListElement",
580                "li" => "HTMLLIElement",
581                "template" => "HTMLTemplateElement",
582                _ => "HTMLUnknownElement",
583            }
584        };
585
586        let ctor_val = super::scope_get(&self.global, cls_name)?;
587        if let Value::Object(ctor_obj) = ctor_val {
588            if let Some(Value::Object(proto)) = ctor_obj.borrow().props.get("prototype") {
589                return Some(proto.clone());
590            }
591        }
592        None
593    }
594
595    /// `l instanceof r`: `r.prototype` を起点に `l` のプロトタイプ連鎖を辿って一致を探す
596    /// (OrdinaryHasInstance の簡略実装。`Symbol.hasInstance` によるカスタマイズは非対応)。
597    pub(crate) fn instance_of(&mut self, l: &Value, r: &Value) -> EvalResult {
598        let ctor = match r {
599            Value::Object(o) if o.borrow().is_callable() => o.clone(),
600            _ => return Err(self.throw("Right-hand side of 'instanceof' is not callable")),
601        };
602        // `class Foo { static [Symbol.hasInstance](x){...} }` によるカスタム
603        // `instanceof` 判定(ES2015)が丸ごと未対応で、常にプロトタイプ連鎖の照合に
604        // 決め打ちされていたバグ。`Symbol.hasInstance` が定義されていれば仕様どおり
605        // そちらを最優先する(無ければ従来のプロトタイプ連鎖判定にフォールバック)。
606        let has_instance = ctor.borrow().props.get("Symbol(Symbol.hasInstance)").cloned();
607        if let Some(f) = has_instance {
608            if matches!(&f, Value::Object(fo) if fo.borrow().is_callable()) {
609                let result = self.call_value(&f, Value::Object(ctor), core::slice::from_ref(l))?;
610                return Ok(Value::Bool(result.truthy()));
611            }
612        }
613        let proto = match ctor.borrow().props.get("prototype") {
614            Some(Value::Object(p)) => p.clone(),
615            _ => return Ok(Value::Bool(false)),
616        };
617        let mut cur = match l {
618            Value::Object(o) => {
619                let dom_idx = match o.borrow().kind {
620                    ObjKind::DomElement(idx) if o.borrow().proto.is_none() => Some(idx),
621                    _ => None,
622                };
623                match dom_idx {
624                    Some(idx) => self.get_dom_node_proto(idx),
625                    None => super::super::builtins::implicit_proto(self, o),
626                }
627            }
628            _ => None,
629        };
630        let mut guard = 0;
631        while let Some(p) = cur {
632            if Rc::ptr_eq(&p, &proto) {
633                return Ok(Value::Bool(true));
634            }
635            // 組込みの prototype(proto を持たない)の先は Object.prototype。
636            cur = super::super::builtins::implicit_proto(self, &p);
637            guard += 1;
638            if guard > 1000 {
639                break;
640            }
641        }
642        Ok(Value::Bool(false))
643    }
644
645    /// BigInt が絡む二項演算。BigInt 同士は任意精度演算、BigInt と Number の
646    /// 混在は算術・ビット演算で TypeError。等価・大小比較は数値として許容する。
647    pub(crate) fn eval_binary_bigint(&mut self, op: &BinaryOp, l: Value, r: Value) -> EvalResult {
648        use super::super::bigint::BigInt;
649        // 文字列連結は BigInt でも許容("x"+1n → "x1")。
650        if matches!(op, BinaryOp::Add) && (matches!(l, Value::Str(_)) || matches!(r, Value::Str(_)))
651        {
652            return Ok(Value::str(format!(
653                "{}{}",
654                l.to_js_string(),
655                r.to_js_string()
656            )));
657        }
658
659        // 等価系: 型混在でも数値的に比較する(=== は型も一致が必要)。
660        match op {
661            BinaryOp::StrictEq => return Ok(Value::Bool(l.strict_eq(&r))),
662            BinaryOp::StrictNotEq => return Ok(Value::Bool(!l.strict_eq(&r))),
663            BinaryOp::Eq => return Ok(Value::Bool(self.bigint_loose_eq(&l, &r))),
664            BinaryOp::NotEq => return Ok(Value::Bool(!self.bigint_loose_eq(&l, &r))),
665            BinaryOp::Lt | BinaryOp::Gt | BinaryOp::LtEq | BinaryOp::GtEq => {
666                return Ok(self.bigint_compare(op, &l, &r));
667            }
668            BinaryOp::In | BinaryOp::InstanceOf => {
669                return Ok(Value::Bool(false));
670            }
671            _ => {}
672        }
673
674        // ここから先は算術・ビット演算。両辺とも BigInt でなければ TypeError。
675        let (a, b) = match (&l, &r) {
676            (Value::BigInt(a), Value::BigInt(b)) => (a.clone(), b.clone()),
677            _ => {
678                return Err(
679                    self.throw("Cannot mix BigInt and other types, use explicit conversions")
680                );
681            }
682        };
683        let result: BigInt = match op {
684            BinaryOp::Add => a.add(&b),
685            BinaryOp::Sub => a.sub(&b),
686            BinaryOp::Mul => a.mul(&b),
687            BinaryOp::Div => match a.div(&b) {
688                Some(v) => v,
689                None => return Err(self.throw("Division by zero")),
690            },
691            BinaryOp::Mod => match a.rem(&b) {
692                Some(v) => v,
693                None => return Err(self.throw("Division by zero")),
694            },
695            BinaryOp::Pow => match a.pow(&b) {
696                Some(v) => v,
697                None => return Err(self.throw("Exponent must be non-negative")),
698            },
699            BinaryOp::BitAnd => a.bitand(&b),
700            BinaryOp::BitOr => a.bitor(&b),
701            BinaryOp::BitXor => a.bitxor(&b),
702            BinaryOp::Shl => {
703                // シフト量は i64 に収まる範囲のみ対応(過大シフトはエラー)。
704                match shift_amount(&b) {
705                    Some(n) => a.shl(n),
706                    None => return Err(self.throw("BigInt shift amount out of range")),
707                }
708            }
709            BinaryOp::Shr => match shift_amount(&b) {
710                Some(n) => a.shr(n),
711                None => return Err(self.throw("BigInt shift amount out of range")),
712            },
713            BinaryOp::UShr => {
714                // BigInt に符号なし右シフト >>> は存在しない(TypeError)。
715                return Err(self.throw("BigInts have no unsigned right shift, use >> instead"));
716            }
717            _ => return Err(self.throw("Unsupported BigInt operation")),
718        };
719        Ok(Value::bigint(result))
720    }
721
722    /// BigInt を含む緩い等価比較(BigInt↔Number/String を数値として比較)。
723    pub(crate) fn bigint_loose_eq(&self, l: &Value, r: &Value) -> bool {
724        use core::cmp::Ordering;
725        match (l, r) {
726            (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b) == Ordering::Equal,
727            (Value::BigInt(a), Value::Number(n)) | (Value::Number(n), Value::BigInt(a)) => {
728                n.is_finite() && a.to_f64() == *n
729            }
730            (Value::BigInt(a), Value::Str(s)) | (Value::Str(s), Value::BigInt(a)) => {
731                match super::super::bigint::BigInt::parse_str(s) {
732                    Some(bi) => a.cmp(&bi) == Ordering::Equal,
733                    None => false,
734                }
735            }
736            (Value::BigInt(a), Value::Bool(bb)) | (Value::Bool(bb), Value::BigInt(a)) => {
737                a.to_f64() == if *bb { 1.0 } else { 0.0 }
738            }
739            _ => false,
740        }
741    }
742
743    /// BigInt を含む大小比較(BigInt↔Number は f64 経由)。
744    pub(crate) fn bigint_compare(&self, op: &BinaryOp, l: &Value, r: &Value) -> Value {
745        use core::cmp::Ordering;
746        let ord: Option<Ordering> = match (l, r) {
747            (Value::BigInt(a), Value::BigInt(b)) => Some(a.cmp(b)),
748            (Value::BigInt(a), _) => {
749                let x = a.to_f64();
750                let y = r.to_number();
751                cmp_f64(x, y)
752            }
753            (_, Value::BigInt(b)) => {
754                let x = l.to_number();
755                let y = b.to_f64();
756                cmp_f64(x, y)
757            }
758            _ => None,
759        };
760        let ord = match ord {
761            Some(o) => o,
762            None => return Value::Bool(false),
763        };
764        let res = match op {
765            BinaryOp::Lt => ord == Ordering::Less,
766            BinaryOp::Gt => ord == Ordering::Greater,
767            BinaryOp::LtEq => ord != Ordering::Greater,
768            BinaryOp::GtEq => ord != Ordering::Less,
769            _ => false,
770        };
771        Value::Bool(res)
772    }
773
774}