1use crate::host::{with_host, JsObj};
16use fusevm::Value;
17use indexmap::IndexMap;
18
19pub const STATIC_METHODS: &[&str] = &["from", "of", "isView"];
20
21pub const PROTOTYPE_METHODS: &[&str] = &[
26 "at",
27 "copyWithin",
28 "entries",
29 "every",
30 "fill",
31 "filter",
32 "find",
33 "findIndex",
34 "findLast",
35 "findLastIndex",
36 "forEach",
37 "includes",
38 "indexOf",
39 "join",
40 "keys",
41 "lastIndexOf",
42 "map",
43 "reduce",
44 "reduceRight",
45 "reverse",
46 "set",
47 "slice",
48 "some",
49 "sort",
50 "subarray",
51 "toString",
52 "values",
53];
54
55pub fn is_ctor(name: &str) -> bool {
58 ELEMENT_KINDS.contains(&name) || name == "ArrayBuffer"
59}
60
61pub const ELEMENT_KINDS: &[&str] = &[
70 "Uint8Array",
71 "Int8Array",
72 "Uint8ClampedArray",
73 "Int16Array",
74 "Uint16Array",
75 "Int32Array",
76 "Uint32Array",
77 "Float32Array",
78 "Float64Array",
79 "BigInt64Array",
81 "BigUint64Array",
82];
83
84pub fn bytes_per_element(kind: &str) -> usize {
86 match kind {
87 "Int8Array" | "Uint8Array" | "Uint8ClampedArray" => 1,
88 "Int16Array" | "Uint16Array" => 2,
89 "Int32Array" | "Uint32Array" | "Float32Array" => 4,
90 "Float64Array" | "BigInt64Array" | "BigUint64Array" => 8,
91 _ => 1,
92 }
93}
94
95fn coerce(kind: &str, n: f64) -> f64 {
98 match kind {
99 "Int8Array" => (n as i64 as i8) as f64,
100 "Uint8Array" => (n as i64 as u8) as f64,
101 "Uint8ClampedArray" => {
102 if n.is_nan() {
103 0.0
104 } else {
105 n.round().clamp(0.0, 255.0)
106 }
107 }
108 "Int16Array" => (n as i64 as i16) as f64,
109 "Uint16Array" => (n as i64 as u16) as f64,
110 "Int32Array" => (n as i64 as i32) as f64,
111 "Uint32Array" => (n as i64 as u32) as f64,
112 "Float32Array" => n as f32 as f64,
113 _ => n, }
115}
116
117pub fn is_bigint_kind(kind: &str) -> bool {
121 matches!(kind, "BigInt64Array" | "BigUint64Array")
122}
123
124fn coerce_val(kind: &str, v: &Value) -> Result<Value, String> {
128 if !is_bigint_kind(kind) {
129 return Ok(Value::Float(coerce(kind, with_host(|h| h.to_number(v)))));
130 }
131 let big = with_host(|h| match h.get(v) {
134 Some(JsObj::BigInt(b)) => Some(b.clone()),
135 _ => None,
136 })
137 .ok_or_else(|| crate::host::type_error("Cannot convert a Number value to a BigInt"))?;
138 Ok(with_host(|h| h.new_bigint(wrap_bigint(kind, big))))
139}
140
141fn wrap_bigint(kind: &str, b: num_bigint::BigInt) -> num_bigint::BigInt {
144 use num_traits::cast::ToPrimitive;
145 let modulus = num_bigint::BigInt::from(1u128 << 64);
146 let mut m = b % &modulus;
147 if m.sign() == num_bigint::Sign::Minus {
148 m += &modulus;
149 }
150 let raw = m.to_u64().unwrap_or(0);
152 if kind == "BigInt64Array" {
153 num_bigint::BigInt::from(raw as i64)
154 } else {
155 num_bigint::BigInt::from(raw)
156 }
157}
158
159fn bigint_of(v: &Value) -> num_bigint::BigInt {
162 with_host(|h| match h.get(v) {
163 Some(JsObj::BigInt(b)) => b.clone(),
164 _ => num_bigint::BigInt::from(0),
165 })
166}
167
168fn same_element(stored: &Value, needle: &Value, nan_matches: bool) -> bool {
176 if nan_matches {
177 if let (Value::Float(a), Value::Float(b)) = (stored, needle) {
178 if a.is_nan() && b.is_nan() {
179 return true;
180 }
181 }
182 }
183 with_host(|h| h.strict_eq(stored, needle))
184}
185
186fn zero_of(kind: &str) -> Value {
188 if is_bigint_kind(kind) {
189 with_host(|h| h.new_bigint(num_bigint::BigInt::from(0)))
190 } else {
191 Value::Float(0.0)
192 }
193}
194
195fn num(v: &Value) -> f64 {
199 with_host(|h| h.to_number(v))
200}
201
202pub fn elem_values(v: &Value) -> Vec<Value> {
206 let Some(tag) = super::native_tag(v) else {
207 return Vec::new();
208 };
209 let field = match tag.as_str() {
210 "TypedArray" => "@@elems",
211 "Buffer" => "@@bytes",
212 _ => return Vec::new(),
213 };
214 with_host(|h| match h.get(v) {
215 Some(JsObj::Object(p)) => match p.get(field).and_then(|a| h.get(a)) {
216 Some(JsObj::Array(items)) => items.clone(),
217 _ => Vec::new(),
218 },
219 _ => Vec::new(),
220 })
221}
222
223fn make(kind: &str, elems: Vec<Value>) -> Value {
225 with_host(|h| {
226 let bpe = bytes_per_element(kind);
227 let len = elems.len();
228 let arr = h.new_array(elems);
229 let mut m = IndexMap::new();
230 m.insert("@@native".into(), h.new_str("TypedArray"));
231 m.insert("@@kind".into(), h.new_str(kind));
232 m.insert("@@elems".into(), arr);
233 m.insert("length".into(), Value::Float(len as f64));
234 m.insert("byteLength".into(), Value::Float((len * bpe) as f64));
235 m.insert("byteOffset".into(), Value::Float(0.0));
240 m.insert("BYTES_PER_ELEMENT".into(), Value::Float(bpe as f64));
241 let obj = h.new_object(m);
242 h.ensure_native_protos();
248 if let Some(p) = h.native_proto(kind) {
249 h.set_proto(&obj, p);
250 }
251 for k in ["length", "byteLength", "byteOffset", "BYTES_PER_ELEMENT"] {
253 h.hide_prop(&obj, k);
254 }
255 obj
256 })
257}
258
259pub fn construct(kind: &str, args: &[Value]) -> Result<Value, String> {
262 if kind == "ArrayBuffer" {
263 let n = super::arg_num(args, 0).max(0.0) as usize;
264 return Ok(with_host(|h| {
265 let mut m = IndexMap::new();
266 m.insert("@@native".into(), h.new_str("ArrayBuffer"));
267 m.insert("byteLength".into(), Value::Float(n as f64));
268 h.new_object(m)
269 }));
270 }
271 let elems = build_elems(kind, args)?;
272 Ok(make(kind, elems))
273}
274
275fn build_elems(kind: &str, args: &[Value]) -> Result<Vec<Value>, String> {
279 match args.first() {
280 None | Some(Value::Undef) => Ok(Vec::new()),
281 Some(Value::Int(_)) | Some(Value::Float(_)) => {
282 let n = super::arg_num(args, 0).max(0.0) as usize;
283 Ok(vec![zero_of(kind); n])
284 }
285 Some(v) => {
286 let items = match super::native_tag(v).as_deref() {
289 Some("TypedArray") | Some("Buffer") => elem_values(v),
290 _ => crate::host::iter_all(v).unwrap_or_default(),
291 };
292 items.iter().map(|x| coerce_val(kind, x)).collect()
293 }
294 }
295}
296
297pub fn static_call(kind: &str, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
299 Some(match method {
300 "of" => args
301 .iter()
302 .map(|x| coerce_val(kind, x))
303 .collect::<Result<Vec<Value>, String>>()
304 .map(|e| make(kind, e)),
305 "from" => from(kind, args),
306 "isView" => Ok(Value::Bool(with_host(|h| {
309 matches!(
310 h.get(&args.first().cloned().unwrap_or(Value::Undef)),
311 Some(crate::host::JsObj::Object(p))
312 if matches!(
313 p.get("@@native").map(|t| h.str_of(t)).as_deref(),
314 Some("TypedArray") | Some("Buffer") | Some("DataView")
315 )
316 )
317 }))),
318 _ => return None,
319 })
320}
321
322fn from(kind: &str, args: &[Value]) -> Result<Value, String> {
323 let src = args.first().cloned().unwrap_or(Value::Undef);
324 let map_fn = args
325 .get(1)
326 .cloned()
327 .filter(|f| with_host(|h| crate::host::is_callable(h, f)));
328 let items = if let Some(e) = elems_of(&src) {
329 e.into_iter().map(Value::Float).collect()
330 } else {
331 crate::host::iter_all(&src).unwrap_or_default()
332 };
333 let mut out = Vec::with_capacity(items.len());
334 for (i, it) in items.into_iter().enumerate() {
335 let mapped = match &map_fn {
336 Some(f) => crate::host::invoke(f, vec![it, Value::Float(i as f64)], None)?,
337 None => it,
338 };
339 out.push(coerce_val(kind, &mapped)?);
340 }
341 Ok(make(kind, out))
342}
343
344pub fn elems_of(v: &Value) -> Option<Vec<f64>> {
346 let tag = super::native_tag(v)?;
347 let field = match tag.as_str() {
348 "TypedArray" => "@@elems",
349 "Buffer" => "@@bytes",
350 _ => return None,
351 };
352 with_host(|h| match h.get(v) {
353 Some(JsObj::Object(p)) => match p.get(field).and_then(|a| h.get(a)) {
354 Some(JsObj::Array(items)) => Some(items.iter().map(|x| h.to_number(x)).collect()),
355 _ => None,
356 },
357 _ => None,
358 })
359}
360
361pub fn index_len(v: &Value) -> Option<usize> {
370 let field = match super::native_tag(v)?.as_str() {
371 "TypedArray" => "@@elems",
372 "Buffer" => "@@bytes",
373 _ => return None,
374 };
375 with_host(|h| match h.get(v) {
376 Some(JsObj::Object(p)) => match p.get(field).and_then(|a| h.get(a)) {
377 Some(JsObj::Array(items)) => Some(items.len()),
378 _ => None,
379 },
380 _ => None,
381 })
382}
383
384pub fn has_index(v: &Value, key: &str) -> Option<bool> {
387 let len = index_len(v)?;
388 Some(key.parse::<usize>().map(|i| i < len).unwrap_or(false))
389}
390
391pub fn kind_of(recv: &Value) -> String {
393 with_host(|h| match h.get(recv) {
394 Some(JsObj::Object(p)) => p
395 .get("@@kind")
396 .map(|v| h.str_of(v))
397 .unwrap_or_else(|| "Uint8Array".into()),
398 _ => "Uint8Array".into(),
399 })
400}
401
402pub fn elem_get(recv: &Value, key: &str) -> Option<Value> {
407 let i: usize = key.parse().ok()?;
408 with_host(|h| match h.get(recv) {
409 Some(JsObj::Object(p)) => match p.get("@@elems").and_then(|a| h.get(a)) {
410 Some(JsObj::Array(items)) => items.get(i).cloned(),
411 _ => None,
412 },
413 _ => None,
414 })
415}
416
417pub fn elem_set(recv: &Value, key: &str, val: &Value) -> Result<bool, String> {
419 let Ok(i) = key.parse::<usize>() else {
420 return Ok(false);
421 };
422 let kind = kind_of(recv);
423 let n = coerce_val(&kind, val)?;
426 Ok(with_host(|h| {
427 if let Some(JsObj::Object(p)) = h.get(recv) {
428 if let Some(arr) = p.get("@@elems").cloned() {
429 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
430 if i < items.len() {
431 items[i] = n;
432 return true;
433 }
434 }
435 }
436 }
437 false
438 }))
439}
440
441fn species(recv: &Value, kind: &str, elems: Vec<Value>) -> Value {
446 if super::native_tag(recv).as_deref() == Some("Buffer") {
447 let bytes: Vec<u8> = elems.iter().map(|x| num(x) as i64 as u8).collect();
448 return super::buffer::from_bytes(&bytes);
449 }
450 make(kind, elems)
451}
452
453fn write_elems(recv: &Value, kind: &str, vals: &[Value]) -> Result<(), String> {
458 let field = match super::native_tag(recv).as_deref() {
459 Some("Buffer") => "@@bytes",
460 _ => "@@elems",
461 };
462 let coerced: Vec<Value> = vals
465 .iter()
466 .map(|v| coerce_val(kind, v))
467 .collect::<Result<_, _>>()?;
468 with_host(|h| {
469 if let Some(JsObj::Object(p)) = h.get(recv) {
470 if let Some(arr) = p.get(field).cloned() {
471 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
472 for (i, v) in coerced.into_iter().enumerate() {
473 if i < items.len() {
474 items[i] = v;
475 }
476 }
477 }
478 }
479 }
480 });
481 Ok(())
482}
483
484fn rel_index(args: &[Value], idx: usize, len: usize, default: usize) -> usize {
488 if args.len() <= idx {
489 return default;
490 }
491 let n = super::arg_num(args, idx);
492 if n < 0.0 {
493 (len as f64 + n).max(0.0) as usize
494 } else {
495 (n as usize).min(len)
496 }
497}
498
499pub fn instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
501 let kind = kind_of(recv);
502 let elems = elem_values(recv);
507 let call_cb = |i: usize, v: &Value| -> Result<Value, String> {
511 crate::host::invoke(
512 &args.first().cloned().unwrap_or(Value::Undef),
513 vec![v.clone(), Value::Float(i as f64), recv.clone()],
514 None,
515 )
516 };
517 match method {
518 "every" => {
519 for (i, v) in elems.iter().enumerate() {
520 let r = call_cb(i, v)?;
521 if !with_host(|h| h.truthy(&r)) {
522 return Ok(Value::Bool(false));
523 }
524 }
525 Ok(Value::Bool(true))
526 }
527 "some" => {
528 for (i, v) in elems.iter().enumerate() {
529 let r = call_cb(i, v)?;
530 if with_host(|h| h.truthy(&r)) {
531 return Ok(Value::Bool(true));
532 }
533 }
534 Ok(Value::Bool(false))
535 }
536 "forEach" => {
537 for (i, v) in elems.iter().enumerate() {
538 call_cb(i, v)?;
539 }
540 Ok(Value::Undef)
541 }
542 "map" => {
543 let mut out = Vec::with_capacity(elems.len());
544 for (i, v) in elems.iter().enumerate() {
545 let r = call_cb(i, v)?;
546 out.push(coerce_val(&kind, &r)?);
547 }
548 Ok(species(recv, &kind, out))
549 }
550 "filter" => {
551 let mut out = Vec::new();
552 for (i, v) in elems.iter().enumerate() {
553 let r = call_cb(i, v)?;
554 if with_host(|h| h.truthy(&r)) {
555 out.push(v.clone());
556 }
557 }
558 Ok(species(recv, &kind, out))
559 }
560 "find" | "findIndex" | "findLast" | "findLastIndex" => {
561 let last = method.starts_with("findLast");
562 let idxs: Vec<usize> = if last {
563 (0..elems.len()).rev().collect()
564 } else {
565 (0..elems.len()).collect()
566 };
567 for i in idxs {
568 let r = call_cb(i, &elems[i])?;
569 if with_host(|h| h.truthy(&r)) {
570 return Ok(if method.ends_with("Index") {
571 Value::Float(i as f64)
572 } else {
573 elems[i].clone()
574 });
575 }
576 }
577 Ok(if method.ends_with("Index") {
578 Value::Float(-1.0)
579 } else {
580 Value::Undef
581 })
582 }
583 "reduce" | "reduceRight" => {
584 let right = method == "reduceRight";
585 let order: Vec<usize> = if right {
586 (0..elems.len()).rev().collect()
587 } else {
588 (0..elems.len()).collect()
589 };
590 let cb = args.first().cloned().unwrap_or(Value::Undef);
591 let mut it = order.into_iter();
592 let mut acc = if args.len() >= 2 {
593 args[1].clone()
594 } else {
595 match it.next() {
596 Some(i) => elems[i].clone(),
597 None => {
598 return Err(crate::host::type_error(
599 "Reduce of empty array with no initial value",
600 ))
601 }
602 }
603 };
604 for i in it {
605 acc = crate::host::invoke(
606 &cb,
607 vec![acc, elems[i].clone(), Value::Float(i as f64), recv.clone()],
608 None,
609 )?;
610 }
611 Ok(acc)
612 }
613 "reverse" => {
614 let mut out = elems.clone();
615 out.reverse();
616 write_elems(recv, &kind, &out)?;
617 Ok(recv.clone())
618 }
619 "sort" => {
620 let mut out = elems.clone();
621 let cmp = args.first().cloned().unwrap_or(Value::Undef);
622 if with_host(|h| crate::host::is_callable(h, &cmp)) {
623 crate::builtins::sort_values(&mut out, Some(&cmp))?;
629 } else {
630 if is_bigint_kind(&kind) {
638 let keys: Vec<num_bigint::BigInt> = out.iter().map(bigint_of).collect();
639 let mut idx: Vec<usize> = (0..out.len()).collect();
640 idx.sort_by(|a, b| keys[*a].cmp(&keys[*b]));
641 out = idx.into_iter().map(|i| out[i].clone()).collect();
642 } else {
643 out.sort_by(|a, b| {
644 num(a)
645 .partial_cmp(&num(b))
646 .unwrap_or(std::cmp::Ordering::Equal)
647 });
648 }
649 }
650 write_elems(recv, &kind, &out)?;
651 Ok(recv.clone())
652 }
653 "copyWithin" => {
654 let len = elems.len();
655 let target = rel_index(args, 0, len, 0);
656 let start = rel_index(args, 1, len, 0);
657 let end = rel_index(args, 2, len, len);
658 let src: Vec<Value> = elems[start.min(end)..end.max(start)].to_vec();
659 let mut out = elems.clone();
660 for (k, v) in src.iter().enumerate() {
661 if target + k < len {
662 out[target + k] = v.clone();
663 }
664 }
665 write_elems(recv, &kind, &out)?;
666 Ok(recv.clone())
667 }
668 "at" => {
669 let n = super::arg_num(args, 0);
670 let i = if n < 0.0 { elems.len() as f64 + n } else { n };
671 if i < 0.0 || i >= elems.len() as f64 {
672 return Ok(Value::Undef);
673 }
674 Ok(elems[i as usize].clone())
675 }
676 "lastIndexOf" => {
677 let needle = args.first().cloned().unwrap_or(Value::Undef);
678 Ok(Value::Float(
679 elems
680 .iter()
681 .rposition(|x| same_element(x, &needle, false))
682 .map(|p| p as f64)
683 .unwrap_or(-1.0),
684 ))
685 }
686 "keys" | "values" | "entries" => {
687 let items: Vec<Value> = with_host(|h| match method {
688 "keys" => (0..elems.len()).map(|i| Value::Float(i as f64)).collect(),
689 "values" => elems.clone(),
690 _ => elems
691 .iter()
692 .enumerate()
693 .map(|(i, v)| h.new_array(vec![Value::Float(i as f64), v.clone()]))
694 .collect(),
695 });
696 Ok(with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })))
697 }
698 "toString" | "join" => {
699 let sep = if method == "join" && !args.is_empty() {
700 super::arg_str(args, 0)
701 } else {
702 ",".into()
703 };
704 let parts: Vec<String> = with_host(|h| elems.iter().map(|n| h.str_of(n)).collect());
705 Ok(with_host(|h| h.new_str(parts.join(&sep))))
706 }
707 "slice" | "subarray" => {
708 let len = elems.len();
709 let norm = |n: f64| -> usize {
710 if n < 0.0 {
711 (len as f64 + n).max(0.0) as usize
712 } else {
713 (n as usize).min(len)
714 }
715 };
716 let s = if args.is_empty() {
717 0
718 } else {
719 norm(super::arg_num(args, 0))
720 };
721 let e = if args.len() < 2 {
722 len
723 } else {
724 norm(super::arg_num(args, 1))
725 };
726 Ok(make(&kind, elems[s.min(e)..e.max(s)].to_vec()))
727 }
728 "indexOf" => {
729 let needle = args.first().cloned().unwrap_or(Value::Undef);
730 Ok(Value::Float(
731 elems
732 .iter()
733 .position(|x| same_element(x, &needle, false))
734 .map(|p| p as f64)
735 .unwrap_or(-1.0),
736 ))
737 }
738 "includes" => {
739 let needle = args.first().cloned().unwrap_or(Value::Undef);
740 Ok(Value::Bool(
741 elems.iter().any(|x| same_element(x, &needle, true)),
742 ))
743 }
744 "fill" => {
745 let v = coerce_val(&kind, args.first().unwrap_or(&Value::Undef))?;
746 Ok(make(&kind, vec![v; elems.len()]))
747 }
748 "set" => {
749 let arg = args.first().cloned().unwrap_or(Value::Undef);
751 let src = match super::native_tag(&arg).as_deref() {
752 Some("TypedArray") | Some("Buffer") => elem_values(&arg),
753 _ => crate::host::iter_all(&arg).unwrap_or_default(),
754 };
755 let off = super::arg_num(args, 1).max(0.0) as usize;
756 let src: Vec<Value> = src
758 .iter()
759 .map(|v| coerce_val(&kind, v))
760 .collect::<Result<_, _>>()?;
761 with_host(|h| {
762 if let Some(JsObj::Object(p)) = h.get(recv) {
763 if let Some(arr) = p.get("@@elems").cloned() {
764 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
765 for (k, v) in src.into_iter().enumerate() {
766 if off + k < items.len() {
767 items[off + k] = v;
768 }
769 }
770 }
771 }
772 }
773 });
774 Ok(Value::Undef)
775 }
776 _ => Err(crate::host::type_error(&format!(
777 "{method} is not a function"
778 ))),
779 }
780}
781
782pub fn construct_weakref(args: &[Value]) -> Result<Value, String> {
785 let target = args.first().cloned().unwrap_or(Value::Undef);
786 Ok(with_host(|h| {
787 let mut m = IndexMap::new();
788 m.insert("@@native".into(), h.new_str("WeakRef"));
789 m.insert("@@target".into(), target);
790 h.new_object(m)
791 }))
792}
793
794pub fn weakref_call(recv: &Value, method: &str) -> Result<Value, String> {
795 match method {
796 "deref" => Ok(with_host(|h| match h.get(recv) {
797 Some(JsObj::Object(p)) => p.get("@@target").cloned().unwrap_or(Value::Undef),
798 _ => Value::Undef,
799 })),
800 _ => Err(crate::host::type_error(&format!(
801 "{method} is not a function"
802 ))),
803 }
804}
805
806fn is_object_value(v: &Value) -> bool {
819 matches!(v, Value::Obj(_))
820 && with_host(|h| {
821 !matches!(
822 h.get(v),
823 Some(JsObj::Str(_))
824 | Some(JsObj::Symbol { .. })
825 | Some(JsObj::BigInt(_))
826 | Some(JsObj::Null)
827 )
828 })
829}
830
831pub fn construct_finalization_registry(args: &[Value]) -> Result<Value, String> {
832 let cb = args.first().cloned().unwrap_or(Value::Undef);
833 if !with_host(|h| crate::host::is_callable(h, &cb)) {
834 return Err(crate::host::type_error(
835 "FinalizationRegistry: cleanup must be callable",
836 ));
837 }
838 Ok(with_host(|h| {
839 let tokens = h.new_array(Vec::new());
840 let mut m = IndexMap::new();
841 m.insert("@@native".into(), h.new_str("FinalizationRegistry"));
842 m.insert("@@fr_cb".into(), cb);
843 m.insert("@@fr_tokens".into(), tokens);
844 h.new_object(m)
845 }))
846}
847
848pub fn finalization_registry_call(
849 recv: &Value,
850 method: &str,
851 args: &[Value],
852) -> Result<Value, String> {
853 match method {
854 "register" => {
855 let target = args.first().cloned().unwrap_or(Value::Undef);
856 let held = args.get(1).cloned().unwrap_or(Value::Undef);
857 let token = args.get(2).cloned().unwrap_or(Value::Undef);
858 if !is_object_value(&target) {
859 return Err(crate::host::type_error(
862 "FinalizationRegistry.prototype.register: invalid target",
863 ));
864 }
865 if with_host(|h| h.strict_eq(&target, &held)) {
866 return Err(crate::host::type_error(
867 "FinalizationRegistry.prototype.register: target and holdings must not be same",
868 ));
869 }
870 if !matches!(token, Value::Undef) {
873 if !is_object_value(&token) {
874 return Err(crate::host::type_error(&format!(
875 "Invalid unregisterToken ('{}')",
876 with_host(|h| h.str_of(&token))
877 )));
878 }
879 with_host(|h| {
880 let toks = registry_tokens(h, recv);
881 if let Some(JsObj::Array(items)) = h.get_mut(&toks) {
882 items.push(token);
883 }
884 });
885 }
886 Ok(Value::Undef)
887 }
888 "unregister" => {
889 let token = args.first().cloned().unwrap_or(Value::Undef);
890 if !is_object_value(&token) {
891 return Err(crate::host::type_error(&format!(
893 "Invalid unregisterToken ('{}')",
894 with_host(|h| h.str_of(&token))
895 )));
896 }
897 Ok(Value::Bool(with_host(|h| {
898 let toks = registry_tokens(h, recv);
899 let kept: Vec<Value> = match h.get(&toks) {
900 Some(JsObj::Array(items)) => items
901 .iter()
902 .filter(|t| !h.strict_eq(t, &token))
903 .cloned()
904 .collect(),
905 _ => Vec::new(),
906 };
907 let removed = match h.get(&toks) {
908 Some(JsObj::Array(items)) => items.len() != kept.len(),
909 _ => false,
910 };
911 if let Some(JsObj::Array(items)) = h.get_mut(&toks) {
912 *items = kept;
913 }
914 removed
915 })))
916 }
917 _ => Err(crate::host::type_error(&format!(
918 "{method} is not a function"
919 ))),
920 }
921}
922
923fn registry_tokens(h: &crate::host::JsHost, recv: &Value) -> Value {
925 match h.get(recv) {
926 Some(JsObj::Object(p)) => p.get("@@fr_tokens").cloned().unwrap_or(Value::Undef),
927 _ => Value::Undef,
928 }
929}
930
931pub fn construct_text_encoder() -> Result<Value, String> {
934 Ok(with_host(|h| {
935 let mut m = IndexMap::new();
936 m.insert("@@native".into(), h.new_str("TextEncoder"));
937 m.insert("encoding".into(), h.new_str("utf-8"));
938 h.new_object(m)
939 }))
940}
941
942pub fn text_encoder_call(_recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
943 match method {
944 "encode" => {
946 let s = super::arg_str(args, 0);
947 Ok(make(
948 "Uint8Array",
949 s.as_bytes()
950 .iter()
951 .map(|b| Value::Float(*b as f64))
952 .collect(),
953 ))
954 }
955 _ => Err(crate::host::type_error(&format!(
956 "{method} is not a function"
957 ))),
958 }
959}
960
961pub fn construct_text_decoder(args: &[Value]) -> Result<Value, String> {
962 let label = if args.is_empty() {
963 "utf-8".to_string()
964 } else {
965 super::arg_str(args, 0)
966 };
967 Ok(with_host(|h| {
968 let mut m = IndexMap::new();
969 m.insert("@@native".into(), h.new_str("TextDecoder"));
970 m.insert("encoding".into(), h.new_str(label.to_ascii_lowercase()));
971 h.new_object(m)
972 }))
973}
974
975pub fn text_decoder_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
976 match method {
977 "decode" => {
979 let bytes: Vec<u8> = elems_of(&args.first().cloned().unwrap_or(Value::Undef))
980 .unwrap_or_default()
981 .iter()
982 .map(|n| *n as u8)
983 .collect();
984 let enc = with_host(|h| match h.get(recv) {
985 Some(JsObj::Object(p)) => p
986 .get("encoding")
987 .map(|v| h.str_of(v))
988 .unwrap_or_else(|| "utf-8".into()),
989 _ => "utf-8".into(),
990 });
991 let s = match enc.as_str() {
992 "latin1" | "iso-8859-1" | "ascii" => bytes.iter().map(|b| *b as char).collect(),
993 _ => String::from_utf8_lossy(&bytes).into_owned(),
994 };
995 Ok(with_host(|h| h.new_str(s)))
996 }
997 _ => Err(crate::host::type_error(&format!(
998 "{method} is not a function"
999 ))),
1000 }
1001}