1use crate::host::{fmt_number, with_host, JsObj};
17use fusevm::Value;
18use indexmap::IndexMap;
19
20pub const STATIC_METHODS: &[&str] = &["from", "of", "isView"];
21
22pub const UINT8_STATIC_METHODS: &[&str] = &["from", "of", "isView", "fromBase64", "fromHex"];
25
26pub const UINT8_PROTOTYPE_METHODS: &[&str] = &["toBase64", "setFromBase64", "toHex", "setFromHex"];
29
30pub fn static_methods(kind: &str) -> &'static [&'static str] {
32 if kind == "Uint8Array" {
33 UINT8_STATIC_METHODS
34 } else {
35 STATIC_METHODS
36 }
37}
38
39pub const PROTOTYPE_METHODS: &[&str] = &[
44 "at",
45 "copyWithin",
46 "entries",
47 "every",
48 "fill",
49 "filter",
50 "find",
51 "findIndex",
52 "findLast",
53 "findLastIndex",
54 "forEach",
55 "includes",
56 "indexOf",
57 "join",
58 "keys",
59 "lastIndexOf",
60 "map",
61 "reduce",
62 "reduceRight",
63 "reverse",
64 "set",
65 "slice",
66 "some",
67 "sort",
68 "subarray",
69 "toReversed",
70 "toSorted",
71 "toString",
72 "values",
73 "with",
74];
75
76pub fn is_ctor(name: &str) -> bool {
78 ELEMENT_KINDS.contains(&name) || matches!(name, "ArrayBuffer" | "DataView")
79}
80
81pub const ELEMENT_KINDS: &[&str] = &[
90 "Uint8Array",
91 "Int8Array",
92 "Uint8ClampedArray",
93 "Int16Array",
94 "Uint16Array",
95 "Int32Array",
96 "Uint32Array",
97 "Float32Array",
98 "Float64Array",
99 "BigInt64Array",
101 "BigUint64Array",
102];
103
104pub fn bytes_per_element(kind: &str) -> usize {
106 match kind {
107 "Int8Array" | "Uint8Array" | "Uint8ClampedArray" => 1,
108 "Int16Array" | "Uint16Array" => 2,
109 "Int32Array" | "Uint32Array" | "Float32Array" => 4,
110 "Float64Array" | "BigInt64Array" | "BigUint64Array" => 8,
111 _ => 1,
112 }
113}
114
115fn coerce(kind: &str, n: f64) -> f64 {
118 match kind {
119 "Int8Array" => (n as i64 as i8) as f64,
120 "Uint8Array" => (n as i64 as u8) as f64,
121 "Uint8ClampedArray" => {
122 if n.is_nan() {
123 0.0
124 } else {
125 n.round().clamp(0.0, 255.0)
126 }
127 }
128 "Int16Array" => (n as i64 as i16) as f64,
129 "Uint16Array" => (n as i64 as u16) as f64,
130 "Int32Array" => (n as i64 as i32) as f64,
131 "Uint32Array" => (n as i64 as u32) as f64,
132 "Float32Array" => n as f32 as f64,
133 _ => n, }
135}
136
137pub fn is_bigint_kind(kind: &str) -> bool {
141 matches!(kind, "BigInt64Array" | "BigUint64Array")
142}
143
144fn coerce_val(kind: &str, v: &Value) -> Result<Value, String> {
148 if !is_bigint_kind(kind) {
149 return Ok(Value::Float(coerce(kind, with_host(|h| h.to_number(v)))));
150 }
151 let big = crate::builtins::to_bigint(v)?;
157 Ok(with_host(|h| h.new_bigint(wrap_bigint(kind, big))))
158}
159
160fn wrap_bigint(kind: &str, b: num_bigint::BigInt) -> num_bigint::BigInt {
163 use num_traits::cast::ToPrimitive;
164 let modulus = num_bigint::BigInt::from(1u128 << 64);
165 let mut m = b % &modulus;
166 if m.sign() == num_bigint::Sign::Minus {
167 m += &modulus;
168 }
169 let raw = m.to_u64().unwrap_or(0);
171 if kind == "BigInt64Array" {
172 num_bigint::BigInt::from(raw as i64)
173 } else {
174 num_bigint::BigInt::from(raw)
175 }
176}
177
178fn bigint_of(v: &Value) -> num_bigint::BigInt {
181 with_host(|h| match h.get(v) {
182 Some(JsObj::BigInt(b)) => b.clone(),
183 _ => num_bigint::BigInt::from(0),
184 })
185}
186
187fn same_element(stored: &Value, needle: &Value, nan_matches: bool) -> bool {
195 if nan_matches {
196 if let (Value::Float(a), Value::Float(b)) = (stored, needle) {
197 if a.is_nan() && b.is_nan() {
198 return true;
199 }
200 }
201 }
202 with_host(|h| h.strict_eq(stored, needle))
203}
204
205fn zero_of(kind: &str) -> Value {
207 if is_bigint_kind(kind) {
208 with_host(|h| h.new_bigint(num_bigint::BigInt::from(0)))
209 } else {
210 Value::Float(0.0)
211 }
212}
213
214fn num(v: &Value) -> f64 {
218 with_host(|h| h.to_number(v))
219}
220
221pub fn elem_values(v: &Value) -> Vec<Value> {
225 let Some(tag) = super::native_tag(v) else {
226 return Vec::new();
227 };
228 if tag == "TypedArray" {
229 let kind = kind_of(v);
230 let bpe = bytes_per_element(&kind);
231 return (0..view_len(v))
232 .map(|i| {
233 view_bytes(v, i * bpe, bpe)
234 .map(|b| decode(&kind, &b))
235 .unwrap_or(Value::Undef)
236 })
237 .collect();
238 }
239 if tag != "Buffer" {
240 return Vec::new();
241 }
242 with_host(|h| match h.get(v) {
243 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|a| h.get(a)) {
244 Some(JsObj::Array(items)) => items.clone(),
245 _ => Vec::new(),
246 },
247 _ => Vec::new(),
248 })
249}
250
251fn make(kind: &str, elems: Vec<Value>) -> Value {
253 let bpe = bytes_per_element(kind);
254 let len = elems.len();
255 let buf = new_array_buffer(len * bpe);
256 let view = make_view(kind, &buf, 0, len);
257 for (i, e) in elems.iter().enumerate() {
258 write_view_bytes(&view, i * bpe, &encode(kind, e));
259 }
260 view
261}
262
263fn make_view(kind: &str, buf: &Value, byte_off: usize, len: usize) -> Value {
265 with_host(|h| {
266 let bpe = bytes_per_element(kind);
267 let mut m = IndexMap::new();
268 m.insert("@@native".into(), h.new_str("TypedArray"));
269 m.insert("@@kind".into(), h.new_str(kind));
270 m.insert("@@buffer".into(), buf.clone());
271 m.insert("buffer".into(), buf.clone());
274 m.insert("length".into(), Value::Float(len as f64));
275 m.insert("byteLength".into(), Value::Float((len * bpe) as f64));
276 m.insert("byteOffset".into(), Value::Float(byte_off as f64));
281 m.insert("BYTES_PER_ELEMENT".into(), Value::Float(bpe as f64));
282 let obj = h.new_object(m);
283 h.ensure_native_protos();
289 if let Some(p) = h.native_proto(kind) {
290 h.set_proto(&obj, p);
291 }
292 for k in [
294 "buffer",
295 "length",
296 "byteLength",
297 "byteOffset",
298 "BYTES_PER_ELEMENT",
299 ] {
300 h.hide_prop(&obj, k);
301 }
302 obj
303 })
304}
305
306fn integer_or_infinity(n: f64) -> f64 {
308 if n.is_nan() {
309 0.0
310 } else {
311 n.trunc() + 0.0
312 }
313}
314
315fn to_index(n: f64) -> Option<usize> {
317 let i = integer_or_infinity(n);
318 (0.0..=9_007_199_254_740_991.0)
319 .contains(&i)
320 .then_some(i as usize)
321}
322
323fn is_primitive(v: &Value) -> bool {
325 match v {
326 Value::Obj(_) => with_host(|h| {
327 matches!(
328 h.get(v),
329 Some(JsObj::Str(_))
330 | Some(JsObj::Null)
331 | Some(JsObj::BigInt(_))
332 | Some(JsObj::Symbol { .. })
333 )
334 }),
335 _ => true,
336 }
337}
338
339pub fn construct(kind: &str, args: &[Value]) -> Result<Value, String> {
342 if kind == "ArrayBuffer" {
343 let n = to_index(super::arg_num(args, 0))
344 .ok_or_else(|| crate::host::range_error("Invalid array buffer length"))?;
345 let max = match args.get(1) {
347 Some(opts) => {
348 crate::builtins::get_property(opts, "maxByteLength").unwrap_or(Value::Undef)
349 }
350 None => Value::Undef,
351 };
352 let max_len = match max {
353 Value::Undef => None,
354 _ => match to_index(with_host(|h| h.to_number(&max))) {
355 Some(m) if m >= n => Some(m),
356 _ => return Err(crate::host::range_error("Invalid array buffer max length")),
357 },
358 };
359 let ab = new_array_buffer(n);
360 if let Some(m) = max_len {
363 with_host(|h| {
364 if let Some(JsObj::Object(p)) = h.get_mut(&ab) {
365 p.insert("@@maxByteLength".into(), Value::Float(m as f64));
366 p.insert("maxByteLength".into(), Value::Float(m as f64));
367 p.insert("resizable".into(), Value::Bool(true));
368 }
369 h.hide_prop(&ab, "maxByteLength");
370 h.hide_prop(&ab, "resizable");
371 });
372 }
373 return Ok(ab);
374 }
375 if let Some(first) = args.first() {
380 if super::native_tag(first).as_deref() == Some("ArrayBuffer") {
381 if is_detached(first) {
383 return Err(crate::host::type_error(
384 "Cannot perform Construct on a detached ArrayBuffer",
385 ));
386 }
387 let bpe = bytes_per_element(kind);
390 let total = buffer_byte_length(first);
391 let off_n = super::arg_num(args, 1);
392 let off = to_index(off_n).ok_or_else(|| {
393 crate::host::range_error(&format!(
394 "Start offset {} is outside the bounds of the buffer",
395 fmt_number(off_n)
396 ))
397 })?;
398 if off % bpe != 0 {
399 return Err(crate::host::range_error(&format!(
400 "start offset of {kind} should be a multiple of {bpe}"
401 )));
402 }
403 let len = match args.get(2) {
404 Some(Value::Undef) | None => {
405 if total % bpe != 0 {
406 return Err(crate::host::range_error(&format!(
407 "byte length of {kind} should be a multiple of {bpe}"
408 )));
409 }
410 if off > total {
411 return Err(crate::host::range_error(&format!(
412 "Start offset {off} is outside the bounds of the buffer"
413 )));
414 }
415 (total - off) / bpe
416 }
417 Some(_) => {
418 let len_n = super::arg_num(args, 2);
419 let bad = || {
420 crate::host::range_error(&format!(
421 "Invalid typed array length: {}",
422 fmt_number(len_n)
423 ))
424 };
425 let len = to_index(len_n).ok_or_else(bad)?;
426 if off + len * bpe > total {
427 return Err(bad());
428 }
429 len
430 }
431 };
432 return Ok(make_view(kind, first, off, len));
433 }
434 }
435 let elems = build_elems(kind, args)?;
436 Ok(make(kind, elems))
437}
438
439pub const DATAVIEW_METHODS: &[&str] = &[
441 "getInt8",
442 "getUint8",
443 "getInt16",
444 "getUint16",
445 "getInt32",
446 "getUint32",
447 "getFloat32",
448 "getFloat64",
449 "getBigInt64",
450 "getBigUint64",
451 "setInt8",
452 "setUint8",
453 "setInt16",
454 "setUint16",
455 "setInt32",
456 "setUint32",
457 "setFloat32",
458 "setFloat64",
459 "setBigInt64",
460 "setBigUint64",
461];
462
463pub fn construct_dataview(args: &[Value]) -> Result<Value, String> {
465 let buf = args.first().cloned().unwrap_or(Value::Undef);
466 if super::native_tag(&buf).as_deref() != Some("ArrayBuffer") {
467 return Err(crate::host::type_error(
468 "First argument to DataView constructor must be an ArrayBuffer",
469 ));
470 }
471 let total = buffer_byte_length(&buf);
474 let off_n = super::arg_num(args, 1);
475 let outside = |n: f64| {
476 crate::host::range_error(&format!(
477 "Start offset {} is outside the bounds of the buffer",
478 fmt_number(integer_or_infinity(n))
479 ))
480 };
481 let off = to_index(off_n).ok_or_else(|| outside(off_n))?;
482 if off > total {
483 return Err(outside(off_n));
484 }
485 let bad_len = |n: f64| {
486 crate::host::range_error(&format!(
487 "Invalid DataView length {}",
488 fmt_number(integer_or_infinity(n))
489 ))
490 };
491 let len = match args.get(2) {
492 Some(Value::Undef) | None => total - off,
493 Some(_) => {
494 let len_n = super::arg_num(args, 2);
495 let len = to_index(len_n).ok_or_else(|| bad_len(len_n))?;
496 if off + len > total {
497 return Err(bad_len(len_n));
498 }
499 len
500 }
501 };
502 Ok(with_host(|h| {
503 let mut m = IndexMap::new();
504 m.insert("@@native".into(), h.new_str("DataView"));
505 m.insert("@@buffer".into(), buf.clone());
506 m.insert("buffer".into(), buf.clone());
507 m.insert("byteOffset".into(), Value::Float(off as f64));
508 m.insert("byteLength".into(), Value::Float(len as f64));
509 let obj = h.new_object(m);
510 for k in ["buffer", "byteOffset", "byteLength"] {
511 h.hide_prop(&obj, k);
512 }
513 h.ensure_native_protos();
514 if let Some(p) = h.ensure_ctor_proto("DataView") {
515 h.set_proto(&obj, p);
516 }
517 obj
518 }))
519}
520
521pub fn dataview_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
524 if view_detached(recv) {
525 return Err(detached_error("DataView.prototype", method, false));
526 }
527 let Some(spec) = method.get(3..) else {
528 return Err(crate::host::type_error(&format!(
529 "{method} is not a function"
530 )));
531 };
532 let width = match spec {
533 "Int8" | "Uint8" => 1,
534 "Int16" | "Uint16" => 2,
535 "Int32" | "Uint32" | "Float32" => 4,
536 "Float64" | "BigInt64" | "BigUint64" => 8,
537 _ => {
538 return Err(crate::host::type_error(&format!(
539 "{method} is not a function"
540 )))
541 }
542 };
543 let is_get = method.starts_with("get");
544 let requested = super::arg_num(args, 0);
549 let requested = if requested.is_nan() {
550 0.0
551 } else {
552 requested.trunc()
553 };
554 let span = with_host(|h| match h.get(recv) {
555 Some(JsObj::Object(p)) => p.get("byteLength").map(|l| h.to_number(l)).unwrap_or(0.0),
556 _ => 0.0,
557 });
558 if requested < 0.0 || requested + width as f64 > span {
559 return Err(crate::host::range_error(
560 "Offset is outside the bounds of the DataView",
561 ));
562 }
563 let at = requested as usize;
564 let le = with_host(|h| {
567 h.truthy(
568 args.get(if is_get { 1 } else { 2 })
569 .unwrap_or(&Value::Undef),
570 )
571 });
572 if is_get {
573 let mut b = view_bytes(recv, at, width).unwrap_or_else(|| vec![0; width]);
574 if !le {
575 b.reverse();
576 }
577 return Ok(match spec {
578 "Int8" => Value::Float(b[0] as i8 as f64),
579 "Uint8" => Value::Float(b[0] as f64),
580 "Int16" => Value::Float(i16::from_le_bytes([b[0], b[1]]) as f64),
581 "Uint16" => Value::Float(u16::from_le_bytes([b[0], b[1]]) as f64),
582 "Int32" => Value::Float(i32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
583 "Uint32" => Value::Float(u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
584 "Float32" => Value::Float(f32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
585 "Float64" => Value::Float(f64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]))),
586 "BigInt64" => {
587 let raw = i64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
588 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
589 }
590 _ => {
591 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
592 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
593 }
594 });
595 }
596 let val = args.get(1).cloned().unwrap_or(Value::Undef);
597 let mut b = match spec {
598 "BigInt64" | "BigUint64" => {
599 use num_traits::cast::ToPrimitive;
600 let big = crate::builtins::to_bigint(&val)?;
603 let raw = if spec == "BigInt64" {
604 big.to_i64().unwrap_or(0) as u64
605 } else {
606 big.to_u64().unwrap_or(0)
607 };
608 raw.to_le_bytes().to_vec()
609 }
610 _ => {
611 let n = with_host(|h| h.to_number(&val));
612 match spec {
613 "Int8" | "Uint8" => vec![n as i64 as u8],
614 "Int16" | "Uint16" => (n as i64 as u16).to_le_bytes().to_vec(),
615 "Int32" | "Uint32" => (n as i64 as u32).to_le_bytes().to_vec(),
616 "Float32" => (n as f32).to_le_bytes().to_vec(),
617 _ => n.to_le_bytes().to_vec(),
618 }
619 }
620 };
621 if !le {
622 b.reverse();
623 }
624 write_view_bytes(recv, at, &b);
625 Ok(Value::Undef)
626}
627
628pub fn buffer_resize(ab: &Value, args: &[Value]) -> Result<Value, String> {
631 let max = with_host(|h| match h.get(ab) {
632 Some(JsObj::Object(p)) => p.get("@@maxByteLength").map(|m| h.to_number(m) as usize),
633 _ => None,
634 })
635 .ok_or_else(|| {
636 crate::host::type_error(
637 "ArrayBuffer.prototype.resize called on a non-resizable ArrayBuffer",
638 )
639 })?;
640 let n = super::arg_num(args, 0).max(0.0) as usize;
641 if n > max {
642 return Err(crate::host::range_error("Invalid array buffer length"));
643 }
644 let store = store_of(ab);
645 with_host(|h| {
646 if let Some(a) = store {
647 if let Some(JsObj::Array(items)) = h.get_mut(&a) {
648 items.resize(n, Value::Float(0.0));
649 }
650 }
651 if let Some(JsObj::Object(p)) = h.get_mut(ab) {
652 p.insert("byteLength".into(), Value::Float(n as f64));
653 }
654 });
655 Ok(Value::Undef)
656}
657
658pub fn write_buffer_bytes(ab: &Value, bytes: &[u8]) {
661 let Some(store) = store_of(ab) else { return };
662 with_host(|h| {
663 if let Some(JsObj::Array(items)) = h.get_mut(&store) {
664 *items = bytes.iter().map(|b| Value::Float(*b as f64)).collect();
665 }
666 if let Some(JsObj::Object(p)) = h.get_mut(ab) {
667 p.insert("byteLength".into(), Value::Float(bytes.len() as f64));
668 }
669 });
670}
671
672pub fn buffer_store(ab: &Value) -> Option<Value> {
675 store_of(ab)
676}
677
678pub fn buffer_bytes_snapshot(ab: &Value) -> Option<Vec<u8>> {
680 let store = store_of(ab)?;
681 with_host(|h| match h.get(&store) {
682 Some(JsObj::Array(items)) => {
683 Some(items.iter().map(|x| h.to_number(x) as i64 as u8).collect())
684 }
685 _ => None,
686 })
687}
688
689pub fn buffer_byte_length(ab: &Value) -> usize {
691 with_host(|h| match h.get(ab) {
692 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|a| h.get(a)) {
693 Some(JsObj::Array(items)) => items.len(),
694 _ => 0,
695 },
696 _ => 0,
697 })
698}
699
700pub fn buffer_slice(ab: &Value, args: &[Value]) -> Value {
703 let total = buffer_byte_length(ab) as i64;
704 let idx = |v: Option<&Value>, dflt: i64| -> usize {
705 let n = match v {
706 None | Some(Value::Undef) => dflt,
707 Some(x) => with_host(|h| h.to_number(x)) as i64,
708 };
709 (if n < 0 { total + n } else { n }).clamp(0, total) as usize
710 };
711 let start = idx(args.first(), 0);
712 let end = idx(args.get(1), total).max(start);
713 let out = new_array_buffer(end - start);
714 let src = with_host(|h| match h.get(ab) {
715 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|a| h.get(a)) {
716 Some(JsObj::Array(items)) => items[start..end].to_vec(),
717 _ => Vec::new(),
718 },
719 _ => Vec::new(),
720 });
721 with_host(|h| {
722 if let Some(JsObj::Object(p)) = h.get(&out) {
723 if let Some(arr) = p.get("@@bytes").cloned() {
724 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
725 *items = src;
726 }
727 }
728 }
729 });
730 out
731}
732
733fn build_elems(kind: &str, args: &[Value]) -> Result<Vec<Value>, String> {
737 match args.first() {
738 None | Some(Value::Undef) => Ok(Vec::new()),
739 Some(v) if is_primitive(v) => {
743 let n_raw = super::arg_num(args, 0);
744 let n = to_index(n_raw).ok_or_else(|| {
745 crate::host::range_error(&format!(
746 "Invalid typed array length: {}",
747 fmt_number(n_raw)
748 ))
749 })?;
750 Ok(vec![zero_of(kind); n])
751 }
752 Some(v) => {
753 let items = match super::native_tag(v).as_deref() {
756 Some("TypedArray") | Some("Buffer") => elem_values(v),
757 _ => crate::host::iter_all(v).unwrap_or_default(),
758 };
759 items.iter().map(|x| coerce_val(kind, x)).collect()
760 }
761 }
762}
763
764#[derive(Clone, Copy, PartialEq)]
769enum LastChunk {
770 Loose,
771 Strict,
772 StopBeforePartial,
773}
774
775fn base64_options(opt: Option<&Value>) -> Result<(bool, LastChunk), String> {
779 let Some(o) = opt.filter(|v| !matches!(v, Value::Undef)) else {
780 return Ok((false, LastChunk::Loose));
781 };
782 if !with_host(|h| matches!(h.get(o), Some(JsObj::Object(_)))) {
783 return Err(crate::host::type_error("invalid_argument"));
784 }
785 let read = |k: &str| {
786 with_host(|h| match h.get(o) {
787 Some(JsObj::Object(p)) => p.get(k).filter(|v| !matches!(v, Value::Undef)).cloned(),
788 _ => None,
789 })
790 };
791 let url = match read("alphabet") {
792 None => false,
793 Some(v) => match with_host(|h| h.str_of(&v)).as_str() {
794 "base64" => false,
795 "base64url" => true,
796 other => return Err(crate::host::type_error(&format!("invalid option {other}"))),
797 },
798 };
799 let last = match read("lastChunkHandling") {
800 None => LastChunk::Loose,
801 Some(v) => match with_host(|h| h.str_of(&v)).as_str() {
802 "loose" => LastChunk::Loose,
803 "strict" => LastChunk::Strict,
804 "stop-before-partial" => LastChunk::StopBeforePartial,
805 other => return Err(crate::host::type_error(&format!("invalid option {other}"))),
806 },
807 };
808 Ok((url, last))
809}
810
811const B64_BAD: &str =
812 "SyntaxError: Found a character that cannot be part of a valid base64 string.";
813const B64_SINGLE: &str =
814 "SyntaxError: The base64 input terminates with a single character, excluding padding (=).";
815
816fn decode_base64_strict(s: &str, url: bool, last: LastChunk) -> Result<(Vec<u8>, usize), String> {
823 let value = |c: char| -> Option<u32> {
824 let table = if url {
825 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
826 } else {
827 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
828 };
829 table.find(c).map(|i| i as u32)
830 };
831 let chars: Vec<char> = s.chars().collect();
832 let mut out = Vec::new();
833 let mut chunk: Vec<u32> = Vec::new();
834 let mut consumed = 0usize;
835 let mut i = 0usize;
836 while i < chars.len() {
837 let c = chars[i];
838 if c.is_ascii_whitespace() {
839 i += 1;
840 continue;
841 }
842 if c == '=' {
843 let pads = chars[i..].iter().filter(|c| **c == '=').count();
846 let rest_ok = chars[i..]
847 .iter()
848 .all(|c| *c == '=' || c.is_ascii_whitespace());
849 let want = 4 - chunk.len();
850 if !rest_ok || chunk.len() < 2 || pads != want {
851 return Err(B64_BAD.into());
852 }
853 out.extend(flush_base64_chunk(&chunk));
854 return Ok((out, chars.len()));
855 }
856 let Some(v) = value(c) else {
857 return Err(B64_BAD.into());
858 };
859 chunk.push(v);
860 i += 1;
861 if chunk.len() == 4 {
862 out.extend(flush_base64_chunk(&chunk));
863 chunk.clear();
864 consumed = i;
865 }
866 }
867 match chunk.len() {
868 0 => Ok((out, consumed)),
869 1 if last != LastChunk::StopBeforePartial => Err(B64_SINGLE.into()),
871 _ if last == LastChunk::StopBeforePartial => Ok((out, consumed)),
872 1 => Ok((out, consumed)),
873 _ if last == LastChunk::Strict => Err(B64_SINGLE.into()),
874 _ => {
875 out.extend(flush_base64_chunk(&chunk));
876 Ok((out, chars.len()))
877 }
878 }
879}
880
881fn flush_base64_chunk(chunk: &[u32]) -> Vec<u8> {
883 let mut acc = 0u32;
884 for v in chunk {
885 acc = (acc << 6) | v;
886 }
887 let bytes = chunk.len() - 1;
888 acc <<= 6 * (4 - chunk.len());
889 let all = [(acc >> 16) as u8, (acc >> 8) as u8, acc as u8];
890 all[..bytes].to_vec()
891}
892
893const HEX_BAD: &str = "SyntaxError: Input string must contain hex characters in even length";
894
895fn decode_hex_strict(s: &str) -> Result<Vec<u8>, String> {
898 let chars: Vec<char> = s.chars().collect();
899 if chars.len() % 2 != 0 || !chars.iter().all(|c| c.is_ascii_hexdigit()) {
900 return Err(HEX_BAD.into());
901 }
902 Ok(chars
903 .chunks(2)
904 .map(|p| {
905 let hi = p[0].to_digit(16).expect("checked");
906 let lo = p[1].to_digit(16).expect("checked");
907 (hi * 16 + lo) as u8
908 })
909 .collect())
910}
911
912fn base64_input(args: &[Value]) -> Result<String, String> {
915 let v = args.first().cloned().unwrap_or(Value::Undef);
916 let is_str = matches!(v, Value::Str(_))
917 || with_host(|h| matches!(h.get(&v), Some(crate::host::JsObj::Str(_))));
918 if !is_str {
919 return Err(crate::host::type_error("input argument must be a string"));
920 }
921 Ok(with_host(|h| h.str_of(&v)))
922}
923
924fn from_base64_static(method: &str, args: &[Value]) -> Result<Value, String> {
926 let s = base64_input(args)?;
927 let bytes = if method == "fromHex" {
928 decode_hex_strict(&s)?
929 } else {
930 let (url, last) = base64_options(args.get(1))?;
931 decode_base64_strict(&s, url, last)?.0
932 };
933 Ok(make(
934 "Uint8Array",
935 bytes.iter().map(|b| Value::Float(*b as f64)).collect(),
936 ))
937}
938
939pub fn static_call(kind: &str, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
942 if matches!(method, "fromBase64" | "fromHex") {
944 if kind != "Uint8Array" {
945 return None;
946 }
947 return Some(from_base64_static(method, args));
948 }
949 Some(match method {
950 "of" => args
951 .iter()
952 .map(|x| coerce_val(kind, x))
953 .collect::<Result<Vec<Value>, String>>()
954 .map(|e| make(kind, e)),
955 "from" => from(kind, args),
956 "isView" => Ok(Value::Bool(with_host(|h| {
959 matches!(
960 h.get(&args.first().cloned().unwrap_or(Value::Undef)),
961 Some(crate::host::JsObj::Object(p))
962 if matches!(
963 p.get("@@native").map(|t| h.str_of(t)).as_deref(),
964 Some("TypedArray") | Some("Buffer") | Some("DataView")
965 )
966 )
967 }))),
968 _ => return None,
969 })
970}
971
972fn from(kind: &str, args: &[Value]) -> Result<Value, String> {
973 let src = args.first().cloned().unwrap_or(Value::Undef);
974 let map_fn = args
975 .get(1)
976 .cloned()
977 .filter(|f| with_host(|h| crate::host::is_callable(h, f)));
978 let items = if let Some(e) = elems_of(&src) {
981 e.into_iter().map(Value::Float).collect()
982 } else {
983 crate::host::iter_all(&src)
984 .unwrap_or_else(|_| crate::builtins::array_like_items(&src))
985 };
986 let mut out = Vec::with_capacity(items.len());
987 for (i, it) in items.into_iter().enumerate() {
988 let mapped = match &map_fn {
989 Some(f) => crate::host::invoke(f, vec![it, Value::Float(i as f64)], None)?,
990 None => it,
991 };
992 out.push(coerce_val(kind, &mapped)?);
993 }
994 Ok(make(kind, out))
995}
996
997pub fn elems_of(v: &Value) -> Option<Vec<f64>> {
999 let tag = super::native_tag(v)?;
1000 if !matches!(tag.as_str(), "TypedArray" | "Buffer") {
1001 return None;
1002 }
1003 let vals = elem_values(v);
1004 Some(with_host(|h| vals.iter().map(|x| h.to_number(x)).collect()))
1005}
1006
1007pub fn index_len(v: &Value) -> Option<usize> {
1016 match super::native_tag(v)?.as_str() {
1017 "TypedArray" => Some(view_len(v)),
1018 "Buffer" => with_host(|h| match h.get(v) {
1019 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|a| h.get(a)) {
1020 Some(JsObj::Array(items)) => Some(items.len()),
1021 _ => None,
1022 },
1023 _ => None,
1024 }),
1025 _ => None,
1026 }
1027}
1028
1029pub fn has_index(v: &Value, key: &str) -> Option<bool> {
1032 let len = index_len(v)?;
1033 Some(key.parse::<usize>().map(|i| i < len).unwrap_or(false))
1034}
1035
1036pub fn kind_of(recv: &Value) -> String {
1038 with_host(|h| match h.get(recv) {
1039 Some(JsObj::Object(p)) => p
1040 .get("@@kind")
1041 .map(|v| h.str_of(v))
1042 .unwrap_or_else(|| "Uint8Array".into()),
1043 _ => "Uint8Array".into(),
1044 })
1045}
1046
1047pub fn new_array_buffer(n: usize) -> Value {
1059 with_host(|h| {
1060 let arr = h.new_array(vec![Value::Float(0.0); n]);
1061 let mut m = IndexMap::new();
1062 m.insert("@@native".into(), h.new_str("ArrayBuffer"));
1063 m.insert("@@bytes".into(), arr);
1064 m.insert("byteLength".into(), Value::Float(n as f64));
1065 m.insert("detached".into(), Value::Bool(false));
1069 m.insert("resizable".into(), Value::Bool(false));
1072 m.insert("maxByteLength".into(), Value::Float(n as f64));
1073 let obj = h.new_object(m);
1074 for k in ["byteLength", "detached", "resizable", "maxByteLength"] {
1075 h.hide_prop(&obj, k);
1076 }
1077 if let Some(p) = h.ensure_ctor_proto("ArrayBuffer") {
1080 h.set_proto(&obj, p);
1081 }
1082 obj
1083 })
1084}
1085
1086pub fn is_detached(ab: &Value) -> bool {
1092 with_host(|h| match h.get(ab) {
1093 Some(JsObj::Object(p)) => p.get("detached").map(|v| h.truthy(v)).unwrap_or(false),
1094 _ => false,
1095 })
1096}
1097
1098pub fn view_detached(v: &Value) -> bool {
1100 with_host(|h| view_detached_h(h, v))
1101}
1102
1103pub fn view_detached_h(h: &crate::host::JsHost, v: &Value) -> bool {
1106 let buf = match h.get(v) {
1107 Some(JsObj::Object(p)) => p.get("@@buffer").cloned(),
1108 _ => None,
1109 };
1110 match buf.and_then(|b| match h.get(&b) {
1111 Some(JsObj::Object(p)) => p.get("detached").cloned(),
1112 _ => None,
1113 }) {
1114 Some(d) => h.truthy(&d),
1115 None => false,
1116 }
1117}
1118
1119pub fn detach_buffer(ab: &Value) {
1122 detach(ab)
1123}
1124
1125fn detach(ab: &Value) {
1126 with_host(|h| {
1127 let empty = h.new_array(Vec::new());
1128 if let Some(JsObj::Object(p)) = h.get_mut(ab) {
1129 p.insert("@@bytes".into(), empty);
1130 p.insert("byteLength".into(), Value::Float(0.0));
1131 p.insert("detached".into(), Value::Bool(true));
1132 }
1133 h.hide_prop(ab, "byteLength");
1134 h.hide_prop(ab, "detached");
1135 });
1136}
1137
1138pub fn buffer_transfer(ab: &Value, args: &[Value], fixed: bool) -> Result<Value, String> {
1144 let method = if fixed {
1145 "transferToFixedLength"
1146 } else {
1147 "transfer"
1148 };
1149 if is_detached(ab) {
1150 return Err(crate::host::type_error(&format!(
1151 "Cannot perform ArrayBuffer.prototype.{method} on a detached ArrayBuffer"
1152 )));
1153 }
1154 let old = byte_len_of(ab);
1155 let new_len = match args.first().filter(|v| !matches!(v, Value::Undef)) {
1156 Some(v) => with_host(|h| h.to_number(v)).max(0.0) as usize,
1157 None => old,
1158 };
1159 let mut bytes = view_bytes_of_buffer(ab, old);
1160 bytes.resize(new_len, 0);
1161 let out = new_array_buffer(new_len);
1162 write_buffer_bytes(&out, &bytes);
1163 if !fixed {
1164 let resizable = with_host(|h| match h.get(ab) {
1167 Some(JsObj::Object(p)) => p.contains_key("@@maxByteLength"),
1168 _ => false,
1169 });
1170 if resizable {
1171 let max = with_host(|h| match h.get(ab) {
1172 Some(JsObj::Object(p)) => p.get("@@maxByteLength").cloned(),
1173 _ => None,
1174 });
1175 if let Some(max) = max {
1176 with_host(|h| {
1177 if let Some(JsObj::Object(p)) = h.get_mut(&out) {
1178 p.insert("@@maxByteLength".into(), max);
1179 }
1180 });
1181 }
1182 }
1183 }
1184 detach(ab);
1185 Ok(out)
1186}
1187
1188fn byte_len_of(ab: &Value) -> usize {
1190 with_host(|h| match h.get(ab) {
1191 Some(JsObj::Object(p)) => {
1192 p.get("byteLength").map(|l| h.to_number(l)).unwrap_or(0.0) as usize
1193 }
1194 _ => 0,
1195 })
1196}
1197
1198fn view_bytes_of_buffer(ab: &Value, n: usize) -> Vec<u8> {
1200 let Some(store) = store_of(ab) else {
1201 return Vec::new();
1202 };
1203 with_host(|h| match h.get(&store) {
1204 Some(JsObj::Array(items)) => items
1205 .iter()
1206 .take(n)
1207 .map(|x| h.to_number(x) as i64 as u8)
1208 .collect(),
1209 _ => Vec::new(),
1210 })
1211}
1212
1213pub fn detached_error(label: &str, method: &str, buffer_only: bool) -> String {
1216 let tail = if buffer_only {
1217 "a detached ArrayBuffer"
1218 } else {
1219 "a detached or out-of-bounds ArrayBuffer"
1220 };
1221 let method = match method {
1227 "@@iterator" => "values",
1228 other => other,
1229 };
1230 crate::host::type_error(&format!("Cannot perform {label}.{method} on {tail}"))
1231}
1232
1233fn store_of(ab: &Value) -> Option<Value> {
1235 with_host(|h| match h.get(ab) {
1236 Some(JsObj::Object(p)) => p.get("@@bytes").cloned(),
1237 _ => None,
1238 })
1239}
1240
1241fn view_base(v: &Value) -> Option<(Value, usize)> {
1243 with_host(|h| match h.get(v) {
1244 Some(JsObj::Object(p)) => {
1245 let buf = p.get("@@buffer").cloned()?;
1246 let off = p.get("byteOffset").map(|o| h.to_number(o)).unwrap_or(0.0);
1247 Some((buf, off.max(0.0) as usize))
1248 }
1249 _ => None,
1250 })
1251}
1252
1253pub fn view_bytes(v: &Value, at: usize, n: usize) -> Option<Vec<u8>> {
1255 let (buf, off) = view_base(v)?;
1256 let store = store_of(&buf)?;
1257 with_host(|h| match h.get(&store) {
1258 Some(JsObj::Array(items)) => {
1259 let start = off + at;
1260 if start + n > items.len() {
1261 return None;
1262 }
1263 Some(
1264 items[start..start + n]
1265 .iter()
1266 .map(|x| h.to_number(x) as i64 as u8)
1267 .collect(),
1268 )
1269 }
1270 _ => None,
1271 })
1272}
1273
1274pub fn write_view_bytes(v: &Value, at: usize, bytes: &[u8]) -> bool {
1277 let Some((buf, off)) = view_base(v) else {
1278 return false;
1279 };
1280 let Some(store) = store_of(&buf) else {
1281 return false;
1282 };
1283 with_host(|h| match h.get_mut(&store) {
1284 Some(JsObj::Array(items)) => {
1285 let start = off + at;
1286 if start + bytes.len() > items.len() {
1287 return false;
1288 }
1289 for (i, b) in bytes.iter().enumerate() {
1290 items[start + i] = Value::Float(*b as f64);
1291 }
1292 true
1293 }
1294 _ => false,
1295 })
1296}
1297
1298fn decode(kind: &str, b: &[u8]) -> Value {
1301 match kind {
1302 "Int8Array" => Value::Float(b[0] as i8 as f64),
1303 "Uint8Array" | "Uint8ClampedArray" => Value::Float(b[0] as f64),
1304 "Int16Array" => Value::Float(i16::from_le_bytes([b[0], b[1]]) as f64),
1305 "Uint16Array" => Value::Float(u16::from_le_bytes([b[0], b[1]]) as f64),
1306 "Int32Array" => Value::Float(i32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
1307 "Uint32Array" => Value::Float(u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
1308 "Float32Array" => Value::Float(f32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
1309 "BigInt64Array" => {
1310 let raw = i64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1311 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
1312 }
1313 "BigUint64Array" => {
1314 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1315 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
1316 }
1317 _ => Value::Float(f64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]))),
1318 }
1319}
1320
1321fn encode(kind: &str, v: &Value) -> Vec<u8> {
1323 if is_bigint_kind(kind) {
1324 use num_traits::cast::ToPrimitive;
1325 let b = bigint_of(v);
1326 let raw = if kind == "BigInt64Array" {
1327 b.to_i64().unwrap_or(0) as u64
1328 } else {
1329 b.to_u64().unwrap_or(0)
1330 };
1331 return raw.to_le_bytes().to_vec();
1332 }
1333 let n = num(v);
1334 match kind {
1335 "Int8Array" => vec![n as i64 as i8 as u8],
1336 "Uint8Array" | "Uint8ClampedArray" => vec![n as i64 as u8],
1337 "Int16Array" => (n as i64 as i16).to_le_bytes().to_vec(),
1338 "Uint16Array" => (n as i64 as u16).to_le_bytes().to_vec(),
1339 "Int32Array" => (n as i64 as i32).to_le_bytes().to_vec(),
1340 "Uint32Array" => (n as i64 as u32).to_le_bytes().to_vec(),
1341 "Float32Array" => (n as f32).to_le_bytes().to_vec(),
1342 _ => n.to_le_bytes().to_vec(),
1343 }
1344}
1345
1346pub fn elems_with_host(h: &crate::host::JsHost, v: &Value) -> Vec<Value> {
1351 if let Some(JsObj::Object(p)) = h.get(v) {
1352 if let Some(arr) = p.get("@@bytes") {
1353 return match h.get(arr) {
1354 Some(JsObj::Array(items)) => items.clone(),
1355 _ => Vec::new(),
1356 };
1357 }
1358 }
1359 let Some((kind, raws)) = raw_elems(h, v) else {
1360 return Vec::new();
1361 };
1362 if is_bigint_kind(&kind) {
1365 return vec![Value::Undef; raws.len()];
1366 }
1367 raws.iter().map(|b| decode(&kind, b)).collect()
1368}
1369
1370fn raw_elems(h: &crate::host::JsHost, v: &Value) -> Option<(String, Vec<Vec<u8>>)> {
1373 let JsObj::Object(p) = h.get(v)? else {
1374 return None;
1375 };
1376 let kind = p
1377 .get("@@kind")
1378 .map(|k| h.str_of(k))
1379 .unwrap_or_else(|| "Uint8Array".into());
1380 let bpe = bytes_per_element(&kind);
1381 let len = if view_detached_h(h, v) {
1385 0
1386 } else {
1387 p.get("length").map(|l| h.to_number(l)).unwrap_or(0.0) as usize
1388 };
1389 let off = p.get("byteOffset").map(|o| h.to_number(o)).unwrap_or(0.0) as usize;
1390 let store = match p.get("@@buffer").and_then(|b| h.get(b)) {
1391 Some(JsObj::Object(bp)) => bp.get("@@bytes").and_then(|a| h.get(a)),
1392 _ => None,
1393 };
1394 let JsObj::Array(bytes) = store? else {
1395 return None;
1396 };
1397 let out = (0..len)
1398 .map(|i| {
1399 let start = off + i * bpe;
1400 if start + bpe > bytes.len() {
1401 return vec![0u8; bpe];
1402 }
1403 bytes[start..start + bpe]
1404 .iter()
1405 .map(|x| h.to_number(x) as i64 as u8)
1406 .collect()
1407 })
1408 .collect();
1409 Some((kind, out))
1410}
1411
1412pub fn elems_mut_host(h: &mut crate::host::JsHost, v: &Value) -> Vec<Value> {
1416 if let Some(JsObj::Object(p)) = h.get(v) {
1417 if let Some(arr) = p.get("@@bytes").cloned() {
1418 return match h.get(&arr) {
1419 Some(JsObj::Array(items)) => items.clone(),
1420 _ => Vec::new(),
1421 };
1422 }
1423 }
1424 let Some((kind, raws)) = raw_elems(h, v) else {
1425 return Vec::new();
1426 };
1427 raws.iter()
1428 .map(|b| {
1429 if !is_bigint_kind(&kind) {
1430 return decode(&kind, b);
1431 }
1432 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1433 h.new_bigint(if kind == "BigInt64Array" {
1434 num_bigint::BigInt::from(raw as i64)
1435 } else {
1436 num_bigint::BigInt::from(raw)
1437 })
1438 })
1439 .collect()
1440}
1441
1442pub fn elems_display(h: &crate::host::JsHost, v: &Value) -> Vec<String> {
1446 let Some((kind, raws)) = raw_elems(h, v) else {
1447 return Vec::new();
1448 };
1449 raws.iter()
1450 .map(|b| {
1451 if !is_bigint_kind(&kind) {
1452 return h.inspect(&decode(&kind, b));
1453 }
1454 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1455 if kind == "BigInt64Array" {
1456 format!("{}n", raw as i64)
1457 } else {
1458 format!("{raw}n")
1459 }
1460 })
1461 .collect()
1462}
1463
1464fn view_len(v: &Value) -> usize {
1466 if view_detached(v) {
1469 return 0;
1470 }
1471 with_host(|h| match h.get(v) {
1472 Some(JsObj::Object(p)) => p.get("length").map(|l| h.to_number(l)).unwrap_or(0.0) as usize,
1473 _ => 0,
1474 })
1475}
1476
1477pub fn elem_get(recv: &Value, key: &str) -> Option<Value> {
1482 let i: usize = key.parse().ok()?;
1483 if i >= view_len(recv) {
1484 return None;
1485 }
1486 let kind = kind_of(recv);
1487 let bpe = bytes_per_element(&kind);
1488 let bytes = view_bytes(recv, i * bpe, bpe)?;
1489 Some(decode(&kind, &bytes))
1490}
1491
1492pub fn elem_set(recv: &Value, key: &str, val: &Value) -> Result<bool, String> {
1494 let Ok(i) = key.parse::<usize>() else {
1495 return Ok(false);
1496 };
1497 let kind = kind_of(recv);
1498 let n = coerce_val(&kind, val)?;
1501 if i >= view_len(recv) {
1502 return Ok(false);
1503 }
1504 let bpe = bytes_per_element(&kind);
1505 Ok(write_view_bytes(recv, i * bpe, &encode(&kind, &n)))
1506}
1507
1508fn species(recv: &Value, kind: &str, elems: Vec<Value>) -> Value {
1513 if super::native_tag(recv).as_deref() == Some("Buffer") {
1514 let bytes: Vec<u8> = elems.iter().map(|x| num(x) as i64 as u8).collect();
1515 return super::buffer::from_bytes(&bytes);
1516 }
1517 make(kind, elems)
1518}
1519
1520fn write_elems(recv: &Value, kind: &str, vals: &[Value]) -> Result<(), String> {
1525 if super::native_tag(recv).as_deref() == Some("TypedArray") {
1526 let bpe = bytes_per_element(kind);
1527 let coerced: Vec<Value> = vals
1528 .iter()
1529 .map(|v| coerce_val(kind, v))
1530 .collect::<Result<_, _>>()?;
1531 for (i, v) in coerced.iter().enumerate() {
1532 write_view_bytes(recv, i * bpe, &encode(kind, v));
1533 }
1534 return Ok(());
1535 }
1536 let field = "@@bytes";
1537 let coerced: Vec<Value> = vals
1540 .iter()
1541 .map(|v| coerce_val(kind, v))
1542 .collect::<Result<_, _>>()?;
1543 with_host(|h| {
1544 if let Some(JsObj::Object(p)) = h.get(recv) {
1545 if let Some(arr) = p.get(field).cloned() {
1546 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
1547 for (i, v) in coerced.into_iter().enumerate() {
1548 if i < items.len() {
1549 items[i] = v;
1550 }
1551 }
1552 }
1553 }
1554 }
1555 });
1556 Ok(())
1557}
1558
1559fn sort_elements(elems: &mut Vec<Value>, kind: &str, cmp: Option<&Value>) -> Result<(), String> {
1563 let cmp = cmp.cloned().unwrap_or(Value::Undef);
1564 if with_host(|h| crate::host::is_callable(h, &cmp)) {
1565 return crate::builtins::sort_values(elems, Some(&cmp));
1571 }
1572 if is_bigint_kind(kind) {
1579 let keys: Vec<num_bigint::BigInt> = elems.iter().map(bigint_of).collect();
1580 let mut idx: Vec<usize> = (0..elems.len()).collect();
1581 idx.sort_by(|a, b| keys[*a].cmp(&keys[*b]));
1582 *elems = idx.into_iter().map(|i| elems[i].clone()).collect();
1583 } else {
1584 elems.sort_by(|a, b| {
1585 num(a)
1586 .partial_cmp(&num(b))
1587 .unwrap_or(std::cmp::Ordering::Equal)
1588 });
1589 }
1590 Ok(())
1591}
1592
1593fn rel_index(args: &[Value], idx: usize, len: usize, default: usize) -> usize {
1597 if args.len() <= idx {
1598 return default;
1599 }
1600 let n = super::arg_num(args, idx);
1601 if n < 0.0 {
1602 (len as f64 + n).max(0.0) as usize
1603 } else {
1604 (n as usize).min(len)
1605 }
1606}
1607
1608fn base64_instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
1613 let kind = kind_of(recv);
1614 if kind != "Uint8Array" {
1615 return Err(crate::host::type_error(&format!(
1622 "Method Uint8Array.prototype.{method} called on incompatible receiver undefined"
1623 )));
1624 }
1625 let bytes: Vec<u8> = elem_values(recv)
1626 .iter()
1627 .map(|v| with_host(|h| h.to_number(v)) as u8)
1628 .collect();
1629 match method {
1630 "toBase64" => {
1631 let (url, _) = base64_options(args.first())?;
1632 let omit = args
1633 .first()
1634 .filter(|v| !matches!(v, Value::Undef))
1635 .map(|o| {
1636 with_host(|h| match h.get(o) {
1637 Some(JsObj::Object(p)) => {
1638 p.get("omitPadding").map(|v| h.truthy(v)).unwrap_or(false)
1639 }
1640 _ => false,
1641 })
1642 })
1643 .unwrap_or(false);
1644 let mut s = super::to_base64(&bytes);
1647 if url {
1648 s = s.replace('+', "-").replace('/', "_");
1649 }
1650 if omit {
1651 s = s.trim_end_matches('=').to_string();
1652 }
1653 Ok(with_host(|h| h.new_str(s)))
1654 }
1655 "toHex" => Ok(with_host(|h| h.new_str(super::to_hex(&bytes)))),
1656 "setFromBase64" | "setFromHex" => {
1659 let s = base64_input(args)?;
1660 let (decoded, read) = if method == "setFromHex" {
1661 let d = decode_hex_strict(&s)?;
1662 let fits = d.len().min(bytes.len());
1663 (d[..fits].to_vec(), fits * 2)
1664 } else {
1665 let (url, last) = base64_options(args.get(1))?;
1666 let whole = (bytes.len() / 3) * 4;
1669 let head: String = s.chars().take(whole).collect();
1670 let (mut d, mut consumed) = decode_base64_strict(&head, url, last)?;
1671 if d.len() < bytes.len() {
1672 let (full, full_read) = decode_base64_strict(&s, url, last)?;
1673 if full.len() <= bytes.len() {
1674 d = full;
1675 consumed = full_read;
1676 }
1677 }
1678 (d, consumed)
1679 };
1680 write_view_bytes(recv, 0, &decoded);
1681 Ok(with_host(|h| {
1682 let mut m = IndexMap::new();
1683 m.insert("read".to_string(), Value::Float(read as f64));
1684 m.insert("written".to_string(), Value::Float(decoded.len() as f64));
1685 h.new_object(m)
1686 }))
1687 }
1688 _ => unreachable!("caller gates the method name"),
1689 }
1690}
1691
1692pub fn instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
1693 if view_detached(recv) {
1697 return Err(detached_error("%TypedArray%.prototype", method, false));
1698 }
1699 if matches!(
1700 method,
1701 "toBase64" | "toHex" | "setFromBase64" | "setFromHex"
1702 ) {
1703 return base64_instance_call(recv, method, args);
1704 }
1705 let kind = kind_of(recv);
1706 let elems = elem_values(recv);
1711 let this_arg = args.get(1).filter(|v| !matches!(v, Value::Undef)).cloned();
1717 let call_cb = |i: usize, v: &Value| -> Result<Value, String> {
1718 crate::host::invoke(
1719 &args.first().cloned().unwrap_or(Value::Undef),
1720 vec![v.clone(), Value::Float(i as f64), recv.clone()],
1721 this_arg.clone(),
1722 )
1723 };
1724 match method {
1725 "every" => {
1726 for (i, v) in elems.iter().enumerate() {
1727 let r = call_cb(i, v)?;
1728 if !with_host(|h| h.truthy(&r)) {
1729 return Ok(Value::Bool(false));
1730 }
1731 }
1732 Ok(Value::Bool(true))
1733 }
1734 "some" => {
1735 for (i, v) in elems.iter().enumerate() {
1736 let r = call_cb(i, v)?;
1737 if with_host(|h| h.truthy(&r)) {
1738 return Ok(Value::Bool(true));
1739 }
1740 }
1741 Ok(Value::Bool(false))
1742 }
1743 "forEach" => {
1744 for (i, v) in elems.iter().enumerate() {
1745 call_cb(i, v)?;
1746 }
1747 Ok(Value::Undef)
1748 }
1749 "map" => {
1750 let mut out = Vec::with_capacity(elems.len());
1751 for (i, v) in elems.iter().enumerate() {
1752 let r = call_cb(i, v)?;
1753 out.push(coerce_val(&kind, &r)?);
1754 }
1755 Ok(species(recv, &kind, out))
1756 }
1757 "filter" => {
1758 let mut out = Vec::new();
1759 for (i, v) in elems.iter().enumerate() {
1760 let r = call_cb(i, v)?;
1761 if with_host(|h| h.truthy(&r)) {
1762 out.push(v.clone());
1763 }
1764 }
1765 Ok(species(recv, &kind, out))
1766 }
1767 "find" | "findIndex" | "findLast" | "findLastIndex" => {
1768 let last = method.starts_with("findLast");
1769 let idxs: Vec<usize> = if last {
1770 (0..elems.len()).rev().collect()
1771 } else {
1772 (0..elems.len()).collect()
1773 };
1774 for i in idxs {
1775 let r = call_cb(i, &elems[i])?;
1776 if with_host(|h| h.truthy(&r)) {
1777 return Ok(if method.ends_with("Index") {
1778 Value::Float(i as f64)
1779 } else {
1780 elems[i].clone()
1781 });
1782 }
1783 }
1784 Ok(if method.ends_with("Index") {
1785 Value::Float(-1.0)
1786 } else {
1787 Value::Undef
1788 })
1789 }
1790 "reduce" | "reduceRight" => {
1791 let right = method == "reduceRight";
1792 let order: Vec<usize> = if right {
1793 (0..elems.len()).rev().collect()
1794 } else {
1795 (0..elems.len()).collect()
1796 };
1797 let cb = args.first().cloned().unwrap_or(Value::Undef);
1798 let mut it = order.into_iter();
1799 let mut acc = if args.len() >= 2 {
1800 args[1].clone()
1801 } else {
1802 match it.next() {
1803 Some(i) => elems[i].clone(),
1804 None => {
1805 return Err(crate::host::type_error(
1806 "Reduce of empty array with no initial value",
1807 ))
1808 }
1809 }
1810 };
1811 for i in it {
1812 acc = crate::host::invoke(
1813 &cb,
1814 vec![acc, elems[i].clone(), Value::Float(i as f64), recv.clone()],
1815 None,
1816 )?;
1817 }
1818 Ok(acc)
1819 }
1820 "reverse" => {
1821 let mut out = elems.clone();
1822 out.reverse();
1823 write_elems(recv, &kind, &out)?;
1824 Ok(recv.clone())
1825 }
1826 "sort" => {
1827 let mut out = elems.clone();
1828 sort_elements(&mut out, &kind, args.first())?;
1829 write_elems(recv, &kind, &out)?;
1830 Ok(recv.clone())
1831 }
1832 "copyWithin" => {
1833 let len = elems.len();
1834 let target = rel_index(args, 0, len, 0);
1835 let start = rel_index(args, 1, len, 0);
1836 let end = rel_index(args, 2, len, len);
1837 let src: Vec<Value> = elems[start.min(end)..end.max(start)].to_vec();
1838 let mut out = elems.clone();
1839 for (k, v) in src.iter().enumerate() {
1840 if target + k < len {
1841 out[target + k] = v.clone();
1842 }
1843 }
1844 write_elems(recv, &kind, &out)?;
1845 Ok(recv.clone())
1846 }
1847 "at" => {
1848 let n = super::arg_num(args, 0);
1849 let i = if n < 0.0 { elems.len() as f64 + n } else { n };
1850 if i < 0.0 || i >= elems.len() as f64 {
1851 return Ok(Value::Undef);
1852 }
1853 Ok(elems[i as usize].clone())
1854 }
1855 "lastIndexOf" => {
1856 let needle = args.first().cloned().unwrap_or(Value::Undef);
1857 let from = (args.len() > 1).then(|| super::arg_num(args, 1));
1858 let found = crate::builtins::search_start_last(from, elems.len()).and_then(|start| {
1859 elems[..=start]
1860 .iter()
1861 .rposition(|x| same_element(x, &needle, false))
1862 });
1863 Ok(Value::Float(found.map(|p| p as f64).unwrap_or(-1.0)))
1864 }
1865 "keys" | "values" | "entries" | "@@iterator" => {
1870 let items: Vec<Value> = with_host(|h| match method {
1871 "keys" => (0..elems.len()).map(|i| Value::Float(i as f64)).collect(),
1872 "values" | "@@iterator" => elems.clone(),
1873 _ => elems
1874 .iter()
1875 .enumerate()
1876 .map(|(i, v)| h.new_array(vec![Value::Float(i as f64), v.clone()]))
1877 .collect(),
1878 });
1879 Ok(with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })))
1880 }
1881 "toString" | "join" => {
1882 let sep = if method == "join" && !args.is_empty() {
1883 super::arg_str(args, 0)
1884 } else {
1885 ",".into()
1886 };
1887 let parts: Vec<String> = with_host(|h| elems.iter().map(|n| h.str_of(n)).collect());
1888 Ok(with_host(|h| h.new_str(parts.join(&sep))))
1889 }
1890 "slice" | "subarray" => {
1891 let len = elems.len();
1892 let norm = |n: f64| -> usize {
1893 if n < 0.0 {
1894 (len as f64 + n).max(0.0) as usize
1895 } else {
1896 (n as usize).min(len)
1897 }
1898 };
1899 let s = if args.is_empty() {
1900 0
1901 } else {
1902 norm(super::arg_num(args, 0))
1903 };
1904 let e = if args.len() < 2 {
1905 len
1906 } else {
1907 norm(super::arg_num(args, 1))
1908 };
1909 let (lo, hi) = (s.min(e), e.max(s));
1910 if method == "subarray" && super::native_tag(recv).as_deref() == Some("TypedArray") {
1913 if let Some((buf, off)) = view_base(recv) {
1914 let bpe = bytes_per_element(&kind);
1915 return Ok(make_view(&kind, &buf, off + lo * bpe, hi - lo));
1916 }
1917 }
1918 Ok(species(recv, &kind, elems[lo..hi].to_vec()))
1919 }
1920 "indexOf" => {
1921 let needle = args.first().cloned().unwrap_or(Value::Undef);
1922 let start = crate::builtins::search_start(super::arg_num(args, 1), elems.len());
1923 Ok(Value::Float(
1924 elems
1925 .iter()
1926 .skip(start)
1927 .position(|x| same_element(x, &needle, false))
1928 .map(|p| (p + start) as f64)
1929 .unwrap_or(-1.0),
1930 ))
1931 }
1932 "includes" => {
1933 let needle = args.first().cloned().unwrap_or(Value::Undef);
1934 let start = crate::builtins::search_start(super::arg_num(args, 1), elems.len());
1935 Ok(Value::Bool(
1936 elems
1937 .iter()
1938 .skip(start)
1939 .any(|x| same_element(x, &needle, true)),
1940 ))
1941 }
1942 "fill" => {
1949 let len = elems.len();
1950 let v = coerce_val(&kind, args.first().unwrap_or(&Value::Undef))?;
1951 let start = rel_index(args, 1, len, 0);
1952 let end = rel_index(args, 2, len, len);
1953 let mut out = elems.clone();
1954 for slot in out.iter_mut().take(end).skip(start) {
1955 *slot = v.clone();
1956 }
1957 write_elems(recv, &kind, &out)?;
1958 Ok(recv.clone())
1959 }
1960 "toReversed" | "toSorted" => {
1965 let mut out = elems.clone();
1966 if method == "toReversed" {
1967 out.reverse();
1968 } else {
1969 sort_elements(&mut out, &kind, args.first())?;
1970 }
1971 Ok(make(&kind, out))
1972 }
1973 "with" => {
1974 let len = elems.len();
1975 let n = super::arg_num(args, 0);
1976 let i = if n < 0.0 { len as f64 + n } else { n };
1977 if !(0.0..len as f64).contains(&i) {
1978 return Err("RangeError: Invalid typed array index".into());
1979 }
1980 let mut out = elems.clone();
1981 out[i as usize] = coerce_val(&kind, args.get(1).unwrap_or(&Value::Undef))?;
1982 Ok(make(&kind, out))
1983 }
1984 "set" => {
1985 let arg = args.first().cloned().unwrap_or(Value::Undef);
1987 let src = match super::native_tag(&arg).as_deref() {
1988 Some("TypedArray") | Some("Buffer") => elem_values(&arg),
1989 _ => crate::host::iter_all(&arg)
1990 .unwrap_or_else(|_| crate::builtins::array_like_items(&arg)),
1991 };
1992 let off = super::arg_num(args, 1);
1995 let off = if off.is_nan() { 0.0 } else { off.trunc() };
1996 if off < 0.0 || off + src.len() as f64 > view_len(recv) as f64 {
1997 return Err(crate::host::range_error("offset is out of bounds"));
1998 }
1999 let off = off as usize;
2000 let src: Vec<Value> = src
2002 .iter()
2003 .map(|v| coerce_val(&kind, v))
2004 .collect::<Result<_, _>>()?;
2005 let bpe = bytes_per_element(&kind);
2006 let len = view_len(recv);
2007 for (k, v) in src.into_iter().enumerate() {
2008 if off + k < len {
2009 write_view_bytes(recv, (off + k) * bpe, &encode(&kind, &v));
2010 }
2011 }
2012 Ok(Value::Undef)
2013 }
2014 _ => Err(crate::host::type_error(&format!(
2015 "{method} is not a function"
2016 ))),
2017 }
2018}
2019
2020pub fn construct_weakref(args: &[Value]) -> Result<Value, String> {
2023 let target = args.first().cloned().unwrap_or(Value::Undef);
2024 Ok(with_host(|h| {
2025 let mut m = IndexMap::new();
2026 m.insert("@@native".into(), h.new_str("WeakRef"));
2027 m.insert("@@target".into(), target);
2028 h.new_object(m)
2029 }))
2030}
2031
2032pub fn weakref_call(recv: &Value, method: &str) -> Result<Value, String> {
2033 match method {
2034 "deref" => Ok(with_host(|h| match h.get(recv) {
2035 Some(JsObj::Object(p)) => p.get("@@target").cloned().unwrap_or(Value::Undef),
2036 _ => Value::Undef,
2037 })),
2038 _ => Err(crate::host::type_error(&format!(
2039 "{method} is not a function"
2040 ))),
2041 }
2042}
2043
2044fn is_object_value(v: &Value) -> bool {
2057 matches!(v, Value::Obj(_))
2058 && with_host(|h| {
2059 !matches!(
2060 h.get(v),
2061 Some(JsObj::Str(_))
2062 | Some(JsObj::Symbol { .. })
2063 | Some(JsObj::BigInt(_))
2064 | Some(JsObj::Null)
2065 )
2066 })
2067}
2068
2069pub fn construct_finalization_registry(args: &[Value]) -> Result<Value, String> {
2070 let cb = args.first().cloned().unwrap_or(Value::Undef);
2071 if !with_host(|h| crate::host::is_callable(h, &cb)) {
2072 return Err(crate::host::type_error(
2073 "FinalizationRegistry: cleanup must be callable",
2074 ));
2075 }
2076 Ok(with_host(|h| {
2077 let tokens = h.new_array(Vec::new());
2078 let mut m = IndexMap::new();
2079 m.insert("@@native".into(), h.new_str("FinalizationRegistry"));
2080 m.insert("@@fr_cb".into(), cb);
2081 m.insert("@@fr_tokens".into(), tokens);
2082 h.new_object(m)
2083 }))
2084}
2085
2086pub fn finalization_registry_call(
2087 recv: &Value,
2088 method: &str,
2089 args: &[Value],
2090) -> Result<Value, String> {
2091 match method {
2092 "register" => {
2093 let target = args.first().cloned().unwrap_or(Value::Undef);
2094 let held = args.get(1).cloned().unwrap_or(Value::Undef);
2095 let token = args.get(2).cloned().unwrap_or(Value::Undef);
2096 if !is_object_value(&target) {
2097 return Err(crate::host::type_error(
2100 "FinalizationRegistry.prototype.register: invalid target",
2101 ));
2102 }
2103 if with_host(|h| h.strict_eq(&target, &held)) {
2104 return Err(crate::host::type_error(
2105 "FinalizationRegistry.prototype.register: target and holdings must not be same",
2106 ));
2107 }
2108 if !matches!(token, Value::Undef) {
2111 if !is_object_value(&token) {
2112 return Err(crate::host::type_error(&format!(
2113 "Invalid unregisterToken ('{}')",
2114 with_host(|h| h.str_of(&token))
2115 )));
2116 }
2117 with_host(|h| {
2118 let toks = registry_tokens(h, recv);
2119 if let Some(JsObj::Array(items)) = h.get_mut(&toks) {
2120 items.push(token);
2121 }
2122 });
2123 }
2124 Ok(Value::Undef)
2125 }
2126 "unregister" => {
2127 let token = args.first().cloned().unwrap_or(Value::Undef);
2128 if !is_object_value(&token) {
2129 return Err(crate::host::type_error(&format!(
2131 "Invalid unregisterToken ('{}')",
2132 with_host(|h| h.str_of(&token))
2133 )));
2134 }
2135 Ok(Value::Bool(with_host(|h| {
2136 let toks = registry_tokens(h, recv);
2137 let kept: Vec<Value> = match h.get(&toks) {
2138 Some(JsObj::Array(items)) => items
2139 .iter()
2140 .filter(|t| !h.strict_eq(t, &token))
2141 .cloned()
2142 .collect(),
2143 _ => Vec::new(),
2144 };
2145 let removed = match h.get(&toks) {
2146 Some(JsObj::Array(items)) => items.len() != kept.len(),
2147 _ => false,
2148 };
2149 if let Some(JsObj::Array(items)) = h.get_mut(&toks) {
2150 *items = kept;
2151 }
2152 removed
2153 })))
2154 }
2155 _ => Err(crate::host::type_error(&format!(
2156 "{method} is not a function"
2157 ))),
2158 }
2159}
2160
2161fn registry_tokens(h: &crate::host::JsHost, recv: &Value) -> Value {
2163 match h.get(recv) {
2164 Some(JsObj::Object(p)) => p.get("@@fr_tokens").cloned().unwrap_or(Value::Undef),
2165 _ => Value::Undef,
2166 }
2167}
2168
2169pub fn construct_text_encoder() -> Result<Value, String> {
2172 Ok(with_host(|h| {
2173 let mut m = IndexMap::new();
2174 m.insert("@@native".into(), h.new_str("TextEncoder"));
2175 m.insert("@@encoding".into(), h.new_str("utf-8"));
2180 h.new_object(m)
2181 }))
2182}
2183
2184pub fn text_encoder_call(_recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
2185 match method {
2186 "encode" => {
2188 let s = super::arg_str(args, 0);
2189 Ok(make(
2190 "Uint8Array",
2191 s.as_bytes()
2192 .iter()
2193 .map(|b| Value::Float(*b as f64))
2194 .collect(),
2195 ))
2196 }
2197 _ => Err(crate::host::type_error(&format!(
2198 "{method} is not a function"
2199 ))),
2200 }
2201}
2202
2203fn encoding_for_label(label: &str) -> Option<&'static str> {
2210 Some(match label.trim().to_ascii_lowercase().as_str() {
2211 "utf-8" | "utf8" | "unicode-1-1-utf-8" | "unicode11utf8" | "unicode20utf8"
2212 | "x-unicode20utf8" => "utf-8",
2213 "latin1" | "iso-8859-1" | "iso8859-1" | "iso88591" | "ascii" | "us-ascii" | "cp1252"
2214 | "cp819" | "ibm819" | "l1" | "windows-1252" | "x-cp1252" => "windows-1252",
2215 "utf-16le" | "utf-16" | "ucs-2" | "ucs2" | "unicodefeff" | "unicodefffe"
2216 | "iso-10646-ucs-2" | "csunicode" => "utf-16le",
2217 _ => return None,
2218 })
2219}
2220
2221pub fn construct_text_decoder(args: &[Value]) -> Result<Value, String> {
2222 let label = if args.is_empty() || matches!(args[0], Value::Undef) {
2223 "utf-8".to_string()
2224 } else {
2225 super::arg_str(args, 0)
2226 };
2227 let Some(encoding) = encoding_for_label(&label) else {
2228 return Err(crate::host::coded_error(
2229 "RangeError",
2230 "ERR_ENCODING_NOT_SUPPORTED",
2231 &format!("The \"{label}\" encoding is not supported"),
2232 ));
2233 };
2234 let flag = |key: &str| {
2238 args.get(1)
2239 .map(|o| crate::builtins::get_property(o, key).unwrap_or(Value::Undef))
2240 .map(|v| with_host(|h| h.truthy(&v)))
2241 .unwrap_or(false)
2242 };
2243 let (fatal, ignore_bom) = (flag("fatal"), flag("ignoreBOM"));
2244 Ok(with_host(|h| {
2245 let mut m = IndexMap::new();
2246 m.insert("@@native".into(), h.new_str("TextDecoder"));
2247 m.insert("@@encoding".into(), h.new_str(encoding.to_string()));
2248 m.insert("@@fatal".into(), Value::Bool(fatal));
2249 m.insert("@@ignoreBOM".into(), Value::Bool(ignore_bom));
2250 h.new_object(m)
2251 }))
2252}
2253
2254const CP1252_HIGH: [char; 32] = [
2260 '\u{20ac}', '\u{81}', '\u{201a}', '\u{192}', '\u{201e}', '\u{2026}', '\u{2020}', '\u{2021}',
2261 '\u{2c6}', '\u{2030}', '\u{160}', '\u{2039}', '\u{152}', '\u{8d}', '\u{17d}', '\u{8f}',
2262 '\u{90}', '\u{2018}', '\u{2019}', '\u{201c}', '\u{201d}', '\u{2022}', '\u{2013}', '\u{2014}',
2263 '\u{2dc}', '\u{2122}', '\u{161}', '\u{203a}', '\u{153}', '\u{9d}', '\u{17e}', '\u{178}',
2264];
2265
2266pub fn text_decoder_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
2267 match method {
2268 "decode" => {
2270 let bytes: Vec<u8> = elems_of(&args.first().cloned().unwrap_or(Value::Undef))
2271 .unwrap_or_default()
2272 .iter()
2273 .map(|n| *n as u8)
2274 .collect();
2275 let slot = |key: &str| {
2276 with_host(|h| match h.get(recv) {
2277 Some(JsObj::Object(p)) => p.get(key).cloned(),
2278 _ => None,
2279 })
2280 };
2281 let enc = slot("@@encoding")
2282 .map(|v| with_host(|h| h.str_of(&v)))
2283 .unwrap_or_else(|| "utf-8".into());
2284 let flag = |key: &str| matches!(slot(key), Some(Value::Bool(true)));
2285 let s = match enc.as_str() {
2286 "windows-1252" => bytes
2287 .iter()
2288 .map(|b| match b {
2289 0x80..=0x9f => CP1252_HIGH[(b - 0x80) as usize],
2290 _ => *b as char,
2291 })
2292 .collect(),
2293 "utf-16le" => {
2294 let units: Vec<u16> = bytes
2295 .chunks_exact(2)
2296 .map(|c| u16::from_le_bytes([c[0], c[1]]))
2297 .collect();
2298 String::from_utf16_lossy(&units)
2299 }
2300 _ if flag("@@fatal") => match std::str::from_utf8(&bytes) {
2305 Ok(s) => s.to_string(),
2306 Err(_) => {
2307 return Err(crate::host::coded_error(
2308 "TypeError",
2309 "ERR_ENCODING_INVALID_ENCODED_DATA",
2310 &format!("The encoded data was not valid for encoding {enc}"),
2311 ))
2312 }
2313 },
2314 _ => String::from_utf8_lossy(&bytes).into_owned(),
2315 };
2316 let s = match s.strip_prefix('\u{feff}') {
2318 Some(rest) if !flag("@@ignoreBOM") => rest.to_string(),
2319 _ => s,
2320 };
2321 Ok(with_host(|h| h.new_str(s)))
2322 }
2323 _ => Err(crate::host::type_error(&format!(
2324 "{method} is not a function"
2325 ))),
2326 }
2327}