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