Skip to main content

atmos/os_lib/js/builtins/
encoding.rs

1// 分割: builtins.rs より機械的に移動(2026-07-16 リファクタ フェーズ2)。
2// ロジック不変。可視性のみ pub(crate) へ昇格し、親が pub(crate) use で再エクスポート。
3use super::*;
4
5// ===== base64(btoa / atob)。バイト列は latin1/ASCII 前提(多バイトは非対応)=====
6pub(crate) const B64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7/// バイト列を base64 文字列へエンコードする共通処理(`btoa`/`Uint8Array.prototype.toBase64` で共有)。
8pub(crate) fn bytes_to_base64(bytes: &[u8]) -> String {
9    let mut out = String::new();
10    for chunk in bytes.chunks(3) {
11        let b0 = chunk.first().copied().unwrap_or(0);
12        let b1 = chunk.get(1).copied().unwrap_or(0);
13        let b2 = chunk.get(2).copied().unwrap_or(0);
14        out.push(B64_CHARS[(b0 >> 2) as usize] as char);
15        out.push(B64_CHARS[(((b0 & 0x3) << 4) | (b1 >> 4)) as usize] as char);
16        out.push(if chunk.len() > 1 {
17            B64_CHARS[(((b1 & 0xf) << 2) | (b2 >> 6)) as usize] as char
18        } else {
19            '='
20        });
21        out.push(if chunk.len() > 2 {
22            B64_CHARS[(b2 & 0x3f) as usize] as char
23        } else {
24            '='
25        });
26    }
27    out
28}
29
30/// base64 文字列をバイト列へデコードする共通処理(`atob`/`Uint8Array.fromBase64` で共有)。
31/// 未知の文字(改行等)は無視する簡略実装。
32pub(crate) fn base64_to_bytes(s: &str) -> Vec<u8> {
33    let mut bits: u32 = 0;
34    let mut nbits = 0u32;
35    let mut out: Vec<u8> = Vec::new();
36    for c in s.bytes() {
37        let v = match c {
38            b'A'..=b'Z' => c - b'A',
39            b'a'..=b'z' => c - b'a' + 26,
40            b'0'..=b'9' => c - b'0' + 52,
41            b'+' | b'-' => 62, // '-' は base64url 版のエイリアスとして許容
42            b'/' | b'_' => 63, // '_' は base64url 版のエイリアスとして許容
43            _ => continue,
44        };
45        bits = (bits << 6) | v as u32;
46        nbits += 6;
47        if nbits >= 8 {
48            nbits -= 8;
49            out.push((bits >> nbits) as u8);
50        }
51    }
52    out
53}
54
55/// バイト列を小文字16進文字列へエンコードする(`Uint8Array.prototype.toHex`)。
56pub(crate) fn bytes_to_hex(bytes: &[u8]) -> String {
57    const HEX: &[u8; 16] = b"0123456789abcdef";
58    let mut out = String::with_capacity(bytes.len() * 2);
59    for &b in bytes {
60        out.push(HEX[(b >> 4) as usize] as char);
61        out.push(HEX[(b & 0xf) as usize] as char);
62    }
63    out
64}
65
66/// 16進文字列をバイト列へデコードする(`Uint8Array.fromHex`)。奇数長や不正文字は `None`。
67pub(crate) fn hex_to_bytes(s: &str) -> Option<Vec<u8>> {
68    let s = s.as_bytes();
69    if !s.len().is_multiple_of(2) {
70        return None;
71    }
72    fn nibble(c: u8) -> Option<u8> {
73        match c {
74            b'0'..=b'9' => Some(c - b'0'),
75            b'a'..=b'f' => Some(c - b'a' + 10),
76            b'A'..=b'F' => Some(c - b'A' + 10),
77            _ => None,
78        }
79    }
80    let mut out = Vec::with_capacity(s.len() / 2);
81    for pair in s.chunks(2) {
82        let hi = nibble(pair[0])?;
83        let lo = nibble(pair[1])?;
84        out.push((hi << 4) | lo);
85    }
86    Some(out)
87}
88
89pub(crate) fn btoa(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
90    let s = arg(a, 0).to_js_string();
91    Ok(Value::str(bytes_to_base64(s.as_bytes())))
92}
93pub(crate) fn atob(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
94    let s = arg(a, 0).to_js_string();
95    let out = base64_to_bytes(&s);
96    Ok(Value::str(
97        alloc::string::String::from_utf8_lossy(&out).into_owned(),
98    ))
99}
100
101/// TypedArray インスタンスの要素を u8 バイト列として取り出す(Uint8Array 専用機能で使用)。
102pub(crate) fn ta_to_bytes(this: &Value) -> Vec<u8> {
103    if let Value::Object(o) = this {
104        if let ObjKind::Bytes(st) = &o.borrow().kind {
105            return st.to_vec();
106        }
107        if let ObjKind::Array(items) = &o.borrow().kind {
108            return items.iter().map(|v| v.to_number() as i64 as u8).collect();
109        }
110    }
111    Vec::new()
112}
113
114/// `Uint8Array.prototype.toBase64()`(ES2024/2025)。
115pub(crate) fn ta_to_base64(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
116    Ok(Value::str(bytes_to_base64(&ta_to_bytes(&this))))
117}
118
119/// `Uint8Array.prototype.toHex()`(ES2024/2025)。
120pub(crate) fn ta_to_hex(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
121    Ok(Value::str(bytes_to_hex(&ta_to_bytes(&this))))
122}
123
124/// `Uint8Array.fromBase64(str)`(ES2024/2025 静的メソッド)。
125pub(crate) fn ta_from_base64(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
126    let s = arg(a, 0).to_js_string();
127    Ok(make_uint8array(it, base64_to_bytes(&s)))
128}
129
130/// `Uint8Array.fromHex(str)`(ES2024/2025 静的メソッド)。不正な16進文字列は例外を投げる。
131pub(crate) fn ta_from_hex(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
132    let s = arg(a, 0).to_js_string();
133    match hex_to_bytes(&s) {
134        Some(bytes) => Ok(make_uint8array(it, bytes)),
135        None => {
136            let err = syntax_error_ctor(
137                it,
138                Value::Undefined,
139                &[Value::str("Uint8Array.fromHex: invalid hex string")],
140            )?;
141            Err(err)
142        }
143    }
144}
145
146/// デコードしたバイト列を既存の `this`(Uint8Array)の先頭から上書きし、
147/// `{ read, written }`(`read` は入力文字列の消費文字数、`written` は書き込んだ
148/// バイト数)を返す共通処理。`setFromBase64`/`setFromHex` で共有する。
149/// `toBase64`/`toHex`(読み取り)と `fromBase64`/`fromHex`(新規生成)は
150/// 実装済みだったが、既存バッファへインプレースで書き込む対になる
151/// `setFromBase64`/`setFromHex`(ES2024/2025)が丸ごと未対応だった。
152pub(crate) fn ta_write_bytes_into(this: &Value, bytes: &[u8], consumed_len: usize) -> Value {
153    let mut written = 0usize;
154    if let Value::Object(o) = this {
155        if let ObjKind::Bytes(st) = &o.borrow().kind {
156            written = bytes.len().min(st.len);
157            for (i, byte) in bytes.iter().take(written).enumerate() {
158                st.set(i, *byte as f64);
159            }
160        }
161        let mut b = o.borrow_mut();
162        if let ObjKind::Array(items) = &mut b.kind {
163            let cap = items.len();
164            written = bytes.len().min(cap);
165            for (slot, byte) in items.iter_mut().zip(bytes.iter()).take(written) {
166                *slot = Value::Number(*byte as f64);
167            }
168        }
169    }
170    let read = if written < bytes.len() {
171        // 途中で容量切れになった場合、書き込めた分に対応する入力文字数のみ「読んだ」扱いにする。
172        consumed_len * written / bytes.len().max(1)
173    } else {
174        consumed_len
175    };
176    let o = Obj::plain();
177    {
178        let mut ob = o.borrow_mut();
179        ob.props.insert("read".into(), Value::Number(read as f64));
180        ob.props.insert("written".into(), Value::Number(written as f64));
181    }
182    Value::Object(o)
183}
184
185/// `Uint8Array.prototype.setFromBase64(str)`(ES2024/2025)。
186pub(crate) fn ta_set_from_base64(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
187    let s = arg(a, 0).to_js_string();
188    let bytes = base64_to_bytes(&s);
189    Ok(ta_write_bytes_into(&this, &bytes, s.chars().count()))
190}
191
192/// `Uint8Array.prototype.setFromHex(str)`(ES2024/2025)。不正な16進文字列は例外を投げる。
193pub(crate) fn ta_set_from_hex(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
194    let s = arg(a, 0).to_js_string();
195    match hex_to_bytes(&s) {
196        Some(bytes) => {
197            let len = s.chars().count();
198            Ok(ta_write_bytes_into(&this, &bytes, len))
199        }
200        None => {
201            let err = syntax_error_ctor(
202                it,
203                Value::Undefined,
204                &[Value::str("Uint8Array.setFromHex: invalid hex string")],
205            )?;
206            Err(err)
207        }
208    }
209}
210
211// ===== URI エンコード(encodeURIComponent/decodeURIComponent/encodeURI/decodeURI)=====
212// URLSearchParams の form-urlencoded(+ = 空白)とは別の RFC3986 %XX 方式。
213
214/// `%XX` を1バイトへ復元する共通デコーダ。`plus_as_space` が true の場合のみ
215/// `+` を空白として扱う(form-urlencoded 用。encodeURIComponent 系では false)。
216pub(crate) fn percent_decode(s: &str, plus_as_space: bool) -> String {
217    let bytes = s.as_bytes();
218    let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
219    let mut i = 0;
220    while i < bytes.len() {
221        if bytes[i] == b'%' && i + 2 < bytes.len() {
222            let h1 = bytes[i + 1];
223            let h2 = bytes[i + 2];
224            if let (Some(n1), Some(n2)) = (char::from(h1).to_digit(16), char::from(h2).to_digit(16)) {
225                let byte = ((n1 << 4) | n2) as u8;
226                out.push(byte);
227                i += 3;
228                continue;
229            }
230        }
231        if plus_as_space && bytes[i] == b'+' {
232            out.push(b' ');
233        } else {
234            out.push(bytes[i]);
235        }
236        i += 1;
237    }
238    String::from_utf8(out).unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned())
239}
240
241/// `encodeURIComponent` 相当。未エスケープ文字は RFC3986 unreserved + `!'()*`。
242pub(crate) fn percent_encode_component(s: &str) -> String {
243    let mut out = String::new();
244    for b in s.as_bytes() {
245        match *b {
246            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'!' | b'~' | b'*'
247            | b'\'' | b'(' | b')' => {
248                out.push(*b as char);
249            }
250            _ => out.push_str(&alloc::format!("%{:02X}", b)),
251        }
252    }
253    out
254}
255
256/// `encodeURI` 相当。encodeURIComponent の非エスケープ集合に加え、URI 予約文字
257/// (`;/?:@&=+$,#`) も未エスケープのまま残す(コンポーネントではなくURI全体用のため)。
258pub(crate) fn percent_encode_uri(s: &str) -> String {
259    let mut out = String::new();
260    for b in s.as_bytes() {
261        match *b {
262            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'!' | b'~' | b'*'
263            | b'\'' | b'(' | b')' | b';' | b'/' | b'?' | b':' | b'@' | b'&' | b'=' | b'+'
264            | b'$' | b',' | b'#' => {
265                out.push(*b as char);
266            }
267            _ => out.push_str(&alloc::format!("%{:02X}", b)),
268        }
269    }
270    out
271}
272
273pub(crate) fn js_encode_uri_component(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
274    Ok(Value::str(percent_encode_component(&arg(a, 0).to_js_string())))
275}
276pub(crate) fn js_decode_uri_component(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
277    Ok(Value::str(percent_decode(&arg(a, 0).to_js_string(), false)))
278}
279pub(crate) fn js_encode_uri(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
280    Ok(Value::str(percent_encode_uri(&arg(a, 0).to_js_string())))
281}
282pub(crate) fn js_decode_uri(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
283    Ok(Value::str(percent_decode(&arg(a, 0).to_js_string(), false)))
284}
285
286/// `escape(str)`(Annex B.2.1.1。ES6 で非推奨だが `encodeURI` 系登場以前からの
287/// 遺産として実務コードになお残る)が丸ごと未対応だった。仕様は UTF-16
288/// コード単位ベースだが、この処理系の文字列は UTF-8(Rust `String`)のため
289/// Unicode スカラー値(`char`)単位で近似する(BMP 外文字のサロゲートペア
290/// 分割は考慮しない、他の Unicode 境界処理と同水準の簡略化)。
291pub(crate) fn js_escape(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
292    let s = arg(a, 0).to_js_string();
293    let mut out = String::new();
294    for c in s.chars() {
295        let unreserved =
296            matches!(c, 'A'..='Z' | 'a'..='z' | '0'..='9' | '@' | '*' | '_' | '+' | '-' | '.' | '/');
297        if unreserved {
298            out.push(c);
299        } else {
300            let cp = c as u32;
301            if cp <= 0xFF {
302                out.push_str(&alloc::format!("%{:02X}", cp));
303            } else {
304                out.push_str(&alloc::format!("%u{:04X}", cp));
305            }
306        }
307    }
308    Ok(Value::str(out))
309}
310/// `unescape(str)`(Annex B.2.1.2)。`escape` の逆変換。`%XX`/`%uXXXX` を復元し、
311/// どちらの形にも一致しない `%` はそのまま素通しする(仕様どおり)。
312pub(crate) fn js_unescape(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
313    let s = arg(a, 0).to_js_string();
314    let chars: Vec<char> = s.chars().collect();
315    let mut out = String::new();
316    let mut i = 0;
317    while i < chars.len() {
318        if chars[i] == '%' && i + 6 <= chars.len() && chars[i + 1] == 'u' {
319            let hex: String = chars[i + 2..i + 6].iter().collect();
320            if let Some(ch) = u32::from_str_radix(&hex, 16).ok().and_then(char::from_u32) {
321                out.push(ch);
322                i += 6;
323                continue;
324            }
325        }
326        if chars[i] == '%' && i + 3 <= chars.len() {
327            let hex: String = chars[i + 1..i + 3].iter().collect();
328            if let Some(ch) = u32::from_str_radix(&hex, 16).ok().and_then(char::from_u32) {
329                out.push(ch);
330                i += 3;
331                continue;
332            }
333        }
334        out.push(chars[i]);
335        i += 1;
336    }
337    Ok(Value::str(out))
338}
339
340pub(crate) static PERF_TS: AtomicU64 = AtomicU64::new(0);
341/// `performance.now()` と同じ単調増加カウンタ(実クロックが無いこの環境の
342/// 簡略近似)。`Event.timeStamp`(イベント生成時刻)にも同じ発生源を使う。
343pub fn next_perf_timestamp() -> f64 {
344    PERF_TS.fetch_add(1, Ordering::Relaxed) as f64
345}
346pub(crate) fn performance_now(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
347    Ok(Value::Number(next_perf_timestamp()))
348}
349pub(crate) fn perf_entries(this: &Value) -> alloc::vec::Vec<Value> {
350    if let Value::Object(o) = this {
351        if let Some(Value::Object(arr)) = o.borrow().props.get("_entries") {
352            if let ObjKind::Array(items) = &arr.borrow().kind {
353                return items.clone();
354            }
355        }
356    }
357    alloc::vec::Vec::new()
358}
359pub(crate) fn perf_push_entry(this: &Value, entry: Value) {
360    if let Value::Object(o) = this {
361        let arr = o.borrow().props.get("_entries").cloned();
362        if let Some(Value::Object(a)) = arr {
363            if let ObjKind::Array(items) = &mut a.borrow_mut().kind {
364                items.push(entry);
365            }
366        }
367    }
368}
369pub(crate) fn make_perf_entry(name: &str, entry_type: &str, start_time: f64, duration: f64) -> Value {
370    let o = Obj::plain();
371    {
372        let mut b = o.borrow_mut();
373        b.props.insert("name".into(), Value::str(name));
374        b.props.insert("entryType".into(), Value::str(entry_type));
375        b.props.insert("startTime".into(), Value::Number(start_time));
376        b.props.insert("duration".into(), Value::Number(duration));
377    }
378    Value::Object(o)
379}
380/// `performance.mark(name)`。呼び出し時点の `performance.now()` を
381/// `startTime` として持つ `PerformanceMark` エントリを記録して返す。
382pub(crate) fn perf_mark(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
383    let name = arg(a, 0).to_js_string();
384    let start = next_perf_timestamp();
385    let entry = make_perf_entry(&name, "mark", start, 0.0);
386    perf_push_entry(&this, entry.clone());
387    perf_notify_observers(it, &this, &entry);
388    Ok(entry)
389}
390pub(crate) fn perf_find_mark_time(this: &Value, name: &str) -> Option<f64> {
391    perf_entries(this).into_iter().rev().find_map(|e| {
392        if let Value::Object(o) = &e {
393            let b = o.borrow();
394            let is_mark = b.props.get("entryType").map(|v| v.to_js_string()) == Some(String::from("mark"));
395            let matches_name = b.props.get("name").map(|v| v.to_js_string()) == Some(String::from(name));
396            if is_mark && matches_name {
397                return b.props.get("startTime").map(|v| v.to_number());
398            }
399        }
400        None
401    })
402}
403/// `performance.measure(name, startMark?, endMark?)`。マーク名は
404/// `performance.mark()` で記録済みのエントリから `startTime` を引く
405/// (未指定/未発見の開始マークは時刻 0、終了マークは現在時刻扱い)。
406pub(crate) fn perf_measure(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
407    let name = arg(a, 0).to_js_string();
408    let start_time = match arg(a, 1) {
409        Value::Undefined => 0.0,
410        v => perf_find_mark_time(&this, &v.to_js_string()).unwrap_or(0.0),
411    };
412    let end_time = match arg(a, 2) {
413        Value::Undefined => next_perf_timestamp(),
414        v => perf_find_mark_time(&this, &v.to_js_string()).unwrap_or_else(next_perf_timestamp),
415    };
416    let entry = make_perf_entry(&name, "measure", start_time, end_time - start_time);
417    perf_push_entry(&this, entry.clone());
418    perf_notify_observers(it, &this, &entry);
419    Ok(entry)
420}
421/// `new PerformanceObserver(callback)` が観測中のエントリ種別(`observe
422/// ({entryTypes:[...]})`)に一致する新規エントリを `performance.mark`/
423/// `.measure` の記録直後に同期的に通知する(仕様上のマイクロタスク経由の
424/// 非同期バッチ通知は簡略化し、エントリ1件ごとに即時コールバックする)。
425pub(crate) fn perf_notify_observers(it: &mut Interp, this: &Value, entry: &Value) {
426    let entry_type = if let Value::Object(o) = entry {
427        o.borrow().props.get("entryType").map(|v| v.to_js_string()).unwrap_or_default()
428    } else {
429        return;
430    };
431    let observers: Vec<Value> = if let Value::Object(o) = this {
432        match o.borrow().props.get("_observers") {
433            Some(Value::Object(arr)) => match &arr.borrow().kind {
434                ObjKind::Array(items) => items.clone(),
435                _ => Vec::new(),
436            },
437            _ => Vec::new(),
438        }
439    } else {
440        Vec::new()
441    };
442    for obs in observers {
443        let matches_type = if let Value::Object(o) = &obs {
444            match o.borrow().props.get("_entry_types") {
445                Some(Value::Object(arr)) => match &arr.borrow().kind {
446                    ObjKind::Array(items) => items.iter().any(|t| t.to_js_string() == entry_type),
447                    _ => false,
448                },
449                _ => false,
450            }
451        } else {
452            false
453        };
454        if !matches_type {
455            continue;
456        }
457        let callback = if let Value::Object(o) = &obs {
458            o.borrow().props.get("_callback").cloned()
459        } else {
460            None
461        };
462        if let Some(cb) = callback.and_then(callable_or_none) {
463            let list = Obj::plain();
464            {
465                let mut b = list.borrow_mut();
466                b.props
467                    .insert("_entries".into(), Value::Object(Obj::array(alloc::vec![entry.clone()])));
468                b.props
469                    .insert("getEntries".into(), nv("getEntries", perf_observer_list_get_entries));
470            }
471            it.call_listener(
472                &cb,
473                Value::Undefined,
474                &[Value::Object(list), obs.clone()],
475                "PerformanceObserver callback",
476            );
477        }
478    }
479}
480pub(crate) fn perf_observer_list_get_entries(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
481    if let Value::Object(o) = &this {
482        if let Some(v) = o.borrow().props.get("_entries").cloned() {
483            return Ok(v);
484        }
485    }
486    Ok(Value::Object(Obj::array(alloc::vec::Vec::new())))
487}
488/// `new PerformanceObserver(callback)`(丸ごと未対応だった)。
489pub(crate) fn performance_observer_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
490    let callback = arg(a, 0);
491    let o = Obj::plain();
492    {
493        let mut b = o.borrow_mut();
494        b.props.insert("_callback".into(), callback);
495        b.props
496            .insert("_entry_types".into(), Value::Object(Obj::array(alloc::vec::Vec::new())));
497        b.props.insert("observe".into(), nv("observe", performance_observer_observe));
498        b.props
499            .insert("disconnect".into(), nv("disconnect", performance_observer_disconnect));
500        b.props.insert(
501            "takeRecords".into(),
502            nv("takeRecords", |_: &mut Interp, _: Value, _: &[Value]| {
503                Ok(Value::Object(Obj::array(alloc::vec::Vec::new())))
504            }),
505        );
506    }
507    Ok(Value::Object(o))
508}
509/// `observer.observe({entryTypes: [...]})`(複数種別・`buffered`非対応の
510/// 従来形式)。監視対象の種別を記録し、`performance._observers` へ自身を
511/// 登録する(同一インスタンスの重複登録は無視する)。
512///
513/// `observer.observe({type: '...', buffered: true})`(単一種別+既存
514/// エントリの即時配信に対応する新形式)が丸ごと未対応だった(`entryTypes`
515/// しか読んでおらず、`type`単体で呼ぶと`_entry_types`が空配列のまま登録
516/// され、以後どんなエントリが記録されても一切発火しない黙殺バグだった。
517/// 2026-07-17 発見・実装)。`buffered: true`時は`performance._entries`
518/// から既存の一致エントリを即座に1回配信する(`perf_notify_observers`と
519/// 同じ`{_entries, getEntries}`ラッパー形状を再利用)。
520pub(crate) fn performance_observer_observe(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
521    let opts = arg(a, 0);
522    let types: Vec<Value> = match obj_prop(&opts, "entryTypes") {
523        Some(Value::Object(arr)) => match &arr.borrow().kind {
524            ObjKind::Array(items) => items.clone(),
525            _ => Vec::new(),
526        },
527        _ => match obj_prop(&opts, "type") {
528            Some(t) => alloc::vec![t],
529            None => Vec::new(),
530        },
531    };
532    let buffered = obj_prop(&opts, "buffered").map(|v| v.truthy()).unwrap_or(false);
533    if let Value::Object(o) = &this {
534        o.borrow_mut()
535            .props
536            .insert("_entry_types".into(), Value::Object(Obj::array(types.clone())));
537    }
538    let performance = it.global.borrow().vars.get("performance").cloned();
539    if let Some(Value::Object(perf)) = &performance {
540        let arr = perf.borrow().props.get("_observers").cloned();
541        if let Some(Value::Object(a2)) = arr {
542            if let ObjKind::Array(items) = &mut a2.borrow_mut().kind {
543                let dup = items.iter().any(|v| match (v, &this) {
544                    (Value::Object(x), Value::Object(y)) => Rc::ptr_eq(x, y),
545                    _ => false,
546                });
547                if !dup {
548                    items.push(this.clone());
549                }
550            }
551        }
552    }
553    if buffered {
554        if let Some(perf_val) = performance {
555            let want: Vec<String> = types.iter().map(|t| t.to_js_string()).collect();
556            let matching: Vec<Value> = perf_entries(&perf_val)
557                .into_iter()
558                .filter(|e| {
559                    if let Value::Object(o) = e {
560                        let et = o.borrow().props.get("entryType").map(|v| v.to_js_string()).unwrap_or_default();
561                        want.iter().any(|w| w == &et)
562                    } else {
563                        false
564                    }
565                })
566                .collect();
567            if !matching.is_empty() {
568                let callback = if let Value::Object(o) = &this {
569                    o.borrow().props.get("_callback").cloned()
570                } else {
571                    None
572                };
573                if let Some(cb) = callback.and_then(callable_or_none) {
574                    let list = Obj::plain();
575                    {
576                        let mut b = list.borrow_mut();
577                        b.props.insert("_entries".into(), Value::Object(Obj::array(matching)));
578                        b.props
579                            .insert("getEntries".into(), nv("getEntries", perf_observer_list_get_entries));
580                    }
581                    it.call_listener(
582                        &cb,
583                        Value::Undefined,
584                        &[Value::Object(list), this.clone()],
585                        "PerformanceObserver callback",
586                    );
587                }
588            }
589        }
590    }
591    Ok(Value::Undefined)
592}
593/// `observer.disconnect()`。`performance._observers` から自身を取り除く。
594pub(crate) fn performance_observer_disconnect(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
595    let performance = it.global.borrow().vars.get("performance").cloned();
596    if let Some(Value::Object(perf)) = performance {
597        let arr = perf.borrow().props.get("_observers").cloned();
598        if let Some(Value::Object(a2)) = arr {
599            if let ObjKind::Array(items) = &mut a2.borrow_mut().kind {
600                items.retain(|v| match (v, &this) {
601                    (Value::Object(x), Value::Object(y)) => !Rc::ptr_eq(x, y),
602                    _ => true,
603                });
604            }
605        }
606    }
607    Ok(Value::Undefined)
608}
609pub(crate) fn perf_get_entries(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
610    Ok(Value::Object(Obj::array(perf_entries(&this))))
611}
612pub(crate) fn perf_get_entries_by_type(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
613    let t = arg(a, 0).to_js_string();
614    let items: alloc::vec::Vec<Value> = perf_entries(&this)
615        .into_iter()
616        .filter(|e| {
617            matches!(e, Value::Object(o) if o.borrow().props.get("entryType").map(|v| v.to_js_string()) == Some(t.clone()))
618        })
619        .collect();
620    Ok(Value::Object(Obj::array(items)))
621}
622pub(crate) fn perf_get_entries_by_name(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
623    let name = arg(a, 0).to_js_string();
624    let type_filter = match arg(a, 1) {
625        Value::Undefined => None,
626        v => Some(v.to_js_string()),
627    };
628    let items: alloc::vec::Vec<Value> = perf_entries(&this)
629        .into_iter()
630        .filter(|e| {
631            if let Value::Object(o) = e {
632                let b = o.borrow();
633                let name_matches = b.props.get("name").map(|v| v.to_js_string()) == Some(name.clone());
634                let type_matches = type_filter
635                    .as_ref()
636                    .map(|t| b.props.get("entryType").map(|v| v.to_js_string()) == Some(t.clone()))
637                    .unwrap_or(true);
638                name_matches && type_matches
639            } else {
640                false
641            }
642        })
643        .collect();
644    Ok(Value::Object(Obj::array(items)))
645}
646pub(crate) fn perf_clear_by_type(this: &Value, entry_type: &str, name_filter: Value) {
647    if let Value::Object(o) = this {
648        let arr = o.borrow().props.get("_entries").cloned();
649        if let Some(Value::Object(a)) = arr {
650            let name_filter = match name_filter {
651                Value::Undefined => None,
652                v => Some(v.to_js_string()),
653            };
654            if let ObjKind::Array(items) = &mut a.borrow_mut().kind {
655                items.retain(|e| {
656                    if let Value::Object(eo) = e {
657                        let b = eo.borrow();
658                        let is_type =
659                            b.props.get("entryType").map(|v| v.to_js_string()) == Some(String::from(entry_type));
660                        let name_ok = name_filter
661                            .as_ref()
662                            .map(|n| b.props.get("name").map(|v| v.to_js_string()) == Some(n.clone()))
663                            .unwrap_or(true);
664                        !(is_type && name_ok)
665                    } else {
666                        true
667                    }
668                });
669            }
670        }
671    }
672}
673pub(crate) fn perf_clear_marks(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
674    perf_clear_by_type(&this, "mark", arg(a, 0));
675    Ok(Value::Undefined)
676}
677pub(crate) fn perf_clear_measures(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
678    perf_clear_by_type(&this, "measure", arg(a, 0));
679    Ok(Value::Undefined)
680}
681
682// ===== 乱数(crypto / randomUUID 用、Math.random と同じ xorshift64*)=====
683pub(crate) fn next_rand_u64() -> u64 {
684    let mut x = RNG_SEED.load(Ordering::Relaxed);
685    x ^= x >> 12;
686    x ^= x << 25;
687    x ^= x >> 27;
688    RNG_SEED.store(x, Ordering::Relaxed);
689    x.wrapping_mul(0x2545F4914F6CDD1D)
690}
691