we (web engine): Experimental web browser project to understand the limits of Claude
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement object shapes (hidden classes) for JS engine

Replace HashMap<String, Property> object representation with a shape
(hidden class) system that tracks object layout transitions. Each shape
maps property names to fixed slot indices with attributes, enabling O(1)
indexed property access once the shape is known.

- Add Shape, ShapeId, ShapeTable, PropertyAttrs in new shape module
- ObjectData uses ObjectStorage enum: Shaped (shape + dense Vec<Value>
slots) or Dictionary (HashMap fallback after deletion/freeze/seal)
- Shape transitions cached by (name, attrs) so identical property
additions reuse shapes across objects
- Property deletion transitions object to dictionary mode
- All 701 existing JS tests pass unchanged

This is the foundation for inline caches and JIT property access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+3199 -1532
+1116 -774
crates/js/src/builtins.rs
··· 6 6 //! via a JS preamble executed at init time. 7 7 8 8 use crate::gc::{Gc, GcRef}; 9 + use crate::shape::ShapeTable; 9 10 use crate::vm::*; 10 11 use std::cell::RefCell; 11 12 use std::collections::{HashMap, HashSet}; ··· 35 36 } 36 37 37 38 /// Set a non-enumerable property on an object. 38 - pub fn set_builtin_prop(gc: &mut Gc<HeapObject>, obj: GcRef, key: &str, val: Value) { 39 + pub fn set_builtin_prop( 40 + gc: &mut Gc<HeapObject>, 41 + shapes: &mut ShapeTable, 42 + obj: GcRef, 43 + key: &str, 44 + val: Value, 45 + ) { 39 46 if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { 40 - data.properties 41 - .insert(key.to_string(), Property::builtin(val)); 47 + data.insert_property(key.to_string(), Property::builtin(val), shapes); 42 48 } 43 49 } 44 50 ··· 52 58 } 53 59 54 60 /// Get own enumerable string keys of an object (for Object.keys, etc.). 55 - fn own_enumerable_keys(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> { 61 + fn own_enumerable_keys(gc: &Gc<HeapObject>, shapes: &ShapeTable, obj_ref: GcRef) -> Vec<String> { 56 62 match gc.get(obj_ref) { 57 63 Some(HeapObject::Object(data)) => { 58 64 let mut int_keys: Vec<(u32, String)> = Vec::new(); 59 65 let mut str_keys: Vec<String> = Vec::new(); 60 - for (k, prop) in &data.properties { 66 + for (k, prop) in data.property_entries(shapes) { 61 67 if prop.enumerable { 62 68 if let Ok(idx) = k.parse::<u32>() { 63 - int_keys.push((idx, k.clone())); 69 + int_keys.push((idx, k)); 64 70 } else { 65 - str_keys.push(k.clone()); 71 + str_keys.push(k); 66 72 } 67 73 } 68 74 } ··· 76 82 } 77 83 78 84 /// Get all own property names (enumerable or not) of an object. 79 - fn own_property_names(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> { 85 + fn own_property_names(gc: &Gc<HeapObject>, shapes: &ShapeTable, obj_ref: GcRef) -> Vec<String> { 80 86 match gc.get(obj_ref) { 81 87 Some(HeapObject::Object(data)) => { 82 88 let mut int_keys: Vec<(u32, String)> = Vec::new(); 83 89 let mut str_keys: Vec<String> = Vec::new(); 84 - for k in data.properties.keys() { 90 + for k in data.property_keys(shapes) { 85 91 if let Ok(idx) = k.parse::<u32>() { 86 - int_keys.push((idx, k.clone())); 92 + int_keys.push((idx, k)); 87 93 } else { 88 - str_keys.push(k.clone()); 94 + str_keys.push(k); 89 95 } 90 96 } 91 97 int_keys.sort_by_key(|(idx, _)| *idx); ··· 98 104 } 99 105 100 106 /// Read the "length" property of an array-like object as usize. 101 - fn array_length(gc: &Gc<HeapObject>, obj: GcRef) -> usize { 107 + fn array_length(gc: &Gc<HeapObject>, shapes: &ShapeTable, obj: GcRef) -> usize { 102 108 match gc.get(obj) { 103 - Some(HeapObject::Object(data)) => match data.properties.get("length") { 109 + Some(HeapObject::Object(data)) => match data.get_property("length", shapes) { 104 110 Some(prop) => prop.value.to_number() as usize, 105 111 None => 0, 106 112 }, ··· 109 115 } 110 116 111 117 /// Set the "length" property on an array-like object. 112 - fn set_array_length(gc: &mut Gc<HeapObject>, obj: GcRef, len: usize) { 118 + fn set_array_length(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, obj: GcRef, len: usize) { 113 119 if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { 114 - if let Some(prop) = data.properties.get_mut("length") { 115 - prop.value = Value::Number(len as f64); 116 - } else { 117 - data.properties.insert( 120 + if !data.update_value("length", Value::Number(len as f64), shapes) { 121 + data.insert_property( 118 122 "length".to_string(), 119 123 Property { 120 124 value: Value::Number(len as f64), ··· 122 126 enumerable: false, 123 127 configurable: false, 124 128 }, 129 + shapes, 125 130 ); 126 131 } 127 132 } 128 133 } 129 134 130 135 /// Check whether an object has a "length" property (i.e. is array-like). 131 - fn array_length_exists(gc: &Gc<HeapObject>, obj: GcRef) -> bool { 136 + fn array_length_exists(gc: &Gc<HeapObject>, shapes: &ShapeTable, obj: GcRef) -> bool { 132 137 match gc.get(obj) { 133 - Some(HeapObject::Object(data)) => data.properties.contains_key("length"), 138 + Some(HeapObject::Object(data)) => data.contains_key("length", shapes), 134 139 _ => false, 135 140 } 136 141 } 137 142 138 143 /// Get an element by index from an array-like object. 139 - fn array_get(gc: &Gc<HeapObject>, obj: GcRef, idx: usize) -> Value { 144 + fn array_get(gc: &Gc<HeapObject>, shapes: &ShapeTable, obj: GcRef, idx: usize) -> Value { 140 145 match gc.get(obj) { 141 146 Some(HeapObject::Object(data)) => { 142 147 let key = idx.to_string(); 143 - data.properties 144 - .get(&key) 145 - .map(|p| p.value.clone()) 148 + data.get_property(&key, shapes) 149 + .map(|p| p.value) 146 150 .unwrap_or(Value::Undefined) 147 151 } 148 152 _ => Value::Undefined, ··· 150 154 } 151 155 152 156 /// Set an element by index on an array-like object. 153 - fn array_set(gc: &mut Gc<HeapObject>, obj: GcRef, idx: usize, val: Value) { 157 + fn array_set(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, obj: GcRef, idx: usize, val: Value) { 154 158 if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { 155 - data.properties.insert(idx.to_string(), Property::data(val)); 159 + data.insert_property(idx.to_string(), Property::data(val), shapes); 156 160 } 157 161 } 158 162 ··· 162 166 pub fn init_builtins(vm: &mut Vm) { 163 167 // Create Object.prototype first (root of the prototype chain). 164 168 let obj_proto = vm.gc.alloc(HeapObject::Object(ObjectData::new())); 165 - init_object_prototype(&mut vm.gc, obj_proto); 169 + init_object_prototype(&mut vm.gc, &mut vm.shapes, obj_proto); 166 170 167 171 // Create Array.prototype (inherits from Object.prototype). 168 172 let mut arr_proto_data = ObjectData::new(); 169 173 arr_proto_data.prototype = Some(obj_proto); 170 174 let arr_proto = vm.gc.alloc(HeapObject::Object(arr_proto_data)); 171 - init_array_prototype(&mut vm.gc, arr_proto); 175 + init_array_prototype(&mut vm.gc, &mut vm.shapes, arr_proto); 172 176 173 177 // Create Error.prototype (inherits from Object.prototype). 174 178 let mut err_proto_data = ObjectData::new(); 175 179 err_proto_data.prototype = Some(obj_proto); 176 - err_proto_data.properties.insert( 180 + err_proto_data.insert_property( 177 181 "name".to_string(), 178 182 Property::builtin(Value::String("Error".to_string())), 183 + &mut vm.shapes, 179 184 ); 180 - err_proto_data.properties.insert( 185 + err_proto_data.insert_property( 181 186 "message".to_string(), 182 187 Property::builtin(Value::String(String::new())), 188 + &mut vm.shapes, 183 189 ); 184 190 let err_proto = vm.gc.alloc(HeapObject::Object(err_proto_data)); 185 - init_error_prototype(&mut vm.gc, err_proto); 191 + init_error_prototype(&mut vm.gc, &mut vm.shapes, err_proto); 186 192 187 193 // Create String.prototype (inherits from Object.prototype). 188 194 let mut str_proto_data = ObjectData::new(); 189 195 str_proto_data.prototype = Some(obj_proto); 190 196 let str_proto = vm.gc.alloc(HeapObject::Object(str_proto_data)); 191 - init_string_prototype(&mut vm.gc, str_proto); 197 + init_string_prototype(&mut vm.gc, &mut vm.shapes, str_proto); 192 198 193 199 // Create Number.prototype (inherits from Object.prototype). 194 200 let mut num_proto_data = ObjectData::new(); 195 201 num_proto_data.prototype = Some(obj_proto); 196 202 let num_proto = vm.gc.alloc(HeapObject::Object(num_proto_data)); 197 - init_number_prototype(&mut vm.gc, num_proto); 203 + init_number_prototype(&mut vm.gc, &mut vm.shapes, num_proto); 198 204 199 205 // Create Boolean.prototype (inherits from Object.prototype). 200 206 let mut bool_proto_data = ObjectData::new(); 201 207 bool_proto_data.prototype = Some(obj_proto); 202 208 let bool_proto = vm.gc.alloc(HeapObject::Object(bool_proto_data)); 203 - init_boolean_prototype(&mut vm.gc, bool_proto); 209 + init_boolean_prototype(&mut vm.gc, &mut vm.shapes, bool_proto); 204 210 205 211 // Store prototypes in VM for use by CreateArray/CreateObject and auto-boxing. 206 212 vm.object_prototype = Some(obj_proto); ··· 265 271 266 272 // ── Object.prototype ───────────────────────────────────────── 267 273 268 - fn init_object_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 274 + fn init_object_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 269 275 let has_own = make_native(gc, "hasOwnProperty", object_proto_has_own_property); 270 - set_builtin_prop(gc, proto, "hasOwnProperty", Value::Function(has_own)); 276 + set_builtin_prop( 277 + gc, 278 + shapes, 279 + proto, 280 + "hasOwnProperty", 281 + Value::Function(has_own), 282 + ); 271 283 272 284 let to_string = make_native(gc, "toString", object_proto_to_string); 273 - set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); 285 + set_builtin_prop(gc, shapes, proto, "toString", Value::Function(to_string)); 274 286 275 287 let value_of = make_native(gc, "valueOf", object_proto_value_of); 276 - set_builtin_prop(gc, proto, "valueOf", Value::Function(value_of)); 288 + set_builtin_prop(gc, shapes, proto, "valueOf", Value::Function(value_of)); 277 289 } 278 290 279 291 fn object_proto_has_own_property( ··· 287 299 match ctx.this.gc_ref() { 288 300 Some(obj_ref) => match ctx.gc.get(obj_ref) { 289 301 Some(HeapObject::Object(data)) => { 290 - Ok(Value::Boolean(data.properties.contains_key(&key))) 302 + Ok(Value::Boolean(data.contains_key(&key, ctx.shapes))) 291 303 } 292 304 Some(HeapObject::Function(fdata)) => { 293 305 Ok(Value::Boolean(fdata.properties.contains_key(&key))) ··· 404 416 Some(Value::Object(r)) | Some(Value::Function(r)) => *r, 405 417 _ => return Err(RuntimeError::type_error("Object.keys requires an object")), 406 418 }; 407 - let keys = own_enumerable_keys(ctx.gc, obj_ref); 408 - Ok(make_string_array(ctx.gc, &keys)) 419 + let keys = own_enumerable_keys(ctx.gc, ctx.shapes, obj_ref); 420 + Ok(make_string_array(ctx.gc, ctx.shapes, &keys)) 409 421 } 410 422 411 423 fn object_values(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 413 425 Some(Value::Object(r)) => *r, 414 426 _ => return Err(RuntimeError::type_error("Object.values requires an object")), 415 427 }; 416 - let keys = own_enumerable_keys(ctx.gc, obj_ref); 428 + let keys = own_enumerable_keys(ctx.gc, ctx.shapes, obj_ref); 417 429 let values: Vec<Value> = keys 418 430 .iter() 419 431 .map(|k| { 420 432 ctx.gc 421 433 .get(obj_ref) 422 434 .and_then(|ho| match ho { 423 - HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), 435 + HeapObject::Object(data) => data.get_property(k, ctx.shapes).map(|p| p.value), 424 436 _ => None, 425 437 }) 426 438 .unwrap_or(Value::Undefined) 427 439 }) 428 440 .collect(); 429 - Ok(make_value_array(ctx.gc, &values)) 441 + Ok(make_value_array(ctx.gc, ctx.shapes, &values)) 430 442 } 431 443 432 444 fn object_entries(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 438 450 )) 439 451 } 440 452 }; 441 - let keys = own_enumerable_keys(ctx.gc, obj_ref); 453 + let keys = own_enumerable_keys(ctx.gc, ctx.shapes, obj_ref); 442 454 let mut entries = Vec::new(); 443 455 for k in &keys { 444 456 let val = ctx 445 457 .gc 446 458 .get(obj_ref) 447 459 .and_then(|ho| match ho { 448 - HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), 460 + HeapObject::Object(data) => data.get_property(k, ctx.shapes).map(|p| p.value), 449 461 _ => None, 450 462 }) 451 463 .unwrap_or(Value::Undefined); 452 464 // Create a [key, value] pair array. 453 - let pair = make_value_array(ctx.gc, &[Value::String(k.clone()), val]); 465 + let pair = make_value_array(ctx.gc, ctx.shapes, &[Value::String(k.clone()), val]); 454 466 entries.push(pair); 455 467 } 456 - Ok(make_value_array(ctx.gc, &entries)) 468 + Ok(make_value_array(ctx.gc, ctx.shapes, &entries)) 457 469 } 458 470 459 471 fn object_assign(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 471 483 Value::Null | Value::Undefined => continue, 472 484 _ => continue, 473 485 }; 474 - let keys = own_enumerable_keys(ctx.gc, src_ref); 486 + let keys = own_enumerable_keys(ctx.gc, ctx.shapes, src_ref); 475 487 for k in &keys { 476 488 let val = ctx 477 489 .gc 478 490 .get(src_ref) 479 491 .and_then(|ho| match ho { 480 - HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), 492 + HeapObject::Object(data) => data.get_property(k, ctx.shapes).map(|p| p.value), 481 493 _ => None, 482 494 }) 483 495 .unwrap_or(Value::Undefined); 484 496 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(target_ref) { 485 - data.properties.insert(k.clone(), Property::data(val)); 497 + data.insert_property(k.clone(), Property::data(val), ctx.shapes); 486 498 } 487 499 } 488 500 } ··· 563 575 )) 564 576 } 565 577 }; 566 - let names = own_property_names(ctx.gc, obj_ref); 567 - Ok(make_string_array(ctx.gc, &names)) 578 + let names = own_property_names(ctx.gc, ctx.shapes, obj_ref); 579 + Ok(make_string_array(ctx.gc, ctx.shapes, &names)) 568 580 } 569 581 570 582 fn object_get_own_prop_desc( ··· 580 592 .map(|v| v.to_js_string(ctx.gc)) 581 593 .unwrap_or_default(); 582 594 let prop = match ctx.gc.get(obj_ref) { 583 - Some(HeapObject::Object(data)) => data.properties.get(&key).cloned(), 595 + Some(HeapObject::Object(data)) => data.get_property(&key, ctx.shapes), 584 596 _ => None, 585 597 }; 586 598 match prop { 587 599 Some(p) => { 588 600 let mut desc = ObjectData::new(); 589 - desc.properties 590 - .insert("value".to_string(), Property::data(p.value)); 591 - desc.properties.insert( 601 + desc.insert_property("value".to_string(), Property::data(p.value), ctx.shapes); 602 + desc.insert_property( 592 603 "writable".to_string(), 593 604 Property::data(Value::Boolean(p.writable)), 605 + ctx.shapes, 594 606 ); 595 - desc.properties.insert( 607 + desc.insert_property( 596 608 "enumerable".to_string(), 597 609 Property::data(Value::Boolean(p.enumerable)), 610 + ctx.shapes, 598 611 ); 599 - desc.properties.insert( 612 + desc.insert_property( 600 613 "configurable".to_string(), 601 614 Property::data(Value::Boolean(p.configurable)), 615 + ctx.shapes, 602 616 ); 603 617 Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(desc)))) 604 618 } ··· 633 647 match ctx.gc.get(desc_ref) { 634 648 Some(HeapObject::Object(desc_data)) => { 635 649 let value = desc_data 636 - .properties 637 - .get("value") 638 - .map(|p| p.value.clone()) 650 + .get_property("value", ctx.shapes) 651 + .map(|p| p.value) 639 652 .unwrap_or(Value::Undefined); 640 653 let writable = desc_data 641 - .properties 642 - .get("writable") 654 + .get_property("writable", ctx.shapes) 643 655 .map(|p| p.value.to_boolean()) 644 656 .unwrap_or(false); 645 657 let enumerable = desc_data 646 - .properties 647 - .get("enumerable") 658 + .get_property("enumerable", ctx.shapes) 648 659 .map(|p| p.value.to_boolean()) 649 660 .unwrap_or(false); 650 661 let configurable = desc_data 651 - .properties 652 - .get("configurable") 662 + .get_property("configurable", ctx.shapes) 653 663 .map(|p| p.value.to_boolean()) 654 664 .unwrap_or(false); 655 665 (value, writable, enumerable, configurable) ··· 659 669 }; 660 670 661 671 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 662 - data.properties.insert( 672 + data.insert_property( 663 673 key, 664 674 Property { 665 675 value, ··· 667 677 enumerable, 668 678 configurable, 669 679 }, 680 + ctx.shapes, 670 681 ); 671 682 } 672 683 Ok(Value::Object(obj_ref)) ··· 680 691 }; 681 692 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 682 693 data.extensible = false; 683 - for prop in data.properties.values_mut() { 694 + data.modify_all_properties(ctx.shapes, |prop| { 684 695 prop.writable = false; 685 696 prop.configurable = false; 686 - } 697 + }); 687 698 } 688 699 Ok(Value::Object(obj_ref)) 689 700 } ··· 696 707 }; 697 708 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 698 709 data.extensible = false; 699 - for prop in data.properties.values_mut() { 710 + data.modify_all_properties(ctx.shapes, |prop| { 700 711 prop.configurable = false; 701 - } 712 + }); 702 713 } 703 714 Ok(Value::Object(obj_ref)) 704 715 } ··· 714 725 return Ok(Value::Boolean(false)); 715 726 } 716 727 let frozen = data 717 - .properties 718 - .values() 719 - .all(|p| !p.writable && !p.configurable); 728 + .property_entries(ctx.shapes) 729 + .iter() 730 + .all(|(_, p)| !p.writable && !p.configurable); 720 731 Ok(Value::Boolean(frozen)) 721 732 } 722 733 _ => Ok(Value::Boolean(true)), ··· 733 744 if data.extensible { 734 745 return Ok(Value::Boolean(false)); 735 746 } 736 - let sealed = data.properties.values().all(|p| !p.configurable); 747 + let sealed = data 748 + .property_entries(ctx.shapes) 749 + .iter() 750 + .all(|(_, p)| !p.configurable); 737 751 Ok(Value::Boolean(sealed)) 738 752 } 739 753 _ => Ok(Value::Boolean(true)), ··· 749 763 )) 750 764 } 751 765 }; 752 - let len = array_length(ctx.gc, arr_ref); 766 + let len = array_length(ctx.gc, ctx.shapes, arr_ref); 753 767 let mut obj = ObjectData::new(); 754 768 for i in 0..len { 755 - let pair_val = array_get(ctx.gc, arr_ref, i); 769 + let pair_val = array_get(ctx.gc, ctx.shapes, arr_ref, i); 756 770 if let Value::Object(pair_ref) = pair_val { 757 - let key = array_get(ctx.gc, pair_ref, 0); 758 - let val = array_get(ctx.gc, pair_ref, 1); 771 + let key = array_get(ctx.gc, ctx.shapes, pair_ref, 0); 772 + let val = array_get(ctx.gc, ctx.shapes, pair_ref, 1); 759 773 let key_str = key.to_js_string(ctx.gc); 760 - obj.properties.insert(key_str, Property::data(val)); 774 + obj.insert_property(key_str, Property::data(val), ctx.shapes); 761 775 } 762 776 } 763 777 Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj)))) ··· 773 787 .map(|v| v.to_js_string(ctx.gc)) 774 788 .unwrap_or_default(); 775 789 match ctx.gc.get(obj_ref) { 776 - Some(HeapObject::Object(data)) => Ok(Value::Boolean(data.properties.contains_key(&key))), 790 + Some(HeapObject::Object(data)) => Ok(Value::Boolean(data.contains_key(&key, ctx.shapes))), 777 791 _ => Ok(Value::Boolean(false)), 778 792 } 779 793 } ··· 781 795 // ── Array helpers ──────────────────────────────────────────── 782 796 783 797 /// Create a JS array from a slice of string values. 784 - fn make_string_array(gc: &mut Gc<HeapObject>, items: &[String]) -> Value { 798 + fn make_string_array(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, items: &[String]) -> Value { 785 799 let mut obj = ObjectData::new(); 786 800 for (i, s) in items.iter().enumerate() { 787 - obj.properties 788 - .insert(i.to_string(), Property::data(Value::String(s.clone()))); 801 + obj.insert_property( 802 + i.to_string(), 803 + Property::data(Value::String(s.clone())), 804 + shapes, 805 + ); 789 806 } 790 - obj.properties.insert( 807 + obj.insert_property( 791 808 "length".to_string(), 792 809 Property { 793 810 value: Value::Number(items.len() as f64), ··· 795 812 enumerable: false, 796 813 configurable: false, 797 814 }, 815 + shapes, 798 816 ); 799 817 Value::Object(gc.alloc(HeapObject::Object(obj))) 800 818 } 801 819 802 820 /// Create a JS array from a slice of Values. 803 - fn make_value_array(gc: &mut Gc<HeapObject>, items: &[Value]) -> Value { 821 + fn make_value_array(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, items: &[Value]) -> Value { 804 822 let mut obj = ObjectData::new(); 805 823 for (i, v) in items.iter().enumerate() { 806 - obj.properties 807 - .insert(i.to_string(), Property::data(v.clone())); 824 + obj.insert_property(i.to_string(), Property::data(v.clone()), shapes); 808 825 } 809 - obj.properties.insert( 826 + obj.insert_property( 810 827 "length".to_string(), 811 828 Property { 812 829 value: Value::Number(items.len() as f64), ··· 814 831 enumerable: false, 815 832 configurable: false, 816 833 }, 834 + shapes, 817 835 ); 818 836 Value::Object(gc.alloc(HeapObject::Object(obj))) 819 837 } 820 838 821 839 // ── Array.prototype ────────────────────────────────────────── 822 840 823 - fn init_array_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 841 + fn init_array_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 824 842 let push = make_native(gc, "push", array_push); 825 - set_builtin_prop(gc, proto, "push", Value::Function(push)); 843 + set_builtin_prop(gc, shapes, proto, "push", Value::Function(push)); 826 844 827 845 let pop = make_native(gc, "pop", array_pop); 828 - set_builtin_prop(gc, proto, "pop", Value::Function(pop)); 846 + set_builtin_prop(gc, shapes, proto, "pop", Value::Function(pop)); 829 847 830 848 let shift = make_native(gc, "shift", array_shift); 831 - set_builtin_prop(gc, proto, "shift", Value::Function(shift)); 849 + set_builtin_prop(gc, shapes, proto, "shift", Value::Function(shift)); 832 850 833 851 let unshift = make_native(gc, "unshift", array_unshift); 834 - set_builtin_prop(gc, proto, "unshift", Value::Function(unshift)); 852 + set_builtin_prop(gc, shapes, proto, "unshift", Value::Function(unshift)); 835 853 836 854 let index_of = make_native(gc, "indexOf", array_index_of); 837 - set_builtin_prop(gc, proto, "indexOf", Value::Function(index_of)); 855 + set_builtin_prop(gc, shapes, proto, "indexOf", Value::Function(index_of)); 838 856 839 857 let last_index_of = make_native(gc, "lastIndexOf", array_last_index_of); 840 - set_builtin_prop(gc, proto, "lastIndexOf", Value::Function(last_index_of)); 858 + set_builtin_prop( 859 + gc, 860 + shapes, 861 + proto, 862 + "lastIndexOf", 863 + Value::Function(last_index_of), 864 + ); 841 865 842 866 let includes = make_native(gc, "includes", array_includes); 843 - set_builtin_prop(gc, proto, "includes", Value::Function(includes)); 867 + set_builtin_prop(gc, shapes, proto, "includes", Value::Function(includes)); 844 868 845 869 let join = make_native(gc, "join", array_join); 846 - set_builtin_prop(gc, proto, "join", Value::Function(join)); 870 + set_builtin_prop(gc, shapes, proto, "join", Value::Function(join)); 847 871 848 872 let slice = make_native(gc, "slice", array_slice); 849 - set_builtin_prop(gc, proto, "slice", Value::Function(slice)); 873 + set_builtin_prop(gc, shapes, proto, "slice", Value::Function(slice)); 850 874 851 875 let concat = make_native(gc, "concat", array_concat); 852 - set_builtin_prop(gc, proto, "concat", Value::Function(concat)); 876 + set_builtin_prop(gc, shapes, proto, "concat", Value::Function(concat)); 853 877 854 878 let reverse = make_native(gc, "reverse", array_reverse); 855 - set_builtin_prop(gc, proto, "reverse", Value::Function(reverse)); 879 + set_builtin_prop(gc, shapes, proto, "reverse", Value::Function(reverse)); 856 880 857 881 let splice = make_native(gc, "splice", array_splice); 858 - set_builtin_prop(gc, proto, "splice", Value::Function(splice)); 882 + set_builtin_prop(gc, shapes, proto, "splice", Value::Function(splice)); 859 883 860 884 let fill = make_native(gc, "fill", array_fill); 861 - set_builtin_prop(gc, proto, "fill", Value::Function(fill)); 885 + set_builtin_prop(gc, shapes, proto, "fill", Value::Function(fill)); 862 886 863 887 let to_string = make_native(gc, "toString", array_to_string); 864 - set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); 888 + set_builtin_prop(gc, shapes, proto, "toString", Value::Function(to_string)); 865 889 866 890 let at = make_native(gc, "at", array_at); 867 - set_builtin_prop(gc, proto, "at", Value::Function(at)); 891 + set_builtin_prop(gc, shapes, proto, "at", Value::Function(at)); 868 892 869 893 // @@iterator: returns an array iterator (values). 870 894 let iter = make_native(gc, "[Symbol.iterator]", array_iterator); 871 - set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter)); 895 + set_builtin_prop(gc, shapes, proto, "@@iterator", Value::Function(iter)); 872 896 873 897 // Array.prototype.keys/values/entries 874 898 let keys_fn = make_native(gc, "keys", array_keys_iter); 875 - set_builtin_prop(gc, proto, "keys", Value::Function(keys_fn)); 899 + set_builtin_prop(gc, shapes, proto, "keys", Value::Function(keys_fn)); 876 900 let values_fn = make_native(gc, "values", array_values_iter); 877 - set_builtin_prop(gc, proto, "values", Value::Function(values_fn)); 901 + set_builtin_prop(gc, shapes, proto, "values", Value::Function(values_fn)); 878 902 let entries_fn = make_native(gc, "entries", array_entries_iter); 879 - set_builtin_prop(gc, proto, "entries", Value::Function(entries_fn)); 903 + set_builtin_prop(gc, shapes, proto, "entries", Value::Function(entries_fn)); 880 904 } 881 905 882 906 fn array_push(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 884 908 Some(r) => r, 885 909 None => return Err(RuntimeError::type_error("push called on non-object")), 886 910 }; 887 - let mut len = array_length(ctx.gc, obj_ref); 911 + let mut len = array_length(ctx.gc, ctx.shapes, obj_ref); 888 912 for val in args { 889 - array_set(ctx.gc, obj_ref, len, val.clone()); 913 + array_set(ctx.gc, ctx.shapes, obj_ref, len, val.clone()); 890 914 len += 1; 891 915 } 892 - set_array_length(ctx.gc, obj_ref, len); 916 + set_array_length(ctx.gc, ctx.shapes, obj_ref, len); 893 917 Ok(Value::Number(len as f64)) 894 918 } 895 919 ··· 899 923 Some(r) => r, 900 924 None => return Err(RuntimeError::type_error("pop called on non-object")), 901 925 }; 902 - let len = array_length(ctx.gc, obj_ref); 926 + let len = array_length(ctx.gc, ctx.shapes, obj_ref); 903 927 if len == 0 { 904 928 return Ok(Value::Undefined); 905 929 } 906 - let val = array_get(ctx.gc, obj_ref, len - 1); 930 + let val = array_get(ctx.gc, ctx.shapes, obj_ref, len - 1); 907 931 // Remove the last element. 908 932 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 909 - data.properties.remove(&(len - 1).to_string()); 933 + data.remove_property(&(len - 1).to_string(), ctx.shapes); 910 934 } 911 - set_array_length(ctx.gc, obj_ref, len - 1); 935 + set_array_length(ctx.gc, ctx.shapes, obj_ref, len - 1); 912 936 Ok(val) 913 937 } 914 938 ··· 918 942 Some(r) => r, 919 943 None => return Err(RuntimeError::type_error("shift called on non-object")), 920 944 }; 921 - let len = array_length(ctx.gc, obj_ref); 945 + let len = array_length(ctx.gc, ctx.shapes, obj_ref); 922 946 if len == 0 { 923 947 return Ok(Value::Undefined); 924 948 } 925 - let first = array_get(ctx.gc, obj_ref, 0); 949 + let first = array_get(ctx.gc, ctx.shapes, obj_ref, 0); 926 950 // Shift all elements down. 927 951 let mut vals = Vec::with_capacity(len - 1); 928 952 for i in 1..len { 929 - vals.push(array_get(ctx.gc, obj_ref, i)); 953 + vals.push(array_get(ctx.gc, ctx.shapes, obj_ref, i)); 930 954 } 931 955 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 932 956 // Remove all numeric keys. 933 957 for i in 0..len { 934 - data.properties.remove(&i.to_string()); 958 + data.remove_property(&i.to_string(), ctx.shapes); 935 959 } 936 960 // Re-insert shifted values. 937 961 for (i, v) in vals.into_iter().enumerate() { 938 - data.properties.insert(i.to_string(), Property::data(v)); 962 + data.insert_property(i.to_string(), Property::data(v), ctx.shapes); 939 963 } 940 964 } 941 - set_array_length(ctx.gc, obj_ref, len - 1); 965 + set_array_length(ctx.gc, ctx.shapes, obj_ref, len - 1); 942 966 Ok(first) 943 967 } 944 968 ··· 947 971 Some(r) => r, 948 972 None => return Err(RuntimeError::type_error("unshift called on non-object")), 949 973 }; 950 - let len = array_length(ctx.gc, obj_ref); 974 + let len = array_length(ctx.gc, ctx.shapes, obj_ref); 951 975 let insert_count = args.len(); 952 976 // Read existing values. 953 977 let mut existing = Vec::with_capacity(len); 954 978 for i in 0..len { 955 - existing.push(array_get(ctx.gc, obj_ref, i)); 979 + existing.push(array_get(ctx.gc, ctx.shapes, obj_ref, i)); 956 980 } 957 981 // Write new values at the start, then existing values after. 958 982 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 959 983 for i in 0..len { 960 - data.properties.remove(&i.to_string()); 984 + data.remove_property(&i.to_string(), ctx.shapes); 961 985 } 962 986 for (i, v) in args.iter().enumerate() { 963 - data.properties 964 - .insert(i.to_string(), Property::data(v.clone())); 987 + data.insert_property(i.to_string(), Property::data(v.clone()), ctx.shapes); 965 988 } 966 989 for (i, v) in existing.into_iter().enumerate() { 967 - data.properties 968 - .insert((i + insert_count).to_string(), Property::data(v)); 990 + data.insert_property( 991 + (i + insert_count).to_string(), 992 + Property::data(v), 993 + ctx.shapes, 994 + ); 969 995 } 970 996 } 971 997 let new_len = len + insert_count; 972 - set_array_length(ctx.gc, obj_ref, new_len); 998 + set_array_length(ctx.gc, ctx.shapes, obj_ref, new_len); 973 999 Ok(Value::Number(new_len as f64)) 974 1000 } 975 1001 ··· 983 1009 .get(1) 984 1010 .map(|v| { 985 1011 let n = v.to_number() as i64; 986 - let len = array_length(ctx.gc, obj_ref) as i64; 1012 + let len = array_length(ctx.gc, ctx.shapes, obj_ref) as i64; 987 1013 if n < 0 { 988 1014 (len + n).max(0) as usize 989 1015 } else { ··· 991 1017 } 992 1018 }) 993 1019 .unwrap_or(0); 994 - let len = array_length(ctx.gc, obj_ref); 1020 + let len = array_length(ctx.gc, ctx.shapes, obj_ref); 995 1021 for i in from..len { 996 - let elem = array_get(ctx.gc, obj_ref, i); 1022 + let elem = array_get(ctx.gc, ctx.shapes, obj_ref, i); 997 1023 if strict_eq_values(&elem, &search) { 998 1024 return Ok(Value::Number(i as f64)); 999 1025 } ··· 1007 1033 None => return Ok(Value::Number(-1.0)), 1008 1034 }; 1009 1035 let search = args.first().cloned().unwrap_or(Value::Undefined); 1010 - let len = array_length(ctx.gc, obj_ref); 1036 + let len = array_length(ctx.gc, ctx.shapes, obj_ref); 1011 1037 if len == 0 { 1012 1038 return Ok(Value::Number(-1.0)); 1013 1039 } ··· 1023 1049 }) 1024 1050 .unwrap_or(len - 1); 1025 1051 for i in (0..=from).rev() { 1026 - let elem = array_get(ctx.gc, obj_ref, i); 1052 + let elem = array_get(ctx.gc, ctx.shapes, obj_ref, i); 1027 1053 if strict_eq_values(&elem, &search) { 1028 1054 return Ok(Value::Number(i as f64)); 1029 1055 } ··· 1037 1063 None => return Ok(Value::Boolean(false)), 1038 1064 }; 1039 1065 let search = args.first().cloned().unwrap_or(Value::Undefined); 1040 - let len = array_length(ctx.gc, obj_ref); 1066 + let len = array_length(ctx.gc, ctx.shapes, obj_ref); 1041 1067 let from = args 1042 1068 .get(1) 1043 1069 .map(|v| { ··· 1050 1076 }) 1051 1077 .unwrap_or(0); 1052 1078 for i in from..len { 1053 - let elem = array_get(ctx.gc, obj_ref, i); 1079 + let elem = array_get(ctx.gc, ctx.shapes, obj_ref, i); 1054 1080 // includes uses SameValueZero (like === but NaN === NaN). 1055 1081 if same_value_zero(&elem, &search) { 1056 1082 return Ok(Value::Boolean(true)); ··· 1074 1100 } 1075 1101 }) 1076 1102 .unwrap_or_else(|| ",".to_string()); 1077 - let len = array_length(ctx.gc, obj_ref); 1103 + let len = array_length(ctx.gc, ctx.shapes, obj_ref); 1078 1104 let mut parts = Vec::with_capacity(len); 1079 1105 for i in 0..len { 1080 - let elem = array_get(ctx.gc, obj_ref, i); 1106 + let elem = array_get(ctx.gc, ctx.shapes, obj_ref, i); 1081 1107 if matches!(elem, Value::Undefined | Value::Null) { 1082 1108 parts.push(String::new()); 1083 1109 } else { ··· 1092 1118 Some(r) => r, 1093 1119 None => return Ok(Value::Undefined), 1094 1120 }; 1095 - let len = array_length(ctx.gc, obj_ref) as i64; 1121 + let len = array_length(ctx.gc, ctx.shapes, obj_ref) as i64; 1096 1122 let start = args 1097 1123 .first() 1098 1124 .map(|v| { ··· 1122 1148 1123 1149 let mut items = Vec::new(); 1124 1150 for i in start..end { 1125 - items.push(array_get(ctx.gc, obj_ref, i)); 1151 + items.push(array_get(ctx.gc, ctx.shapes, obj_ref, i)); 1126 1152 } 1127 - Ok(make_value_array(ctx.gc, &items)) 1153 + Ok(make_value_array(ctx.gc, ctx.shapes, &items)) 1128 1154 } 1129 1155 1130 1156 fn array_concat(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 1134 1160 }; 1135 1161 let mut items = Vec::new(); 1136 1162 // First, add elements from `this`. 1137 - let len = array_length(ctx.gc, obj_ref); 1163 + let len = array_length(ctx.gc, ctx.shapes, obj_ref); 1138 1164 for i in 0..len { 1139 - items.push(array_get(ctx.gc, obj_ref, i)); 1165 + items.push(array_get(ctx.gc, ctx.shapes, obj_ref, i)); 1140 1166 } 1141 1167 // Then add from each argument. 1142 1168 for arg in args { 1143 1169 match arg { 1144 1170 Value::Object(r) => { 1145 - let arg_len = array_length(ctx.gc, *r); 1171 + let arg_len = array_length(ctx.gc, ctx.shapes, *r); 1146 1172 // Only spread array-like objects (those with a length property). 1147 - if array_length_exists(ctx.gc, *r) { 1173 + if array_length_exists(ctx.gc, ctx.shapes, *r) { 1148 1174 for i in 0..arg_len { 1149 - items.push(array_get(ctx.gc, *r, i)); 1175 + items.push(array_get(ctx.gc, ctx.shapes, *r, i)); 1150 1176 } 1151 1177 } else { 1152 1178 items.push(arg.clone()); ··· 1155 1181 _ => items.push(arg.clone()), 1156 1182 } 1157 1183 } 1158 - Ok(make_value_array(ctx.gc, &items)) 1184 + Ok(make_value_array(ctx.gc, ctx.shapes, &items)) 1159 1185 } 1160 1186 1161 1187 fn array_reverse(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 1164 1190 Some(r) => r, 1165 1191 None => return Ok(Value::Undefined), 1166 1192 }; 1167 - let len = array_length(ctx.gc, obj_ref); 1193 + let len = array_length(ctx.gc, ctx.shapes, obj_ref); 1168 1194 // Read all values. 1169 - let mut vals: Vec<Value> = (0..len).map(|i| array_get(ctx.gc, obj_ref, i)).collect(); 1195 + let mut vals: Vec<Value> = (0..len) 1196 + .map(|i| array_get(ctx.gc, ctx.shapes, obj_ref, i)) 1197 + .collect(); 1170 1198 vals.reverse(); 1171 1199 // Write back. 1172 1200 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 1173 1201 for (i, v) in vals.into_iter().enumerate() { 1174 - data.properties.insert(i.to_string(), Property::data(v)); 1202 + data.insert_property(i.to_string(), Property::data(v), ctx.shapes); 1175 1203 } 1176 1204 } 1177 1205 Ok(ctx.this.clone()) ··· 1182 1210 Some(r) => r, 1183 1211 None => return Ok(Value::Undefined), 1184 1212 }; 1185 - let len = array_length(ctx.gc, obj_ref) as i64; 1213 + let len = array_length(ctx.gc, ctx.shapes, obj_ref) as i64; 1186 1214 let start = args 1187 1215 .first() 1188 1216 .map(|v| { ··· 1205 1233 1206 1234 // Collect current values. 1207 1235 let all_vals: Vec<Value> = (0..len as usize) 1208 - .map(|i| array_get(ctx.gc, obj_ref, i)) 1236 + .map(|i| array_get(ctx.gc, ctx.shapes, obj_ref, i)) 1209 1237 .collect(); 1210 1238 1211 1239 // Build removed slice. ··· 1221 1249 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 1222 1250 // Remove all numeric keys. 1223 1251 let old_keys: Vec<String> = data 1224 - .properties 1225 - .keys() 1252 + .property_keys(ctx.shapes) 1253 + .into_iter() 1226 1254 .filter(|k| k.parse::<usize>().is_ok()) 1227 - .cloned() 1228 1255 .collect(); 1229 1256 for k in old_keys { 1230 - data.properties.remove(&k); 1257 + data.remove_property(&k, ctx.shapes); 1231 1258 } 1232 1259 // Write new values. 1233 1260 for (i, v) in new_vals.iter().enumerate() { 1234 - data.properties 1235 - .insert(i.to_string(), Property::data(v.clone())); 1261 + data.insert_property(i.to_string(), Property::data(v.clone()), ctx.shapes); 1236 1262 } 1237 1263 } 1238 - set_array_length(ctx.gc, obj_ref, new_vals.len()); 1239 - Ok(make_value_array(ctx.gc, &removed)) 1264 + set_array_length(ctx.gc, ctx.shapes, obj_ref, new_vals.len()); 1265 + Ok(make_value_array(ctx.gc, ctx.shapes, &removed)) 1240 1266 } 1241 1267 1242 1268 fn array_fill(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 1245 1271 None => return Ok(Value::Undefined), 1246 1272 }; 1247 1273 let val = args.first().cloned().unwrap_or(Value::Undefined); 1248 - let len = array_length(ctx.gc, obj_ref) as i64; 1274 + let len = array_length(ctx.gc, ctx.shapes, obj_ref) as i64; 1249 1275 let start = args 1250 1276 .get(1) 1251 1277 .map(|v| { ··· 1273 1299 }) 1274 1300 .unwrap_or(len) as usize; 1275 1301 for i in start..end { 1276 - array_set(ctx.gc, obj_ref, i, val.clone()); 1302 + array_set(ctx.gc, ctx.shapes, obj_ref, i, val.clone()); 1277 1303 } 1278 1304 Ok(ctx.this.clone()) 1279 1305 } ··· 1284 1310 &[Value::String(",".to_string())], 1285 1311 &mut NativeContext { 1286 1312 gc: ctx.gc, 1313 + shapes: ctx.shapes, 1287 1314 this: ctx.this.clone(), 1288 1315 console_output: ctx.console_output, 1289 1316 dom_bridge: ctx.dom_bridge, ··· 1297 1324 Some(r) => r, 1298 1325 None => return Ok(Value::Undefined), 1299 1326 }; 1300 - let len = array_length(ctx.gc, obj_ref) as i64; 1327 + let len = array_length(ctx.gc, ctx.shapes, obj_ref) as i64; 1301 1328 let index = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 1302 1329 let actual = if index < 0 { len + index } else { index }; 1303 1330 if actual < 0 || actual >= len { 1304 1331 Ok(Value::Undefined) 1305 1332 } else { 1306 - Ok(array_get(ctx.gc, obj_ref, actual as usize)) 1333 + Ok(array_get(ctx.gc, ctx.shapes, obj_ref, actual as usize)) 1307 1334 } 1308 1335 } 1309 1336 ··· 1337 1364 if let Value::Number(n) = &args[0] { 1338 1365 let len = *n as usize; 1339 1366 let mut obj = ObjectData::new(); 1340 - obj.properties.insert( 1367 + obj.insert_property( 1341 1368 "length".to_string(), 1342 1369 Property { 1343 1370 value: Value::Number(len as f64), ··· 1345 1372 enumerable: false, 1346 1373 configurable: false, 1347 1374 }, 1375 + ctx.shapes, 1348 1376 ); 1349 1377 return Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj)))); 1350 1378 } 1351 1379 } 1352 - Ok(make_value_array(ctx.gc, args)) 1380 + Ok(make_value_array(ctx.gc, ctx.shapes, args)) 1353 1381 } 1354 1382 1355 1383 fn array_is_array(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 1358 1386 match args.first() { 1359 1387 Some(Value::Object(r)) => match ctx.gc.get(*r) { 1360 1388 Some(HeapObject::Object(data)) => { 1361 - Ok(Value::Boolean(data.properties.contains_key("length"))) 1389 + Ok(Value::Boolean(data.contains_key("length", ctx.shapes))) 1362 1390 } 1363 1391 _ => Ok(Value::Boolean(false)), 1364 1392 }, ··· 1370 1398 let iterable = args.first().cloned().unwrap_or(Value::Undefined); 1371 1399 match iterable { 1372 1400 Value::Object(r) => { 1373 - let len = array_length(ctx.gc, r); 1401 + let len = array_length(ctx.gc, ctx.shapes, r); 1374 1402 let mut items = Vec::with_capacity(len); 1375 1403 for i in 0..len { 1376 - items.push(array_get(ctx.gc, r, i)); 1404 + items.push(array_get(ctx.gc, ctx.shapes, r, i)); 1377 1405 } 1378 - Ok(make_value_array(ctx.gc, &items)) 1406 + Ok(make_value_array(ctx.gc, ctx.shapes, &items)) 1379 1407 } 1380 1408 Value::String(s) => { 1381 1409 let chars: Vec<Value> = s.chars().map(|c| Value::String(c.to_string())).collect(); 1382 - Ok(make_value_array(ctx.gc, &chars)) 1410 + Ok(make_value_array(ctx.gc, ctx.shapes, &chars)) 1383 1411 } 1384 - _ => Ok(make_value_array(ctx.gc, &[])), 1412 + _ => Ok(make_value_array(ctx.gc, ctx.shapes, &[])), 1385 1413 } 1386 1414 } 1387 1415 1388 1416 fn array_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1389 - Ok(make_value_array(ctx.gc, args)) 1417 + Ok(make_value_array(ctx.gc, ctx.shapes, args)) 1390 1418 } 1391 1419 1392 1420 // ── Error constructors ─────────────────────────────────────── 1393 1421 1394 - fn init_error_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 1422 + fn init_error_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 1395 1423 let to_string = make_native(gc, "toString", error_proto_to_string); 1396 - set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); 1424 + set_builtin_prop(gc, shapes, proto, "toString", Value::Function(to_string)); 1397 1425 } 1398 1426 1399 1427 fn init_error_constructors(vm: &mut Vm, err_proto: GcRef) { ··· 1402 1430 vm.set_global("Error", Value::Function(error_ctor)); 1403 1431 1404 1432 // TypeError. 1405 - let te_proto = make_error_subclass_proto(&mut vm.gc, "TypeError", err_proto); 1433 + let te_proto = make_error_subclass_proto(&mut vm.gc, &mut vm.shapes, "TypeError", err_proto); 1406 1434 let te_ctor = make_error_constructor(&mut vm.gc, "TypeError", te_proto); 1407 1435 vm.set_global("TypeError", Value::Function(te_ctor)); 1408 1436 1409 1437 // ReferenceError. 1410 - let re_proto = make_error_subclass_proto(&mut vm.gc, "ReferenceError", err_proto); 1438 + let re_proto = 1439 + make_error_subclass_proto(&mut vm.gc, &mut vm.shapes, "ReferenceError", err_proto); 1411 1440 let re_ctor = make_error_constructor(&mut vm.gc, "ReferenceError", re_proto); 1412 1441 vm.set_global("ReferenceError", Value::Function(re_ctor)); 1413 1442 1414 1443 // SyntaxError. 1415 - let se_proto = make_error_subclass_proto(&mut vm.gc, "SyntaxError", err_proto); 1444 + let se_proto = make_error_subclass_proto(&mut vm.gc, &mut vm.shapes, "SyntaxError", err_proto); 1416 1445 let se_ctor = make_error_constructor(&mut vm.gc, "SyntaxError", se_proto); 1417 1446 vm.set_global("SyntaxError", Value::Function(se_ctor)); 1418 1447 1419 1448 // RangeError. 1420 - let rae_proto = make_error_subclass_proto(&mut vm.gc, "RangeError", err_proto); 1449 + let rae_proto = make_error_subclass_proto(&mut vm.gc, &mut vm.shapes, "RangeError", err_proto); 1421 1450 let rae_ctor = make_error_constructor(&mut vm.gc, "RangeError", rae_proto); 1422 1451 vm.set_global("RangeError", Value::Function(rae_ctor)); 1423 1452 1424 1453 // URIError. 1425 - let ue_proto = make_error_subclass_proto(&mut vm.gc, "URIError", err_proto); 1454 + let ue_proto = make_error_subclass_proto(&mut vm.gc, &mut vm.shapes, "URIError", err_proto); 1426 1455 let ue_ctor = make_error_constructor(&mut vm.gc, "URIError", ue_proto); 1427 1456 vm.set_global("URIError", Value::Function(ue_ctor)); 1428 1457 1429 1458 // EvalError. 1430 - let ee_proto = make_error_subclass_proto(&mut vm.gc, "EvalError", err_proto); 1459 + let ee_proto = make_error_subclass_proto(&mut vm.gc, &mut vm.shapes, "EvalError", err_proto); 1431 1460 let ee_ctor = make_error_constructor(&mut vm.gc, "EvalError", ee_proto); 1432 1461 vm.set_global("EvalError", Value::Function(ee_ctor)); 1433 1462 } 1434 1463 1435 - fn make_error_subclass_proto(gc: &mut Gc<HeapObject>, name: &str, parent_proto: GcRef) -> GcRef { 1464 + fn make_error_subclass_proto( 1465 + gc: &mut Gc<HeapObject>, 1466 + shapes: &mut ShapeTable, 1467 + name: &str, 1468 + parent_proto: GcRef, 1469 + ) -> GcRef { 1436 1470 let mut data = ObjectData::new(); 1437 1471 data.prototype = Some(parent_proto); 1438 - data.properties.insert( 1472 + data.insert_property( 1439 1473 "name".to_string(), 1440 1474 Property::builtin(Value::String(name.to_string())), 1475 + shapes, 1441 1476 ); 1442 - data.properties.insert( 1477 + data.insert_property( 1443 1478 "message".to_string(), 1444 1479 Property::builtin(Value::String(String::new())), 1480 + shapes, 1445 1481 ); 1446 1482 gc.alloc(HeapObject::Object(data)) 1447 1483 } ··· 1464 1500 .map(|v| v.to_js_string(ctx.gc)) 1465 1501 .unwrap_or_default(); 1466 1502 let mut obj = ObjectData::new(); 1467 - obj.properties.insert( 1503 + obj.insert_property( 1468 1504 "message".to_string(), 1469 1505 Property::data(Value::String(message)), 1506 + ctx.shapes, 1470 1507 ); 1471 1508 // The "name" property comes from the prototype chain. 1472 1509 Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj)))) ··· 1479 1516 }; 1480 1517 let name = match ctx.gc.get(obj_ref) { 1481 1518 Some(HeapObject::Object(data)) => data 1482 - .properties 1483 - .get("name") 1519 + .get_property("name", ctx.shapes) 1484 1520 .map(|p| p.value.to_js_string(ctx.gc)) 1485 1521 .unwrap_or_else(|| "Error".to_string()), 1486 1522 _ => "Error".to_string(), 1487 1523 }; 1488 1524 let message = match ctx.gc.get(obj_ref) { 1489 1525 Some(HeapObject::Object(data)) => data 1490 - .properties 1491 - .get("message") 1526 + .get_property("message", ctx.shapes) 1492 1527 .map(|p| p.value.to_js_string(ctx.gc)) 1493 1528 .unwrap_or_default(), 1494 1529 _ => String::new(), ··· 1506 1541 /// `state` is a GcRef to an object with `__items__` (array) and `__idx__` (number). 1507 1542 fn make_simple_iterator( 1508 1543 gc: &mut Gc<HeapObject>, 1544 + shapes: &mut ShapeTable, 1509 1545 items: GcRef, 1510 1546 next_fn: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>, 1511 1547 ) -> Value { 1512 1548 let mut obj = ObjectData::new(); 1513 - obj.properties.insert( 1549 + obj.insert_property( 1514 1550 "__items__".to_string(), 1515 1551 Property::builtin(Value::Object(items)), 1552 + shapes, 1516 1553 ); 1517 - obj.properties 1518 - .insert("__idx__".to_string(), Property::builtin(Value::Number(0.0))); 1554 + obj.insert_property( 1555 + "__idx__".to_string(), 1556 + Property::builtin(Value::Number(0.0)), 1557 + shapes, 1558 + ); 1519 1559 let next = gc.alloc(HeapObject::Function(Box::new(FunctionData { 1520 1560 name: "next".to_string(), 1521 1561 kind: FunctionKind::Native(NativeFunc { callback: next_fn }), ··· 1523 1563 properties: HashMap::new(), 1524 1564 upvalues: Vec::new(), 1525 1565 }))); 1526 - obj.properties 1527 - .insert("next".to_string(), Property::builtin(Value::Function(next))); 1566 + obj.insert_property( 1567 + "next".to_string(), 1568 + Property::builtin(Value::Function(next)), 1569 + shapes, 1570 + ); 1528 1571 1529 1572 // @@iterator returns self. 1530 1573 let self_iter = gc.alloc(HeapObject::Function(Box::new(FunctionData { ··· 1536 1579 properties: HashMap::new(), 1537 1580 upvalues: Vec::new(), 1538 1581 }))); 1539 - obj.properties.insert( 1582 + obj.insert_property( 1540 1583 "@@iterator".to_string(), 1541 1584 Property::builtin(Value::Function(self_iter)), 1585 + shapes, 1542 1586 ); 1543 1587 1544 1588 let r = gc.alloc(HeapObject::Object(obj)); ··· 1549 1593 Ok(ctx.this.clone()) 1550 1594 } 1551 1595 1552 - fn make_iterator_result_native(gc: &mut Gc<HeapObject>, value: Value, done: bool) -> Value { 1596 + fn make_iterator_result_native( 1597 + gc: &mut Gc<HeapObject>, 1598 + shapes: &mut ShapeTable, 1599 + value: Value, 1600 + done: bool, 1601 + ) -> Value { 1553 1602 let mut obj = ObjectData::new(); 1554 - obj.properties 1555 - .insert("value".to_string(), Property::data(value)); 1556 - obj.properties 1557 - .insert("done".to_string(), Property::data(Value::Boolean(done))); 1603 + obj.insert_property("value".to_string(), Property::data(value), shapes); 1604 + obj.insert_property( 1605 + "done".to_string(), 1606 + Property::data(Value::Boolean(done)), 1607 + shapes, 1608 + ); 1558 1609 let r = gc.alloc(HeapObject::Object(obj)); 1559 1610 Value::Object(r) 1560 1611 } ··· 1570 1621 .this 1571 1622 .gc_ref() 1572 1623 .ok_or_else(|| RuntimeError::type_error("values called on non-object"))?; 1573 - Ok(make_simple_iterator(ctx.gc, obj_ref, array_values_next)) 1624 + Ok(make_simple_iterator( 1625 + ctx.gc, 1626 + ctx.shapes, 1627 + obj_ref, 1628 + array_values_next, 1629 + )) 1574 1630 } 1575 1631 1576 1632 fn array_values_next(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 1579 1635 .gc_ref() 1580 1636 .ok_or_else(|| RuntimeError::type_error("next called on non-iterator"))?; 1581 1637 1582 - let (items_ref, idx) = get_iter_state(ctx.gc, iter_ref); 1638 + let (items_ref, idx) = get_iter_state(ctx.gc, ctx.shapes, iter_ref); 1583 1639 let items_ref = match items_ref { 1584 1640 Some(r) => r, 1585 - None => return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)), 1641 + None => { 1642 + return Ok(make_iterator_result_native( 1643 + ctx.gc, 1644 + ctx.shapes, 1645 + Value::Undefined, 1646 + true, 1647 + )) 1648 + } 1586 1649 }; 1587 1650 1588 - let len = array_length(ctx.gc, items_ref); 1651 + let len = array_length(ctx.gc, ctx.shapes, items_ref); 1589 1652 if idx >= len { 1590 - return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)); 1653 + return Ok(make_iterator_result_native( 1654 + ctx.gc, 1655 + ctx.shapes, 1656 + Value::Undefined, 1657 + true, 1658 + )); 1591 1659 } 1592 1660 1593 - let val = array_get(ctx.gc, items_ref, idx); 1594 - set_iter_idx(ctx.gc, iter_ref, idx + 1); 1595 - Ok(make_iterator_result_native(ctx.gc, val, false)) 1661 + let val = array_get(ctx.gc, ctx.shapes, items_ref, idx); 1662 + set_iter_idx(ctx.gc, ctx.shapes, iter_ref, idx + 1); 1663 + Ok(make_iterator_result_native(ctx.gc, ctx.shapes, val, false)) 1596 1664 } 1597 1665 1598 1666 /// Array.prototype.keys() — returns iterator over indices. ··· 1601 1669 .this 1602 1670 .gc_ref() 1603 1671 .ok_or_else(|| RuntimeError::type_error("keys called on non-object"))?; 1604 - Ok(make_simple_iterator(ctx.gc, obj_ref, array_keys_next)) 1672 + Ok(make_simple_iterator( 1673 + ctx.gc, 1674 + ctx.shapes, 1675 + obj_ref, 1676 + array_keys_next, 1677 + )) 1605 1678 } 1606 1679 1607 1680 fn array_keys_next(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 1609 1682 .this 1610 1683 .gc_ref() 1611 1684 .ok_or_else(|| RuntimeError::type_error("next called on non-iterator"))?; 1612 - let (items_ref, idx) = get_iter_state(ctx.gc, iter_ref); 1685 + let (items_ref, idx) = get_iter_state(ctx.gc, ctx.shapes, iter_ref); 1613 1686 let items_ref = match items_ref { 1614 1687 Some(r) => r, 1615 - None => return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)), 1688 + None => { 1689 + return Ok(make_iterator_result_native( 1690 + ctx.gc, 1691 + ctx.shapes, 1692 + Value::Undefined, 1693 + true, 1694 + )) 1695 + } 1616 1696 }; 1617 - let len = array_length(ctx.gc, items_ref); 1697 + let len = array_length(ctx.gc, ctx.shapes, items_ref); 1618 1698 if idx >= len { 1619 - return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)); 1699 + return Ok(make_iterator_result_native( 1700 + ctx.gc, 1701 + ctx.shapes, 1702 + Value::Undefined, 1703 + true, 1704 + )); 1620 1705 } 1621 - set_iter_idx(ctx.gc, iter_ref, idx + 1); 1706 + set_iter_idx(ctx.gc, ctx.shapes, iter_ref, idx + 1); 1622 1707 Ok(make_iterator_result_native( 1623 1708 ctx.gc, 1709 + ctx.shapes, 1624 1710 Value::Number(idx as f64), 1625 1711 false, 1626 1712 )) ··· 1632 1718 .this 1633 1719 .gc_ref() 1634 1720 .ok_or_else(|| RuntimeError::type_error("entries called on non-object"))?; 1635 - Ok(make_simple_iterator(ctx.gc, obj_ref, array_entries_next)) 1721 + Ok(make_simple_iterator( 1722 + ctx.gc, 1723 + ctx.shapes, 1724 + obj_ref, 1725 + array_entries_next, 1726 + )) 1636 1727 } 1637 1728 1638 1729 fn array_entries_next(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 1640 1731 .this 1641 1732 .gc_ref() 1642 1733 .ok_or_else(|| RuntimeError::type_error("next called on non-iterator"))?; 1643 - let (items_ref, idx) = get_iter_state(ctx.gc, iter_ref); 1734 + let (items_ref, idx) = get_iter_state(ctx.gc, ctx.shapes, iter_ref); 1644 1735 let items_ref = match items_ref { 1645 1736 Some(r) => r, 1646 - None => return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)), 1737 + None => { 1738 + return Ok(make_iterator_result_native( 1739 + ctx.gc, 1740 + ctx.shapes, 1741 + Value::Undefined, 1742 + true, 1743 + )) 1744 + } 1647 1745 }; 1648 - let len = array_length(ctx.gc, items_ref); 1746 + let len = array_length(ctx.gc, ctx.shapes, items_ref); 1649 1747 if idx >= len { 1650 - return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)); 1748 + return Ok(make_iterator_result_native( 1749 + ctx.gc, 1750 + ctx.shapes, 1751 + Value::Undefined, 1752 + true, 1753 + )); 1651 1754 } 1652 - let val = array_get(ctx.gc, items_ref, idx); 1653 - set_iter_idx(ctx.gc, iter_ref, idx + 1); 1755 + let val = array_get(ctx.gc, ctx.shapes, items_ref, idx); 1756 + set_iter_idx(ctx.gc, ctx.shapes, iter_ref, idx + 1); 1654 1757 1655 1758 // Create [index, value] pair array. 1656 1759 let mut pair = ObjectData::new(); 1657 - pair.properties 1658 - .insert("0".to_string(), Property::data(Value::Number(idx as f64))); 1659 - pair.properties.insert("1".to_string(), Property::data(val)); 1660 - pair.properties.insert( 1760 + pair.insert_property( 1761 + "0".to_string(), 1762 + Property::data(Value::Number(idx as f64)), 1763 + ctx.shapes, 1764 + ); 1765 + pair.insert_property("1".to_string(), Property::data(val), ctx.shapes); 1766 + pair.insert_property( 1661 1767 "length".to_string(), 1662 1768 Property { 1663 1769 value: Value::Number(2.0), ··· 1665 1771 enumerable: false, 1666 1772 configurable: false, 1667 1773 }, 1774 + ctx.shapes, 1668 1775 ); 1669 1776 let pair_ref = ctx.gc.alloc(HeapObject::Object(pair)); 1670 1777 Ok(make_iterator_result_native( 1671 1778 ctx.gc, 1779 + ctx.shapes, 1672 1780 Value::Object(pair_ref), 1673 1781 false, 1674 1782 )) 1675 1783 } 1676 1784 1677 1785 /// Helper to read __items__ and __idx__ from an iterator state object. 1678 - fn get_iter_state(gc: &Gc<HeapObject>, iter_ref: GcRef) -> (Option<GcRef>, usize) { 1786 + fn get_iter_state( 1787 + gc: &Gc<HeapObject>, 1788 + shapes: &ShapeTable, 1789 + iter_ref: GcRef, 1790 + ) -> (Option<GcRef>, usize) { 1679 1791 match gc.get(iter_ref) { 1680 1792 Some(HeapObject::Object(data)) => { 1681 1793 let items = data 1682 - .properties 1683 - .get("__items__") 1794 + .get_property("__items__", shapes) 1684 1795 .and_then(|p| p.value.gc_ref()); 1685 1796 let idx = data 1686 - .properties 1687 - .get("__idx__") 1797 + .get_property("__idx__", shapes) 1688 1798 .map(|p| p.value.to_number() as usize) 1689 1799 .unwrap_or(0); 1690 1800 (items, idx) ··· 1694 1804 } 1695 1805 1696 1806 /// Helper to update __idx__ on an iterator state object. 1697 - fn set_iter_idx(gc: &mut Gc<HeapObject>, iter_ref: GcRef, idx: usize) { 1807 + fn set_iter_idx(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, iter_ref: GcRef, idx: usize) { 1698 1808 if let Some(HeapObject::Object(data)) = gc.get_mut(iter_ref) { 1699 - data.properties.insert( 1809 + data.insert_property( 1700 1810 "__idx__".to_string(), 1701 1811 Property::builtin(Value::Number(idx as f64)), 1812 + shapes, 1702 1813 ); 1703 1814 } 1704 1815 } 1705 1816 1706 1817 // ── String built-in ────────────────────────────────────────── 1707 1818 1708 - fn init_string_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 1819 + fn init_string_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 1709 1820 let methods: &[NativeMethod] = &[ 1710 1821 ("charAt", string_proto_char_at), 1711 1822 ("charCodeAt", string_proto_char_code_at), ··· 1739 1850 ]; 1740 1851 for &(name, callback) in methods { 1741 1852 let f = make_native(gc, name, callback); 1742 - set_builtin_prop(gc, proto, name, Value::Function(f)); 1853 + set_builtin_prop(gc, shapes, proto, name, Value::Function(f)); 1743 1854 } 1744 1855 1745 1856 // @@iterator: iterates over characters. 1746 1857 let iter_fn = make_native(gc, "[Symbol.iterator]", string_iterator); 1747 - set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter_fn)); 1858 + set_builtin_prop(gc, shapes, proto, "@@iterator", Value::Function(iter_fn)); 1748 1859 } 1749 1860 1750 1861 /// String.prototype[@@iterator]() — returns an iterator over characters. ··· 1754 1865 // Store the string's characters in an array-like object. 1755 1866 let mut items = ObjectData::new(); 1756 1867 for (i, ch) in s.chars().enumerate() { 1757 - items 1758 - .properties 1759 - .insert(i.to_string(), Property::data(Value::String(ch.to_string()))); 1868 + items.insert_property( 1869 + i.to_string(), 1870 + Property::data(Value::String(ch.to_string())), 1871 + ctx.shapes, 1872 + ); 1760 1873 } 1761 - items.properties.insert( 1874 + items.insert_property( 1762 1875 "length".to_string(), 1763 1876 Property { 1764 1877 value: Value::Number(s.chars().count() as f64), ··· 1766 1879 enumerable: false, 1767 1880 configurable: false, 1768 1881 }, 1882 + ctx.shapes, 1769 1883 ); 1770 1884 let items_ref = ctx.gc.alloc(HeapObject::Object(items)); 1771 - Ok(make_simple_iterator(ctx.gc, items_ref, array_values_next)) 1885 + Ok(make_simple_iterator( 1886 + ctx.gc, 1887 + ctx.shapes, 1888 + items_ref, 1889 + array_values_next, 1890 + )) 1772 1891 } 1773 1892 1774 1893 fn init_string_constructor(gc: &mut Gc<HeapObject>, str_proto: GcRef) -> GcRef { ··· 2123 2242 fn string_proto_split(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2124 2243 let s = this_string(ctx); 2125 2244 if args.is_empty() || matches!(args.first(), Some(Value::Undefined)) { 2126 - return Ok(make_value_array(ctx.gc, &[Value::String(s)])); 2245 + return Ok(make_value_array(ctx.gc, ctx.shapes, &[Value::String(s)])); 2127 2246 } 2128 2247 let limit = args 2129 2248 .get(1) ··· 2132 2251 2133 2252 // Check if separator is a RegExp. 2134 2253 if let Some(arg0) = args.first() { 2135 - if is_regexp(ctx.gc, arg0) { 2136 - return string_split_regexp(ctx.gc, &s, arg0, limit); 2254 + if is_regexp(ctx.gc, ctx.shapes, arg0) { 2255 + return string_split_regexp(ctx.gc, ctx.shapes, &s, arg0, limit); 2137 2256 } 2138 2257 } 2139 2258 ··· 2144 2263 .take(limit) 2145 2264 .map(|c| Value::String(c.to_string())) 2146 2265 .collect(); 2147 - return Ok(make_value_array(ctx.gc, &items)); 2266 + return Ok(make_value_array(ctx.gc, ctx.shapes, &items)); 2148 2267 } 2149 2268 let mut items = Vec::new(); 2150 2269 let mut start = 0; ··· 2159 2278 if items.len() < limit { 2160 2279 items.push(Value::String(s[start..].to_string())); 2161 2280 } 2162 - Ok(make_value_array(ctx.gc, &items)) 2281 + Ok(make_value_array(ctx.gc, ctx.shapes, &items)) 2163 2282 } 2164 2283 2165 2284 fn string_split_regexp( 2166 2285 gc: &mut Gc<HeapObject>, 2286 + shapes: &mut ShapeTable, 2167 2287 s: &str, 2168 2288 regexp: &Value, 2169 2289 limit: usize, 2170 2290 ) -> Result<Value, RuntimeError> { 2171 2291 use crate::regex::{exec, CompiledRegex}; 2172 2292 2173 - let pattern = regexp_get_pattern(gc, regexp).unwrap_or_default(); 2174 - let flags_str = regexp_get_flags(gc, regexp).unwrap_or_default(); 2293 + let pattern = regexp_get_pattern(gc, shapes, regexp).unwrap_or_default(); 2294 + let flags_str = regexp_get_flags(gc, shapes, regexp).unwrap_or_default(); 2175 2295 let compiled = CompiledRegex::new(&pattern, &flags_str).map_err(RuntimeError::syntax_error)?; 2176 2296 let chars: Vec<char> = s.chars().collect(); 2177 2297 ··· 2217 2337 let rest: String = chars[last_end..].iter().collect(); 2218 2338 items.push(Value::String(rest)); 2219 2339 } 2220 - Ok(make_value_array(gc, &items)) 2340 + Ok(make_value_array(gc, shapes, &items)) 2221 2341 } 2222 2342 2223 2343 fn string_proto_replace(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 2225 2345 2226 2346 // Check if search argument is a RegExp. 2227 2347 if let Some(arg0) = args.first() { 2228 - if is_regexp(ctx.gc, arg0) { 2348 + if is_regexp(ctx.gc, ctx.shapes, arg0) { 2229 2349 let replacement = args 2230 2350 .get(1) 2231 2351 .map(|v| v.to_js_string(ctx.gc)) 2232 2352 .unwrap_or_default(); 2233 - return string_replace_regexp(ctx.gc, &s, arg0, &replacement); 2353 + return string_replace_regexp(ctx.gc, ctx.shapes, &s, arg0, &replacement); 2234 2354 } 2235 2355 } 2236 2356 ··· 2256 2376 2257 2377 fn string_replace_regexp( 2258 2378 gc: &mut Gc<HeapObject>, 2379 + shapes: &mut ShapeTable, 2259 2380 s: &str, 2260 2381 regexp: &Value, 2261 2382 replacement: &str, 2262 2383 ) -> Result<Value, RuntimeError> { 2263 2384 use crate::regex::{exec, CompiledRegex}; 2264 2385 2265 - let pattern = regexp_get_pattern(gc, regexp).unwrap_or_default(); 2266 - let flags_str = regexp_get_flags(gc, regexp).unwrap_or_default(); 2386 + let pattern = regexp_get_pattern(gc, shapes, regexp).unwrap_or_default(); 2387 + let flags_str = regexp_get_flags(gc, shapes, regexp).unwrap_or_default(); 2267 2388 let compiled = CompiledRegex::new(&pattern, &flags_str).map_err(RuntimeError::syntax_error)?; 2268 2389 let is_global = compiled.flags.global; 2269 2390 let chars: Vec<char> = s.chars().collect(); ··· 2377 2498 2378 2499 // If search is a RegExp, it must have the global flag. 2379 2500 if let Some(arg0) = args.first() { 2380 - if is_regexp(ctx.gc, arg0) { 2381 - let flags = regexp_get_flags(ctx.gc, arg0).unwrap_or_default(); 2501 + if is_regexp(ctx.gc, ctx.shapes, arg0) { 2502 + let flags = regexp_get_flags(ctx.gc, ctx.shapes, arg0).unwrap_or_default(); 2382 2503 if !flags.contains('g') { 2383 2504 return Err(RuntimeError::type_error( 2384 2505 "String.prototype.replaceAll called with a non-global RegExp argument", ··· 2388 2509 .get(1) 2389 2510 .map(|v| v.to_js_string(ctx.gc)) 2390 2511 .unwrap_or_default(); 2391 - return string_replace_regexp(ctx.gc, &s, arg0, &replacement); 2512 + return string_replace_regexp(ctx.gc, ctx.shapes, &s, arg0, &replacement); 2392 2513 } 2393 2514 } 2394 2515 ··· 2411 2532 2412 2533 let arg0 = &args[0]; 2413 2534 // If arg is not a RegExp, create one. 2414 - let regexp_val = if is_regexp(ctx.gc, arg0) { 2535 + let regexp_val = if is_regexp(ctx.gc, ctx.shapes, arg0) { 2415 2536 arg0.clone() 2416 2537 } else { 2417 2538 let pattern = arg0.to_js_string(ctx.gc); 2418 2539 let proto = REGEXP_PROTO.with(|cell| cell.get()); 2419 - make_regexp_obj(ctx.gc, &pattern, "", proto).map_err(RuntimeError::syntax_error)? 2540 + make_regexp_obj(ctx.gc, ctx.shapes, &pattern, "", proto) 2541 + .map_err(RuntimeError::syntax_error)? 2420 2542 }; 2421 2543 2422 - let is_global = regexp_get_flags(ctx.gc, &regexp_val) 2544 + let is_global = regexp_get_flags(ctx.gc, ctx.shapes, &regexp_val) 2423 2545 .map(|f| f.contains('g')) 2424 2546 .unwrap_or(false); 2425 2547 2426 2548 if !is_global { 2427 2549 // Non-global: return exec result. 2428 - return regexp_exec_internal(ctx.gc, &regexp_val, &s); 2550 + return regexp_exec_internal(ctx.gc, ctx.shapes, &regexp_val, &s); 2429 2551 } 2430 2552 2431 2553 // Global: collect all matches. 2432 - regexp_set_last_index(ctx.gc, &regexp_val, 0.0); 2554 + regexp_set_last_index(ctx.gc, ctx.shapes, &regexp_val, 0.0); 2433 2555 let mut matches = Vec::new(); 2434 2556 loop { 2435 - let result = regexp_exec_internal(ctx.gc, &regexp_val, &s)?; 2557 + let result = regexp_exec_internal(ctx.gc, ctx.shapes, &regexp_val, &s)?; 2436 2558 if matches!(result, Value::Null) { 2437 2559 break; 2438 2560 } 2439 2561 // Get the matched string (index 0 of the result array). 2440 2562 if let Value::Object(r) = &result { 2441 2563 if let Some(HeapObject::Object(data)) = ctx.gc.get(*r) { 2442 - if let Some(prop) = data.properties.get("0") { 2564 + if let Some(prop) = data.get_property("0", ctx.shapes) { 2443 2565 matches.push(prop.value.clone()); 2444 2566 // Advance past zero-length matches. 2445 2567 let match_str = prop.value.to_js_string(ctx.gc); 2446 2568 if match_str.is_empty() { 2447 - let li = regexp_get_last_index(ctx.gc, &regexp_val); 2448 - regexp_set_last_index(ctx.gc, &regexp_val, li + 1.0); 2569 + let li = regexp_get_last_index(ctx.gc, ctx.shapes, &regexp_val); 2570 + regexp_set_last_index(ctx.gc, ctx.shapes, &regexp_val, li + 1.0); 2449 2571 } 2450 2572 } 2451 2573 } ··· 2454 2576 if matches.is_empty() { 2455 2577 Ok(Value::Null) 2456 2578 } else { 2457 - Ok(make_value_array(ctx.gc, &matches)) 2579 + Ok(make_value_array(ctx.gc, ctx.shapes, &matches)) 2458 2580 } 2459 2581 } 2460 2582 2461 2583 fn string_proto_match_all(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2462 2584 let s = this_string(ctx); 2463 2585 if args.is_empty() { 2464 - return Ok(make_value_array(ctx.gc, &[])); 2586 + return Ok(make_value_array(ctx.gc, ctx.shapes, &[])); 2465 2587 } 2466 2588 2467 2589 let arg0 = &args[0]; 2468 2590 // If arg is a RegExp, it must have global flag. 2469 - let regexp_val = if is_regexp(ctx.gc, arg0) { 2470 - let flags = regexp_get_flags(ctx.gc, arg0).unwrap_or_default(); 2591 + let regexp_val = if is_regexp(ctx.gc, ctx.shapes, arg0) { 2592 + let flags = regexp_get_flags(ctx.gc, ctx.shapes, arg0).unwrap_or_default(); 2471 2593 if !flags.contains('g') { 2472 2594 return Err(RuntimeError::type_error( 2473 2595 "String.prototype.matchAll called with a non-global RegExp argument", ··· 2477 2599 } else { 2478 2600 let pattern = arg0.to_js_string(ctx.gc); 2479 2601 let proto = REGEXP_PROTO.with(|cell| cell.get()); 2480 - make_regexp_obj(ctx.gc, &pattern, "g", proto).map_err(RuntimeError::syntax_error)? 2602 + make_regexp_obj(ctx.gc, ctx.shapes, &pattern, "g", proto) 2603 + .map_err(RuntimeError::syntax_error)? 2481 2604 }; 2482 2605 2483 2606 // Collect all match results. 2484 - regexp_set_last_index(ctx.gc, &regexp_val, 0.0); 2607 + regexp_set_last_index(ctx.gc, ctx.shapes, &regexp_val, 0.0); 2485 2608 let mut results = Vec::new(); 2486 2609 loop { 2487 - let result = regexp_exec_internal(ctx.gc, &regexp_val, &s)?; 2610 + let result = regexp_exec_internal(ctx.gc, ctx.shapes, &regexp_val, &s)?; 2488 2611 if matches!(result, Value::Null) { 2489 2612 break; 2490 2613 } 2491 2614 // Advance past zero-length matches. 2492 2615 if let Value::Object(r) = &result { 2493 2616 if let Some(HeapObject::Object(data)) = ctx.gc.get(*r) { 2494 - if let Some(prop) = data.properties.get("0") { 2617 + if let Some(prop) = data.get_property("0", ctx.shapes) { 2495 2618 let match_str = prop.value.to_js_string(ctx.gc); 2496 2619 if match_str.is_empty() { 2497 - let li = regexp_get_last_index(ctx.gc, &regexp_val); 2498 - regexp_set_last_index(ctx.gc, &regexp_val, li + 1.0); 2620 + let li = regexp_get_last_index(ctx.gc, ctx.shapes, &regexp_val); 2621 + regexp_set_last_index(ctx.gc, ctx.shapes, &regexp_val, li + 1.0); 2499 2622 } 2500 2623 } 2501 2624 } 2502 2625 } 2503 2626 results.push(result); 2504 2627 } 2505 - Ok(make_value_array(ctx.gc, &results)) 2628 + Ok(make_value_array(ctx.gc, ctx.shapes, &results)) 2506 2629 } 2507 2630 2508 2631 fn string_proto_search(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 2512 2635 } 2513 2636 2514 2637 let arg0 = &args[0]; 2515 - let regexp_val = if is_regexp(ctx.gc, arg0) { 2638 + let regexp_val = if is_regexp(ctx.gc, ctx.shapes, arg0) { 2516 2639 arg0.clone() 2517 2640 } else { 2518 2641 let pattern = arg0.to_js_string(ctx.gc); 2519 2642 let proto = REGEXP_PROTO.with(|cell| cell.get()); 2520 - make_regexp_obj(ctx.gc, &pattern, "", proto).map_err(RuntimeError::syntax_error)? 2643 + make_regexp_obj(ctx.gc, ctx.shapes, &pattern, "", proto) 2644 + .map_err(RuntimeError::syntax_error)? 2521 2645 }; 2522 2646 2523 2647 // search always starts from 0 and ignores global/lastIndex. 2524 2648 // Save and restore lastIndex so exec_internal's global/sticky handling doesn't interfere. 2525 - let saved_last_index = regexp_get_last_index(ctx.gc, &regexp_val); 2526 - regexp_set_last_index(ctx.gc, &regexp_val, 0.0); 2527 - let result = regexp_exec_internal(ctx.gc, &regexp_val, &s)?; 2528 - regexp_set_last_index(ctx.gc, &regexp_val, saved_last_index); 2649 + let saved_last_index = regexp_get_last_index(ctx.gc, ctx.shapes, &regexp_val); 2650 + regexp_set_last_index(ctx.gc, ctx.shapes, &regexp_val, 0.0); 2651 + let result = regexp_exec_internal(ctx.gc, ctx.shapes, &regexp_val, &s)?; 2652 + regexp_set_last_index(ctx.gc, ctx.shapes, &regexp_val, saved_last_index); 2529 2653 match result { 2530 2654 Value::Null => Ok(Value::Number(-1.0)), 2531 2655 Value::Object(r) => { 2532 2656 let idx = match ctx.gc.get(r) { 2533 2657 Some(HeapObject::Object(data)) => data 2534 - .properties 2535 - .get("index") 2658 + .get_property("index", ctx.shapes) 2536 2659 .map(|p| p.value.to_number()) 2537 2660 .unwrap_or(-1.0), 2538 2661 _ => -1.0, ··· 2587 2710 2588 2711 // ── Number built-in ────────────────────────────────────────── 2589 2712 2590 - fn init_number_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 2713 + fn init_number_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 2591 2714 let methods: &[NativeMethod] = &[ 2592 2715 ("toString", number_proto_to_string), 2593 2716 ("valueOf", number_proto_value_of), ··· 2597 2720 ]; 2598 2721 for &(name, callback) in methods { 2599 2722 let f = make_native(gc, name, callback); 2600 - set_builtin_prop(gc, proto, name, Value::Function(f)); 2723 + set_builtin_prop(gc, shapes, proto, name, Value::Function(f)); 2601 2724 } 2602 2725 } 2603 2726 ··· 2785 2908 2786 2909 // ── Boolean built-in ───────────────────────────────────────── 2787 2910 2788 - fn init_boolean_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 2911 + fn init_boolean_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 2789 2912 let to_string = make_native(gc, "toString", boolean_proto_to_string); 2790 - set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); 2913 + set_builtin_prop(gc, shapes, proto, "toString", Value::Function(to_string)); 2791 2914 let value_of = make_native(gc, "valueOf", boolean_proto_value_of); 2792 - set_builtin_prop(gc, proto, "valueOf", Value::Function(value_of)); 2915 + set_builtin_prop(gc, shapes, proto, "valueOf", Value::Function(value_of)); 2793 2916 } 2794 2917 2795 2918 fn init_boolean_constructor(gc: &mut Gc<HeapObject>, bool_proto: GcRef) -> GcRef { ··· 2940 3063 ("SQRT2", std::f64::consts::SQRT_2), 2941 3064 ]; 2942 3065 for &(name, val) in constants { 2943 - set_builtin_prop(&mut vm.gc, math_ref, name, Value::Number(val)); 3066 + set_builtin_prop( 3067 + &mut vm.gc, 3068 + &mut vm.shapes, 3069 + math_ref, 3070 + name, 3071 + Value::Number(val), 3072 + ); 2944 3073 } 2945 3074 2946 3075 // Methods. ··· 2977 3106 ]; 2978 3107 for &(name, cb) in methods { 2979 3108 let f = make_native(&mut vm.gc, name, cb); 2980 - set_builtin_prop(&mut vm.gc, math_ref, name, Value::Function(f)); 3109 + set_builtin_prop( 3110 + &mut vm.gc, 3111 + &mut vm.shapes, 3112 + math_ref, 3113 + name, 3114 + Value::Function(f), 3115 + ); 2981 3116 } 2982 3117 2983 3118 vm.set_global("Math", Value::Object(math_ref)); ··· 3303 3438 date_proto_data.prototype = Some(proto); 3304 3439 } 3305 3440 let date_proto = vm.gc.alloc(HeapObject::Object(date_proto_data)); 3306 - init_date_prototype(&mut vm.gc, date_proto); 3441 + init_date_prototype(&mut vm.gc, &mut vm.shapes, date_proto); 3307 3442 3308 3443 // Store prototype for constructor access. 3309 3444 vm.date_prototype = Some(date_proto); ··· 3334 3469 } 3335 3470 3336 3471 /// Internal: create a Date object (plain object with __date_ms__ property). 3337 - fn make_date_obj(gc: &mut Gc<HeapObject>, ms: f64, proto: Option<GcRef>) -> Value { 3472 + fn make_date_obj( 3473 + gc: &mut Gc<HeapObject>, 3474 + shapes: &mut ShapeTable, 3475 + ms: f64, 3476 + proto: Option<GcRef>, 3477 + ) -> Value { 3338 3478 let mut data = ObjectData::new(); 3339 3479 if let Some(p) = proto { 3340 3480 data.prototype = Some(p); 3341 3481 } 3342 - data.properties.insert( 3482 + data.insert_property( 3343 3483 "__date_ms__".to_string(), 3344 3484 Property::builtin(Value::Number(ms)), 3485 + shapes, 3345 3486 ); 3346 3487 Value::Object(gc.alloc(HeapObject::Object(data))) 3347 3488 } 3348 3489 3349 3490 /// Extract timestamp from a Date object. 3350 - fn date_get_ms(gc: &Gc<HeapObject>, this: &Value) -> f64 { 3491 + fn date_get_ms(gc: &Gc<HeapObject>, shapes: &ShapeTable, this: &Value) -> f64 { 3351 3492 match this { 3352 3493 Value::Object(r) => match gc.get(*r) { 3353 3494 Some(HeapObject::Object(data)) => data 3354 - .properties 3355 - .get("__date_ms__") 3495 + .get_property("__date_ms__", shapes) 3356 3496 .map(|p| p.value.to_number()) 3357 3497 .unwrap_or(f64::NAN), 3358 3498 _ => f64::NAN, ··· 3362 3502 } 3363 3503 3364 3504 /// Set timestamp on a Date object and return the new value. 3365 - fn date_set_ms(gc: &mut Gc<HeapObject>, this: &Value, ms: f64) -> f64 { 3505 + fn date_set_ms(gc: &mut Gc<HeapObject>, shapes: &ShapeTable, this: &Value, ms: f64) -> f64 { 3366 3506 if let Value::Object(r) = this { 3367 3507 if let Some(HeapObject::Object(data)) = gc.get_mut(*r) { 3368 - if let Some(prop) = data.properties.get_mut("__date_ms__") { 3369 - prop.value = Value::Number(ms); 3370 - } 3508 + data.update_value("__date_ms__", Value::Number(ms), shapes); 3371 3509 } 3372 3510 } 3373 3511 ms ··· 3400 3538 utc_components_to_ms(year, month, day, h, min, sec, ms) 3401 3539 } 3402 3540 }; 3403 - Ok(make_date_obj(ctx.gc, ms, proto)) 3541 + Ok(make_date_obj(ctx.gc, ctx.shapes, ms, proto)) 3404 3542 } 3405 3543 3406 3544 fn date_now(_args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 3433 3571 ))) 3434 3572 } 3435 3573 3436 - fn init_date_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 3574 + fn init_date_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 3437 3575 let methods: &[NativeMethod] = &[ 3438 3576 ("getTime", date_get_time), 3439 3577 ("valueOf", date_get_time), // valueOf === getTime ··· 3470 3608 ]; 3471 3609 for &(name, cb) in methods { 3472 3610 let f = make_native(gc, name, cb); 3473 - set_builtin_prop(gc, proto, name, Value::Function(f)); 3611 + set_builtin_prop(gc, shapes, proto, name, Value::Function(f)); 3474 3612 } 3475 3613 } 3476 3614 3477 3615 fn date_get_time(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3478 3616 let _ = args; 3479 - Ok(Value::Number(date_get_ms(ctx.gc, &ctx.this))) 3617 + Ok(Value::Number(date_get_ms(ctx.gc, ctx.shapes, &ctx.this))) 3480 3618 } 3481 3619 3482 3620 fn date_get_full_year(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3483 - let ms = date_get_ms(ctx.gc, &ctx.this); 3621 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3484 3622 if ms.is_nan() { 3485 3623 return Ok(Value::Number(f64::NAN)); 3486 3624 } ··· 3489 3627 } 3490 3628 3491 3629 fn date_get_month(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3492 - let ms = date_get_ms(ctx.gc, &ctx.this); 3630 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3493 3631 if ms.is_nan() { 3494 3632 return Ok(Value::Number(f64::NAN)); 3495 3633 } ··· 3498 3636 } 3499 3637 3500 3638 fn date_get_date(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3501 - let ms = date_get_ms(ctx.gc, &ctx.this); 3639 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3502 3640 if ms.is_nan() { 3503 3641 return Ok(Value::Number(f64::NAN)); 3504 3642 } ··· 3507 3645 } 3508 3646 3509 3647 fn date_get_day(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3510 - let ms = date_get_ms(ctx.gc, &ctx.this); 3648 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3511 3649 if ms.is_nan() { 3512 3650 return Ok(Value::Number(f64::NAN)); 3513 3651 } ··· 3516 3654 } 3517 3655 3518 3656 fn date_get_hours(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3519 - let ms = date_get_ms(ctx.gc, &ctx.this); 3657 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3520 3658 if ms.is_nan() { 3521 3659 return Ok(Value::Number(f64::NAN)); 3522 3660 } ··· 3525 3663 } 3526 3664 3527 3665 fn date_get_minutes(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3528 - let ms = date_get_ms(ctx.gc, &ctx.this); 3666 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3529 3667 if ms.is_nan() { 3530 3668 return Ok(Value::Number(f64::NAN)); 3531 3669 } ··· 3534 3672 } 3535 3673 3536 3674 fn date_get_seconds(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3537 - let ms = date_get_ms(ctx.gc, &ctx.this); 3675 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3538 3676 if ms.is_nan() { 3539 3677 return Ok(Value::Number(f64::NAN)); 3540 3678 } ··· 3543 3681 } 3544 3682 3545 3683 fn date_get_milliseconds(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3546 - let ms = date_get_ms(ctx.gc, &ctx.this); 3684 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3547 3685 if ms.is_nan() { 3548 3686 return Ok(Value::Number(f64::NAN)); 3549 3687 } ··· 3561 3699 3562 3700 fn date_set_time(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3563 3701 let ms = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3564 - date_set_ms(ctx.gc, &ctx.this, ms); 3702 + date_set_ms(ctx.gc, ctx.shapes, &ctx.this, ms); 3565 3703 Ok(Value::Number(ms)) 3566 3704 } 3567 3705 3568 3706 fn date_set_full_year(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3569 - let old = date_get_ms(ctx.gc, &ctx.this); 3707 + let old = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3570 3708 let (_, m, d, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3571 3709 let year = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3572 3710 let month = args.get(1).map(|v| v.to_number() as i64).unwrap_or(m); 3573 3711 let day = args.get(2).map(|v| v.to_number() as i64).unwrap_or(d); 3574 3712 let new_ms = utc_components_to_ms(year, month, day, h, min, s, ms); 3575 - date_set_ms(ctx.gc, &ctx.this, new_ms); 3713 + date_set_ms(ctx.gc, ctx.shapes, &ctx.this, new_ms); 3576 3714 Ok(Value::Number(new_ms)) 3577 3715 } 3578 3716 3579 3717 fn date_set_month(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3580 - let old = date_get_ms(ctx.gc, &ctx.this); 3718 + let old = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3581 3719 let (y, _, d, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3582 3720 let month = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3583 3721 let day = args.get(1).map(|v| v.to_number() as i64).unwrap_or(d); 3584 3722 let new_ms = utc_components_to_ms(y, month, day, h, min, s, ms); 3585 - date_set_ms(ctx.gc, &ctx.this, new_ms); 3723 + date_set_ms(ctx.gc, ctx.shapes, &ctx.this, new_ms); 3586 3724 Ok(Value::Number(new_ms)) 3587 3725 } 3588 3726 3589 3727 fn date_set_date(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3590 - let old = date_get_ms(ctx.gc, &ctx.this); 3728 + let old = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3591 3729 let (y, m, _, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3592 3730 let day = args.first().map(|v| v.to_number() as i64).unwrap_or(1); 3593 3731 let new_ms = utc_components_to_ms(y, m, day, h, min, s, ms); 3594 - date_set_ms(ctx.gc, &ctx.this, new_ms); 3732 + date_set_ms(ctx.gc, ctx.shapes, &ctx.this, new_ms); 3595 3733 Ok(Value::Number(new_ms)) 3596 3734 } 3597 3735 3598 3736 fn date_set_hours(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3599 - let old = date_get_ms(ctx.gc, &ctx.this); 3737 + let old = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3600 3738 let (y, m, d, _, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3601 3739 let h = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3602 3740 let min_v = args.get(1).map(|v| v.to_number() as i64).unwrap_or(min); 3603 3741 let sec = args.get(2).map(|v| v.to_number() as i64).unwrap_or(s); 3604 3742 let ms_v = args.get(3).map(|v| v.to_number() as i64).unwrap_or(ms); 3605 3743 let new_ms = utc_components_to_ms(y, m, d, h, min_v, sec, ms_v); 3606 - date_set_ms(ctx.gc, &ctx.this, new_ms); 3744 + date_set_ms(ctx.gc, ctx.shapes, &ctx.this, new_ms); 3607 3745 Ok(Value::Number(new_ms)) 3608 3746 } 3609 3747 3610 3748 fn date_set_minutes(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3611 - let old = date_get_ms(ctx.gc, &ctx.this); 3749 + let old = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3612 3750 let (y, m, d, h, _, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3613 3751 let min = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3614 3752 let sec = args.get(1).map(|v| v.to_number() as i64).unwrap_or(s); 3615 3753 let ms_v = args.get(2).map(|v| v.to_number() as i64).unwrap_or(ms); 3616 3754 let new_ms = utc_components_to_ms(y, m, d, h, min, sec, ms_v); 3617 - date_set_ms(ctx.gc, &ctx.this, new_ms); 3755 + date_set_ms(ctx.gc, ctx.shapes, &ctx.this, new_ms); 3618 3756 Ok(Value::Number(new_ms)) 3619 3757 } 3620 3758 3621 3759 fn date_set_seconds(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3622 - let old = date_get_ms(ctx.gc, &ctx.this); 3760 + let old = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3623 3761 let (y, m, d, h, min, _, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3624 3762 let sec = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3625 3763 let ms_v = args.get(1).map(|v| v.to_number() as i64).unwrap_or(ms); 3626 3764 let new_ms = utc_components_to_ms(y, m, d, h, min, sec, ms_v); 3627 - date_set_ms(ctx.gc, &ctx.this, new_ms); 3765 + date_set_ms(ctx.gc, ctx.shapes, &ctx.this, new_ms); 3628 3766 Ok(Value::Number(new_ms)) 3629 3767 } 3630 3768 3631 3769 fn date_set_milliseconds(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3632 - let old = date_get_ms(ctx.gc, &ctx.this); 3770 + let old = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3633 3771 let (y, m, d, h, min, s, _, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3634 3772 let ms_v = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3635 3773 let new_ms = utc_components_to_ms(y, m, d, h, min, s, ms_v); 3636 - date_set_ms(ctx.gc, &ctx.this, new_ms); 3774 + date_set_ms(ctx.gc, ctx.shapes, &ctx.this, new_ms); 3637 3775 Ok(Value::Number(new_ms)) 3638 3776 } 3639 3777 3640 3778 fn date_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3641 - let ms = date_get_ms(ctx.gc, &ctx.this); 3779 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3642 3780 if ms.is_nan() { 3643 3781 return Ok(Value::String("Invalid Date".to_string())); 3644 3782 } ··· 3650 3788 } 3651 3789 3652 3790 fn date_to_iso_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3653 - let ms = date_get_ms(ctx.gc, &ctx.this); 3791 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3654 3792 if ms.is_nan() { 3655 3793 return Err(RuntimeError::range_error("Invalid time value")); 3656 3794 } ··· 3668 3806 } 3669 3807 3670 3808 fn date_to_utc_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3671 - let ms = date_get_ms(ctx.gc, &ctx.this); 3809 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3672 3810 if ms.is_nan() { 3673 3811 return Ok(Value::String("Invalid Date".to_string())); 3674 3812 } ··· 3684 3822 ctx: &mut NativeContext, 3685 3823 ) -> Result<Value, RuntimeError> { 3686 3824 // Simplified: same as toISOString date portion. 3687 - let ms = date_get_ms(ctx.gc, &ctx.this); 3825 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3688 3826 if ms.is_nan() { 3689 3827 return Ok(Value::String("Invalid Date".to_string())); 3690 3828 } ··· 3693 3831 } 3694 3832 3695 3833 fn date_to_json(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3696 - let ms = date_get_ms(ctx.gc, &ctx.this); 3834 + let ms = date_get_ms(ctx.gc, ctx.shapes, &ctx.this); 3697 3835 if ms.is_nan() { 3698 3836 return Ok(Value::Null); 3699 3837 } ··· 3713 3851 regexp_proto_data.prototype = Some(proto); 3714 3852 } 3715 3853 let regexp_proto = vm.gc.alloc(HeapObject::Object(regexp_proto_data)); 3716 - init_regexp_prototype(&mut vm.gc, regexp_proto); 3854 + init_regexp_prototype(&mut vm.gc, &mut vm.shapes, regexp_proto); 3717 3855 3718 3856 vm.regexp_prototype = Some(regexp_proto); 3719 3857 REGEXP_PROTO.with(|cell| cell.set(Some(regexp_proto))); ··· 3732 3870 vm.set_global("RegExp", Value::Function(ctor)); 3733 3871 } 3734 3872 3735 - fn init_regexp_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 3873 + fn init_regexp_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 3736 3874 let methods: &[NativeMethod] = &[ 3737 3875 ("test", regexp_proto_test), 3738 3876 ("exec", regexp_proto_exec), ··· 3740 3878 ]; 3741 3879 for &(name, callback) in methods { 3742 3880 let f = make_native(gc, name, callback); 3743 - set_builtin_prop(gc, proto, name, Value::Function(f)); 3881 + set_builtin_prop(gc, shapes, proto, name, Value::Function(f)); 3744 3882 } 3745 3883 } 3746 3884 3747 3885 /// Create a RegExp object storing compiled regex state in hidden properties. 3748 3886 pub fn make_regexp_obj( 3749 3887 gc: &mut Gc<HeapObject>, 3888 + shapes: &mut ShapeTable, 3750 3889 pattern: &str, 3751 3890 flags_str: &str, 3752 3891 proto: Option<GcRef>, ··· 3761 3900 data.prototype = Some(p); 3762 3901 } 3763 3902 // Store pattern and flags as properties. 3764 - data.properties.insert( 3903 + data.insert_property( 3765 3904 "source".to_string(), 3766 3905 Property::builtin(Value::String(pattern.to_string())), 3906 + shapes, 3767 3907 ); 3768 3908 let flags_string = flags.as_flag_string(); 3769 - data.properties.insert( 3909 + data.insert_property( 3770 3910 "flags".to_string(), 3771 3911 Property::builtin(Value::String(flags_string)), 3912 + shapes, 3772 3913 ); 3773 - data.properties.insert( 3914 + data.insert_property( 3774 3915 "global".to_string(), 3775 3916 Property::builtin(Value::Boolean(flags.global)), 3917 + shapes, 3776 3918 ); 3777 - data.properties.insert( 3919 + data.insert_property( 3778 3920 "ignoreCase".to_string(), 3779 3921 Property::builtin(Value::Boolean(flags.ignore_case)), 3922 + shapes, 3780 3923 ); 3781 - data.properties.insert( 3924 + data.insert_property( 3782 3925 "multiline".to_string(), 3783 3926 Property::builtin(Value::Boolean(flags.multiline)), 3927 + shapes, 3784 3928 ); 3785 - data.properties.insert( 3929 + data.insert_property( 3786 3930 "dotAll".to_string(), 3787 3931 Property::builtin(Value::Boolean(flags.dot_all)), 3932 + shapes, 3788 3933 ); 3789 - data.properties.insert( 3934 + data.insert_property( 3790 3935 "unicode".to_string(), 3791 3936 Property::builtin(Value::Boolean(flags.unicode)), 3937 + shapes, 3792 3938 ); 3793 - data.properties.insert( 3939 + data.insert_property( 3794 3940 "sticky".to_string(), 3795 3941 Property::builtin(Value::Boolean(flags.sticky)), 3942 + shapes, 3796 3943 ); 3797 - data.properties 3798 - .insert("lastIndex".to_string(), Property::data(Value::Number(0.0))); 3944 + data.insert_property( 3945 + "lastIndex".to_string(), 3946 + Property::data(Value::Number(0.0)), 3947 + shapes, 3948 + ); 3799 3949 // Hidden: serialized pattern for re-compilation. 3800 - data.properties.insert( 3950 + data.insert_property( 3801 3951 "__regexp_pattern__".to_string(), 3802 3952 Property::builtin(Value::String(pattern.to_string())), 3953 + shapes, 3803 3954 ); 3804 - data.properties.insert( 3955 + data.insert_property( 3805 3956 "__regexp_flags__".to_string(), 3806 3957 Property::builtin(Value::String(flags.as_flag_string())), 3958 + shapes, 3807 3959 ); 3808 3960 3809 3961 Ok(Value::Object(gc.alloc(HeapObject::Object(data)))) 3810 3962 } 3811 3963 3812 3964 /// Check if a Value is a RegExp object. 3813 - pub fn is_regexp(gc: &Gc<HeapObject>, val: &Value) -> bool { 3965 + pub fn is_regexp(gc: &Gc<HeapObject>, shapes: &ShapeTable, val: &Value) -> bool { 3814 3966 match val { 3815 3967 Value::Object(r) => match gc.get(*r) { 3816 - Some(HeapObject::Object(data)) => data.properties.contains_key("__regexp_pattern__"), 3968 + Some(HeapObject::Object(data)) => data.contains_key("__regexp_pattern__", shapes), 3817 3969 _ => false, 3818 3970 }, 3819 3971 _ => false, ··· 3821 3973 } 3822 3974 3823 3975 /// Extract the pattern from a RegExp object. 3824 - fn regexp_get_pattern(gc: &Gc<HeapObject>, val: &Value) -> Option<String> { 3976 + fn regexp_get_pattern(gc: &Gc<HeapObject>, shapes: &ShapeTable, val: &Value) -> Option<String> { 3825 3977 match val { 3826 3978 Value::Object(r) => match gc.get(*r) { 3827 - Some(HeapObject::Object(data)) => { 3828 - data.properties 3829 - .get("__regexp_pattern__") 3830 - .and_then(|p| match &p.value { 3831 - Value::String(s) => Some(s.clone()), 3832 - _ => None, 3833 - }) 3834 - } 3979 + Some(HeapObject::Object(data)) => data 3980 + .get_property("__regexp_pattern__", shapes) 3981 + .and_then(|p| match &p.value { 3982 + Value::String(s) => Some(s.clone()), 3983 + _ => None, 3984 + }), 3835 3985 _ => None, 3836 3986 }, 3837 3987 _ => None, ··· 3839 3989 } 3840 3990 3841 3991 /// Extract the flags string from a RegExp object. 3842 - fn regexp_get_flags(gc: &Gc<HeapObject>, val: &Value) -> Option<String> { 3992 + fn regexp_get_flags(gc: &Gc<HeapObject>, shapes: &ShapeTable, val: &Value) -> Option<String> { 3843 3993 match val { 3844 3994 Value::Object(r) => match gc.get(*r) { 3845 - Some(HeapObject::Object(data)) => { 3846 - data.properties 3847 - .get("__regexp_flags__") 3848 - .and_then(|p| match &p.value { 3849 - Value::String(s) => Some(s.clone()), 3850 - _ => None, 3851 - }) 3852 - } 3995 + Some(HeapObject::Object(data)) => data 3996 + .get_property("__regexp_flags__", shapes) 3997 + .and_then(|p| match &p.value { 3998 + Value::String(s) => Some(s.clone()), 3999 + _ => None, 4000 + }), 3853 4001 _ => None, 3854 4002 }, 3855 4003 _ => None, ··· 3857 4005 } 3858 4006 3859 4007 /// Get lastIndex from a RegExp object. 3860 - fn regexp_get_last_index(gc: &Gc<HeapObject>, val: &Value) -> f64 { 4008 + fn regexp_get_last_index(gc: &Gc<HeapObject>, shapes: &ShapeTable, val: &Value) -> f64 { 3861 4009 match val { 3862 4010 Value::Object(r) => match gc.get(*r) { 3863 4011 Some(HeapObject::Object(data)) => data 3864 - .properties 3865 - .get("lastIndex") 4012 + .get_property("lastIndex", shapes) 3866 4013 .map(|p| p.value.to_number()) 3867 4014 .unwrap_or(0.0), 3868 4015 _ => 0.0, ··· 3872 4019 } 3873 4020 3874 4021 /// Set lastIndex on a RegExp object. 3875 - fn regexp_set_last_index(gc: &mut Gc<HeapObject>, val: &Value, idx: f64) { 4022 + fn regexp_set_last_index(gc: &mut Gc<HeapObject>, shapes: &ShapeTable, val: &Value, idx: f64) { 3876 4023 if let Value::Object(r) = val { 3877 4024 if let Some(HeapObject::Object(data)) = gc.get_mut(*r) { 3878 - if let Some(prop) = data.properties.get_mut("lastIndex") { 3879 - prop.value = Value::Number(idx); 3880 - } 4025 + data.update_value("lastIndex", Value::Number(idx), shapes); 3881 4026 } 3882 4027 } 3883 4028 } ··· 3885 4030 /// Execute the regex on a string and return a match result array or null. 3886 4031 fn regexp_exec_internal( 3887 4032 gc: &mut Gc<HeapObject>, 4033 + shapes: &mut ShapeTable, 3888 4034 this: &Value, 3889 4035 input: &str, 3890 4036 ) -> Result<Value, RuntimeError> { 3891 4037 use crate::regex::{exec, CompiledRegex}; 3892 4038 3893 - let pattern = regexp_get_pattern(gc, this) 4039 + let pattern = regexp_get_pattern(gc, shapes, this) 3894 4040 .ok_or_else(|| RuntimeError::type_error("not a RegExp".to_string()))?; 3895 - let flags_str = regexp_get_flags(gc, this).unwrap_or_default(); 4041 + let flags_str = regexp_get_flags(gc, shapes, this).unwrap_or_default(); 3896 4042 let compiled = CompiledRegex::new(&pattern, &flags_str).map_err(RuntimeError::syntax_error)?; 3897 4043 let is_global = compiled.flags.global; 3898 4044 let is_sticky = compiled.flags.sticky; 3899 4045 3900 4046 let start_index = if is_global || is_sticky { 3901 - let li = regexp_get_last_index(gc, this); 4047 + let li = regexp_get_last_index(gc, shapes, this); 3902 4048 if li < 0.0 { 3903 4049 0 3904 4050 } else { ··· 3914 4060 match result { 3915 4061 Some(m) => { 3916 4062 if is_global || is_sticky { 3917 - regexp_set_last_index(gc, this, m.end as f64); 4063 + regexp_set_last_index(gc, shapes, this, m.end as f64); 3918 4064 } 3919 4065 3920 4066 // Build result array: [fullMatch, ...groups] ··· 3957 4103 } else { 3958 4104 Value::Undefined 3959 4105 }; 3960 - groups_data 3961 - .properties 3962 - .insert(name.clone(), Property::data(val)); 4106 + groups_data.insert_property(name.clone(), Property::data(val), shapes); 3963 4107 } 3964 4108 Value::Object(gc.alloc(HeapObject::Object(groups_data))) 3965 4109 } 3966 4110 }; 3967 4111 3968 - let arr = make_value_array(gc, &items); 4112 + let arr = make_value_array(gc, shapes, &items); 3969 4113 // Set index, input, and groups properties on the result array. 3970 4114 if let Value::Object(r) = arr { 3971 4115 if let Some(HeapObject::Object(data)) = gc.get_mut(r) { 3972 - data.properties.insert( 4116 + data.insert_property( 3973 4117 "index".to_string(), 3974 4118 Property::data(Value::Number(m.start as f64)), 4119 + shapes, 3975 4120 ); 3976 - data.properties.insert( 4121 + data.insert_property( 3977 4122 "input".to_string(), 3978 4123 Property::data(Value::String(input.to_string())), 4124 + shapes, 3979 4125 ); 3980 - data.properties 3981 - .insert("groups".to_string(), Property::data(groups_val)); 4126 + data.insert_property("groups".to_string(), Property::data(groups_val), shapes); 3982 4127 } 3983 4128 Ok(Value::Object(r)) 3984 4129 } else { ··· 3987 4132 } 3988 4133 None => { 3989 4134 if is_global || is_sticky { 3990 - regexp_set_last_index(gc, this, 0.0); 4135 + regexp_set_last_index(gc, shapes, this, 0.0); 3991 4136 } 3992 4137 Ok(Value::Null) 3993 4138 } ··· 4043 4188 }) 4044 4189 .unwrap_or_default(); 4045 4190 4046 - make_regexp_obj(ctx.gc, &pattern, &flags, proto).map_err(RuntimeError::syntax_error) 4191 + make_regexp_obj(ctx.gc, ctx.shapes, &pattern, &flags, proto).map_err(RuntimeError::syntax_error) 4047 4192 } 4048 4193 4049 4194 fn regexp_proto_test(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 4051 4196 .first() 4052 4197 .map(|v| v.to_js_string(ctx.gc)) 4053 4198 .unwrap_or_default(); 4054 - let result = regexp_exec_internal(ctx.gc, &ctx.this, &input)?; 4199 + let result = regexp_exec_internal(ctx.gc, ctx.shapes, &ctx.this, &input)?; 4055 4200 Ok(Value::Boolean(!matches!(result, Value::Null))) 4056 4201 } 4057 4202 ··· 4060 4205 .first() 4061 4206 .map(|v| v.to_js_string(ctx.gc)) 4062 4207 .unwrap_or_default(); 4063 - regexp_exec_internal(ctx.gc, &ctx.this, &input) 4208 + regexp_exec_internal(ctx.gc, ctx.shapes, &ctx.this, &input) 4064 4209 } 4065 4210 4066 4211 fn regexp_proto_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4067 - let pattern = regexp_get_pattern(ctx.gc, &ctx.this).unwrap_or_default(); 4068 - let flags = regexp_get_flags(ctx.gc, &ctx.this).unwrap_or_default(); 4212 + let pattern = regexp_get_pattern(ctx.gc, ctx.shapes, &ctx.this).unwrap_or_default(); 4213 + let flags = regexp_get_flags(ctx.gc, ctx.shapes, &ctx.this).unwrap_or_default(); 4069 4214 Ok(Value::String(format!("/{}/{}", pattern, flags))) 4070 4215 } 4071 4216 ··· 4085 4230 map_proto_data.prototype = Some(proto); 4086 4231 } 4087 4232 let map_proto = vm.gc.alloc(HeapObject::Object(map_proto_data)); 4088 - init_map_prototype(&mut vm.gc, map_proto); 4233 + init_map_prototype(&mut vm.gc, &mut vm.shapes, map_proto); 4089 4234 MAP_PROTO.with(|cell| cell.set(Some(map_proto))); 4090 4235 4091 4236 let map_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { ··· 4105 4250 set_proto_data.prototype = Some(proto); 4106 4251 } 4107 4252 let set_proto = vm.gc.alloc(HeapObject::Object(set_proto_data)); 4108 - init_set_prototype(&mut vm.gc, set_proto); 4253 + init_set_prototype(&mut vm.gc, &mut vm.shapes, set_proto); 4109 4254 SET_PROTO.with(|cell| cell.set(Some(set_proto))); 4110 4255 4111 4256 let set_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { ··· 4125 4270 wm_proto_data.prototype = Some(proto); 4126 4271 } 4127 4272 let wm_proto = vm.gc.alloc(HeapObject::Object(wm_proto_data)); 4128 - init_weakmap_prototype(&mut vm.gc, wm_proto); 4273 + init_weakmap_prototype(&mut vm.gc, &mut vm.shapes, wm_proto); 4129 4274 WEAKMAP_PROTO.with(|cell| cell.set(Some(wm_proto))); 4130 4275 4131 4276 let wm_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { ··· 4145 4290 ws_proto_data.prototype = Some(proto); 4146 4291 } 4147 4292 let ws_proto = vm.gc.alloc(HeapObject::Object(ws_proto_data)); 4148 - init_weakset_prototype(&mut vm.gc, ws_proto); 4293 + init_weakset_prototype(&mut vm.gc, &mut vm.shapes, ws_proto); 4149 4294 WEAKSET_PROTO.with(|cell| cell.set(Some(ws_proto))); 4150 4295 4151 4296 let ws_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { ··· 4174 4319 const DELETED_MARKER: &str = "__deleted__"; 4175 4320 4176 4321 /// Create a new empty Map/Set internal storage object. 4177 - fn make_collection_obj(gc: &mut Gc<HeapObject>, proto: Option<GcRef>) -> GcRef { 4322 + fn make_collection_obj( 4323 + gc: &mut Gc<HeapObject>, 4324 + shapes: &mut ShapeTable, 4325 + proto: Option<GcRef>, 4326 + ) -> GcRef { 4178 4327 let entries_obj = gc.alloc(HeapObject::Object(ObjectData::new())); 4179 4328 let mut data = ObjectData::new(); 4180 4329 if let Some(p) = proto { 4181 4330 data.prototype = Some(p); 4182 4331 } 4183 - data.properties.insert( 4332 + data.insert_property( 4184 4333 ENTRIES_KEY.to_string(), 4185 4334 Property::builtin(Value::Object(entries_obj)), 4335 + shapes, 4186 4336 ); 4187 - data.properties.insert( 4337 + data.insert_property( 4188 4338 ENTRY_COUNT_KEY.to_string(), 4189 4339 Property::builtin(Value::Number(0.0)), 4340 + shapes, 4190 4341 ); 4191 - data.properties.insert( 4342 + data.insert_property( 4192 4343 LIVE_COUNT_KEY.to_string(), 4193 4344 Property::builtin(Value::Number(0.0)), 4345 + shapes, 4194 4346 ); 4195 - // size is a read-only, non-enumerable property. 4196 - data.properties 4197 - .insert("size".to_string(), Property::builtin(Value::Number(0.0))); 4347 + data.insert_property( 4348 + "size".to_string(), 4349 + Property::builtin(Value::Number(0.0)), 4350 + shapes, 4351 + ); 4198 4352 gc.alloc(HeapObject::Object(data)) 4199 4353 } 4200 4354 4201 4355 /// Get the entries object GcRef from a Map/Set object. 4202 - fn collection_entries(gc: &Gc<HeapObject>, obj: &Value) -> Option<GcRef> { 4356 + fn collection_entries(gc: &Gc<HeapObject>, shapes: &ShapeTable, obj: &Value) -> Option<GcRef> { 4203 4357 let gc_ref = obj.gc_ref()?; 4204 4358 let heap = gc.get(gc_ref)?; 4205 4359 if let HeapObject::Object(data) = heap { 4206 - if let Some(prop) = data.properties.get(ENTRIES_KEY) { 4360 + if let Some(prop) = data.get_property(ENTRIES_KEY, shapes) { 4207 4361 return prop.value.gc_ref(); 4208 4362 } 4209 4363 } ··· 4211 4365 } 4212 4366 4213 4367 /// Get the entry count from a Map/Set object. 4214 - fn collection_entry_count(gc: &Gc<HeapObject>, obj: &Value) -> usize { 4368 + fn collection_entry_count(gc: &Gc<HeapObject>, shapes: &ShapeTable, obj: &Value) -> usize { 4215 4369 let gc_ref = match obj.gc_ref() { 4216 4370 Some(r) => r, 4217 4371 None => return 0, 4218 4372 }; 4219 4373 match gc.get(gc_ref) { 4220 4374 Some(HeapObject::Object(data)) => data 4221 - .properties 4222 - .get(ENTRY_COUNT_KEY) 4375 + .get_property(ENTRY_COUNT_KEY, shapes) 4223 4376 .map(|p| p.value.to_number() as usize) 4224 4377 .unwrap_or(0), 4225 4378 _ => 0, ··· 4227 4380 } 4228 4381 4229 4382 /// Get the live count from a Map/Set object. 4230 - fn collection_live_count(gc: &Gc<HeapObject>, obj: &Value) -> usize { 4383 + fn collection_live_count(gc: &Gc<HeapObject>, shapes: &ShapeTable, obj: &Value) -> usize { 4231 4384 let gc_ref = match obj.gc_ref() { 4232 4385 Some(r) => r, 4233 4386 None => return 0, 4234 4387 }; 4235 4388 match gc.get(gc_ref) { 4236 4389 Some(HeapObject::Object(data)) => data 4237 - .properties 4238 - .get(LIVE_COUNT_KEY) 4390 + .get_property(LIVE_COUNT_KEY, shapes) 4239 4391 .map(|p| p.value.to_number() as usize) 4240 4392 .unwrap_or(0), 4241 4393 _ => 0, ··· 4243 4395 } 4244 4396 4245 4397 /// Set the entry count on a Map/Set object and update the `size` property. 4246 - fn set_collection_count(gc: &mut Gc<HeapObject>, obj: &Value, entry_count: usize, live: usize) { 4398 + fn set_collection_count( 4399 + gc: &mut Gc<HeapObject>, 4400 + shapes: &mut ShapeTable, 4401 + obj: &Value, 4402 + entry_count: usize, 4403 + live: usize, 4404 + ) { 4247 4405 let gc_ref = match obj.gc_ref() { 4248 4406 Some(r) => r, 4249 4407 None => return, 4250 4408 }; 4251 4409 if let Some(HeapObject::Object(data)) = gc.get_mut(gc_ref) { 4252 - data.properties.insert( 4410 + data.insert_property( 4253 4411 ENTRY_COUNT_KEY.to_string(), 4254 4412 Property::builtin(Value::Number(entry_count as f64)), 4413 + shapes, 4255 4414 ); 4256 - data.properties.insert( 4415 + data.insert_property( 4257 4416 LIVE_COUNT_KEY.to_string(), 4258 4417 Property::builtin(Value::Number(live as f64)), 4418 + shapes, 4259 4419 ); 4260 - data.properties.insert( 4420 + data.insert_property( 4261 4421 "size".to_string(), 4262 4422 Property::builtin(Value::Number(live as f64)), 4423 + shapes, 4263 4424 ); 4264 4425 } 4265 4426 } 4266 4427 4267 4428 /// Get the key at index `i` in the entries object (returns None if deleted or missing). 4268 - fn entry_key_at(gc: &Gc<HeapObject>, entries: GcRef, i: usize) -> Option<Value> { 4429 + fn entry_key_at( 4430 + gc: &Gc<HeapObject>, 4431 + shapes: &ShapeTable, 4432 + entries: GcRef, 4433 + i: usize, 4434 + ) -> Option<Value> { 4269 4435 match gc.get(entries) { 4270 4436 Some(HeapObject::Object(data)) => { 4271 4437 let k = format!("{i}_k"); 4272 - let prop = data.properties.get(&k)?; 4438 + let prop = data.get_property(&k, shapes)?; 4273 4439 if let Value::String(s) = &prop.value { 4274 4440 if s == DELETED_MARKER { 4275 4441 return None; ··· 4282 4448 } 4283 4449 4284 4450 /// Get the value at index `i` in the entries object. 4285 - fn entry_value_at(gc: &Gc<HeapObject>, entries: GcRef, i: usize) -> Value { 4451 + fn entry_value_at(gc: &Gc<HeapObject>, shapes: &ShapeTable, entries: GcRef, i: usize) -> Value { 4286 4452 match gc.get(entries) { 4287 4453 Some(HeapObject::Object(data)) => { 4288 4454 let v = format!("{i}_v"); 4289 - data.properties 4290 - .get(&v) 4291 - .map(|p| p.value.clone()) 4455 + data.get_property(&v, shapes) 4456 + .map(|p| p.value) 4292 4457 .unwrap_or(Value::Undefined) 4293 4458 } 4294 4459 _ => Value::Undefined, ··· 4296 4461 } 4297 4462 4298 4463 /// Set an entry at index `i`. 4299 - fn set_entry_at(gc: &mut Gc<HeapObject>, entries: GcRef, i: usize, key: Value, value: Value) { 4464 + fn set_entry_at( 4465 + gc: &mut Gc<HeapObject>, 4466 + shapes: &mut ShapeTable, 4467 + entries: GcRef, 4468 + i: usize, 4469 + key: Value, 4470 + value: Value, 4471 + ) { 4300 4472 if let Some(HeapObject::Object(data)) = gc.get_mut(entries) { 4301 - data.properties 4302 - .insert(format!("{i}_k"), Property::builtin(key)); 4303 - data.properties 4304 - .insert(format!("{i}_v"), Property::builtin(value)); 4473 + data.insert_property(format!("{i}_k"), Property::builtin(key), shapes); 4474 + data.insert_property(format!("{i}_v"), Property::builtin(value), shapes); 4305 4475 } 4306 4476 } 4307 4477 4308 4478 /// Mark entry at index `i` as deleted. 4309 - fn delete_entry_at(gc: &mut Gc<HeapObject>, entries: GcRef, i: usize) { 4479 + fn delete_entry_at(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, entries: GcRef, i: usize) { 4310 4480 if let Some(HeapObject::Object(data)) = gc.get_mut(entries) { 4311 - data.properties.insert( 4481 + data.insert_property( 4312 4482 format!("{i}_k"), 4313 4483 Property::builtin(Value::String(DELETED_MARKER.to_string())), 4484 + shapes, 4314 4485 ); 4315 - data.properties.remove(&format!("{i}_v")); 4486 + data.remove_property(&format!("{i}_v"), shapes); 4316 4487 } 4317 4488 } 4318 4489 4319 4490 /// Find the index of a key in the entries, using SameValueZero. 4320 - fn find_key_index(gc: &Gc<HeapObject>, entries: GcRef, count: usize, key: &Value) -> Option<usize> { 4491 + fn find_key_index( 4492 + gc: &Gc<HeapObject>, 4493 + shapes: &ShapeTable, 4494 + entries: GcRef, 4495 + count: usize, 4496 + key: &Value, 4497 + ) -> Option<usize> { 4321 4498 for i in 0..count { 4322 - if let Some(existing) = entry_key_at(gc, entries, i) { 4499 + if let Some(existing) = entry_key_at(gc, shapes, entries, i) { 4323 4500 if same_value_zero(&existing, key) { 4324 4501 return Some(i); 4325 4502 } ··· 4332 4509 4333 4510 fn map_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4334 4511 let proto = MAP_PROTO.with(|cell| cell.get()); 4335 - let obj_ref = make_collection_obj(ctx.gc, proto); 4512 + let obj_ref = make_collection_obj(ctx.gc, ctx.shapes, proto); 4336 4513 let obj = Value::Object(obj_ref); 4337 4514 4338 4515 // If an iterable argument is provided, add entries from it. ··· 4340 4517 if let Some(arg) = args.first() { 4341 4518 if !matches!(arg, Value::Undefined | Value::Null) { 4342 4519 if let Some(arr_ref) = arg.gc_ref() { 4343 - let len = array_length(ctx.gc, arr_ref); 4520 + let len = array_length(ctx.gc, ctx.shapes, arr_ref); 4344 4521 for i in 0..len { 4345 - let pair = get_property(ctx.gc, &Value::Object(arr_ref), &i.to_string()); 4522 + let pair = 4523 + get_property(ctx.gc, ctx.shapes, &Value::Object(arr_ref), &i.to_string()); 4346 4524 if let Some(pair_ref) = pair.gc_ref() { 4347 - let k = get_property(ctx.gc, &Value::Object(pair_ref), "0"); 4348 - let v = get_property(ctx.gc, &Value::Object(pair_ref), "1"); 4349 - map_set_internal(ctx.gc, &obj, k, v); 4525 + let k = get_property(ctx.gc, ctx.shapes, &Value::Object(pair_ref), "0"); 4526 + let v = get_property(ctx.gc, ctx.shapes, &Value::Object(pair_ref), "1"); 4527 + map_set_internal(ctx.gc, ctx.shapes, &obj, k, v); 4350 4528 } 4351 4529 } 4352 4530 } ··· 4356 4534 Ok(obj) 4357 4535 } 4358 4536 4359 - fn map_set_internal(gc: &mut Gc<HeapObject>, map: &Value, key: Value, value: Value) { 4360 - let entries = match collection_entries(gc, map) { 4537 + fn map_set_internal( 4538 + gc: &mut Gc<HeapObject>, 4539 + shapes: &mut ShapeTable, 4540 + map: &Value, 4541 + key: Value, 4542 + value: Value, 4543 + ) { 4544 + let entries = match collection_entries(gc, shapes, map) { 4361 4545 Some(e) => e, 4362 4546 None => return, 4363 4547 }; 4364 - let count = collection_entry_count(gc, map); 4365 - let live = collection_live_count(gc, map); 4548 + let count = collection_entry_count(gc, shapes, map); 4549 + let live = collection_live_count(gc, shapes, map); 4366 4550 4367 4551 // Check if key already exists. 4368 - if let Some(idx) = find_key_index(gc, entries, count, &key) { 4369 - set_entry_at(gc, entries, idx, key, value); 4552 + if let Some(idx) = find_key_index(gc, shapes, entries, count, &key) { 4553 + set_entry_at(gc, shapes, entries, idx, key, value); 4370 4554 return; 4371 4555 } 4372 4556 4373 4557 // Add new entry. 4374 - set_entry_at(gc, entries, count, key, value); 4375 - set_collection_count(gc, map, count + 1, live + 1); 4558 + set_entry_at(gc, shapes, entries, count, key, value); 4559 + set_collection_count(gc, shapes, map, count + 1, live + 1); 4376 4560 } 4377 4561 4378 - fn init_map_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 4562 + fn init_map_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 4379 4563 let methods: &[NativeMethod] = &[ 4380 4564 ("set", map_proto_set), 4381 4565 ("get", map_proto_get), ··· 4389 4573 ]; 4390 4574 for &(name, callback) in methods { 4391 4575 let f = make_native(gc, name, callback); 4392 - set_builtin_prop(gc, proto, name, Value::Function(f)); 4576 + set_builtin_prop(gc, shapes, proto, name, Value::Function(f)); 4393 4577 } 4394 4578 // Map.prototype[@@iterator] — returns an iterator of [key, value] pairs. 4395 4579 let iter_fn = make_native(gc, "[Symbol.iterator]", map_symbol_iterator); 4396 - set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter_fn)); 4580 + set_builtin_prop(gc, shapes, proto, "@@iterator", Value::Function(iter_fn)); 4397 4581 } 4398 4582 4399 4583 fn map_proto_set(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 4401 4585 let value = args.get(1).cloned().unwrap_or(Value::Undefined); 4402 4586 // Normalize -0 to +0 for key. 4403 4587 let key = normalize_zero(key); 4404 - map_set_internal(ctx.gc, &ctx.this, key, value); 4588 + map_set_internal(ctx.gc, ctx.shapes, &ctx.this, key, value); 4405 4589 Ok(ctx.this.clone()) 4406 4590 } 4407 4591 4408 4592 fn map_proto_get(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4409 4593 let key = args.first().cloned().unwrap_or(Value::Undefined); 4410 4594 let key = normalize_zero(key); 4411 - let entries = match collection_entries(ctx.gc, &ctx.this) { 4595 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4412 4596 Some(e) => e, 4413 4597 None => return Ok(Value::Undefined), 4414 4598 }; 4415 - let count = collection_entry_count(ctx.gc, &ctx.this); 4416 - if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4417 - return Ok(entry_value_at(ctx.gc, entries, idx)); 4599 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4600 + if let Some(idx) = find_key_index(ctx.gc, ctx.shapes, entries, count, &key) { 4601 + return Ok(entry_value_at(ctx.gc, ctx.shapes, entries, idx)); 4418 4602 } 4419 4603 Ok(Value::Undefined) 4420 4604 } ··· 4422 4606 fn map_proto_has(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4423 4607 let key = args.first().cloned().unwrap_or(Value::Undefined); 4424 4608 let key = normalize_zero(key); 4425 - let entries = match collection_entries(ctx.gc, &ctx.this) { 4609 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4426 4610 Some(e) => e, 4427 4611 None => return Ok(Value::Boolean(false)), 4428 4612 }; 4429 - let count = collection_entry_count(ctx.gc, &ctx.this); 4613 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4430 4614 Ok(Value::Boolean( 4431 - find_key_index(ctx.gc, entries, count, &key).is_some(), 4615 + find_key_index(ctx.gc, ctx.shapes, entries, count, &key).is_some(), 4432 4616 )) 4433 4617 } 4434 4618 4435 4619 fn map_proto_delete(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4436 4620 let key = args.first().cloned().unwrap_or(Value::Undefined); 4437 4621 let key = normalize_zero(key); 4438 - let entries = match collection_entries(ctx.gc, &ctx.this) { 4622 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4439 4623 Some(e) => e, 4440 4624 None => return Ok(Value::Boolean(false)), 4441 4625 }; 4442 - let count = collection_entry_count(ctx.gc, &ctx.this); 4443 - let live = collection_live_count(ctx.gc, &ctx.this); 4444 - if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4445 - delete_entry_at(ctx.gc, entries, idx); 4446 - set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 4626 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4627 + let live = collection_live_count(ctx.gc, ctx.shapes, &ctx.this); 4628 + if let Some(idx) = find_key_index(ctx.gc, ctx.shapes, entries, count, &key) { 4629 + delete_entry_at(ctx.gc, ctx.shapes, entries, idx); 4630 + set_collection_count(ctx.gc, ctx.shapes, &ctx.this, count, live.saturating_sub(1)); 4447 4631 return Ok(Value::Boolean(true)); 4448 4632 } 4449 4633 Ok(Value::Boolean(false)) 4450 4634 } 4451 4635 4452 4636 fn map_proto_clear(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4453 - clear_collection(ctx.gc, &ctx.this); 4637 + clear_collection(ctx.gc, ctx.shapes, &ctx.this); 4454 4638 Ok(Value::Undefined) 4455 4639 } 4456 4640 4457 4641 /// Clear all entries from a Map/Set collection. 4458 - fn clear_collection(gc: &mut Gc<HeapObject>, obj: &Value) { 4642 + fn clear_collection(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, obj: &Value) { 4459 4643 let new_entries = gc.alloc(HeapObject::Object(ObjectData::new())); 4460 4644 if let Some(gc_ref) = obj.gc_ref() { 4461 4645 if let Some(HeapObject::Object(data)) = gc.get_mut(gc_ref) { 4462 - data.properties.insert( 4646 + data.insert_property( 4463 4647 ENTRIES_KEY.to_string(), 4464 4648 Property::builtin(Value::Object(new_entries)), 4649 + shapes, 4465 4650 ); 4466 - data.properties.insert( 4651 + data.insert_property( 4467 4652 ENTRY_COUNT_KEY.to_string(), 4468 4653 Property::builtin(Value::Number(0.0)), 4654 + shapes, 4469 4655 ); 4470 - data.properties.insert( 4656 + data.insert_property( 4471 4657 LIVE_COUNT_KEY.to_string(), 4472 4658 Property::builtin(Value::Number(0.0)), 4659 + shapes, 4473 4660 ); 4474 - data.properties 4475 - .insert("size".to_string(), Property::builtin(Value::Number(0.0))); 4661 + data.insert_property( 4662 + "size".to_string(), 4663 + Property::builtin(Value::Number(0.0)), 4664 + shapes, 4665 + ); 4476 4666 } 4477 4667 } 4478 4668 } ··· 4486 4676 )) 4487 4677 } 4488 4678 }; 4489 - let entries = match collection_entries(ctx.gc, &ctx.this) { 4679 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4490 4680 Some(e) => e, 4491 4681 None => return Ok(Value::Undefined), 4492 4682 }; 4493 - let count = collection_entry_count(ctx.gc, &ctx.this); 4683 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4494 4684 // Collect entries first to avoid borrow issues. 4495 4685 let mut pairs = Vec::new(); 4496 4686 for i in 0..count { 4497 - if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4498 - let v = entry_value_at(ctx.gc, entries, i); 4687 + if let Some(k) = entry_key_at(ctx.gc, ctx.shapes, entries, i) { 4688 + let v = entry_value_at(ctx.gc, ctx.shapes, entries, i); 4499 4689 pairs.push((k, v)); 4500 4690 } 4501 4691 } ··· 4503 4693 for (k, v) in pairs { 4504 4694 call_native_callback( 4505 4695 ctx.gc, 4696 + ctx.shapes, 4506 4697 callback, 4507 4698 &[v, k, this_val.clone()], 4508 4699 ctx.console_output, ··· 4535 4726 ctx: &mut NativeContext, 4536 4727 kind: IterKind, 4537 4728 ) -> Result<Value, RuntimeError> { 4538 - let entries = match collection_entries(ctx.gc, &ctx.this) { 4729 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4539 4730 Some(e) => e, 4540 - None => return Ok(make_value_array(ctx.gc, &[])), 4731 + None => return Ok(make_value_array(ctx.gc, ctx.shapes, &[])), 4541 4732 }; 4542 - let count = collection_entry_count(ctx.gc, &ctx.this); 4733 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4543 4734 let mut items = Vec::new(); 4544 4735 for i in 0..count { 4545 - if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4736 + if let Some(k) = entry_key_at(ctx.gc, ctx.shapes, entries, i) { 4546 4737 match kind { 4547 4738 IterKind::Keys => items.push(k), 4548 4739 IterKind::Values => { 4549 - let v = entry_value_at(ctx.gc, entries, i); 4740 + let v = entry_value_at(ctx.gc, ctx.shapes, entries, i); 4550 4741 items.push(v); 4551 4742 } 4552 4743 IterKind::Entries => { 4553 - let v = entry_value_at(ctx.gc, entries, i); 4554 - let pair = make_value_array(ctx.gc, &[k, v]); 4744 + let v = entry_value_at(ctx.gc, ctx.shapes, entries, i); 4745 + let pair = make_value_array(ctx.gc, ctx.shapes, &[k, v]); 4555 4746 items.push(pair); 4556 4747 } 4557 4748 } 4558 4749 } 4559 4750 } 4560 - Ok(make_value_array(ctx.gc, &items)) 4751 + Ok(make_value_array(ctx.gc, ctx.shapes, &items)) 4561 4752 } 4562 4753 4563 4754 /// Map[@@iterator]() — wraps entries array into an iterator. ··· 4567 4758 Some(r) => r, 4568 4759 None => return Ok(Value::Undefined), 4569 4760 }; 4570 - Ok(make_simple_iterator(ctx.gc, arr_ref, array_values_next)) 4761 + Ok(make_simple_iterator( 4762 + ctx.gc, 4763 + ctx.shapes, 4764 + arr_ref, 4765 + array_values_next, 4766 + )) 4571 4767 } 4572 4768 4573 4769 /// Set[@@iterator]() — wraps values array into an iterator. ··· 4577 4773 Some(r) => r, 4578 4774 None => return Ok(Value::Undefined), 4579 4775 }; 4580 - Ok(make_simple_iterator(ctx.gc, arr_ref, array_values_next)) 4776 + Ok(make_simple_iterator( 4777 + ctx.gc, 4778 + ctx.shapes, 4779 + arr_ref, 4780 + array_values_next, 4781 + )) 4581 4782 } 4582 4783 4583 4784 /// Normalize -0 to +0 for Map/Set key equality. ··· 4591 4792 } 4592 4793 4593 4794 /// Helper to get a property from an object value by key (used for reading iterable pairs). 4594 - fn get_property(gc: &Gc<HeapObject>, obj: &Value, key: &str) -> Value { 4795 + fn get_property(gc: &Gc<HeapObject>, shapes: &ShapeTable, obj: &Value, key: &str) -> Value { 4595 4796 let gc_ref = match obj.gc_ref() { 4596 4797 Some(r) => r, 4597 4798 None => return Value::Undefined, 4598 4799 }; 4599 4800 match gc.get(gc_ref) { 4600 4801 Some(HeapObject::Object(data)) => data 4601 - .properties 4602 - .get(key) 4603 - .map(|p| p.value.clone()) 4802 + .get_property(key, shapes) 4803 + .map(|p| p.value) 4604 4804 .unwrap_or(Value::Undefined), 4605 4805 _ => Value::Undefined, 4606 4806 } ··· 4609 4809 /// Call a native callback function (for forEach). 4610 4810 fn call_native_callback( 4611 4811 gc: &mut Gc<HeapObject>, 4812 + shapes: &mut ShapeTable, 4612 4813 func_ref: GcRef, 4613 4814 args: &[Value], 4614 4815 console_output: &dyn ConsoleOutput, ··· 4620 4821 let cb = native.callback; 4621 4822 let mut ctx = NativeContext { 4622 4823 gc, 4824 + shapes, 4623 4825 this: Value::Undefined, 4624 4826 console_output, 4625 4827 dom_bridge, ··· 4638 4840 4639 4841 fn set_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4640 4842 let proto = SET_PROTO.with(|cell| cell.get()); 4641 - let obj_ref = make_collection_obj(ctx.gc, proto); 4843 + let obj_ref = make_collection_obj(ctx.gc, ctx.shapes, proto); 4642 4844 let obj = Value::Object(obj_ref); 4643 4845 4644 4846 // If an iterable argument is provided, add values from it. 4645 4847 if let Some(arg) = args.first() { 4646 4848 if !matches!(arg, Value::Undefined | Value::Null) { 4647 4849 if let Some(arr_ref) = arg.gc_ref() { 4648 - let len = array_length(ctx.gc, arr_ref); 4850 + let len = array_length(ctx.gc, ctx.shapes, arr_ref); 4649 4851 for i in 0..len { 4650 - let v = get_property(ctx.gc, &Value::Object(arr_ref), &i.to_string()); 4651 - set_add_internal(ctx.gc, &obj, v); 4852 + let v = 4853 + get_property(ctx.gc, ctx.shapes, &Value::Object(arr_ref), &i.to_string()); 4854 + set_add_internal(ctx.gc, ctx.shapes, &obj, v); 4652 4855 } 4653 4856 } 4654 4857 } ··· 4657 4860 Ok(obj) 4658 4861 } 4659 4862 4660 - fn set_add_internal(gc: &mut Gc<HeapObject>, set: &Value, value: Value) { 4661 - let entries = match collection_entries(gc, set) { 4863 + fn set_add_internal(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, set: &Value, value: Value) { 4864 + let entries = match collection_entries(gc, shapes, set) { 4662 4865 Some(e) => e, 4663 4866 None => return, 4664 4867 }; 4665 - let count = collection_entry_count(gc, set); 4666 - let live = collection_live_count(gc, set); 4868 + let count = collection_entry_count(gc, shapes, set); 4869 + let live = collection_live_count(gc, shapes, set); 4667 4870 4668 4871 // Check if value already exists. 4669 - if find_key_index(gc, entries, count, &value).is_some() { 4872 + if find_key_index(gc, shapes, entries, count, &value).is_some() { 4670 4873 return; 4671 4874 } 4672 4875 4673 4876 // Add new entry (value stored as key, value slot unused). 4674 - set_entry_at(gc, entries, count, value, Value::Undefined); 4675 - set_collection_count(gc, set, count + 1, live + 1); 4877 + set_entry_at(gc, shapes, entries, count, value, Value::Undefined); 4878 + set_collection_count(gc, shapes, set, count + 1, live + 1); 4676 4879 } 4677 4880 4678 - fn init_set_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 4881 + fn init_set_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 4679 4882 let methods: &[NativeMethod] = &[ 4680 4883 ("add", set_proto_add), 4681 4884 ("has", set_proto_has), ··· 4688 4891 ]; 4689 4892 for &(name, callback) in methods { 4690 4893 let f = make_native(gc, name, callback); 4691 - set_builtin_prop(gc, proto, name, Value::Function(f)); 4894 + set_builtin_prop(gc, shapes, proto, name, Value::Function(f)); 4692 4895 } 4693 4896 // Set.prototype[@@iterator] — returns an iterator of values. 4694 4897 let iter_fn = make_native(gc, "[Symbol.iterator]", set_symbol_iterator); 4695 - set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter_fn)); 4898 + set_builtin_prop(gc, shapes, proto, "@@iterator", Value::Function(iter_fn)); 4696 4899 } 4697 4900 4698 4901 fn set_proto_add(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4699 4902 let value = args.first().cloned().unwrap_or(Value::Undefined); 4700 4903 let value = normalize_zero(value); 4701 - set_add_internal(ctx.gc, &ctx.this, value); 4904 + set_add_internal(ctx.gc, ctx.shapes, &ctx.this, value); 4702 4905 Ok(ctx.this.clone()) 4703 4906 } 4704 4907 4705 4908 fn set_proto_has(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4706 4909 let value = args.first().cloned().unwrap_or(Value::Undefined); 4707 4910 let value = normalize_zero(value); 4708 - let entries = match collection_entries(ctx.gc, &ctx.this) { 4911 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4709 4912 Some(e) => e, 4710 4913 None => return Ok(Value::Boolean(false)), 4711 4914 }; 4712 - let count = collection_entry_count(ctx.gc, &ctx.this); 4915 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4713 4916 Ok(Value::Boolean( 4714 - find_key_index(ctx.gc, entries, count, &value).is_some(), 4917 + find_key_index(ctx.gc, ctx.shapes, entries, count, &value).is_some(), 4715 4918 )) 4716 4919 } 4717 4920 4718 4921 fn set_proto_delete(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4719 4922 let value = args.first().cloned().unwrap_or(Value::Undefined); 4720 4923 let value = normalize_zero(value); 4721 - let entries = match collection_entries(ctx.gc, &ctx.this) { 4924 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4722 4925 Some(e) => e, 4723 4926 None => return Ok(Value::Boolean(false)), 4724 4927 }; 4725 - let count = collection_entry_count(ctx.gc, &ctx.this); 4726 - let live = collection_live_count(ctx.gc, &ctx.this); 4727 - if let Some(idx) = find_key_index(ctx.gc, entries, count, &value) { 4728 - delete_entry_at(ctx.gc, entries, idx); 4729 - set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 4928 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4929 + let live = collection_live_count(ctx.gc, ctx.shapes, &ctx.this); 4930 + if let Some(idx) = find_key_index(ctx.gc, ctx.shapes, entries, count, &value) { 4931 + delete_entry_at(ctx.gc, ctx.shapes, entries, idx); 4932 + set_collection_count(ctx.gc, ctx.shapes, &ctx.this, count, live.saturating_sub(1)); 4730 4933 return Ok(Value::Boolean(true)); 4731 4934 } 4732 4935 Ok(Value::Boolean(false)) 4733 4936 } 4734 4937 4735 4938 fn set_proto_clear(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4736 - clear_collection(ctx.gc, &ctx.this); 4939 + clear_collection(ctx.gc, ctx.shapes, &ctx.this); 4737 4940 Ok(Value::Undefined) 4738 4941 } 4739 4942 ··· 4746 4949 )) 4747 4950 } 4748 4951 }; 4749 - let entries = match collection_entries(ctx.gc, &ctx.this) { 4952 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4750 4953 Some(e) => e, 4751 4954 None => return Ok(Value::Undefined), 4752 4955 }; 4753 - let count = collection_entry_count(ctx.gc, &ctx.this); 4956 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4754 4957 let mut values = Vec::new(); 4755 4958 for i in 0..count { 4756 - if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4959 + if let Some(k) = entry_key_at(ctx.gc, ctx.shapes, entries, i) { 4757 4960 values.push(k); 4758 4961 } 4759 4962 } ··· 4761 4964 for v in values { 4762 4965 call_native_callback( 4763 4966 ctx.gc, 4967 + ctx.shapes, 4764 4968 callback, 4765 4969 &[v.clone(), v, this_val.clone()], 4766 4970 ctx.console_output, ··· 4775 4979 } 4776 4980 4777 4981 fn set_proto_values(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4778 - let entries = match collection_entries(ctx.gc, &ctx.this) { 4982 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4779 4983 Some(e) => e, 4780 - None => return Ok(make_value_array(ctx.gc, &[])), 4984 + None => return Ok(make_value_array(ctx.gc, ctx.shapes, &[])), 4781 4985 }; 4782 - let count = collection_entry_count(ctx.gc, &ctx.this); 4986 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4783 4987 let mut items = Vec::new(); 4784 4988 for i in 0..count { 4785 - if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4989 + if let Some(k) = entry_key_at(ctx.gc, ctx.shapes, entries, i) { 4786 4990 items.push(k); 4787 4991 } 4788 4992 } 4789 - Ok(make_value_array(ctx.gc, &items)) 4993 + Ok(make_value_array(ctx.gc, ctx.shapes, &items)) 4790 4994 } 4791 4995 4792 4996 fn set_proto_entries(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4793 - let entries = match collection_entries(ctx.gc, &ctx.this) { 4997 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4794 4998 Some(e) => e, 4795 - None => return Ok(make_value_array(ctx.gc, &[])), 4999 + None => return Ok(make_value_array(ctx.gc, ctx.shapes, &[])), 4796 5000 }; 4797 - let count = collection_entry_count(ctx.gc, &ctx.this); 5001 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4798 5002 let mut items = Vec::new(); 4799 5003 for i in 0..count { 4800 - if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4801 - let pair = make_value_array(ctx.gc, &[k.clone(), k]); 5004 + if let Some(k) = entry_key_at(ctx.gc, ctx.shapes, entries, i) { 5005 + let pair = make_value_array(ctx.gc, ctx.shapes, &[k.clone(), k]); 4802 5006 items.push(pair); 4803 5007 } 4804 5008 } 4805 - Ok(make_value_array(ctx.gc, &items)) 5009 + Ok(make_value_array(ctx.gc, ctx.shapes, &items)) 4806 5010 } 4807 5011 4808 5012 // ── WeakMap constructor & prototype ────────────────────────── 4809 5013 4810 5014 fn weakmap_constructor(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4811 5015 let proto = WEAKMAP_PROTO.with(|cell| cell.get()); 4812 - let obj_ref = make_collection_obj(ctx.gc, proto); 5016 + let obj_ref = make_collection_obj(ctx.gc, ctx.shapes, proto); 4813 5017 Ok(Value::Object(obj_ref)) 4814 5018 } 4815 5019 ··· 4817 5021 matches!(val, Value::Object(_) | Value::Function(_)) 4818 5022 } 4819 5023 4820 - fn init_weakmap_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 5024 + fn init_weakmap_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 4821 5025 let methods: &[NativeMethod] = &[ 4822 5026 ("set", weakmap_proto_set), 4823 5027 ("get", weakmap_proto_get), ··· 4826 5030 ]; 4827 5031 for &(name, callback) in methods { 4828 5032 let f = make_native(gc, name, callback); 4829 - set_builtin_prop(gc, proto, name, Value::Function(f)); 5033 + set_builtin_prop(gc, shapes, proto, name, Value::Function(f)); 4830 5034 } 4831 5035 } 4832 5036 ··· 4836 5040 return Err(RuntimeError::type_error("WeakMap key must be an object")); 4837 5041 } 4838 5042 let value = args.get(1).cloned().unwrap_or(Value::Undefined); 4839 - map_set_internal(ctx.gc, &ctx.this, key, value); 5043 + map_set_internal(ctx.gc, ctx.shapes, &ctx.this, key, value); 4840 5044 Ok(ctx.this.clone()) 4841 5045 } 4842 5046 ··· 4845 5049 if !is_object_value(&key) { 4846 5050 return Ok(Value::Undefined); 4847 5051 } 4848 - let entries = match collection_entries(ctx.gc, &ctx.this) { 5052 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4849 5053 Some(e) => e, 4850 5054 None => return Ok(Value::Undefined), 4851 5055 }; 4852 - let count = collection_entry_count(ctx.gc, &ctx.this); 4853 - if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4854 - return Ok(entry_value_at(ctx.gc, entries, idx)); 5056 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 5057 + if let Some(idx) = find_key_index(ctx.gc, ctx.shapes, entries, count, &key) { 5058 + return Ok(entry_value_at(ctx.gc, ctx.shapes, entries, idx)); 4855 5059 } 4856 5060 Ok(Value::Undefined) 4857 5061 } ··· 4861 5065 if !is_object_value(&key) { 4862 5066 return Ok(Value::Boolean(false)); 4863 5067 } 4864 - let entries = match collection_entries(ctx.gc, &ctx.this) { 5068 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4865 5069 Some(e) => e, 4866 5070 None => return Ok(Value::Boolean(false)), 4867 5071 }; 4868 - let count = collection_entry_count(ctx.gc, &ctx.this); 5072 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4869 5073 Ok(Value::Boolean( 4870 - find_key_index(ctx.gc, entries, count, &key).is_some(), 5074 + find_key_index(ctx.gc, ctx.shapes, entries, count, &key).is_some(), 4871 5075 )) 4872 5076 } 4873 5077 ··· 4876 5080 if !is_object_value(&key) { 4877 5081 return Ok(Value::Boolean(false)); 4878 5082 } 4879 - let entries = match collection_entries(ctx.gc, &ctx.this) { 5083 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4880 5084 Some(e) => e, 4881 5085 None => return Ok(Value::Boolean(false)), 4882 5086 }; 4883 - let count = collection_entry_count(ctx.gc, &ctx.this); 4884 - let live = collection_live_count(ctx.gc, &ctx.this); 4885 - if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4886 - delete_entry_at(ctx.gc, entries, idx); 4887 - set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 5087 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 5088 + let live = collection_live_count(ctx.gc, ctx.shapes, &ctx.this); 5089 + if let Some(idx) = find_key_index(ctx.gc, ctx.shapes, entries, count, &key) { 5090 + delete_entry_at(ctx.gc, ctx.shapes, entries, idx); 5091 + set_collection_count(ctx.gc, ctx.shapes, &ctx.this, count, live.saturating_sub(1)); 4888 5092 return Ok(Value::Boolean(true)); 4889 5093 } 4890 5094 Ok(Value::Boolean(false)) ··· 4894 5098 4895 5099 fn weakset_constructor(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4896 5100 let proto = WEAKSET_PROTO.with(|cell| cell.get()); 4897 - let obj_ref = make_collection_obj(ctx.gc, proto); 5101 + let obj_ref = make_collection_obj(ctx.gc, ctx.shapes, proto); 4898 5102 Ok(Value::Object(obj_ref)) 4899 5103 } 4900 5104 4901 - fn init_weakset_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 5105 + fn init_weakset_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 4902 5106 let methods: &[NativeMethod] = &[ 4903 5107 ("add", weakset_proto_add), 4904 5108 ("has", weakset_proto_has), ··· 4906 5110 ]; 4907 5111 for &(name, callback) in methods { 4908 5112 let f = make_native(gc, name, callback); 4909 - set_builtin_prop(gc, proto, name, Value::Function(f)); 5113 + set_builtin_prop(gc, shapes, proto, name, Value::Function(f)); 4910 5114 } 4911 5115 } 4912 5116 ··· 4915 5119 if !is_object_value(&value) { 4916 5120 return Err(RuntimeError::type_error("WeakSet value must be an object")); 4917 5121 } 4918 - set_add_internal(ctx.gc, &ctx.this, value); 5122 + set_add_internal(ctx.gc, ctx.shapes, &ctx.this, value); 4919 5123 Ok(ctx.this.clone()) 4920 5124 } 4921 5125 ··· 4924 5128 if !is_object_value(&value) { 4925 5129 return Ok(Value::Boolean(false)); 4926 5130 } 4927 - let entries = match collection_entries(ctx.gc, &ctx.this) { 5131 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4928 5132 Some(e) => e, 4929 5133 None => return Ok(Value::Boolean(false)), 4930 5134 }; 4931 - let count = collection_entry_count(ctx.gc, &ctx.this); 5135 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 4932 5136 Ok(Value::Boolean( 4933 - find_key_index(ctx.gc, entries, count, &value).is_some(), 5137 + find_key_index(ctx.gc, ctx.shapes, entries, count, &value).is_some(), 4934 5138 )) 4935 5139 } 4936 5140 ··· 4939 5143 if !is_object_value(&value) { 4940 5144 return Ok(Value::Boolean(false)); 4941 5145 } 4942 - let entries = match collection_entries(ctx.gc, &ctx.this) { 5146 + let entries = match collection_entries(ctx.gc, ctx.shapes, &ctx.this) { 4943 5147 Some(e) => e, 4944 5148 None => return Ok(Value::Boolean(false)), 4945 5149 }; 4946 - let count = collection_entry_count(ctx.gc, &ctx.this); 4947 - let live = collection_live_count(ctx.gc, &ctx.this); 4948 - if let Some(idx) = find_key_index(ctx.gc, entries, count, &value) { 4949 - delete_entry_at(ctx.gc, entries, idx); 4950 - set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 5150 + let count = collection_entry_count(ctx.gc, ctx.shapes, &ctx.this); 5151 + let live = collection_live_count(ctx.gc, ctx.shapes, &ctx.this); 5152 + if let Some(idx) = find_key_index(ctx.gc, ctx.shapes, entries, count, &value) { 5153 + delete_entry_at(ctx.gc, ctx.shapes, entries, idx); 5154 + set_collection_count(ctx.gc, ctx.shapes, &ctx.this, count, live.saturating_sub(1)); 4951 5155 return Ok(Value::Boolean(true)); 4952 5156 } 4953 5157 Ok(Value::Boolean(false)) ··· 4993 5197 } 4994 5198 4995 5199 /// Public wrappers for functions used by the VM's microtask drain. 4996 - pub fn promise_get_prop_pub(gc: &Gc<HeapObject>, promise: GcRef, key: &str) -> Value { 4997 - promise_get_prop(gc, promise, key) 5200 + pub fn promise_get_prop_pub( 5201 + gc: &Gc<HeapObject>, 5202 + shapes: &ShapeTable, 5203 + promise: GcRef, 5204 + key: &str, 5205 + ) -> Value { 5206 + promise_get_prop(gc, shapes, promise, key) 4998 5207 } 4999 5208 5000 - pub fn promise_state_pub(gc: &Gc<HeapObject>, promise: GcRef) -> f64 { 5001 - promise_state(gc, promise) 5209 + pub fn promise_state_pub(gc: &Gc<HeapObject>, shapes: &ShapeTable, promise: GcRef) -> f64 { 5210 + promise_state(gc, shapes, promise) 5002 5211 } 5003 5212 5004 - pub fn is_promise_pub(gc: &Gc<HeapObject>, value: &Value) -> bool { 5005 - is_promise(gc, value) 5213 + pub fn is_promise_pub(gc: &Gc<HeapObject>, shapes: &ShapeTable, value: &Value) -> bool { 5214 + is_promise(gc, shapes, value) 5006 5215 } 5007 5216 5008 - pub fn chain_promise_pub(gc: &mut Gc<HeapObject>, source: GcRef, target: GcRef) { 5009 - chain_promise(gc, source, target) 5217 + pub fn chain_promise_pub( 5218 + gc: &mut Gc<HeapObject>, 5219 + shapes: &mut ShapeTable, 5220 + source: GcRef, 5221 + target: GcRef, 5222 + ) { 5223 + chain_promise(gc, shapes, source, target) 5010 5224 } 5011 5225 5012 - pub fn create_promise_object_pub(gc: &mut Gc<HeapObject>) -> GcRef { 5013 - create_promise_object(gc) 5226 + pub fn create_promise_object_pub(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable) -> GcRef { 5227 + create_promise_object(gc, shapes) 5014 5228 } 5015 5229 5016 5230 pub fn enqueue_microtask_pub(task: Microtask) { ··· 5019 5233 5020 5234 pub fn add_reaction_pub( 5021 5235 gc: &mut Gc<HeapObject>, 5236 + shapes: &mut ShapeTable, 5022 5237 promise: GcRef, 5023 5238 on_fulfilled: Value, 5024 5239 on_rejected: Value, 5025 5240 ) -> GcRef { 5026 - add_reaction(gc, promise, on_fulfilled, on_rejected) 5241 + add_reaction(gc, shapes, promise, on_fulfilled, on_rejected) 5027 5242 } 5028 5243 5029 5244 /// Initialize Promise.prototype in a standalone GC (for unit tests that 5030 5245 /// create promise objects without a full VM). 5031 - pub fn init_promise_proto_for_test(gc: &mut Gc<HeapObject>) { 5246 + pub fn init_promise_proto_for_test(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable) { 5032 5247 let proto_data = ObjectData::new(); 5033 5248 let promise_proto = gc.alloc(HeapObject::Object(proto_data)); 5034 - init_promise_prototype(gc, promise_proto); 5249 + init_promise_prototype(gc, shapes, promise_proto); 5035 5250 PROMISE_PROTO.with(|cell| cell.set(Some(promise_proto))); 5036 5251 } 5037 5252 ··· 5042 5257 proto_data.prototype = Some(proto); 5043 5258 } 5044 5259 let promise_proto = vm.gc.alloc(HeapObject::Object(proto_data)); 5045 - init_promise_prototype(&mut vm.gc, promise_proto); 5260 + init_promise_prototype(&mut vm.gc, &mut vm.shapes, promise_proto); 5046 5261 PROMISE_PROTO.with(|cell| cell.set(Some(promise_proto))); 5047 5262 vm.promise_prototype = Some(promise_proto); 5048 5263 ··· 5080 5295 vm.set_global("__Promise_static_any", Value::Function(static_any)); 5081 5296 } 5082 5297 5083 - fn init_promise_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 5298 + fn init_promise_prototype(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, proto: GcRef) { 5084 5299 let methods: &[NativeMethod] = &[ 5085 5300 ("then", promise_proto_then), 5086 5301 ("catch", promise_proto_catch), ··· 5088 5303 ]; 5089 5304 for &(name, callback) in methods { 5090 5305 let f = make_native(gc, name, callback); 5091 - set_builtin_prop(gc, proto, name, Value::Function(f)); 5306 + set_builtin_prop(gc, shapes, proto, name, Value::Function(f)); 5092 5307 } 5093 5308 } 5094 5309 5095 5310 /// Create a new pending promise object. 5096 - fn create_promise_object(gc: &mut Gc<HeapObject>) -> GcRef { 5311 + fn create_promise_object(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable) -> GcRef { 5097 5312 let reactions = gc.alloc(HeapObject::Object(ObjectData::new())); 5098 5313 let proto = PROMISE_PROTO.with(|cell| cell.get()); 5099 5314 let mut data = ObjectData::new(); 5100 5315 if let Some(p) = proto { 5101 5316 data.prototype = Some(p); 5102 5317 } 5103 - data.properties.insert( 5318 + data.insert_property( 5104 5319 PROMISE_STATE_KEY.to_string(), 5105 5320 Property::builtin(Value::Number(PROMISE_PENDING)), 5321 + shapes, 5106 5322 ); 5107 - data.properties.insert( 5323 + data.insert_property( 5108 5324 PROMISE_RESULT_KEY.to_string(), 5109 5325 Property::builtin(Value::Undefined), 5326 + shapes, 5110 5327 ); 5111 - data.properties.insert( 5328 + data.insert_property( 5112 5329 PROMISE_REACTIONS_KEY.to_string(), 5113 5330 Property::builtin(Value::Object(reactions)), 5331 + shapes, 5114 5332 ); 5115 - data.properties.insert( 5333 + data.insert_property( 5116 5334 PROMISE_REACTION_COUNT_KEY.to_string(), 5117 5335 Property::builtin(Value::Number(0.0)), 5336 + shapes, 5118 5337 ); 5119 5338 gc.alloc(HeapObject::Object(data)) 5120 5339 } 5121 5340 5122 5341 /// Get a hidden property from a promise object. 5123 - fn promise_get_prop(gc: &Gc<HeapObject>, promise: GcRef, key: &str) -> Value { 5342 + fn promise_get_prop(gc: &Gc<HeapObject>, shapes: &ShapeTable, promise: GcRef, key: &str) -> Value { 5124 5343 match gc.get(promise) { 5125 5344 Some(HeapObject::Object(data)) => data 5126 - .properties 5127 - .get(key) 5128 - .map(|p| p.value.clone()) 5345 + .get_property(key, shapes) 5346 + .map(|p| p.value) 5129 5347 .unwrap_or(Value::Undefined), 5130 5348 _ => Value::Undefined, 5131 5349 } 5132 5350 } 5133 5351 5134 5352 /// Set a hidden property on a promise object. 5135 - fn promise_set_prop(gc: &mut Gc<HeapObject>, promise: GcRef, key: &str, value: Value) { 5353 + fn promise_set_prop( 5354 + gc: &mut Gc<HeapObject>, 5355 + shapes: &mut ShapeTable, 5356 + promise: GcRef, 5357 + key: &str, 5358 + value: Value, 5359 + ) { 5136 5360 if let Some(HeapObject::Object(data)) = gc.get_mut(promise) { 5137 - data.properties 5138 - .insert(key.to_string(), Property::builtin(value)); 5361 + data.insert_property(key.to_string(), Property::builtin(value), shapes); 5139 5362 } 5140 5363 } 5141 5364 5142 5365 /// Get the state of a promise (PROMISE_PENDING/FULFILLED/REJECTED). 5143 - fn promise_state(gc: &Gc<HeapObject>, promise: GcRef) -> f64 { 5144 - match promise_get_prop(gc, promise, PROMISE_STATE_KEY) { 5366 + fn promise_state(gc: &Gc<HeapObject>, shapes: &ShapeTable, promise: GcRef) -> f64 { 5367 + match promise_get_prop(gc, shapes, promise, PROMISE_STATE_KEY) { 5145 5368 Value::Number(n) => n, 5146 5369 _ => PROMISE_PENDING, 5147 5370 } 5148 5371 } 5149 5372 5150 5373 /// Resolve a pending promise with a value. 5151 - pub fn resolve_promise_internal(gc: &mut Gc<HeapObject>, promise: GcRef, value: Value) { 5152 - if promise_state(gc, promise) != PROMISE_PENDING { 5374 + pub fn resolve_promise_internal( 5375 + gc: &mut Gc<HeapObject>, 5376 + shapes: &mut ShapeTable, 5377 + promise: GcRef, 5378 + value: Value, 5379 + ) { 5380 + if promise_state(gc, shapes, promise) != PROMISE_PENDING { 5153 5381 return; // Already settled. 5154 5382 } 5155 5383 promise_set_prop( 5156 5384 gc, 5385 + shapes, 5157 5386 promise, 5158 5387 PROMISE_STATE_KEY, 5159 5388 Value::Number(PROMISE_FULFILLED), 5160 5389 ); 5161 - promise_set_prop(gc, promise, PROMISE_RESULT_KEY, value.clone()); 5162 - trigger_reactions(gc, promise, value, true); 5390 + promise_set_prop(gc, shapes, promise, PROMISE_RESULT_KEY, value.clone()); 5391 + trigger_reactions(gc, shapes, promise, value, true); 5163 5392 } 5164 5393 5165 5394 /// Reject a pending promise with a reason. 5166 - pub fn reject_promise_internal(gc: &mut Gc<HeapObject>, promise: GcRef, reason: Value) { 5167 - if promise_state(gc, promise) != PROMISE_PENDING { 5395 + pub fn reject_promise_internal( 5396 + gc: &mut Gc<HeapObject>, 5397 + shapes: &mut ShapeTable, 5398 + promise: GcRef, 5399 + reason: Value, 5400 + ) { 5401 + if promise_state(gc, shapes, promise) != PROMISE_PENDING { 5168 5402 return; // Already settled. 5169 5403 } 5170 5404 promise_set_prop( 5171 5405 gc, 5406 + shapes, 5172 5407 promise, 5173 5408 PROMISE_STATE_KEY, 5174 5409 Value::Number(PROMISE_REJECTED), 5175 5410 ); 5176 - promise_set_prop(gc, promise, PROMISE_RESULT_KEY, reason.clone()); 5177 - trigger_reactions(gc, promise, reason, false); 5411 + promise_set_prop(gc, shapes, promise, PROMISE_RESULT_KEY, reason.clone()); 5412 + trigger_reactions(gc, shapes, promise, reason, false); 5178 5413 } 5179 5414 5180 5415 /// Enqueue microtasks for all registered reactions on a promise. 5181 - fn trigger_reactions(gc: &mut Gc<HeapObject>, promise: GcRef, value: Value, fulfilled: bool) { 5182 - let reactions_ref = match promise_get_prop(gc, promise, PROMISE_REACTIONS_KEY) { 5416 + fn trigger_reactions( 5417 + gc: &mut Gc<HeapObject>, 5418 + shapes: &mut ShapeTable, 5419 + promise: GcRef, 5420 + value: Value, 5421 + fulfilled: bool, 5422 + ) { 5423 + let reactions_ref = match promise_get_prop(gc, shapes, promise, PROMISE_REACTIONS_KEY) { 5183 5424 Value::Object(r) => r, 5184 5425 _ => return, 5185 5426 }; 5186 - let count = match promise_get_prop(gc, promise, PROMISE_REACTION_COUNT_KEY) { 5427 + let count = match promise_get_prop(gc, shapes, promise, PROMISE_REACTION_COUNT_KEY) { 5187 5428 Value::Number(n) => n as usize, 5188 5429 _ => 0, 5189 5430 }; ··· 5197 5438 5198 5439 let on_fulfilled = match gc.get(reactions_ref) { 5199 5440 Some(HeapObject::Object(data)) => data 5200 - .properties 5201 - .get(&fulfill_key) 5202 - .map(|p| p.value.clone()) 5441 + .get_property(&fulfill_key, shapes) 5442 + .map(|p| p.value) 5203 5443 .unwrap_or(Value::Undefined), 5204 5444 _ => Value::Undefined, 5205 5445 }; 5206 5446 let on_rejected = match gc.get(reactions_ref) { 5207 5447 Some(HeapObject::Object(data)) => data 5208 - .properties 5209 - .get(&reject_key) 5210 - .map(|p| p.value.clone()) 5448 + .get_property(&reject_key, shapes) 5449 + .map(|p| p.value) 5211 5450 .unwrap_or(Value::Undefined), 5212 5451 _ => Value::Undefined, 5213 5452 }; 5214 5453 let chained = match gc.get(reactions_ref) { 5215 5454 Some(HeapObject::Object(data)) => data 5216 - .properties 5217 - .get(&promise_key) 5455 + .get_property(&promise_key, shapes) 5218 5456 .and_then(|p| p.value.gc_ref()), 5219 5457 _ => None, 5220 5458 }; ··· 5241 5479 /// Add a reaction to a promise. Returns the chained promise GcRef. 5242 5480 fn add_reaction( 5243 5481 gc: &mut Gc<HeapObject>, 5482 + shapes: &mut ShapeTable, 5244 5483 promise: GcRef, 5245 5484 on_fulfilled: Value, 5246 5485 on_rejected: Value, 5247 5486 ) -> GcRef { 5248 - let chained = create_promise_object(gc); 5487 + let chained = create_promise_object(gc, shapes); 5249 5488 5250 - let reactions_ref = match promise_get_prop(gc, promise, PROMISE_REACTIONS_KEY) { 5489 + let reactions_ref = match promise_get_prop(gc, shapes, promise, PROMISE_REACTIONS_KEY) { 5251 5490 Value::Object(r) => r, 5252 5491 _ => return chained, 5253 5492 }; 5254 - let count = match promise_get_prop(gc, promise, PROMISE_REACTION_COUNT_KEY) { 5493 + let count = match promise_get_prop(gc, shapes, promise, PROMISE_REACTION_COUNT_KEY) { 5255 5494 Value::Number(n) => n as usize, 5256 5495 _ => 0, 5257 5496 }; 5258 5497 5259 5498 if let Some(HeapObject::Object(data)) = gc.get_mut(reactions_ref) { 5260 - data.properties 5261 - .insert(format!("{count}_fulfill"), Property::builtin(on_fulfilled)); 5262 - data.properties 5263 - .insert(format!("{count}_reject"), Property::builtin(on_rejected)); 5264 - data.properties.insert( 5499 + data.insert_property( 5500 + format!("{count}_fulfill"), 5501 + Property::builtin(on_fulfilled), 5502 + shapes, 5503 + ); 5504 + data.insert_property( 5505 + format!("{count}_reject"), 5506 + Property::builtin(on_rejected), 5507 + shapes, 5508 + ); 5509 + data.insert_property( 5265 5510 format!("{count}_promise"), 5266 5511 Property::builtin(Value::Object(chained)), 5512 + shapes, 5267 5513 ); 5268 5514 } 5269 5515 5270 5516 promise_set_prop( 5271 5517 gc, 5518 + shapes, 5272 5519 promise, 5273 5520 PROMISE_REACTION_COUNT_KEY, 5274 5521 Value::Number((count + 1) as f64), ··· 5281 5528 5282 5529 /// `__Promise_create()` — create a new pending promise object. 5283 5530 fn promise_native_create(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5284 - let promise = create_promise_object(ctx.gc); 5531 + let promise = create_promise_object(ctx.gc, ctx.shapes); 5285 5532 Ok(Value::Object(promise)) 5286 5533 } 5287 5534 ··· 5293 5540 let value = args.get(1).cloned().unwrap_or(Value::Undefined); 5294 5541 5295 5542 // If value is a thenable (promise), chain it. 5296 - if is_promise(ctx.gc, &value) { 5543 + if is_promise(ctx.gc, ctx.shapes, &value) { 5297 5544 let value_ref = value.gc_ref().unwrap(); 5298 - let state = promise_state(ctx.gc, value_ref); 5545 + let state = promise_state(ctx.gc, ctx.shapes, value_ref); 5299 5546 if state == PROMISE_FULFILLED { 5300 - let result = promise_get_prop(ctx.gc, value_ref, PROMISE_RESULT_KEY); 5301 - resolve_promise_internal(ctx.gc, promise_ref, result); 5547 + let result = promise_get_prop(ctx.gc, ctx.shapes, value_ref, PROMISE_RESULT_KEY); 5548 + resolve_promise_internal(ctx.gc, ctx.shapes, promise_ref, result); 5302 5549 } else if state == PROMISE_REJECTED { 5303 - let reason = promise_get_prop(ctx.gc, value_ref, PROMISE_RESULT_KEY); 5304 - reject_promise_internal(ctx.gc, promise_ref, reason); 5550 + let reason = promise_get_prop(ctx.gc, ctx.shapes, value_ref, PROMISE_RESULT_KEY); 5551 + reject_promise_internal(ctx.gc, ctx.shapes, promise_ref, reason); 5305 5552 } else { 5306 5553 // Pending thenable: chain so that when value_ref settles, 5307 5554 // promise_ref settles the same way. 5308 - chain_promise(ctx.gc, value_ref, promise_ref); 5555 + chain_promise(ctx.gc, ctx.shapes, value_ref, promise_ref); 5309 5556 } 5310 5557 } else { 5311 - resolve_promise_internal(ctx.gc, promise_ref, value); 5558 + resolve_promise_internal(ctx.gc, ctx.shapes, promise_ref, value); 5312 5559 } 5313 5560 5314 5561 Ok(Value::Undefined) 5315 5562 } 5316 5563 5317 5564 /// Chain a source promise to a target: when source settles, settle target the same way. 5318 - fn chain_promise(gc: &mut Gc<HeapObject>, source: GcRef, target: GcRef) { 5319 - let reactions_ref = match promise_get_prop(gc, source, PROMISE_REACTIONS_KEY) { 5565 + fn chain_promise(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, source: GcRef, target: GcRef) { 5566 + let reactions_ref = match promise_get_prop(gc, shapes, source, PROMISE_REACTIONS_KEY) { 5320 5567 Value::Object(r) => r, 5321 5568 _ => return, 5322 5569 }; 5323 - let count = match promise_get_prop(gc, source, PROMISE_REACTION_COUNT_KEY) { 5570 + let count = match promise_get_prop(gc, shapes, source, PROMISE_REACTION_COUNT_KEY) { 5324 5571 Value::Number(n) => n as usize, 5325 5572 _ => 0, 5326 5573 }; 5327 5574 5328 5575 // Store the target promise directly — the microtask drain handles identity propagation. 5329 5576 if let Some(HeapObject::Object(data)) = gc.get_mut(reactions_ref) { 5330 - data.properties.insert( 5577 + data.insert_property( 5331 5578 format!("{count}_fulfill"), 5332 5579 Property::builtin(Value::Undefined), 5580 + shapes, 5333 5581 ); 5334 - data.properties.insert( 5582 + data.insert_property( 5335 5583 format!("{count}_reject"), 5336 5584 Property::builtin(Value::Undefined), 5585 + shapes, 5337 5586 ); 5338 - data.properties.insert( 5587 + data.insert_property( 5339 5588 format!("{count}_promise"), 5340 5589 Property::builtin(Value::Object(target)), 5590 + shapes, 5341 5591 ); 5342 5592 } 5343 5593 5344 5594 promise_set_prop( 5345 5595 gc, 5596 + shapes, 5346 5597 source, 5347 5598 PROMISE_REACTION_COUNT_KEY, 5348 5599 Value::Number((count + 1) as f64), ··· 5350 5601 } 5351 5602 5352 5603 /// Check if a value is a promise (has __promise_state__ property). 5353 - fn is_promise(gc: &Gc<HeapObject>, value: &Value) -> bool { 5604 + fn is_promise(gc: &Gc<HeapObject>, shapes: &ShapeTable, value: &Value) -> bool { 5354 5605 let gc_ref = match value.gc_ref() { 5355 5606 Some(r) => r, 5356 5607 None => return false, 5357 5608 }; 5358 5609 match gc.get(gc_ref) { 5359 - Some(HeapObject::Object(data)) => data.properties.contains_key(PROMISE_STATE_KEY), 5610 + Some(HeapObject::Object(data)) => data.contains_key(PROMISE_STATE_KEY, shapes), 5360 5611 _ => false, 5361 5612 } 5362 5613 } ··· 5368 5619 .and_then(|v| v.gc_ref()) 5369 5620 .ok_or_else(|| RuntimeError::type_error("__Promise_reject: first arg must be a promise"))?; 5370 5621 let reason = args.get(1).cloned().unwrap_or(Value::Undefined); 5371 - reject_promise_internal(ctx.gc, promise_ref, reason); 5622 + reject_promise_internal(ctx.gc, ctx.shapes, promise_ref, reason); 5372 5623 Ok(Value::Undefined) 5373 5624 } 5374 5625 ··· 5383 5634 let on_fulfilled = args.first().cloned().unwrap_or(Value::Undefined); 5384 5635 let on_rejected = args.get(1).cloned().unwrap_or(Value::Undefined); 5385 5636 5386 - let state = promise_state(ctx.gc, promise_ref); 5637 + let state = promise_state(ctx.gc, ctx.shapes, promise_ref); 5387 5638 5388 5639 if state == PROMISE_PENDING { 5389 5640 // Register reaction for later. 5390 - let chained = add_reaction(ctx.gc, promise_ref, on_fulfilled, on_rejected); 5641 + let chained = add_reaction(ctx.gc, ctx.shapes, promise_ref, on_fulfilled, on_rejected); 5391 5642 return Ok(Value::Object(chained)); 5392 5643 } 5393 5644 5394 5645 // Already settled — enqueue microtask immediately. 5395 - let result = promise_get_prop(ctx.gc, promise_ref, PROMISE_RESULT_KEY); 5396 - let chained = create_promise_object(ctx.gc); 5646 + let result = promise_get_prop(ctx.gc, ctx.shapes, promise_ref, PROMISE_RESULT_KEY); 5647 + let chained = create_promise_object(ctx.gc, ctx.shapes); 5397 5648 let fulfilled = state == PROMISE_FULFILLED; 5398 5649 let handler = if fulfilled { 5399 5650 &on_fulfilled ··· 5427 5678 }; 5428 5679 5429 5680 let on_finally = args.first().cloned().unwrap_or(Value::Undefined); 5430 - let state = promise_state(ctx.gc, promise_ref); 5681 + let state = promise_state(ctx.gc, ctx.shapes, promise_ref); 5431 5682 5432 5683 if state == PROMISE_PENDING { 5433 5684 // Register reaction: finally handler doesn't receive value, just runs. 5434 - let chained = add_reaction(ctx.gc, promise_ref, on_finally.clone(), on_finally); 5685 + let chained = add_reaction( 5686 + ctx.gc, 5687 + ctx.shapes, 5688 + promise_ref, 5689 + on_finally.clone(), 5690 + on_finally, 5691 + ); 5435 5692 // Mark the chained promise reactions as "finally" so drain can handle them. 5436 - promise_set_prop(ctx.gc, chained, "__finally__", Value::Boolean(true)); 5693 + promise_set_prop( 5694 + ctx.gc, 5695 + ctx.shapes, 5696 + chained, 5697 + "__finally__", 5698 + Value::Boolean(true), 5699 + ); 5437 5700 // Store parent result for propagation. 5438 5701 promise_set_prop( 5439 5702 ctx.gc, 5703 + ctx.shapes, 5440 5704 chained, 5441 5705 "__finally_parent__", 5442 5706 Value::Object(promise_ref), ··· 5445 5709 } 5446 5710 5447 5711 // Already settled. 5448 - let _result = promise_get_prop(ctx.gc, promise_ref, PROMISE_RESULT_KEY); 5449 - let chained = create_promise_object(ctx.gc); 5712 + let _result = promise_get_prop(ctx.gc, ctx.shapes, promise_ref, PROMISE_RESULT_KEY); 5713 + let chained = create_promise_object(ctx.gc, ctx.shapes); 5450 5714 let handler_ref = match &on_finally { 5451 5715 Value::Function(r) => Some(*r), 5452 5716 _ => None, 5453 5717 }; 5454 5718 5455 5719 // For finally, we enqueue the callback but propagate the original result. 5456 - promise_set_prop(ctx.gc, chained, "__finally__", Value::Boolean(true)); 5457 5720 promise_set_prop( 5458 5721 ctx.gc, 5722 + ctx.shapes, 5723 + chained, 5724 + "__finally__", 5725 + Value::Boolean(true), 5726 + ); 5727 + promise_set_prop( 5728 + ctx.gc, 5729 + ctx.shapes, 5459 5730 chained, 5460 5731 "__finally_parent__", 5461 5732 Value::Object(promise_ref), ··· 5477 5748 let value = args.first().cloned().unwrap_or(Value::Undefined); 5478 5749 5479 5750 // If already a promise, return it. 5480 - if is_promise(ctx.gc, &value) { 5751 + if is_promise(ctx.gc, ctx.shapes, &value) { 5481 5752 return Ok(value); 5482 5753 } 5483 5754 5484 - let promise = create_promise_object(ctx.gc); 5485 - resolve_promise_internal(ctx.gc, promise, value); 5755 + let promise = create_promise_object(ctx.gc, ctx.shapes); 5756 + resolve_promise_internal(ctx.gc, ctx.shapes, promise, value); 5486 5757 Ok(Value::Object(promise)) 5487 5758 } 5488 5759 5489 5760 fn promise_static_reject(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5490 5761 let reason = args.first().cloned().unwrap_or(Value::Undefined); 5491 - let promise = create_promise_object(ctx.gc); 5492 - reject_promise_internal(ctx.gc, promise, reason); 5762 + let promise = create_promise_object(ctx.gc, ctx.shapes); 5763 + reject_promise_internal(ctx.gc, ctx.shapes, promise, reason); 5493 5764 Ok(Value::Object(promise)) 5494 5765 } 5495 5766 ··· 5498 5769 let arr_ref = match iterable.gc_ref() { 5499 5770 Some(r) => r, 5500 5771 None => { 5501 - let p = create_promise_object(ctx.gc); 5772 + let p = create_promise_object(ctx.gc, ctx.shapes); 5502 5773 reject_promise_internal( 5503 5774 ctx.gc, 5775 + ctx.shapes, 5504 5776 p, 5505 5777 Value::String("Promise.all requires an iterable".to_string()), 5506 5778 ); ··· 5508 5780 } 5509 5781 }; 5510 5782 5511 - let len = array_length(ctx.gc, arr_ref); 5512 - let result_promise = create_promise_object(ctx.gc); 5783 + let len = array_length(ctx.gc, ctx.shapes, arr_ref); 5784 + let result_promise = create_promise_object(ctx.gc, ctx.shapes); 5513 5785 5514 5786 if len == 0 { 5515 - let empty = make_value_array(ctx.gc, &[]); 5516 - resolve_promise_internal(ctx.gc, result_promise, empty); 5787 + let empty = make_value_array(ctx.gc, ctx.shapes, &[]); 5788 + resolve_promise_internal(ctx.gc, ctx.shapes, result_promise, empty); 5517 5789 return Ok(Value::Object(result_promise)); 5518 5790 } 5519 5791 5520 5792 // Create a results array and a counter object. 5521 - let results = make_value_array(ctx.gc, &vec![Value::Undefined; len]); 5793 + let results = make_value_array(ctx.gc, ctx.shapes, &vec![Value::Undefined; len]); 5522 5794 let results_ref = results.gc_ref().unwrap(); 5523 5795 5524 5796 // We track remaining count and results in hidden props on result_promise. 5525 5797 promise_set_prop( 5526 5798 ctx.gc, 5799 + ctx.shapes, 5527 5800 result_promise, 5528 5801 "__all_remaining__", 5529 5802 Value::Number(len as f64), 5530 5803 ); 5531 - promise_set_prop(ctx.gc, result_promise, "__all_results__", results.clone()); 5804 + promise_set_prop( 5805 + ctx.gc, 5806 + ctx.shapes, 5807 + result_promise, 5808 + "__all_results__", 5809 + results.clone(), 5810 + ); 5532 5811 5533 5812 for i in 0..len { 5534 - let item = array_get(ctx.gc, arr_ref, i); 5535 - if is_promise(ctx.gc, &item) { 5813 + let item = array_get(ctx.gc, ctx.shapes, arr_ref, i); 5814 + if is_promise(ctx.gc, ctx.shapes, &item) { 5536 5815 let item_ref = item.gc_ref().unwrap(); 5537 - let state = promise_state(ctx.gc, item_ref); 5816 + let state = promise_state(ctx.gc, ctx.shapes, item_ref); 5538 5817 if state == PROMISE_FULFILLED { 5539 - let val = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5540 - array_set(ctx.gc, results_ref, i, val); 5541 - promise_all_decrement(ctx.gc, result_promise); 5818 + let val = promise_get_prop(ctx.gc, ctx.shapes, item_ref, PROMISE_RESULT_KEY); 5819 + array_set(ctx.gc, ctx.shapes, results_ref, i, val); 5820 + promise_all_decrement(ctx.gc, ctx.shapes, result_promise); 5542 5821 } else if state == PROMISE_REJECTED { 5543 - let reason = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5544 - reject_promise_internal(ctx.gc, result_promise, reason); 5822 + let reason = promise_get_prop(ctx.gc, ctx.shapes, item_ref, PROMISE_RESULT_KEY); 5823 + reject_promise_internal(ctx.gc, ctx.shapes, result_promise, reason); 5545 5824 return Ok(Value::Object(result_promise)); 5546 5825 } else { 5547 5826 // Pending: we need to register a reaction. Store index info. 5548 5827 promise_set_prop( 5549 5828 ctx.gc, 5829 + ctx.shapes, 5550 5830 item_ref, 5551 5831 &format!("__all_target_{i}__"), 5552 5832 Value::Object(result_promise), 5553 5833 ); 5554 5834 // Add a reaction — the microtask drain will handle all-tracking. 5555 - let chained = add_reaction(ctx.gc, item_ref, Value::Undefined, Value::Undefined); 5556 - promise_set_prop(ctx.gc, chained, "__all_index__", Value::Number(i as f64)); 5835 + let chained = add_reaction( 5836 + ctx.gc, 5837 + ctx.shapes, 5838 + item_ref, 5839 + Value::Undefined, 5840 + Value::Undefined, 5841 + ); 5557 5842 promise_set_prop( 5558 5843 ctx.gc, 5844 + ctx.shapes, 5845 + chained, 5846 + "__all_index__", 5847 + Value::Number(i as f64), 5848 + ); 5849 + promise_set_prop( 5850 + ctx.gc, 5851 + ctx.shapes, 5559 5852 chained, 5560 5853 "__all_target__", 5561 5854 Value::Object(result_promise), ··· 5563 5856 } 5564 5857 } else { 5565 5858 // Non-promise value: treat as immediately resolved. 5566 - array_set(ctx.gc, results_ref, i, item); 5567 - promise_all_decrement(ctx.gc, result_promise); 5859 + array_set(ctx.gc, ctx.shapes, results_ref, i, item); 5860 + promise_all_decrement(ctx.gc, ctx.shapes, result_promise); 5568 5861 } 5569 5862 } 5570 5863 5571 5864 Ok(Value::Object(result_promise)) 5572 5865 } 5573 5866 5574 - fn promise_all_decrement(gc: &mut Gc<HeapObject>, result_promise: GcRef) { 5575 - let remaining = match promise_get_prop(gc, result_promise, "__all_remaining__") { 5867 + fn promise_all_decrement(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, result_promise: GcRef) { 5868 + let remaining = match promise_get_prop(gc, shapes, result_promise, "__all_remaining__") { 5576 5869 Value::Number(n) => n as usize, 5577 5870 _ => return, 5578 5871 }; 5579 5872 let new_remaining = remaining.saturating_sub(1); 5580 5873 promise_set_prop( 5581 5874 gc, 5875 + shapes, 5582 5876 result_promise, 5583 5877 "__all_remaining__", 5584 5878 Value::Number(new_remaining as f64), 5585 5879 ); 5586 5880 if new_remaining == 0 { 5587 - let results = promise_get_prop(gc, result_promise, "__all_results__"); 5588 - resolve_promise_internal(gc, result_promise, results); 5881 + let results = promise_get_prop(gc, shapes, result_promise, "__all_results__"); 5882 + resolve_promise_internal(gc, shapes, result_promise, results); 5589 5883 } 5590 5884 } 5591 5885 ··· 5594 5888 let arr_ref = match iterable.gc_ref() { 5595 5889 Some(r) => r, 5596 5890 None => { 5597 - let p = create_promise_object(ctx.gc); 5891 + let p = create_promise_object(ctx.gc, ctx.shapes); 5598 5892 reject_promise_internal( 5599 5893 ctx.gc, 5894 + ctx.shapes, 5600 5895 p, 5601 5896 Value::String("Promise.race requires an iterable".to_string()), 5602 5897 ); ··· 5604 5899 } 5605 5900 }; 5606 5901 5607 - let len = array_length(ctx.gc, arr_ref); 5608 - let result_promise = create_promise_object(ctx.gc); 5902 + let len = array_length(ctx.gc, ctx.shapes, arr_ref); 5903 + let result_promise = create_promise_object(ctx.gc, ctx.shapes); 5609 5904 5610 5905 for i in 0..len { 5611 - let item = array_get(ctx.gc, arr_ref, i); 5612 - if is_promise(ctx.gc, &item) { 5906 + let item = array_get(ctx.gc, ctx.shapes, arr_ref, i); 5907 + if is_promise(ctx.gc, ctx.shapes, &item) { 5613 5908 let item_ref = item.gc_ref().unwrap(); 5614 - let state = promise_state(ctx.gc, item_ref); 5909 + let state = promise_state(ctx.gc, ctx.shapes, item_ref); 5615 5910 if state == PROMISE_FULFILLED { 5616 - let val = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5617 - resolve_promise_internal(ctx.gc, result_promise, val); 5911 + let val = promise_get_prop(ctx.gc, ctx.shapes, item_ref, PROMISE_RESULT_KEY); 5912 + resolve_promise_internal(ctx.gc, ctx.shapes, result_promise, val); 5618 5913 return Ok(Value::Object(result_promise)); 5619 5914 } else if state == PROMISE_REJECTED { 5620 - let reason = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5621 - reject_promise_internal(ctx.gc, result_promise, reason); 5915 + let reason = promise_get_prop(ctx.gc, ctx.shapes, item_ref, PROMISE_RESULT_KEY); 5916 + reject_promise_internal(ctx.gc, ctx.shapes, result_promise, reason); 5622 5917 return Ok(Value::Object(result_promise)); 5623 5918 } else { 5624 - chain_promise(ctx.gc, item_ref, result_promise); 5919 + chain_promise(ctx.gc, ctx.shapes, item_ref, result_promise); 5625 5920 } 5626 5921 } else { 5627 - resolve_promise_internal(ctx.gc, result_promise, item); 5922 + resolve_promise_internal(ctx.gc, ctx.shapes, result_promise, item); 5628 5923 return Ok(Value::Object(result_promise)); 5629 5924 } 5630 5925 } ··· 5640 5935 let arr_ref = match iterable.gc_ref() { 5641 5936 Some(r) => r, 5642 5937 None => { 5643 - let p = create_promise_object(ctx.gc); 5938 + let p = create_promise_object(ctx.gc, ctx.shapes); 5644 5939 reject_promise_internal( 5645 5940 ctx.gc, 5941 + ctx.shapes, 5646 5942 p, 5647 5943 Value::String("Promise.allSettled requires an iterable".to_string()), 5648 5944 ); ··· 5650 5946 } 5651 5947 }; 5652 5948 5653 - let len = array_length(ctx.gc, arr_ref); 5654 - let result_promise = create_promise_object(ctx.gc); 5949 + let len = array_length(ctx.gc, ctx.shapes, arr_ref); 5950 + let result_promise = create_promise_object(ctx.gc, ctx.shapes); 5655 5951 5656 5952 if len == 0 { 5657 - let empty = make_value_array(ctx.gc, &[]); 5658 - resolve_promise_internal(ctx.gc, result_promise, empty); 5953 + let empty = make_value_array(ctx.gc, ctx.shapes, &[]); 5954 + resolve_promise_internal(ctx.gc, ctx.shapes, result_promise, empty); 5659 5955 return Ok(Value::Object(result_promise)); 5660 5956 } 5661 5957 5662 - let results = make_value_array(ctx.gc, &vec![Value::Undefined; len]); 5958 + let results = make_value_array(ctx.gc, ctx.shapes, &vec![Value::Undefined; len]); 5663 5959 let results_ref = results.gc_ref().unwrap(); 5664 5960 5665 5961 promise_set_prop( 5666 5962 ctx.gc, 5963 + ctx.shapes, 5667 5964 result_promise, 5668 5965 "__all_remaining__", 5669 5966 Value::Number(len as f64), 5670 5967 ); 5671 - promise_set_prop(ctx.gc, result_promise, "__all_results__", results); 5968 + promise_set_prop( 5969 + ctx.gc, 5970 + ctx.shapes, 5971 + result_promise, 5972 + "__all_results__", 5973 + results, 5974 + ); 5672 5975 5673 5976 for i in 0..len { 5674 - let item = array_get(ctx.gc, arr_ref, i); 5675 - if is_promise(ctx.gc, &item) { 5977 + let item = array_get(ctx.gc, ctx.shapes, arr_ref, i); 5978 + if is_promise(ctx.gc, ctx.shapes, &item) { 5676 5979 let item_ref = item.gc_ref().unwrap(); 5677 - let state = promise_state(ctx.gc, item_ref); 5980 + let state = promise_state(ctx.gc, ctx.shapes, item_ref); 5678 5981 if state != PROMISE_PENDING { 5679 - let val = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5982 + let val = promise_get_prop(ctx.gc, ctx.shapes, item_ref, PROMISE_RESULT_KEY); 5680 5983 let status_str = if state == PROMISE_FULFILLED { 5681 5984 "fulfilled" 5682 5985 } else { 5683 5986 "rejected" 5684 5987 }; 5685 - let entry = 5686 - make_settled_entry(ctx.gc, status_str, &val, state == PROMISE_FULFILLED); 5687 - array_set(ctx.gc, results_ref, i, entry); 5688 - promise_all_decrement(ctx.gc, result_promise); 5988 + let entry = make_settled_entry( 5989 + ctx.gc, 5990 + ctx.shapes, 5991 + status_str, 5992 + &val, 5993 + state == PROMISE_FULFILLED, 5994 + ); 5995 + array_set(ctx.gc, ctx.shapes, results_ref, i, entry); 5996 + promise_all_decrement(ctx.gc, ctx.shapes, result_promise); 5689 5997 } else { 5690 - chain_promise(ctx.gc, item_ref, result_promise); 5998 + chain_promise(ctx.gc, ctx.shapes, item_ref, result_promise); 5691 5999 } 5692 6000 } else { 5693 - let entry = make_settled_entry(ctx.gc, "fulfilled", &item, true); 5694 - array_set(ctx.gc, results_ref, i, entry); 5695 - promise_all_decrement(ctx.gc, result_promise); 6001 + let entry = make_settled_entry(ctx.gc, ctx.shapes, "fulfilled", &item, true); 6002 + array_set(ctx.gc, ctx.shapes, results_ref, i, entry); 6003 + promise_all_decrement(ctx.gc, ctx.shapes, result_promise); 5696 6004 } 5697 6005 } 5698 6006 ··· 5701 6009 5702 6010 fn make_settled_entry( 5703 6011 gc: &mut Gc<HeapObject>, 6012 + shapes: &mut ShapeTable, 5704 6013 status: &str, 5705 6014 value: &Value, 5706 6015 is_fulfilled: bool, 5707 6016 ) -> Value { 5708 6017 let mut data = ObjectData::new(); 5709 - data.properties.insert( 6018 + data.insert_property( 5710 6019 "status".to_string(), 5711 6020 Property::data(Value::String(status.to_string())), 6021 + shapes, 5712 6022 ); 5713 6023 if is_fulfilled { 5714 - data.properties 5715 - .insert("value".to_string(), Property::data(value.clone())); 6024 + data.insert_property("value".to_string(), Property::data(value.clone()), shapes); 5716 6025 } else { 5717 - data.properties 5718 - .insert("reason".to_string(), Property::data(value.clone())); 6026 + data.insert_property("reason".to_string(), Property::data(value.clone()), shapes); 5719 6027 } 5720 6028 Value::Object(gc.alloc(HeapObject::Object(data))) 5721 6029 } ··· 5725 6033 let arr_ref = match iterable.gc_ref() { 5726 6034 Some(r) => r, 5727 6035 None => { 5728 - let p = create_promise_object(ctx.gc); 6036 + let p = create_promise_object(ctx.gc, ctx.shapes); 5729 6037 reject_promise_internal( 5730 6038 ctx.gc, 6039 + ctx.shapes, 5731 6040 p, 5732 6041 Value::String("Promise.any requires an iterable".to_string()), 5733 6042 ); ··· 5735 6044 } 5736 6045 }; 5737 6046 5738 - let len = array_length(ctx.gc, arr_ref); 5739 - let result_promise = create_promise_object(ctx.gc); 6047 + let len = array_length(ctx.gc, ctx.shapes, arr_ref); 6048 + let result_promise = create_promise_object(ctx.gc, ctx.shapes); 5740 6049 5741 6050 if len == 0 { 5742 6051 // Reject with AggregateError. 5743 6052 let err = Value::String("All promises were rejected".to_string()); 5744 - reject_promise_internal(ctx.gc, result_promise, err); 6053 + reject_promise_internal(ctx.gc, ctx.shapes, result_promise, err); 5745 6054 return Ok(Value::Object(result_promise)); 5746 6055 } 5747 6056 5748 - let errors = make_value_array(ctx.gc, &vec![Value::Undefined; len]); 6057 + let errors = make_value_array(ctx.gc, ctx.shapes, &vec![Value::Undefined; len]); 5749 6058 let errors_ref = errors.gc_ref().unwrap(); 5750 6059 5751 6060 promise_set_prop( 5752 6061 ctx.gc, 6062 + ctx.shapes, 5753 6063 result_promise, 5754 6064 "__any_remaining__", 5755 6065 Value::Number(len as f64), 5756 6066 ); 5757 - promise_set_prop(ctx.gc, result_promise, "__any_errors__", errors); 6067 + promise_set_prop(ctx.gc, ctx.shapes, result_promise, "__any_errors__", errors); 5758 6068 5759 6069 for i in 0..len { 5760 - let item = array_get(ctx.gc, arr_ref, i); 5761 - if is_promise(ctx.gc, &item) { 6070 + let item = array_get(ctx.gc, ctx.shapes, arr_ref, i); 6071 + if is_promise(ctx.gc, ctx.shapes, &item) { 5762 6072 let item_ref = item.gc_ref().unwrap(); 5763 - let state = promise_state(ctx.gc, item_ref); 6073 + let state = promise_state(ctx.gc, ctx.shapes, item_ref); 5764 6074 if state == PROMISE_FULFILLED { 5765 - let val = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5766 - resolve_promise_internal(ctx.gc, result_promise, val); 6075 + let val = promise_get_prop(ctx.gc, ctx.shapes, item_ref, PROMISE_RESULT_KEY); 6076 + resolve_promise_internal(ctx.gc, ctx.shapes, result_promise, val); 5767 6077 return Ok(Value::Object(result_promise)); 5768 6078 } else if state == PROMISE_REJECTED { 5769 - let reason = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5770 - array_set(ctx.gc, errors_ref, i, reason); 5771 - promise_any_decrement(ctx.gc, result_promise); 6079 + let reason = promise_get_prop(ctx.gc, ctx.shapes, item_ref, PROMISE_RESULT_KEY); 6080 + array_set(ctx.gc, ctx.shapes, errors_ref, i, reason); 6081 + promise_any_decrement(ctx.gc, ctx.shapes, result_promise); 5772 6082 } else { 5773 - chain_promise(ctx.gc, item_ref, result_promise); 6083 + chain_promise(ctx.gc, ctx.shapes, item_ref, result_promise); 5774 6084 } 5775 6085 } else { 5776 - resolve_promise_internal(ctx.gc, result_promise, item); 6086 + resolve_promise_internal(ctx.gc, ctx.shapes, result_promise, item); 5777 6087 return Ok(Value::Object(result_promise)); 5778 6088 } 5779 6089 } ··· 5781 6091 Ok(Value::Object(result_promise)) 5782 6092 } 5783 6093 5784 - fn promise_any_decrement(gc: &mut Gc<HeapObject>, result_promise: GcRef) { 5785 - let remaining = match promise_get_prop(gc, result_promise, "__any_remaining__") { 6094 + fn promise_any_decrement(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, result_promise: GcRef) { 6095 + let remaining = match promise_get_prop(gc, shapes, result_promise, "__any_remaining__") { 5786 6096 Value::Number(n) => n as usize, 5787 6097 _ => return, 5788 6098 }; 5789 6099 let new_remaining = remaining.saturating_sub(1); 5790 6100 promise_set_prop( 5791 6101 gc, 6102 + shapes, 5792 6103 result_promise, 5793 6104 "__any_remaining__", 5794 6105 Value::Number(new_remaining as f64), 5795 6106 ); 5796 6107 if new_remaining == 0 { 5797 6108 let err = Value::String("All promises were rejected".to_string()); 5798 - reject_promise_internal(gc, result_promise, err); 6109 + reject_promise_internal(gc, shapes, result_promise, err); 5799 6110 } 5800 6111 } 5801 6112 ··· 5809 6120 let json_ref = vm.gc.alloc(HeapObject::Object(data)); 5810 6121 5811 6122 let parse_fn = make_native(&mut vm.gc, "parse", json_parse); 5812 - set_builtin_prop(&mut vm.gc, json_ref, "parse", Value::Function(parse_fn)); 6123 + set_builtin_prop( 6124 + &mut vm.gc, 6125 + &mut vm.shapes, 6126 + json_ref, 6127 + "parse", 6128 + Value::Function(parse_fn), 6129 + ); 5813 6130 5814 6131 let stringify_fn = make_native(&mut vm.gc, "stringify", json_stringify); 5815 6132 set_builtin_prop( 5816 6133 &mut vm.gc, 6134 + &mut vm.shapes, 5817 6135 json_ref, 5818 6136 "stringify", 5819 6137 Value::Function(stringify_fn), ··· 6028 6346 Ok(()) 6029 6347 } 6030 6348 6031 - fn parse_value(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { 6349 + fn parse_value( 6350 + &mut self, 6351 + gc: &mut Gc<HeapObject>, 6352 + shapes: &mut ShapeTable, 6353 + ) -> Result<Value, RuntimeError> { 6032 6354 match self.current.take() { 6033 6355 Some(JsonToken::Null) => { 6034 6356 self.advance()?; ··· 6050 6372 self.advance()?; 6051 6373 Ok(Value::String(s)) 6052 6374 } 6053 - Some(JsonToken::LBracket) => self.parse_array(gc), 6054 - Some(JsonToken::LBrace) => self.parse_object(gc), 6375 + Some(JsonToken::LBracket) => self.parse_array(gc, shapes), 6376 + Some(JsonToken::LBrace) => self.parse_object(gc, shapes), 6055 6377 Some(other) => { 6056 6378 // Put it back for error context. 6057 6379 self.current = Some(other); ··· 6061 6383 } 6062 6384 } 6063 6385 6064 - fn parse_array(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { 6386 + fn parse_array( 6387 + &mut self, 6388 + gc: &mut Gc<HeapObject>, 6389 + shapes: &mut ShapeTable, 6390 + ) -> Result<Value, RuntimeError> { 6065 6391 // Current token was LBracket, already consumed via take(). 6066 6392 self.advance()?; // move past '[' 6067 6393 let mut items: Vec<Value> = Vec::new(); 6068 6394 if self.current == Some(JsonToken::RBracket) { 6069 6395 self.advance()?; 6070 6396 let mut obj = ObjectData::new(); 6071 - obj.properties.insert( 6397 + obj.insert_property( 6072 6398 "length".to_string(), 6073 6399 Property { 6074 6400 value: Value::Number(0.0), ··· 6076 6402 enumerable: false, 6077 6403 configurable: false, 6078 6404 }, 6405 + shapes, 6079 6406 ); 6080 6407 return Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))); 6081 6408 } 6082 6409 loop { 6083 - let val = self.parse_value(gc)?; 6410 + let val = self.parse_value(gc, shapes)?; 6084 6411 items.push(val); 6085 6412 match &self.current { 6086 6413 Some(JsonToken::Comma) => { ··· 6099 6426 } 6100 6427 let mut obj = ObjectData::new(); 6101 6428 for (i, v) in items.iter().enumerate() { 6102 - obj.properties 6103 - .insert(i.to_string(), Property::data(v.clone())); 6429 + obj.insert_property(i.to_string(), Property::data(v.clone()), shapes); 6104 6430 } 6105 - obj.properties.insert( 6431 + obj.insert_property( 6106 6432 "length".to_string(), 6107 6433 Property { 6108 6434 value: Value::Number(items.len() as f64), ··· 6110 6436 enumerable: false, 6111 6437 configurable: false, 6112 6438 }, 6439 + shapes, 6113 6440 ); 6114 6441 Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))) 6115 6442 } 6116 6443 6117 - fn parse_object(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { 6444 + fn parse_object( 6445 + &mut self, 6446 + gc: &mut Gc<HeapObject>, 6447 + shapes: &mut ShapeTable, 6448 + ) -> Result<Value, RuntimeError> { 6118 6449 self.advance()?; // move past '{' 6119 6450 let mut obj = ObjectData::new(); 6120 6451 if self.current == Some(JsonToken::RBrace) { ··· 6137 6468 )); 6138 6469 } 6139 6470 self.advance()?; 6140 - let val = self.parse_value(gc)?; 6141 - obj.properties.insert(key, Property::data(val)); 6471 + let val = self.parse_value(gc, shapes)?; 6472 + obj.insert_property(key, Property::data(val), shapes); 6142 6473 match &self.current { 6143 6474 Some(JsonToken::Comma) => { 6144 6475 self.advance()?; ··· 6169 6500 .map(|v| v.to_js_string(ctx.gc)) 6170 6501 .unwrap_or_default(); 6171 6502 let mut parser = JsonParser::new(&text)?; 6172 - let value = parser.parse_value(ctx.gc)?; 6503 + let value = parser.parse_value(ctx.gc, ctx.shapes)?; 6173 6504 // Ensure no trailing content. 6174 6505 if parser.current.is_some() { 6175 6506 return Err(RuntimeError::syntax_error( ··· 6198 6529 }; 6199 6530 // Track visited objects for circular reference detection. 6200 6531 let mut visited: Vec<GcRef> = Vec::new(); 6201 - let result = json_stringify_value(&value, ctx.gc, &indent, "", &mut visited)?; 6532 + let result = json_stringify_value(&value, ctx.gc, ctx.shapes, &indent, "", &mut visited)?; 6202 6533 match result { 6203 6534 Some(s) => Ok(Value::String(s)), 6204 6535 None => Ok(Value::Undefined), ··· 6208 6539 fn json_stringify_value( 6209 6540 value: &Value, 6210 6541 gc: &Gc<HeapObject>, 6542 + shapes: &ShapeTable, 6211 6543 indent: &str, 6212 6544 current_indent: &str, 6213 6545 visited: &mut Vec<GcRef>, ··· 6237 6569 let result = match gc.get(*gc_ref) { 6238 6570 Some(HeapObject::Object(data)) => { 6239 6571 // Check for toJSON method. 6240 - if let Some(prop) = data.properties.get("toJSON") { 6572 + if let Some(prop) = data.get_property("toJSON", shapes) { 6241 6573 if matches!(prop.value, Value::Function(_)) { 6242 6574 // We can't call JS functions from here easily, 6243 6575 // but for Date objects we recognize the __date_ms__ pattern. 6244 - if let Some(date_prop) = data.properties.get("__date_ms__") { 6576 + if let Some(date_prop) = data.get_property("__date_ms__", shapes) { 6245 6577 let ms = date_prop.value.to_number(); 6246 6578 if ms.is_nan() { 6247 6579 visited.pop(); ··· 6262 6594 } 6263 6595 } 6264 6596 } 6265 - if array_length_exists(gc, *gc_ref) { 6266 - json_stringify_array(gc, *gc_ref, indent, current_indent, visited) 6597 + if array_length_exists(gc, shapes, *gc_ref) { 6598 + json_stringify_array(gc, shapes, *gc_ref, indent, current_indent, visited) 6267 6599 } else { 6268 - json_stringify_object(gc, data, indent, current_indent, visited) 6600 + json_stringify_object(gc, shapes, data, indent, current_indent, visited) 6269 6601 } 6270 6602 } 6271 6603 _ => Ok(Some("{}".to_string())), ··· 6279 6611 6280 6612 fn json_stringify_array( 6281 6613 gc: &Gc<HeapObject>, 6614 + shapes: &ShapeTable, 6282 6615 arr: GcRef, 6283 6616 indent: &str, 6284 6617 current_indent: &str, 6285 6618 visited: &mut Vec<GcRef>, 6286 6619 ) -> Result<Option<String>, RuntimeError> { 6287 - let len = array_length(gc, arr); 6620 + let len = array_length(gc, shapes, arr); 6288 6621 if len == 0 { 6289 6622 return Ok(Some("[]".to_string())); 6290 6623 } ··· 6296 6629 }; 6297 6630 let mut parts: Vec<String> = Vec::new(); 6298 6631 for i in 0..len { 6299 - let val = array_get(gc, arr, i); 6300 - match json_stringify_value(&val, gc, indent, &next_indent, visited)? { 6632 + let val = array_get(gc, shapes, arr, i); 6633 + match json_stringify_value(&val, gc, shapes, indent, &next_indent, visited)? { 6301 6634 Some(s) => parts.push(s), 6302 6635 None => parts.push("null".to_string()), 6303 6636 } ··· 6316 6649 6317 6650 fn json_stringify_object( 6318 6651 gc: &Gc<HeapObject>, 6652 + shapes: &ShapeTable, 6319 6653 data: &ObjectData, 6320 6654 indent: &str, 6321 6655 current_indent: &str, ··· 6329 6663 }; 6330 6664 let mut parts: Vec<String> = Vec::new(); 6331 6665 // Collect and sort keys for deterministic output. 6332 - let mut keys: Vec<&String> = data.properties.keys().collect(); 6333 - keys.sort(); 6334 - for key in keys { 6335 - let prop = &data.properties[key]; 6666 + let entries = data.property_entries(shapes); 6667 + let mut sorted_entries: Vec<(String, Property)> = entries; 6668 + sorted_entries.sort_by(|(a, _), (b, _)| a.cmp(b)); 6669 + for (key, prop) in &sorted_entries { 6336 6670 if !prop.enumerable { 6337 6671 continue; 6338 6672 } 6339 - if let Some(val_str) = json_stringify_value(&prop.value, gc, indent, &next_indent, visited)? 6673 + if let Some(val_str) = 6674 + json_stringify_value(&prop.value, gc, shapes, indent, &next_indent, visited)? 6340 6675 { 6341 6676 if has_indent { 6342 6677 parts.push(format!("{}: {}", json_quote_string(key), val_str)); ··· 6400 6735 ]; 6401 6736 for &(name, callback) in methods { 6402 6737 let func = make_native(&mut vm.gc, name, callback); 6403 - set_builtin_prop(&mut vm.gc, console_ref, name, Value::Function(func)); 6738 + set_builtin_prop( 6739 + &mut vm.gc, 6740 + &mut vm.shapes, 6741 + console_ref, 6742 + name, 6743 + Value::Function(func), 6744 + ); 6404 6745 } 6405 6746 6406 6747 vm.set_global("console", Value::Object(console_ref)); ··· 6411 6752 fn console_format_value( 6412 6753 value: &Value, 6413 6754 gc: &Gc<HeapObject>, 6755 + shapes: &ShapeTable, 6414 6756 depth: usize, 6415 6757 seen: &mut HashSet<GcRef>, 6416 6758 ) -> String { ··· 6436 6778 let result = match gc.get(*gc_ref) { 6437 6779 Some(HeapObject::Object(obj_data)) => { 6438 6780 // Check if it's an array (has a "length" property). 6439 - if obj_data.properties.contains_key("length") { 6440 - format_array(obj_data, gc, depth, seen) 6781 + if obj_data.contains_key("length", shapes) { 6782 + format_array(obj_data, gc, shapes, depth, seen) 6441 6783 } else { 6442 - format_object(obj_data, gc, depth, seen) 6784 + format_object(obj_data, gc, shapes, depth, seen) 6443 6785 } 6444 6786 } 6445 6787 _ => "[Object]".to_string(), ··· 6453 6795 fn format_array( 6454 6796 data: &ObjectData, 6455 6797 gc: &Gc<HeapObject>, 6798 + shapes: &ShapeTable, 6456 6799 depth: usize, 6457 6800 seen: &mut HashSet<GcRef>, 6458 6801 ) -> String { 6459 6802 let len = data 6460 - .properties 6461 - .get("length") 6803 + .get_property("length", shapes) 6462 6804 .map(|p| p.value.to_number() as usize) 6463 6805 .unwrap_or(0); 6464 6806 let mut parts = Vec::with_capacity(len); 6465 6807 for i in 0..len { 6466 6808 let val = data 6467 - .properties 6468 - .get(&i.to_string()) 6469 - .map(|p| &p.value) 6470 - .unwrap_or(&Value::Undefined); 6471 - parts.push(console_format_value(val, gc, depth + 1, seen)); 6809 + .get_property(&i.to_string(), shapes) 6810 + .map(|p| p.value) 6811 + .unwrap_or(Value::Undefined); 6812 + parts.push(console_format_value(&val, gc, shapes, depth + 1, seen)); 6472 6813 } 6473 6814 format!("[ {} ]", parts.join(", ")) 6474 6815 } ··· 6476 6817 fn format_object( 6477 6818 data: &ObjectData, 6478 6819 gc: &Gc<HeapObject>, 6820 + shapes: &ShapeTable, 6479 6821 depth: usize, 6480 6822 seen: &mut HashSet<GcRef>, 6481 6823 ) -> String { 6482 - if data.properties.is_empty() { 6824 + if data.is_empty() { 6483 6825 return "{}".to_string(); 6484 6826 } 6485 6827 let mut parts = Vec::new(); 6486 - for (key, prop) in &data.properties { 6487 - let val_str = console_format_value(&prop.value, gc, depth + 1, seen); 6828 + for (key, prop) in data.property_entries(shapes) { 6829 + let val_str = console_format_value(&prop.value, gc, shapes, depth + 1, seen); 6488 6830 parts.push(format!("{}: {}", key, val_str)); 6489 6831 } 6490 6832 parts.sort(); ··· 6492 6834 } 6493 6835 6494 6836 /// Format all arguments for a console method, separated by spaces. 6495 - fn console_format_args(args: &[Value], gc: &Gc<HeapObject>) -> String { 6837 + fn console_format_args(args: &[Value], gc: &Gc<HeapObject>, shapes: &ShapeTable) -> String { 6496 6838 let mut seen = HashSet::new(); 6497 6839 args.iter() 6498 - .map(|v| console_format_value(v, gc, 0, &mut seen)) 6840 + .map(|v| console_format_value(v, gc, shapes, 0, &mut seen)) 6499 6841 .collect::<Vec<_>>() 6500 6842 .join(" ") 6501 6843 } 6502 6844 6503 6845 fn console_log(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6504 - let msg = console_format_args(args, ctx.gc); 6846 + let msg = console_format_args(args, ctx.gc, ctx.shapes); 6505 6847 ctx.console_output.log(&msg); 6506 6848 Ok(Value::Undefined) 6507 6849 } 6508 6850 6509 6851 fn console_error(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6510 - let msg = console_format_args(args, ctx.gc); 6852 + let msg = console_format_args(args, ctx.gc, ctx.shapes); 6511 6853 ctx.console_output.error(&msg); 6512 6854 Ok(Value::Undefined) 6513 6855 } 6514 6856 6515 6857 fn console_warn(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6516 - let msg = console_format_args(args, ctx.gc); 6858 + let msg = console_format_args(args, ctx.gc, ctx.shapes); 6517 6859 ctx.console_output.warn(&msg); 6518 6860 Ok(Value::Undefined) 6519 6861 }
+488 -207
crates/js/src/dom_bridge.rs
··· 6 6 7 7 use crate::builtins::{make_native, set_builtin_prop}; 8 8 use crate::gc::{Gc, GcRef}; 9 + use crate::shape::ShapeTable; 9 10 use crate::vm::*; 10 11 use std::rc::Rc; 11 12 use we_css::parser::Parser as CssParser; ··· 30 31 fn get_or_create_wrapper( 31 32 node_id: NodeId, 32 33 gc: &mut Gc<HeapObject>, 34 + shapes: &mut ShapeTable, 33 35 bridge: &DomBridge, 34 36 object_proto: Option<GcRef>, 35 37 ) -> GcRef { ··· 48 50 } 49 51 50 52 // Store the NodeId index as an internal property. 51 - data.properties.insert( 53 + data.insert_property( 52 54 NODE_ID_KEY.to_string(), 53 55 Property::builtin(Value::Number(idx as f64)), 56 + shapes, 54 57 ); 55 58 56 59 // Populate properties based on node type. ··· 61 64 .. 62 65 } => { 63 66 let upper_tag = tag_name.to_ascii_uppercase(); 64 - data.properties.insert( 67 + data.insert_property( 65 68 "tagName".to_string(), 66 69 Property::builtin(Value::String(upper_tag.clone())), 70 + shapes, 67 71 ); 68 - data.properties.insert( 72 + data.insert_property( 69 73 "nodeName".to_string(), 70 74 Property::builtin(Value::String(upper_tag)), 75 + shapes, 71 76 ); 72 - data.properties.insert( 77 + data.insert_property( 73 78 "nodeType".to_string(), 74 79 Property::builtin(Value::Number(1.0)), 80 + shapes, 75 81 ); 76 82 77 83 // id attribute ··· 80 86 .find(|a| a.name == "id") 81 87 .map(|a| Value::String(a.value.clone())) 82 88 .unwrap_or(Value::String(String::new())); 83 - data.properties 84 - .insert("id".to_string(), Property::builtin(id_val)); 89 + data.insert_property("id".to_string(), Property::builtin(id_val), shapes); 85 90 86 91 // className attribute 87 92 let class_val = attributes ··· 89 94 .find(|a| a.name == "class") 90 95 .map(|a| Value::String(a.value.clone())) 91 96 .unwrap_or(Value::String(String::new())); 92 - data.properties 93 - .insert("className".to_string(), Property::builtin(class_val)); 97 + data.insert_property( 98 + "className".to_string(), 99 + Property::builtin(class_val), 100 + shapes, 101 + ); 94 102 } 95 103 NodeData::Text { .. } => { 96 - data.properties.insert( 104 + data.insert_property( 97 105 "nodeName".to_string(), 98 106 Property::builtin(Value::String("#text".to_string())), 107 + shapes, 99 108 ); 100 - data.properties.insert( 109 + data.insert_property( 101 110 "nodeType".to_string(), 102 111 Property::builtin(Value::Number(3.0)), 112 + shapes, 103 113 ); 104 114 } 105 115 NodeData::Comment { .. } => { 106 - data.properties.insert( 116 + data.insert_property( 107 117 "nodeName".to_string(), 108 118 Property::builtin(Value::String("#comment".to_string())), 119 + shapes, 109 120 ); 110 - data.properties.insert( 121 + data.insert_property( 111 122 "nodeType".to_string(), 112 123 Property::builtin(Value::Number(8.0)), 124 + shapes, 113 125 ); 114 126 } 115 127 NodeData::Document => { 116 - data.properties.insert( 128 + data.insert_property( 117 129 "nodeName".to_string(), 118 130 Property::builtin(Value::String("#document".to_string())), 131 + shapes, 119 132 ); 120 - data.properties.insert( 133 + data.insert_property( 121 134 "nodeType".to_string(), 122 135 Property::builtin(Value::Number(9.0)), 136 + shapes, 123 137 ); 124 138 } 125 139 } ··· 127 141 let gc_ref = gc.alloc(HeapObject::Object(data)); 128 142 129 143 // Register DOM methods on the wrapper based on node type. 130 - register_node_methods(gc, gc_ref, &doc, node_id); 144 + register_node_methods(gc, shapes, gc_ref, &doc, node_id); 131 145 132 146 bridge.node_wrappers.borrow_mut().insert(idx, gc_ref); 133 147 gc_ref ··· 153 167 fn make_wrapper_array( 154 168 nodes: &[NodeId], 155 169 gc: &mut Gc<HeapObject>, 170 + shapes: &mut ShapeTable, 156 171 bridge: &DomBridge, 157 172 object_proto: Option<GcRef>, 158 173 ) -> Value { 159 174 let mut obj = ObjectData::new(); 160 175 for (i, &nid) in nodes.iter().enumerate() { 161 - let wrapper = get_or_create_wrapper(nid, gc, bridge, object_proto); 162 - obj.properties 163 - .insert(i.to_string(), Property::data(Value::Object(wrapper))); 176 + let wrapper = get_or_create_wrapper(nid, gc, shapes, bridge, object_proto); 177 + obj.insert_property( 178 + i.to_string(), 179 + Property::data(Value::Object(wrapper)), 180 + shapes, 181 + ); 164 182 } 165 - obj.properties.insert( 183 + obj.insert_property( 166 184 "length".to_string(), 167 185 Property { 168 186 value: Value::Number(nodes.len() as f64), ··· 170 188 enumerable: false, 171 189 configurable: false, 172 190 }, 191 + shapes, 173 192 ); 174 193 Value::Object(gc.alloc(HeapObject::Object(obj))) 175 194 } ··· 185 204 } 186 205 187 206 // nodeType 9 = Document 188 - data.properties.insert( 207 + data.insert_property( 189 208 "nodeType".to_string(), 190 209 Property::builtin(Value::Number(9.0)), 210 + &mut vm.shapes, 191 211 ); 192 - data.properties.insert( 212 + data.insert_property( 193 213 "nodeName".to_string(), 194 214 Property::builtin(Value::String("#document".to_string())), 215 + &mut vm.shapes, 195 216 ); 196 217 197 218 // Set document.title from the DOM. 198 219 if let Some(bridge) = &vm.dom_bridge { 199 220 let doc = bridge.document.borrow(); 200 221 let title = find_title_text(&doc); 201 - data.properties 202 - .insert("title".to_string(), Property::builtin(Value::String(title))); 222 + data.insert_property( 223 + "title".to_string(), 224 + Property::builtin(Value::String(title)), 225 + &mut vm.shapes, 226 + ); 203 227 } 204 228 205 229 let doc_ref = vm.gc.alloc(HeapObject::Object(data)); ··· 211 235 find_structural_elements(&doc) 212 236 }; 213 237 if let Some(html) = html_id { 214 - let wrapper = get_or_create_wrapper(html, &mut vm.gc, bridge, vm.object_prototype); 238 + let wrapper = get_or_create_wrapper( 239 + html, 240 + &mut vm.gc, 241 + &mut vm.shapes, 242 + bridge, 243 + vm.object_prototype, 244 + ); 215 245 set_builtin_prop( 216 246 &mut vm.gc, 247 + &mut vm.shapes, 217 248 doc_ref, 218 249 "documentElement", 219 250 Value::Object(wrapper), 220 251 ); 221 252 } 222 253 if let Some(head) = head_id { 223 - let wrapper = get_or_create_wrapper(head, &mut vm.gc, bridge, vm.object_prototype); 224 - set_builtin_prop(&mut vm.gc, doc_ref, "head", Value::Object(wrapper)); 254 + let wrapper = get_or_create_wrapper( 255 + head, 256 + &mut vm.gc, 257 + &mut vm.shapes, 258 + bridge, 259 + vm.object_prototype, 260 + ); 261 + set_builtin_prop( 262 + &mut vm.gc, 263 + &mut vm.shapes, 264 + doc_ref, 265 + "head", 266 + Value::Object(wrapper), 267 + ); 225 268 } 226 269 if let Some(body) = body_id { 227 - let wrapper = get_or_create_wrapper(body, &mut vm.gc, bridge, vm.object_prototype); 228 - set_builtin_prop(&mut vm.gc, doc_ref, "body", Value::Object(wrapper)); 270 + let wrapper = get_or_create_wrapper( 271 + body, 272 + &mut vm.gc, 273 + &mut vm.shapes, 274 + bridge, 275 + vm.object_prototype, 276 + ); 277 + set_builtin_prop( 278 + &mut vm.gc, 279 + &mut vm.shapes, 280 + doc_ref, 281 + "body", 282 + Value::Object(wrapper), 283 + ); 229 284 } 230 285 } 231 286 ··· 234 289 if let Some(bridge) = &vm.dom_bridge { 235 290 let origin_str = bridge.origin.borrow().clone(); 236 291 let domain = extract_domain_from_origin(&origin_str); 237 - set_builtin_prop(&mut vm.gc, doc_ref, "domain", Value::String(domain)); 292 + set_builtin_prop( 293 + &mut vm.gc, 294 + &mut vm.shapes, 295 + doc_ref, 296 + "domain", 297 + Value::String(domain), 298 + ); 238 299 } 239 300 240 301 // Register methods on the document object. ··· 249 310 ]; 250 311 for &(name, callback) in methods { 251 312 let func = make_native(&mut vm.gc, name, callback); 252 - set_builtin_prop(&mut vm.gc, doc_ref, name, Value::Function(func)); 313 + set_builtin_prop( 314 + &mut vm.gc, 315 + &mut vm.shapes, 316 + doc_ref, 317 + name, 318 + Value::Function(func), 319 + ); 253 320 } 254 321 255 322 vm.set_global("document", Value::Object(doc_ref)); ··· 332 399 333 400 match found { 334 401 Some(node_id) => { 335 - let wrapper = get_or_create_wrapper(node_id, ctx.gc, bridge, None); 402 + let wrapper = get_or_create_wrapper(node_id, ctx.gc, ctx.shapes, bridge, None); 336 403 Ok(Value::Object(wrapper)) 337 404 } 338 405 None => Ok(Value::Null), ··· 350 417 351 418 let bridge = match ctx.dom_bridge { 352 419 Some(b) => b, 353 - None => return Ok(make_empty_array(ctx.gc)), 420 + None => return Ok(make_empty_array(ctx.gc, ctx.shapes)), 354 421 }; 355 422 356 423 let matches = { ··· 367 434 matches 368 435 }; 369 436 370 - Ok(make_wrapper_array(&matches, ctx.gc, bridge, None)) 437 + Ok(make_wrapper_array( 438 + &matches, ctx.gc, ctx.shapes, bridge, None, 439 + )) 371 440 } 372 441 373 442 fn doc_get_elements_by_class_name( ··· 381 450 382 451 let bridge = match ctx.dom_bridge { 383 452 Some(b) => b, 384 - None => return Ok(make_empty_array(ctx.gc)), 453 + None => return Ok(make_empty_array(ctx.gc, ctx.shapes)), 385 454 }; 386 455 387 456 let matches = { ··· 398 467 matches 399 468 }; 400 469 401 - Ok(make_wrapper_array(&matches, ctx.gc, bridge, None)) 470 + Ok(make_wrapper_array( 471 + &matches, ctx.gc, ctx.shapes, bridge, None, 472 + )) 402 473 } 403 474 404 475 fn doc_query_selector(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 434 505 435 506 match found { 436 507 Some(node_id) => { 437 - let wrapper = get_or_create_wrapper(node_id, ctx.gc, bridge, None); 508 + let wrapper = get_or_create_wrapper(node_id, ctx.gc, ctx.shapes, bridge, None); 438 509 Ok(Value::Object(wrapper)) 439 510 } 440 511 None => Ok(Value::Null), ··· 449 520 450 521 let bridge = match ctx.dom_bridge { 451 522 Some(b) => b, 452 - None => return Ok(make_empty_array(ctx.gc)), 523 + None => return Ok(make_empty_array(ctx.gc, ctx.shapes)), 453 524 }; 454 525 455 526 let selector_list = CssParser::parse_selectors(&selector_str); ··· 468 539 matches 469 540 }; 470 541 471 - Ok(make_wrapper_array(&matches, ctx.gc, bridge, None)) 542 + Ok(make_wrapper_array( 543 + &matches, ctx.gc, ctx.shapes, bridge, None, 544 + )) 472 545 } 473 546 474 547 fn doc_create_element(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 483 556 }; 484 557 485 558 let node_id = bridge.document.borrow_mut().create_element(&tag); 486 - let wrapper = get_or_create_wrapper(node_id, ctx.gc, bridge, None); 559 + let wrapper = get_or_create_wrapper(node_id, ctx.gc, ctx.shapes, bridge, None); 487 560 Ok(Value::Object(wrapper)) 488 561 } 489 562 ··· 499 572 }; 500 573 501 574 let node_id = bridge.document.borrow_mut().create_text(&text); 502 - let wrapper = get_or_create_wrapper(node_id, ctx.gc, bridge, None); 575 + let wrapper = get_or_create_wrapper(node_id, ctx.gc, ctx.shapes, bridge, None); 503 576 Ok(Value::Object(wrapper)) 504 577 } 505 578 506 579 /// Create an empty JS array. 507 - fn make_empty_array(gc: &mut Gc<HeapObject>) -> Value { 580 + fn make_empty_array(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable) -> Value { 508 581 let mut obj = ObjectData::new(); 509 - obj.properties.insert( 582 + obj.insert_property( 510 583 "length".to_string(), 511 584 Property { 512 585 value: Value::Number(0.0), ··· 514 587 enumerable: false, 515 588 configurable: false, 516 589 }, 590 + shapes, 517 591 ); 518 592 Value::Object(gc.alloc(HeapObject::Object(obj))) 519 593 } ··· 521 595 // ── Method registration on wrappers ────────────────────────────────── 522 596 523 597 /// Register DOM methods on a wrapper object based on node type. 524 - fn register_node_methods(gc: &mut Gc<HeapObject>, wrapper: GcRef, doc: &Document, node_id: NodeId) { 598 + fn register_node_methods( 599 + gc: &mut Gc<HeapObject>, 600 + shapes: &mut ShapeTable, 601 + wrapper: GcRef, 602 + doc: &Document, 603 + node_id: NodeId, 604 + ) { 525 605 // Methods available on all node types. 526 606 let node_methods: &[NativeMethod] = &[ 527 607 ("appendChild", node_append_child), ··· 536 616 ]; 537 617 for &(name, callback) in node_methods { 538 618 let func = make_native(gc, name, callback); 539 - set_builtin_prop(gc, wrapper, name, Value::Function(func)); 619 + set_builtin_prop(gc, shapes, wrapper, name, Value::Function(func)); 540 620 } 541 621 542 622 // Methods available only on Element nodes. ··· 549 629 ]; 550 630 for &(name, callback) in element_methods { 551 631 let func = make_native(gc, name, callback); 552 - set_builtin_prop(gc, wrapper, name, Value::Function(func)); 632 + set_builtin_prop(gc, shapes, wrapper, name, Value::Function(func)); 553 633 } 554 634 } 555 635 } 556 636 557 637 // ── Helper: extract NodeId from a wrapper ─────────────────────────── 558 638 559 - fn get_node_id(gc: &Gc<HeapObject>, wrapper: GcRef) -> Option<NodeId> { 639 + fn get_node_id(gc: &Gc<HeapObject>, shapes: &ShapeTable, wrapper: GcRef) -> Option<NodeId> { 560 640 match gc.get(wrapper) { 561 - Some(HeapObject::Object(data)) => match data.properties.get(NODE_ID_KEY) { 641 + Some(HeapObject::Object(data)) => match data.get_property(NODE_ID_KEY, shapes) { 562 642 Some(Property { 563 643 value: Value::Number(n), 564 644 .. 565 - }) => Some(NodeId::from_index(*n as usize)), 645 + }) => Some(NodeId::from_index(n as usize)), 566 646 _ => None, 567 647 }, 568 648 _ => None, ··· 570 650 } 571 651 572 652 /// Extract a `NodeId` from a JS Value that should be a DOM wrapper object. 573 - fn value_to_node_id(gc: &Gc<HeapObject>, val: &Value) -> Option<NodeId> { 653 + fn value_to_node_id(gc: &Gc<HeapObject>, shapes: &ShapeTable, val: &Value) -> Option<NodeId> { 574 654 match val { 575 - Value::Object(r) => get_node_id(gc, *r), 655 + Value::Object(r) => get_node_id(gc, shapes, *r), 576 656 _ => None, 577 657 } 578 658 } ··· 584 664 .dom_bridge 585 665 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 586 666 let parent_id = match &ctx.this { 587 - Value::Object(r) => get_node_id(ctx.gc, *r), 667 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 588 668 _ => None, 589 669 } 590 670 .ok_or_else(|| RuntimeError::type_error("appendChild called on non-node"))?; 591 671 let child_val = args 592 672 .first() 593 673 .ok_or_else(|| RuntimeError::type_error("appendChild requires an argument"))?; 594 - let child_id = value_to_node_id(ctx.gc, child_val) 674 + let child_id = value_to_node_id(ctx.gc, ctx.shapes, child_val) 595 675 .ok_or_else(|| RuntimeError::type_error("appendChild argument is not a node"))?; 596 676 597 677 bridge ··· 606 686 .dom_bridge 607 687 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 608 688 let parent_id = match &ctx.this { 609 - Value::Object(r) => get_node_id(ctx.gc, *r), 689 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 610 690 _ => None, 611 691 } 612 692 .ok_or_else(|| RuntimeError::type_error("removeChild called on non-node"))?; 613 693 let child_val = args 614 694 .first() 615 695 .ok_or_else(|| RuntimeError::type_error("removeChild requires an argument"))?; 616 - let child_id = value_to_node_id(ctx.gc, child_val) 696 + let child_id = value_to_node_id(ctx.gc, ctx.shapes, child_val) 617 697 .ok_or_else(|| RuntimeError::type_error("removeChild argument is not a node"))?; 618 698 619 699 bridge ··· 628 708 .dom_bridge 629 709 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 630 710 let parent_id = match &ctx.this { 631 - Value::Object(r) => get_node_id(ctx.gc, *r), 711 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 632 712 _ => None, 633 713 } 634 714 .ok_or_else(|| RuntimeError::type_error("insertBefore called on non-node"))?; 635 715 let new_node_val = args 636 716 .first() 637 717 .ok_or_else(|| RuntimeError::type_error("insertBefore requires two arguments"))?; 638 - let new_node_id = value_to_node_id(ctx.gc, new_node_val) 718 + let new_node_id = value_to_node_id(ctx.gc, ctx.shapes, new_node_val) 639 719 .ok_or_else(|| RuntimeError::type_error("insertBefore: first argument is not a node"))?; 640 720 641 721 let ref_val = args.get(1).cloned().unwrap_or(Value::Null); ··· 646 726 .borrow_mut() 647 727 .append_child(parent_id, new_node_id); 648 728 } else { 649 - let ref_id = value_to_node_id(ctx.gc, &ref_val).ok_or_else(|| { 729 + let ref_id = value_to_node_id(ctx.gc, ctx.shapes, &ref_val).ok_or_else(|| { 650 730 RuntimeError::type_error("insertBefore: second argument is not a node") 651 731 })?; 652 732 bridge ··· 662 742 .dom_bridge 663 743 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 664 744 let parent_id = match &ctx.this { 665 - Value::Object(r) => get_node_id(ctx.gc, *r), 745 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 666 746 _ => None, 667 747 } 668 748 .ok_or_else(|| RuntimeError::type_error("replaceChild called on non-node"))?; 669 749 let new_child_val = args 670 750 .first() 671 751 .ok_or_else(|| RuntimeError::type_error("replaceChild requires two arguments"))?; 672 - let new_child_id = value_to_node_id(ctx.gc, new_child_val) 752 + let new_child_id = value_to_node_id(ctx.gc, ctx.shapes, new_child_val) 673 753 .ok_or_else(|| RuntimeError::type_error("replaceChild: first argument is not a node"))?; 674 754 let old_child_val = args 675 755 .get(1) 676 756 .ok_or_else(|| RuntimeError::type_error("replaceChild requires two arguments"))?; 677 - let old_child_id = value_to_node_id(ctx.gc, old_child_val) 757 + let old_child_id = value_to_node_id(ctx.gc, ctx.shapes, old_child_val) 678 758 .ok_or_else(|| RuntimeError::type_error("replaceChild: second argument is not a node"))?; 679 759 680 760 bridge ··· 689 769 .dom_bridge 690 770 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 691 771 let node_id = match &ctx.this { 692 - Value::Object(r) => get_node_id(ctx.gc, *r), 772 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 693 773 _ => None, 694 774 } 695 775 .ok_or_else(|| RuntimeError::type_error("cloneNode called on non-node"))?; ··· 697 777 let deep = args.first().map(|v| v.to_boolean()).unwrap_or(false); 698 778 699 779 let cloned_id = bridge.document.borrow_mut().clone_node(node_id, deep); 700 - let wrapper = get_or_create_wrapper(cloned_id, ctx.gc, bridge, None); 780 + let wrapper = get_or_create_wrapper(cloned_id, ctx.gc, ctx.shapes, bridge, None); 701 781 Ok(Value::Object(wrapper)) 702 782 } 703 783 ··· 707 787 .dom_bridge 708 788 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 709 789 let node_id = match &ctx.this { 710 - Value::Object(r) => get_node_id(ctx.gc, *r), 790 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 711 791 _ => None, 712 792 } 713 793 .ok_or_else(|| RuntimeError::type_error("hasChildNodes called on non-node"))?; ··· 723 803 .dom_bridge 724 804 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 725 805 let node_id = match &ctx.this { 726 - Value::Object(r) => get_node_id(ctx.gc, *r), 806 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 727 807 _ => None, 728 808 } 729 809 .ok_or_else(|| RuntimeError::type_error("getAttribute called on non-element"))?; ··· 744 824 .dom_bridge 745 825 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 746 826 let node_id = match &ctx.this { 747 - Value::Object(r) => get_node_id(ctx.gc, *r), 827 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 748 828 _ => None, 749 829 } 750 830 .ok_or_else(|| RuntimeError::type_error("setAttribute called on non-element"))?; ··· 765 845 // Sync special attributes to wrapper properties. 766 846 if let Value::Object(wrapper) = &ctx.this { 767 847 if name == "id" { 768 - set_builtin_prop(ctx.gc, *wrapper, "id", Value::String(value.clone())); 848 + set_builtin_prop( 849 + ctx.gc, 850 + ctx.shapes, 851 + *wrapper, 852 + "id", 853 + Value::String(value.clone()), 854 + ); 769 855 } else if name == "class" { 770 - set_builtin_prop(ctx.gc, *wrapper, "className", Value::String(value.clone())); 856 + set_builtin_prop( 857 + ctx.gc, 858 + ctx.shapes, 859 + *wrapper, 860 + "className", 861 + Value::String(value.clone()), 862 + ); 771 863 } 772 864 } 773 865 ··· 782 874 .dom_bridge 783 875 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 784 876 let node_id = match &ctx.this { 785 - Value::Object(r) => get_node_id(ctx.gc, *r), 877 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 786 878 _ => None, 787 879 } 788 880 .ok_or_else(|| RuntimeError::type_error("removeAttribute called on non-element"))?; ··· 799 891 // Sync to wrapper. 800 892 if let Value::Object(wrapper) = &ctx.this { 801 893 if name == "id" { 802 - set_builtin_prop(ctx.gc, *wrapper, "id", Value::String(String::new())); 894 + set_builtin_prop( 895 + ctx.gc, 896 + ctx.shapes, 897 + *wrapper, 898 + "id", 899 + Value::String(String::new()), 900 + ); 803 901 } else if name == "class" { 804 - set_builtin_prop(ctx.gc, *wrapper, "className", Value::String(String::new())); 902 + set_builtin_prop( 903 + ctx.gc, 904 + ctx.shapes, 905 + *wrapper, 906 + "className", 907 + Value::String(String::new()), 908 + ); 805 909 } 806 910 } 807 911 ··· 813 917 .dom_bridge 814 918 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 815 919 let node_id = match &ctx.this { 816 - Value::Object(r) => get_node_id(ctx.gc, *r), 920 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 817 921 _ => None, 818 922 } 819 923 .ok_or_else(|| RuntimeError::type_error("hasAttribute called on non-element"))?; ··· 937 1041 /// Returns `Some(value)` if the key is a recognized DOM property, `None` otherwise. 938 1042 pub fn resolve_dom_get( 939 1043 gc: &mut Gc<HeapObject>, 1044 + shapes: &mut ShapeTable, 940 1045 bridge: &Rc<DomBridge>, 941 1046 gc_ref: GcRef, 942 1047 key: &str, 943 1048 ) -> Option<Value> { 944 - let node_id = get_node_id(gc, gc_ref)?; 1049 + let node_id = get_node_id(gc, shapes, gc_ref)?; 945 1050 let doc = bridge.document.borrow(); 946 1051 947 1052 match key { ··· 950 1055 let parent = doc.parent(node_id); 951 1056 drop(doc); 952 1057 Some(match parent { 953 - Some(p) => Value::Object(get_or_create_wrapper(p, gc, bridge, None)), 1058 + Some(p) => Value::Object(get_or_create_wrapper(p, gc, shapes, bridge, None)), 954 1059 None => Value::Null, 955 1060 }) 956 1061 } ··· 961 1066 .unwrap_or(false); 962 1067 drop(doc); 963 1068 Some(if is_element { 964 - Value::Object(get_or_create_wrapper(parent.unwrap(), gc, bridge, None)) 1069 + Value::Object(get_or_create_wrapper( 1070 + parent.unwrap(), 1071 + gc, 1072 + shapes, 1073 + bridge, 1074 + None, 1075 + )) 965 1076 } else { 966 1077 Value::Null 967 1078 }) ··· 969 1080 "childNodes" => { 970 1081 let children: Vec<NodeId> = doc.children(node_id).collect(); 971 1082 drop(doc); 972 - Some(make_wrapper_array(&children, gc, bridge, None)) 1083 + Some(make_wrapper_array(&children, gc, shapes, bridge, None)) 973 1084 } 974 1085 "children" => { 975 1086 let children: Vec<NodeId> = doc ··· 977 1088 .filter(|&c| matches!(doc.node_data(c), NodeData::Element { .. })) 978 1089 .collect(); 979 1090 drop(doc); 980 - Some(make_wrapper_array(&children, gc, bridge, None)) 1091 + Some(make_wrapper_array(&children, gc, shapes, bridge, None)) 981 1092 } 982 1093 "firstChild" => { 983 1094 let fc = doc.first_child(node_id); 984 1095 drop(doc); 985 1096 Some(match fc { 986 - Some(c) => Value::Object(get_or_create_wrapper(c, gc, bridge, None)), 1097 + Some(c) => Value::Object(get_or_create_wrapper(c, gc, shapes, bridge, None)), 987 1098 None => Value::Null, 988 1099 }) 989 1100 } ··· 991 1102 let lc = doc.last_child(node_id); 992 1103 drop(doc); 993 1104 Some(match lc { 994 - Some(c) => Value::Object(get_or_create_wrapper(c, gc, bridge, None)), 1105 + Some(c) => Value::Object(get_or_create_wrapper(c, gc, shapes, bridge, None)), 995 1106 None => Value::Null, 996 1107 }) 997 1108 } ··· 1001 1112 .find(|&c| matches!(doc.node_data(c), NodeData::Element { .. })); 1002 1113 drop(doc); 1003 1114 Some(match fc { 1004 - Some(c) => Value::Object(get_or_create_wrapper(c, gc, bridge, None)), 1115 + Some(c) => Value::Object(get_or_create_wrapper(c, gc, shapes, bridge, None)), 1005 1116 None => Value::Null, 1006 1117 }) 1007 1118 } ··· 1013 1124 let last = children.last().copied(); 1014 1125 drop(doc); 1015 1126 Some(match last { 1016 - Some(c) => Value::Object(get_or_create_wrapper(c, gc, bridge, None)), 1127 + Some(c) => Value::Object(get_or_create_wrapper(c, gc, shapes, bridge, None)), 1017 1128 None => Value::Null, 1018 1129 }) 1019 1130 } ··· 1021 1132 let ns = doc.next_sibling(node_id); 1022 1133 drop(doc); 1023 1134 Some(match ns { 1024 - Some(s) => Value::Object(get_or_create_wrapper(s, gc, bridge, None)), 1135 + Some(s) => Value::Object(get_or_create_wrapper(s, gc, shapes, bridge, None)), 1025 1136 None => Value::Null, 1026 1137 }) 1027 1138 } ··· 1029 1140 let ps = doc.prev_sibling(node_id); 1030 1141 drop(doc); 1031 1142 Some(match ps { 1032 - Some(s) => Value::Object(get_or_create_wrapper(s, gc, bridge, None)), 1143 + Some(s) => Value::Object(get_or_create_wrapper(s, gc, shapes, bridge, None)), 1033 1144 None => Value::Null, 1034 1145 }) 1035 1146 } ··· 1038 1149 while let Some(s) = current { 1039 1150 if matches!(doc.node_data(s), NodeData::Element { .. }) { 1040 1151 drop(doc); 1041 - return Some(Value::Object(get_or_create_wrapper(s, gc, bridge, None))); 1152 + return Some(Value::Object(get_or_create_wrapper( 1153 + s, gc, shapes, bridge, None, 1154 + ))); 1042 1155 } 1043 1156 current = doc.next_sibling(s); 1044 1157 } ··· 1049 1162 while let Some(s) = current { 1050 1163 if matches!(doc.node_data(s), NodeData::Element { .. }) { 1051 1164 drop(doc); 1052 - return Some(Value::Object(get_or_create_wrapper(s, gc, bridge, None))); 1165 + return Some(Value::Object(get_or_create_wrapper( 1166 + s, gc, shapes, bridge, None, 1167 + ))); 1053 1168 } 1054 1169 current = doc.prev_sibling(s); 1055 1170 } ··· 1082 1197 let mut obj = ObjectData::new(); 1083 1198 for (i, attr) in attrs.iter().enumerate() { 1084 1199 let mut attr_obj = ObjectData::new(); 1085 - attr_obj.properties.insert( 1200 + attr_obj.insert_property( 1086 1201 "name".to_string(), 1087 1202 Property::data(Value::String(attr.name.clone())), 1203 + shapes, 1088 1204 ); 1089 - attr_obj.properties.insert( 1205 + attr_obj.insert_property( 1090 1206 "value".to_string(), 1091 1207 Property::data(Value::String(attr.value.clone())), 1208 + shapes, 1092 1209 ); 1093 1210 let attr_ref = gc.alloc(HeapObject::Object(attr_obj)); 1094 - obj.properties 1095 - .insert(i.to_string(), Property::data(Value::Object(attr_ref))); 1211 + obj.insert_property( 1212 + i.to_string(), 1213 + Property::data(Value::Object(attr_ref)), 1214 + shapes, 1215 + ); 1096 1216 } 1097 - obj.properties.insert( 1217 + obj.insert_property( 1098 1218 "length".to_string(), 1099 1219 Property { 1100 1220 value: Value::Number(doc.attributes(node_id).map_or(0, |a| a.len()) as f64), ··· 1102 1222 enumerable: false, 1103 1223 configurable: false, 1104 1224 }, 1225 + shapes, 1105 1226 ); 1106 1227 drop(doc); 1107 1228 Some(Value::Object(gc.alloc(HeapObject::Object(obj)))) ··· 1116 1237 return None; 1117 1238 } 1118 1239 drop(doc); 1119 - Some(create_class_list(gc, bridge, node_id)) 1240 + Some(create_class_list(gc, shapes, bridge, node_id)) 1120 1241 } 1121 1242 1122 1243 // ── style ──────────────────────────────── ··· 1129 1250 .unwrap_or("") 1130 1251 .to_string(); 1131 1252 drop(doc); 1132 - Some(create_style_object(gc, bridge, node_id, &style_str)) 1253 + Some(create_style_object(gc, shapes, bridge, node_id, &style_str)) 1133 1254 } 1134 1255 1135 1256 _ => None, ··· 1140 1261 1141 1262 /// Check whether `gc_ref` is the document object (has nodeType === 9 and 1142 1263 /// nodeName === "#document"). 1143 - fn is_document_object(gc: &Gc<HeapObject>, gc_ref: GcRef) -> bool { 1264 + fn is_document_object(gc: &Gc<HeapObject>, shapes: &ShapeTable, gc_ref: GcRef) -> bool { 1144 1265 if let Some(HeapObject::Object(data)) = gc.get(gc_ref) { 1145 - if let Some(prop) = data.properties.get("nodeType") { 1146 - if let Value::Number(n) = &prop.value { 1147 - if *n == 9.0 { 1266 + if let Some(prop) = data.get_property("nodeType", shapes) { 1267 + if let Value::Number(n) = prop.value { 1268 + if n == 9.0 { 1148 1269 return true; 1149 1270 } 1150 1271 } ··· 1158 1279 /// Currently handles `document.cookie`. 1159 1280 pub fn resolve_document_get( 1160 1281 gc: &Gc<HeapObject>, 1282 + shapes: &ShapeTable, 1161 1283 bridge: &Rc<DomBridge>, 1162 1284 gc_ref: GcRef, 1163 1285 key: &str, ··· 1165 1287 if key != "cookie" { 1166 1288 return None; 1167 1289 } 1168 - if !is_document_object(gc, gc_ref) { 1290 + if !is_document_object(gc, shapes, gc_ref) { 1169 1291 return None; 1170 1292 } 1171 1293 ··· 1186 1308 key: &str, 1187 1309 val: &Value, 1188 1310 gc: &Gc<HeapObject>, 1311 + shapes: &ShapeTable, 1189 1312 ) -> bool { 1190 1313 if key != "cookie" { 1191 1314 return false; 1192 1315 } 1193 - if !is_document_object(gc, gc_ref) { 1316 + if !is_document_object(gc, shapes, gc_ref) { 1194 1317 return false; 1195 1318 } 1196 1319 ··· 1213 1336 /// Returns `true` if the key was handled (caller should skip normal property set). 1214 1337 pub fn handle_dom_set( 1215 1338 gc: &mut Gc<HeapObject>, 1339 + shapes: &mut ShapeTable, 1216 1340 bridge: &Rc<DomBridge>, 1217 1341 gc_ref: GcRef, 1218 1342 key: &str, 1219 1343 val: &Value, 1220 1344 ) -> bool { 1221 - let node_id = match get_node_id(gc, gc_ref) { 1345 + let node_id = match get_node_id(gc, shapes, gc_ref) { 1222 1346 Some(id) => id, 1223 1347 None => return false, 1224 1348 }; ··· 1268 1392 .borrow_mut() 1269 1393 .set_attribute(node_id, "id", &id_val); 1270 1394 // Also update the wrapper's static `id` property. 1271 - set_builtin_prop(gc, gc_ref, "id", Value::String(id_val)); 1395 + set_builtin_prop(gc, shapes, gc_ref, "id", Value::String(id_val)); 1272 1396 true 1273 1397 } 1274 1398 "className" => { ··· 1277 1401 .document 1278 1402 .borrow_mut() 1279 1403 .set_attribute(node_id, "class", &class_val); 1280 - set_builtin_prop(gc, gc_ref, "className", Value::String(class_val)); 1404 + set_builtin_prop(gc, shapes, gc_ref, "className", Value::String(class_val)); 1281 1405 true 1282 1406 } 1283 1407 _ => false, ··· 1320 1444 /// Returns `true` if handled. 1321 1445 pub fn handle_style_set( 1322 1446 gc: &mut Gc<HeapObject>, 1447 + shapes: &mut ShapeTable, 1323 1448 bridge: &Rc<DomBridge>, 1324 1449 gc_ref: GcRef, 1325 1450 key: &str, ··· 1327 1452 ) -> bool { 1328 1453 // Check for __style_node_id__ marker. 1329 1454 let node_id = match gc.get(gc_ref) { 1330 - Some(HeapObject::Object(data)) => match data.properties.get("__style_node_id__") { 1455 + Some(HeapObject::Object(data)) => match data.get_property("__style_node_id__", shapes) { 1331 1456 Some(Property { 1332 1457 value: Value::Number(n), 1333 1458 .. 1334 - }) => NodeId::from_index(*n as usize), 1459 + }) => NodeId::from_index(n as usize), 1335 1460 _ => return false, 1336 1461 }, 1337 1462 _ => return false, ··· 1339 1464 1340 1465 // Set the property on the style object normally. 1341 1466 if let Some(HeapObject::Object(data)) = gc.get_mut(gc_ref) { 1342 - data.properties 1343 - .insert(key.to_string(), Property::data(val.clone())); 1467 + data.insert_property(key.to_string(), Property::data(val.clone()), shapes); 1344 1468 } 1345 1469 1346 1470 // Serialize all style properties back to the element's style attribute. 1347 - let style_str = serialize_style_object(gc, gc_ref); 1471 + let style_str = serialize_style_object(gc, shapes, gc_ref); 1348 1472 bridge 1349 1473 .document 1350 1474 .borrow_mut() ··· 1355 1479 1356 1480 // ── classList helpers ─────────────────────────────────────────────── 1357 1481 1358 - fn create_class_list(gc: &mut Gc<HeapObject>, _bridge: &DomBridge, node_id: NodeId) -> Value { 1482 + fn create_class_list( 1483 + gc: &mut Gc<HeapObject>, 1484 + shapes: &mut ShapeTable, 1485 + _bridge: &DomBridge, 1486 + node_id: NodeId, 1487 + ) -> Value { 1359 1488 let mut data = ObjectData::new(); 1360 1489 // Store the node ID for method callbacks. 1361 - data.properties.insert( 1490 + data.insert_property( 1362 1491 NODE_ID_KEY.to_string(), 1363 1492 Property::builtin(Value::Number(node_id.index() as f64)), 1493 + shapes, 1364 1494 ); 1365 1495 1366 1496 let gc_ref = gc.alloc(HeapObject::Object(data)); ··· 1373 1503 ]; 1374 1504 for &(name, callback) in methods { 1375 1505 let func = make_native(gc, name, callback); 1376 - set_builtin_prop(gc, gc_ref, name, Value::Function(func)); 1506 + set_builtin_prop(gc, shapes, gc_ref, name, Value::Function(func)); 1377 1507 } 1378 1508 1379 1509 Value::Object(gc_ref) 1380 1510 } 1381 1511 1382 - fn get_class_list_node_id(gc: &Gc<HeapObject>, this: &Value) -> Option<NodeId> { 1512 + fn get_class_list_node_id( 1513 + gc: &Gc<HeapObject>, 1514 + shapes: &ShapeTable, 1515 + this: &Value, 1516 + ) -> Option<NodeId> { 1383 1517 match this { 1384 - Value::Object(r) => get_node_id(gc, *r), 1518 + Value::Object(r) => get_node_id(gc, shapes, *r), 1385 1519 _ => None, 1386 1520 } 1387 1521 } ··· 1390 1524 let bridge = ctx 1391 1525 .dom_bridge 1392 1526 .ok_or_else(|| RuntimeError::type_error("no document"))?; 1393 - let node_id = get_class_list_node_id(ctx.gc, &ctx.this) 1527 + let node_id = get_class_list_node_id(ctx.gc, ctx.shapes, &ctx.this) 1394 1528 .ok_or_else(|| RuntimeError::type_error("invalid classList"))?; 1395 1529 1396 1530 for arg in args { ··· 1420 1554 let bridge = ctx 1421 1555 .dom_bridge 1422 1556 .ok_or_else(|| RuntimeError::type_error("no document"))?; 1423 - let node_id = get_class_list_node_id(ctx.gc, &ctx.this) 1557 + let node_id = get_class_list_node_id(ctx.gc, ctx.shapes, &ctx.this) 1424 1558 .ok_or_else(|| RuntimeError::type_error("invalid classList"))?; 1425 1559 1426 1560 for arg in args { ··· 1447 1581 let bridge = ctx 1448 1582 .dom_bridge 1449 1583 .ok_or_else(|| RuntimeError::type_error("no document"))?; 1450 - let node_id = get_class_list_node_id(ctx.gc, &ctx.this) 1584 + let node_id = get_class_list_node_id(ctx.gc, ctx.shapes, &ctx.this) 1451 1585 .ok_or_else(|| RuntimeError::type_error("invalid classList"))?; 1452 1586 let class_name = args 1453 1587 .first() ··· 1490 1624 let bridge = ctx 1491 1625 .dom_bridge 1492 1626 .ok_or_else(|| RuntimeError::type_error("no document"))?; 1493 - let node_id = get_class_list_node_id(ctx.gc, &ctx.this) 1627 + let node_id = get_class_list_node_id(ctx.gc, ctx.shapes, &ctx.this) 1494 1628 .ok_or_else(|| RuntimeError::type_error("invalid classList"))?; 1495 1629 let class_name = args 1496 1630 .first() ··· 1507 1641 1508 1642 fn create_style_object( 1509 1643 gc: &mut Gc<HeapObject>, 1644 + shapes: &mut ShapeTable, 1510 1645 _bridge: &DomBridge, 1511 1646 node_id: NodeId, 1512 1647 style_str: &str, 1513 1648 ) -> Value { 1514 1649 let mut data = ObjectData::new(); 1515 1650 // Marker for style proxy interception. 1516 - data.properties.insert( 1651 + data.insert_property( 1517 1652 "__style_node_id__".to_string(), 1518 1653 Property::builtin(Value::Number(node_id.index() as f64)), 1654 + shapes, 1519 1655 ); 1520 1656 1521 1657 // Parse existing inline style into camelCase properties. ··· 1526 1662 } 1527 1663 if let Some((prop, val)) = decl.split_once(':') { 1528 1664 let camel = kebab_to_camel(prop.trim()); 1529 - data.properties 1530 - .insert(camel, Property::data(Value::String(val.trim().to_string()))); 1665 + data.insert_property( 1666 + camel, 1667 + Property::data(Value::String(val.trim().to_string())), 1668 + shapes, 1669 + ); 1531 1670 } 1532 1671 } 1533 1672 1534 1673 Value::Object(gc.alloc(HeapObject::Object(data))) 1535 1674 } 1536 1675 1537 - fn serialize_style_object(gc: &Gc<HeapObject>, style_ref: GcRef) -> String { 1676 + fn serialize_style_object(gc: &Gc<HeapObject>, shapes: &ShapeTable, style_ref: GcRef) -> String { 1538 1677 let mut parts = Vec::new(); 1539 1678 if let Some(HeapObject::Object(data)) = gc.get(style_ref) { 1540 - for (key, prop) in &data.properties { 1679 + for (key, prop) in data.property_entries(shapes) { 1541 1680 if key.starts_with("__") { 1542 1681 continue; 1543 1682 } 1544 - let kebab = camel_to_kebab(key); 1683 + let kebab = camel_to_kebab(&key); 1545 1684 let val = match &prop.value { 1546 1685 Value::String(s) => s.clone(), 1547 1686 Value::Number(n) => format!("{n}"), ··· 1596 1735 /// Create an Event JS object with the given type string and options. 1597 1736 fn create_event_object( 1598 1737 gc: &mut Gc<HeapObject>, 1738 + shapes: &mut ShapeTable, 1599 1739 event_type: &str, 1600 1740 bubbles: bool, 1601 1741 cancelable: bool, ··· 1603 1743 let mut data = ObjectData::new(); 1604 1744 1605 1745 // Public properties. 1606 - data.properties.insert( 1746 + data.insert_property( 1607 1747 "type".to_string(), 1608 1748 Property::data(Value::String(event_type.to_string())), 1749 + shapes, 1609 1750 ); 1610 - data.properties.insert( 1751 + data.insert_property( 1611 1752 "bubbles".to_string(), 1612 1753 Property::data(Value::Boolean(bubbles)), 1754 + shapes, 1613 1755 ); 1614 - data.properties.insert( 1756 + data.insert_property( 1615 1757 "cancelable".to_string(), 1616 1758 Property::data(Value::Boolean(cancelable)), 1759 + shapes, 1617 1760 ); 1618 - data.properties.insert( 1761 + data.insert_property( 1619 1762 "defaultPrevented".to_string(), 1620 1763 Property::data(Value::Boolean(false)), 1764 + shapes, 1621 1765 ); 1622 - data.properties 1623 - .insert("eventPhase".to_string(), Property::data(Value::Number(0.0))); 1624 - data.properties 1625 - .insert("target".to_string(), Property::data(Value::Null)); 1626 - data.properties 1627 - .insert("currentTarget".to_string(), Property::data(Value::Null)); 1628 - data.properties 1629 - .insert("timeStamp".to_string(), Property::data(Value::Number(0.0))); 1766 + data.insert_property( 1767 + "eventPhase".to_string(), 1768 + Property::data(Value::Number(0.0)), 1769 + shapes, 1770 + ); 1771 + data.insert_property("target".to_string(), Property::data(Value::Null), shapes); 1772 + data.insert_property( 1773 + "currentTarget".to_string(), 1774 + Property::data(Value::Null), 1775 + shapes, 1776 + ); 1777 + data.insert_property( 1778 + "timeStamp".to_string(), 1779 + Property::data(Value::Number(0.0)), 1780 + shapes, 1781 + ); 1630 1782 1631 1783 // Internal state. 1632 - data.properties.insert( 1784 + data.insert_property( 1633 1785 EVENT_TYPE_KEY.to_string(), 1634 1786 Property::builtin(Value::String(event_type.to_string())), 1787 + shapes, 1635 1788 ); 1636 - data.properties.insert( 1789 + data.insert_property( 1637 1790 EVENT_BUBBLES_KEY.to_string(), 1638 1791 Property::builtin(Value::Boolean(bubbles)), 1792 + shapes, 1639 1793 ); 1640 - data.properties.insert( 1794 + data.insert_property( 1641 1795 EVENT_CANCELABLE_KEY.to_string(), 1642 1796 Property::builtin(Value::Boolean(cancelable)), 1797 + shapes, 1643 1798 ); 1644 - data.properties.insert( 1799 + data.insert_property( 1645 1800 EVENT_STOP_PROP_KEY.to_string(), 1646 1801 Property::builtin(Value::Boolean(false)), 1802 + shapes, 1647 1803 ); 1648 - data.properties.insert( 1804 + data.insert_property( 1649 1805 EVENT_STOP_IMMEDIATE_KEY.to_string(), 1650 1806 Property::builtin(Value::Boolean(false)), 1807 + shapes, 1651 1808 ); 1652 - data.properties.insert( 1809 + data.insert_property( 1653 1810 EVENT_DEFAULT_PREVENTED_KEY.to_string(), 1654 1811 Property::builtin(Value::Boolean(false)), 1812 + shapes, 1655 1813 ); 1656 - data.properties.insert( 1814 + data.insert_property( 1657 1815 EVENT_PHASE_KEY.to_string(), 1658 1816 Property::builtin(Value::Number(0.0)), 1817 + shapes, 1659 1818 ); 1660 1819 1661 1820 // Event phase constants. 1662 - data.properties 1663 - .insert("NONE".to_string(), Property::builtin(Value::Number(0.0))); 1664 - data.properties.insert( 1821 + data.insert_property( 1822 + "NONE".to_string(), 1823 + Property::builtin(Value::Number(0.0)), 1824 + shapes, 1825 + ); 1826 + data.insert_property( 1665 1827 "CAPTURING_PHASE".to_string(), 1666 1828 Property::builtin(Value::Number(1.0)), 1829 + shapes, 1667 1830 ); 1668 - data.properties.insert( 1831 + data.insert_property( 1669 1832 "AT_TARGET".to_string(), 1670 1833 Property::builtin(Value::Number(2.0)), 1834 + shapes, 1671 1835 ); 1672 - data.properties.insert( 1836 + data.insert_property( 1673 1837 "BUBBLING_PHASE".to_string(), 1674 1838 Property::builtin(Value::Number(3.0)), 1839 + shapes, 1675 1840 ); 1676 1841 1677 1842 let event_ref = gc.alloc(HeapObject::Object(data)); ··· 1684 1849 ]; 1685 1850 for &(name, callback) in methods { 1686 1851 let func = make_native(gc, name, callback); 1687 - set_builtin_prop(gc, event_ref, name, Value::Function(func)); 1852 + set_builtin_prop(gc, shapes, event_ref, name, Value::Function(func)); 1688 1853 } 1689 1854 1690 1855 event_ref ··· 1703 1868 // Parse options object if provided. 1704 1869 if let Some(Value::Object(opts_ref)) = args.get(1) { 1705 1870 if let Some(HeapObject::Object(opts)) = ctx.gc.get(*opts_ref) { 1706 - if let Some(prop) = opts.properties.get("bubbles") { 1871 + if let Some(prop) = opts.get_property("bubbles", ctx.shapes) { 1707 1872 bubbles = prop.value.to_boolean(); 1708 1873 } 1709 - if let Some(prop) = opts.properties.get("cancelable") { 1874 + if let Some(prop) = opts.get_property("cancelable", ctx.shapes) { 1710 1875 cancelable = prop.value.to_boolean(); 1711 1876 } 1712 1877 } 1713 1878 } 1714 1879 1715 - let event_ref = create_event_object(ctx.gc, &event_type, bubbles, cancelable); 1880 + let event_ref = create_event_object(ctx.gc, ctx.shapes, &event_type, bubbles, cancelable); 1716 1881 Ok(Value::Object(event_ref)) 1717 1882 } 1718 1883 ··· 1721 1886 // Only prevent if cancelable. 1722 1887 let cancelable = match ctx.gc.get(*r) { 1723 1888 Some(HeapObject::Object(data)) => data 1724 - .properties 1725 - .get(EVENT_CANCELABLE_KEY) 1889 + .get_property(EVENT_CANCELABLE_KEY, ctx.shapes) 1726 1890 .map(|p| p.value.to_boolean()) 1727 1891 .unwrap_or(false), 1728 1892 _ => false, ··· 1730 1894 if cancelable { 1731 1895 set_builtin_prop( 1732 1896 ctx.gc, 1897 + ctx.shapes, 1733 1898 *r, 1734 1899 EVENT_DEFAULT_PREVENTED_KEY, 1735 1900 Value::Boolean(true), 1736 1901 ); 1737 - set_builtin_prop(ctx.gc, *r, "defaultPrevented", Value::Boolean(true)); 1902 + set_builtin_prop( 1903 + ctx.gc, 1904 + ctx.shapes, 1905 + *r, 1906 + "defaultPrevented", 1907 + Value::Boolean(true), 1908 + ); 1738 1909 } 1739 1910 } 1740 1911 Ok(Value::Undefined) ··· 1742 1913 1743 1914 fn event_stop_propagation(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1744 1915 if let Value::Object(r) = &ctx.this { 1745 - set_builtin_prop(ctx.gc, *r, EVENT_STOP_PROP_KEY, Value::Boolean(true)); 1916 + set_builtin_prop( 1917 + ctx.gc, 1918 + ctx.shapes, 1919 + *r, 1920 + EVENT_STOP_PROP_KEY, 1921 + Value::Boolean(true), 1922 + ); 1746 1923 } 1747 1924 Ok(Value::Undefined) 1748 1925 } ··· 1752 1929 ctx: &mut NativeContext, 1753 1930 ) -> Result<Value, RuntimeError> { 1754 1931 if let Value::Object(r) = &ctx.this { 1755 - set_builtin_prop(ctx.gc, *r, EVENT_STOP_PROP_KEY, Value::Boolean(true)); 1756 - set_builtin_prop(ctx.gc, *r, EVENT_STOP_IMMEDIATE_KEY, Value::Boolean(true)); 1932 + set_builtin_prop( 1933 + ctx.gc, 1934 + ctx.shapes, 1935 + *r, 1936 + EVENT_STOP_PROP_KEY, 1937 + Value::Boolean(true), 1938 + ); 1939 + set_builtin_prop( 1940 + ctx.gc, 1941 + ctx.shapes, 1942 + *r, 1943 + EVENT_STOP_IMMEDIATE_KEY, 1944 + Value::Boolean(true), 1945 + ); 1757 1946 } 1758 1947 Ok(Value::Undefined) 1759 1948 } ··· 1768 1957 .dom_bridge 1769 1958 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 1770 1959 let node_id = match &ctx.this { 1771 - Value::Object(r) => get_node_id(ctx.gc, *r), 1960 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 1772 1961 _ => None, 1773 1962 } 1774 1963 .ok_or_else(|| RuntimeError::type_error("addEventListener called on non-node"))?; ··· 1792 1981 Value::Boolean(b) => capture = *b, 1793 1982 Value::Object(opts_ref) => { 1794 1983 if let Some(HeapObject::Object(opts)) = ctx.gc.get(*opts_ref) { 1795 - if let Some(prop) = opts.properties.get("capture") { 1984 + if let Some(prop) = opts.get_property("capture", ctx.shapes) { 1796 1985 capture = prop.value.to_boolean(); 1797 1986 } 1798 - if let Some(prop) = opts.properties.get("once") { 1987 + if let Some(prop) = opts.get_property("once", ctx.shapes) { 1799 1988 once = prop.value.to_boolean(); 1800 1989 } 1801 1990 } ··· 1832 2021 .dom_bridge 1833 2022 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 1834 2023 let node_id = match &ctx.this { 1835 - Value::Object(r) => get_node_id(ctx.gc, *r), 2024 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 1836 2025 _ => None, 1837 2026 } 1838 2027 .ok_or_else(|| RuntimeError::type_error("removeEventListener called on non-node"))?; ··· 1853 2042 Value::Boolean(b) => capture = *b, 1854 2043 Value::Object(opts_ref) => { 1855 2044 if let Some(HeapObject::Object(opts)) = ctx.gc.get(*opts_ref) { 1856 - if let Some(prop) = opts.properties.get("capture") { 2045 + if let Some(prop) = opts.get_property("capture", ctx.shapes) { 1857 2046 capture = prop.value.to_boolean(); 1858 2047 } 1859 2048 } ··· 1879 2068 ctx: &mut NativeContext, 1880 2069 ) -> Result<Value, RuntimeError> { 1881 2070 let node_id = match &ctx.this { 1882 - Value::Object(r) => get_node_id(ctx.gc, *r), 2071 + Value::Object(r) => get_node_id(ctx.gc, ctx.shapes, *r), 1883 2072 _ => None, 1884 2073 } 1885 2074 .ok_or_else(|| RuntimeError::type_error("dispatchEvent called on non-node"))?; ··· 1895 2084 1896 2085 // Build a marker object for the VM to process. 1897 2086 let mut marker = ObjectData::new(); 1898 - marker.properties.insert( 2087 + marker.insert_property( 1899 2088 "__event_dispatch__".to_string(), 1900 2089 Property::builtin(Value::Boolean(true)), 2090 + ctx.shapes, 1901 2091 ); 1902 - marker.properties.insert( 2092 + marker.insert_property( 1903 2093 "__target_id__".to_string(), 1904 2094 Property::builtin(Value::Number(node_id.index() as f64)), 2095 + ctx.shapes, 1905 2096 ); 1906 - marker.properties.insert( 2097 + marker.insert_property( 1907 2098 "__event_ref__".to_string(), 1908 2099 Property::builtin(Value::Object(event_ref)), 2100 + ctx.shapes, 1909 2101 ); 1910 2102 1911 2103 Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(marker)))) ··· 1926 2118 ]; 1927 2119 for &(name, callback) in methods { 1928 2120 let func = make_native(&mut vm.gc, name, callback); 1929 - set_builtin_prop(&mut vm.gc, doc_ref, name, Value::Function(func)); 2121 + set_builtin_prop( 2122 + &mut vm.gc, 2123 + &mut vm.shapes, 2124 + doc_ref, 2125 + name, 2126 + Value::Function(func), 2127 + ); 1930 2128 } 1931 2129 1932 2130 // Ensure the document object has __node_id__ set to the root node. ··· 1934 2132 let root_idx = bridge.document.borrow().root().index(); 1935 2133 set_builtin_prop( 1936 2134 &mut vm.gc, 2135 + &mut vm.shapes, 1937 2136 doc_ref, 1938 2137 NODE_ID_KEY, 1939 2138 Value::Number(root_idx as f64), ··· 1970 2169 }; 1971 2170 1972 2171 // Set event.target to the target wrapper. 1973 - let target_wrapper = get_or_create_wrapper(target_id, &mut vm.gc, &bridge, vm.object_prototype); 2172 + let target_wrapper = get_or_create_wrapper( 2173 + target_id, 2174 + &mut vm.gc, 2175 + &mut vm.shapes, 2176 + &bridge, 2177 + vm.object_prototype, 2178 + ); 1974 2179 set_builtin_prop( 1975 2180 &mut vm.gc, 2181 + &mut vm.shapes, 1976 2182 event_ref, 1977 2183 "target", 1978 2184 Value::Object(target_wrapper), ··· 1981 2187 let target_pos = path.len() - 1; 1982 2188 1983 2189 // --- Capture phase (root to target, excluding target) --- 1984 - set_builtin_prop(&mut vm.gc, event_ref, "eventPhase", Value::Number(1.0)); 1985 - set_builtin_prop(&mut vm.gc, event_ref, EVENT_PHASE_KEY, Value::Number(1.0)); 2190 + set_builtin_prop( 2191 + &mut vm.gc, 2192 + &mut vm.shapes, 2193 + event_ref, 2194 + "eventPhase", 2195 + Value::Number(1.0), 2196 + ); 2197 + set_builtin_prop( 2198 + &mut vm.gc, 2199 + &mut vm.shapes, 2200 + event_ref, 2201 + EVENT_PHASE_KEY, 2202 + Value::Number(1.0), 2203 + ); 1986 2204 1987 2205 for &node_id in &path[..target_pos] { 1988 - if is_propagation_stopped(&vm.gc, event_ref) { 2206 + if is_propagation_stopped(&vm.gc, &vm.shapes, event_ref) { 1989 2207 break; 1990 2208 } 1991 - let wrapper = get_or_create_wrapper(node_id, &mut vm.gc, &bridge, vm.object_prototype); 2209 + let wrapper = get_or_create_wrapper( 2210 + node_id, 2211 + &mut vm.gc, 2212 + &mut vm.shapes, 2213 + &bridge, 2214 + vm.object_prototype, 2215 + ); 1992 2216 set_builtin_prop( 1993 2217 &mut vm.gc, 2218 + &mut vm.shapes, 1994 2219 event_ref, 1995 2220 "currentTarget", 1996 2221 Value::Object(wrapper), ··· 1999 2224 } 2000 2225 2001 2226 // --- At-target phase --- 2002 - if !is_propagation_stopped(&vm.gc, event_ref) { 2003 - set_builtin_prop(&mut vm.gc, event_ref, "eventPhase", Value::Number(2.0)); 2004 - set_builtin_prop(&mut vm.gc, event_ref, EVENT_PHASE_KEY, Value::Number(2.0)); 2227 + if !is_propagation_stopped(&vm.gc, &vm.shapes, event_ref) { 2228 + set_builtin_prop( 2229 + &mut vm.gc, 2230 + &mut vm.shapes, 2231 + event_ref, 2232 + "eventPhase", 2233 + Value::Number(2.0), 2234 + ); 2005 2235 set_builtin_prop( 2006 2236 &mut vm.gc, 2237 + &mut vm.shapes, 2238 + event_ref, 2239 + EVENT_PHASE_KEY, 2240 + Value::Number(2.0), 2241 + ); 2242 + set_builtin_prop( 2243 + &mut vm.gc, 2244 + &mut vm.shapes, 2007 2245 event_ref, 2008 2246 "currentTarget", 2009 2247 Value::Object(target_wrapper), ··· 2015 2253 // --- Bubble phase (target to root, excluding target) --- 2016 2254 let bubbles = match vm.gc.get(event_ref) { 2017 2255 Some(HeapObject::Object(data)) => data 2018 - .properties 2019 - .get(EVENT_BUBBLES_KEY) 2256 + .get_property(EVENT_BUBBLES_KEY, &vm.shapes) 2020 2257 .map(|p| p.value.to_boolean()) 2021 2258 .unwrap_or(false), 2022 2259 _ => false, 2023 2260 }; 2024 2261 2025 - if bubbles && !is_propagation_stopped(&vm.gc, event_ref) { 2026 - set_builtin_prop(&mut vm.gc, event_ref, "eventPhase", Value::Number(3.0)); 2027 - set_builtin_prop(&mut vm.gc, event_ref, EVENT_PHASE_KEY, Value::Number(3.0)); 2262 + if bubbles && !is_propagation_stopped(&vm.gc, &vm.shapes, event_ref) { 2263 + set_builtin_prop( 2264 + &mut vm.gc, 2265 + &mut vm.shapes, 2266 + event_ref, 2267 + "eventPhase", 2268 + Value::Number(3.0), 2269 + ); 2270 + set_builtin_prop( 2271 + &mut vm.gc, 2272 + &mut vm.shapes, 2273 + event_ref, 2274 + EVENT_PHASE_KEY, 2275 + Value::Number(3.0), 2276 + ); 2028 2277 2029 2278 for &node_id in path[..target_pos].iter().rev() { 2030 - if is_propagation_stopped(&vm.gc, event_ref) { 2279 + if is_propagation_stopped(&vm.gc, &vm.shapes, event_ref) { 2031 2280 break; 2032 2281 } 2033 - let wrapper = get_or_create_wrapper(node_id, &mut vm.gc, &bridge, vm.object_prototype); 2282 + let wrapper = get_or_create_wrapper( 2283 + node_id, 2284 + &mut vm.gc, 2285 + &mut vm.shapes, 2286 + &bridge, 2287 + vm.object_prototype, 2288 + ); 2034 2289 set_builtin_prop( 2035 2290 &mut vm.gc, 2291 + &mut vm.shapes, 2036 2292 event_ref, 2037 2293 "currentTarget", 2038 2294 Value::Object(wrapper), ··· 2042 2298 } 2043 2299 2044 2300 // Reset event state. 2045 - set_builtin_prop(&mut vm.gc, event_ref, "eventPhase", Value::Number(0.0)); 2046 - set_builtin_prop(&mut vm.gc, event_ref, EVENT_PHASE_KEY, Value::Number(0.0)); 2047 - set_builtin_prop(&mut vm.gc, event_ref, "currentTarget", Value::Null); 2301 + set_builtin_prop( 2302 + &mut vm.gc, 2303 + &mut vm.shapes, 2304 + event_ref, 2305 + "eventPhase", 2306 + Value::Number(0.0), 2307 + ); 2308 + set_builtin_prop( 2309 + &mut vm.gc, 2310 + &mut vm.shapes, 2311 + event_ref, 2312 + EVENT_PHASE_KEY, 2313 + Value::Number(0.0), 2314 + ); 2315 + set_builtin_prop( 2316 + &mut vm.gc, 2317 + &mut vm.shapes, 2318 + event_ref, 2319 + "currentTarget", 2320 + Value::Null, 2321 + ); 2048 2322 2049 2323 // Return !defaultPrevented. 2050 2324 let default_prevented = match vm.gc.get(event_ref) { 2051 2325 Some(HeapObject::Object(data)) => data 2052 - .properties 2053 - .get(EVENT_DEFAULT_PREVENTED_KEY) 2326 + .get_property(EVENT_DEFAULT_PREVENTED_KEY, &vm.shapes) 2054 2327 .map(|p| p.value.to_boolean()) 2055 2328 .unwrap_or(false), 2056 2329 _ => false, ··· 2058 2331 Value::Boolean(!default_prevented) 2059 2332 } 2060 2333 2061 - fn is_propagation_stopped(gc: &Gc<HeapObject>, event_ref: GcRef) -> bool { 2334 + fn is_propagation_stopped(gc: &Gc<HeapObject>, shapes: &ShapeTable, event_ref: GcRef) -> bool { 2062 2335 match gc.get(event_ref) { 2063 2336 Some(HeapObject::Object(data)) => data 2064 - .properties 2065 - .get(EVENT_STOP_PROP_KEY) 2337 + .get_property(EVENT_STOP_PROP_KEY, shapes) 2066 2338 .map(|p| p.value.to_boolean()) 2067 2339 .unwrap_or(false), 2068 2340 _ => false, 2069 2341 } 2070 2342 } 2071 2343 2072 - fn is_immediate_stopped(gc: &Gc<HeapObject>, event_ref: GcRef) -> bool { 2344 + fn is_immediate_stopped(gc: &Gc<HeapObject>, shapes: &ShapeTable, event_ref: GcRef) -> bool { 2073 2345 match gc.get(event_ref) { 2074 2346 Some(HeapObject::Object(data)) => data 2075 - .properties 2076 - .get(EVENT_STOP_IMMEDIATE_KEY) 2347 + .get_property(EVENT_STOP_IMMEDIATE_KEY, shapes) 2077 2348 .map(|p| p.value.to_boolean()) 2078 2349 .unwrap_or(false), 2079 2350 _ => false, ··· 2095 2366 capture_only: bool, 2096 2367 ) { 2097 2368 let event_type = match vm.gc.get(event_ref) { 2098 - Some(HeapObject::Object(data)) => match data.properties.get(EVENT_TYPE_KEY) { 2369 + Some(HeapObject::Object(data)) => match data.get_property(EVENT_TYPE_KEY, &vm.shapes) { 2099 2370 Some(Property { 2100 2371 value: Value::String(s), 2101 2372 .. 2102 - }) => s.clone(), 2373 + }) => s, 2103 2374 _ => return, 2104 2375 }, 2105 2376 _ => return, 2106 2377 }; 2107 2378 2108 2379 let phase = match vm.gc.get(event_ref) { 2109 - Some(HeapObject::Object(data)) => match data.properties.get(EVENT_PHASE_KEY) { 2380 + Some(HeapObject::Object(data)) => match data.get_property(EVENT_PHASE_KEY, &vm.shapes) { 2110 2381 Some(Property { 2111 2382 value: Value::Number(n), 2112 2383 .. 2113 - }) => *n as u8, 2384 + }) => n as u8, 2114 2385 _ => 0, 2115 2386 }, 2116 2387 _ => 0, ··· 2147 2418 2148 2419 // Invoke each listener. 2149 2420 for (callback_ref, once) in &matching { 2150 - if is_immediate_stopped(&vm.gc, event_ref) { 2421 + if is_immediate_stopped(&vm.gc, &vm.shapes, event_ref) { 2151 2422 break; 2152 2423 } 2153 2424 // Set `this` to currentTarget for the callback. ··· 2225 2496 ]; 2226 2497 2227 2498 /// Check whether `gc_ref` is a Storage proxy object and return the type. 2228 - fn storage_type(gc: &Gc<HeapObject>, gc_ref: GcRef) -> Option<String> { 2499 + fn storage_type(gc: &Gc<HeapObject>, shapes: &ShapeTable, gc_ref: GcRef) -> Option<String> { 2229 2500 if let Some(HeapObject::Object(data)) = gc.get(gc_ref) { 2230 - if let Some(prop) = data.properties.get(STORAGE_TYPE_KEY) { 2231 - if let Value::String(s) = &prop.value { 2232 - return Some(s.clone()); 2501 + if let Some(prop) = data.get_property(STORAGE_TYPE_KEY, shapes) { 2502 + if let Value::String(s) = prop.value { 2503 + return Some(s); 2233 2504 } 2234 2505 } 2235 2506 } ··· 2243 2514 /// built-in method. 2244 2515 pub fn resolve_storage_get( 2245 2516 gc: &Gc<HeapObject>, 2517 + shapes: &ShapeTable, 2246 2518 bridge: &Rc<DomBridge>, 2247 2519 gc_ref: GcRef, 2248 2520 key: &str, 2249 2521 ) -> Option<Value> { 2250 - let stype = storage_type(gc, gc_ref)?; 2522 + let stype = storage_type(gc, shapes, gc_ref)?; 2251 2523 2252 2524 // "length" is always dynamic — return the current item count. 2253 2525 if key == "length" { ··· 2295 2567 key: &str, 2296 2568 val: &Value, 2297 2569 gc: &Gc<HeapObject>, 2570 + shapes: &ShapeTable, 2298 2571 ) -> bool { 2299 - let stype = match storage_type(gc, gc_ref) { 2572 + let stype = match storage_type(gc, shapes, gc_ref) { 2300 2573 Some(s) => s, 2301 2574 None => return false, 2302 2575 }; ··· 2331 2604 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 2332 2605 let stype = storage_type( 2333 2606 ctx.gc, 2607 + ctx.shapes, 2334 2608 match &ctx.this { 2335 2609 Value::Object(r) => *r, 2336 2610 _ => return Err(RuntimeError::type_error("getItem called on non-Storage")), ··· 2369 2643 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 2370 2644 let stype = storage_type( 2371 2645 ctx.gc, 2646 + ctx.shapes, 2372 2647 match &ctx.this { 2373 2648 Value::Object(r) => *r, 2374 2649 _ => return Err(RuntimeError::type_error("setItem called on non-Storage")), ··· 2407 2682 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 2408 2683 let stype = storage_type( 2409 2684 ctx.gc, 2685 + ctx.shapes, 2410 2686 match &ctx.this { 2411 2687 Value::Object(r) => *r, 2412 2688 _ => return Err(RuntimeError::type_error("removeItem called on non-Storage")), ··· 2434 2710 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 2435 2711 let stype = storage_type( 2436 2712 ctx.gc, 2713 + ctx.shapes, 2437 2714 match &ctx.this { 2438 2715 Value::Object(r) => *r, 2439 2716 _ => return Err(RuntimeError::type_error("clear called on non-Storage")), ··· 2456 2733 .ok_or_else(|| RuntimeError::type_error("no document attached"))?; 2457 2734 let stype = storage_type( 2458 2735 ctx.gc, 2736 + ctx.shapes, 2459 2737 match &ctx.this { 2460 2738 Value::Object(r) => *r, 2461 2739 _ => return Err(RuntimeError::type_error("key called on non-Storage")), ··· 2483 2761 /// Create a Storage JS object (used for both localStorage and sessionStorage). 2484 2762 fn create_storage_object( 2485 2763 gc: &mut Gc<HeapObject>, 2764 + shapes: &mut ShapeTable, 2486 2765 storage_type_str: &str, 2487 2766 object_proto: Option<GcRef>, 2488 2767 ) -> GcRef { ··· 2492 2771 } 2493 2772 2494 2773 // Mark this object as a Storage proxy. 2495 - data.properties.insert( 2774 + data.insert_property( 2496 2775 STORAGE_TYPE_KEY.to_string(), 2497 2776 Property::builtin(Value::String(storage_type_str.to_string())), 2777 + shapes, 2498 2778 ); 2499 2779 2500 2780 let obj_ref = gc.alloc(HeapObject::Object(data)); ··· 2509 2789 ]; 2510 2790 for &(name, callback) in methods { 2511 2791 let func = make_native(gc, name, callback); 2512 - set_builtin_prop(gc, obj_ref, name, Value::Function(func)); 2792 + set_builtin_prop(gc, shapes, obj_ref, name, Value::Function(func)); 2513 2793 } 2514 2794 2515 2795 obj_ref ··· 2518 2798 /// Register `localStorage` and `sessionStorage` globals on the VM. 2519 2799 /// Called from `Vm::attach_document`. 2520 2800 pub fn init_storage_objects(vm: &mut Vm) { 2521 - let local_ref = create_storage_object(&mut vm.gc, "local", vm.object_prototype); 2801 + let local_ref = create_storage_object(&mut vm.gc, &mut vm.shapes, "local", vm.object_prototype); 2522 2802 vm.set_global("localStorage", Value::Object(local_ref)); 2523 2803 2524 - let session_ref = create_storage_object(&mut vm.gc, "session", vm.object_prototype); 2804 + let session_ref = 2805 + create_storage_object(&mut vm.gc, &mut vm.shapes, "session", vm.object_prototype); 2525 2806 vm.set_global("sessionStorage", Value::Object(session_ref)); 2526 2807 } 2527 2808 2528 2809 // ── Public wrappers for cross-module access ──────────────────────── 2529 2810 2530 2811 /// Public wrapper for `get_node_id` — used by `iframe_bridge`. 2531 - pub fn get_node_id_pub(gc: &Gc<HeapObject>, wrapper: GcRef) -> Option<NodeId> { 2532 - get_node_id(gc, wrapper) 2812 + pub fn get_node_id_pub(gc: &Gc<HeapObject>, shapes: &ShapeTable, wrapper: GcRef) -> Option<NodeId> { 2813 + get_node_id(gc, shapes, wrapper) 2533 2814 } 2534 2815 2535 2816 /// Public wrapper for `event_target_add_listener`.
+108 -79
crates/js/src/fetch.rs
··· 11 11 set_builtin_prop, 12 12 }; 13 13 use crate::gc::{Gc, GcRef}; 14 + use crate::shape::ShapeTable; 14 15 use crate::vm::*; 15 16 16 17 // ── Pending fetch state ───────────────────────────────────────── ··· 127 128 if let Some(Value::Object(opts_ref)) = args.get(1) { 128 129 if let Some(HeapObject::Object(data)) = ctx.gc.get(*opts_ref) { 129 130 // method 130 - if let Some(prop) = data.properties.get("method") { 131 - if let Value::String(m) = &prop.value { 131 + if let Some(prop) = data.get_property("method", ctx.shapes) { 132 + if let Value::String(m) = prop.value { 132 133 method = m.to_uppercase(); 133 134 } 134 135 } 135 136 // body 136 - if let Some(prop) = data.properties.get("body") { 137 - if let Value::String(b) = &prop.value { 137 + if let Some(prop) = data.get_property("body", ctx.shapes) { 138 + if let Value::String(b) = prop.value { 138 139 body = Some(b.as_bytes().to_vec()); 139 140 } 140 141 } 141 142 // headers — read from nested object 142 - if let Some(prop) = data.properties.get("headers") { 143 + if let Some(prop) = data.get_property("headers", ctx.shapes) { 143 144 if let Some(hdrs_ref) = prop.value.gc_ref() { 144 145 if let Some(HeapObject::Object(hdr_data)) = ctx.gc.get(hdrs_ref) { 145 - for (key, hdr_prop) in &hdr_data.properties { 146 + for (key, hdr_prop) in hdr_data.property_entries(ctx.shapes) { 146 147 if hdr_prop.enumerable { 147 - if let Value::String(v) = &hdr_prop.value { 148 - req_headers.push((key.clone(), v.clone())); 148 + if let Value::String(v) = hdr_prop.value { 149 + req_headers.push((key, v)); 149 150 } 150 151 } 151 152 } ··· 153 154 } 154 155 } 155 156 // mode 156 - if let Some(prop) = data.properties.get("mode") { 157 - if let Value::String(m) = &prop.value { 158 - cors_mode = m.clone(); 157 + if let Some(prop) = data.get_property("mode", ctx.shapes) { 158 + if let Value::String(m) = prop.value { 159 + cors_mode = m; 159 160 } 160 161 } 161 162 // credentials 162 - if let Some(prop) = data.properties.get("credentials") { 163 - if let Value::String(c) = &prop.value { 164 - credentials_mode = c.clone(); 163 + if let Some(prop) = data.get_property("credentials", ctx.shapes) { 164 + if let Value::String(c) = prop.value { 165 + credentials_mode = c; 165 166 } 166 167 } 167 168 } ··· 171 172 let doc_origin = get_document_origin(); 172 173 173 174 // Create a pending promise. 174 - let promise = create_promise_object_pub(ctx.gc); 175 + let promise = create_promise_object_pub(ctx.gc, ctx.shapes); 175 176 176 177 // Shared slot for the result. 177 178 let result_slot: Arc<Mutex<Option<Result<FetchResult, String>>>> = Arc::new(Mutex::new(None)); ··· 360 361 // ── Response object creation ──────────────────────────────────── 361 362 362 363 /// Create a JS Response object from a completed fetch result. 363 - pub fn create_response_object(gc: &mut Gc<HeapObject>, result: &FetchResult) -> GcRef { 364 + pub fn create_response_object( 365 + gc: &mut Gc<HeapObject>, 366 + shapes: &mut ShapeTable, 367 + result: &FetchResult, 368 + ) -> GcRef { 364 369 let mut data = ObjectData::new(); 365 370 366 - data.properties.insert( 371 + data.insert_property( 367 372 "status".to_string(), 368 373 Property::data(Value::Number(result.status as f64)), 374 + shapes, 369 375 ); 370 - data.properties.insert( 376 + data.insert_property( 371 377 "statusText".to_string(), 372 378 Property::data(Value::String(result.status_text.clone())), 379 + shapes, 373 380 ); 374 - data.properties.insert( 381 + data.insert_property( 375 382 "ok".to_string(), 376 383 Property::data(Value::Boolean((200..300).contains(&result.status))), 384 + shapes, 377 385 ); 378 - data.properties.insert( 386 + data.insert_property( 379 387 "url".to_string(), 380 388 Property::data(Value::String(result.url.clone())), 389 + shapes, 381 390 ); 382 391 383 392 // Store body as hidden property for text()/json(). 384 393 let body_str = String::from_utf8_lossy(&result.body).to_string(); 385 - data.properties.insert( 394 + data.insert_property( 386 395 "__body__".to_string(), 387 396 Property::builtin(Value::String(body_str)), 397 + shapes, 388 398 ); 389 399 390 400 let resp_ref = gc.alloc(HeapObject::Object(data)); 391 401 392 402 // Create Headers object. 393 - let headers_ref = create_headers_object(gc, &result.headers); 394 - set_builtin_prop(gc, resp_ref, "headers", Value::Object(headers_ref)); 403 + let headers_ref = create_headers_object(gc, shapes, &result.headers); 404 + set_builtin_prop(gc, shapes, resp_ref, "headers", Value::Object(headers_ref)); 395 405 396 406 // Register methods. 397 407 let text_fn = make_native(gc, "text", response_text); 398 - set_builtin_prop(gc, resp_ref, "text", Value::Function(text_fn)); 408 + set_builtin_prop(gc, shapes, resp_ref, "text", Value::Function(text_fn)); 399 409 400 410 let json_fn = make_native(gc, "json", response_json); 401 - set_builtin_prop(gc, resp_ref, "json", Value::Function(json_fn)); 411 + set_builtin_prop(gc, shapes, resp_ref, "json", Value::Function(json_fn)); 402 412 403 413 resp_ref 404 414 } 405 415 406 416 /// `response.text()` — returns Promise<String> with the response body. 407 417 fn response_text(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 408 - let body_str = get_hidden_body(ctx.gc, &ctx.this); 409 - let promise = create_promise_object_pub(ctx.gc); 410 - resolve_promise_internal(ctx.gc, promise, Value::String(body_str)); 418 + let body_str = get_hidden_body(ctx.gc, ctx.shapes, &ctx.this); 419 + let promise = create_promise_object_pub(ctx.gc, ctx.shapes); 420 + resolve_promise_internal(ctx.gc, ctx.shapes, promise, Value::String(body_str)); 411 421 Ok(Value::Object(promise)) 412 422 } 413 423 414 424 /// `response.json()` — returns Promise<Object> with parsed JSON body. 415 425 fn response_json(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 416 - let body_str = get_hidden_body(ctx.gc, &ctx.this); 417 - let promise = create_promise_object_pub(ctx.gc); 426 + let body_str = get_hidden_body(ctx.gc, ctx.shapes, &ctx.this); 427 + let promise = create_promise_object_pub(ctx.gc, ctx.shapes); 418 428 let json_args = [Value::String(body_str)]; 419 429 match crate::builtins::json_parse_pub(&json_args, ctx) { 420 - Ok(parsed) => resolve_promise_internal(ctx.gc, promise, parsed), 430 + Ok(parsed) => resolve_promise_internal(ctx.gc, ctx.shapes, promise, parsed), 421 431 Err(e) => { 422 432 let err_val = Value::String(e.to_string()); 423 - reject_promise_internal(ctx.gc, promise, err_val); 433 + reject_promise_internal(ctx.gc, ctx.shapes, promise, err_val); 424 434 } 425 435 } 426 436 Ok(Value::Object(promise)) 427 437 } 428 438 429 439 /// Read the hidden `__body__` string from a Response object. 430 - fn get_hidden_body(gc: &Gc<HeapObject>, this: &Value) -> String { 440 + fn get_hidden_body(gc: &Gc<HeapObject>, shapes: &ShapeTable, this: &Value) -> String { 431 441 if let Some(obj_ref) = this.gc_ref() { 432 442 if let Some(HeapObject::Object(data)) = gc.get(obj_ref) { 433 - if let Some(prop) = data.properties.get("__body__") { 434 - if let Value::String(s) = &prop.value { 435 - return s.clone(); 443 + if let Some(prop) = data.get_property("__body__", shapes) { 444 + if let Value::String(s) = prop.value { 445 + return s; 436 446 } 437 447 } 438 448 } ··· 446 456 /// 447 457 /// Headers are stored as hidden indexed properties for case-insensitive 448 458 /// lookup. Duplicate header names are combined with ", " per HTTP spec. 449 - fn create_headers_object(gc: &mut Gc<HeapObject>, headers: &[(String, String)]) -> GcRef { 459 + fn create_headers_object( 460 + gc: &mut Gc<HeapObject>, 461 + shapes: &mut ShapeTable, 462 + headers: &[(String, String)], 463 + ) -> GcRef { 450 464 // Combine headers with same name (case-insensitive). 451 465 let mut combined: Vec<(String, String)> = Vec::new(); 452 466 for (name, value) in headers { ··· 459 473 } 460 474 461 475 let mut data = ObjectData::new(); 462 - data.properties.insert( 476 + data.insert_property( 463 477 "__hdr_count__".to_string(), 464 478 Property::builtin(Value::Number(combined.len() as f64)), 479 + shapes, 465 480 ); 466 481 for (i, (name, value)) in combined.iter().enumerate() { 467 - data.properties.insert( 482 + data.insert_property( 468 483 format!("__hdr_{i}_n__"), 469 484 Property::builtin(Value::String(name.clone())), 485 + shapes, 470 486 ); 471 - data.properties.insert( 487 + data.insert_property( 472 488 format!("__hdr_{i}_v__"), 473 489 Property::builtin(Value::String(value.clone())), 490 + shapes, 474 491 ); 475 492 } 476 493 477 494 let obj_ref = gc.alloc(HeapObject::Object(data)); 478 495 479 496 let get_fn = make_native(gc, "get", headers_get); 480 - set_builtin_prop(gc, obj_ref, "get", Value::Function(get_fn)); 497 + set_builtin_prop(gc, shapes, obj_ref, "get", Value::Function(get_fn)); 481 498 482 499 let has_fn = make_native(gc, "has", headers_has); 483 - set_builtin_prop(gc, obj_ref, "has", Value::Function(has_fn)); 500 + set_builtin_prop(gc, shapes, obj_ref, "has", Value::Function(has_fn)); 484 501 485 502 let set_fn = make_native(gc, "set", headers_set); 486 - set_builtin_prop(gc, obj_ref, "set", Value::Function(set_fn)); 503 + set_builtin_prop(gc, shapes, obj_ref, "set", Value::Function(set_fn)); 487 504 488 505 let delete_fn = make_native(gc, "delete", headers_delete); 489 - set_builtin_prop(gc, obj_ref, "delete", Value::Function(delete_fn)); 506 + set_builtin_prop(gc, shapes, obj_ref, "delete", Value::Function(delete_fn)); 490 507 491 508 obj_ref 492 509 } 493 510 494 511 /// Read header entries from a Headers object: returns (count, Vec<(name, value)>). 495 - fn read_header_entries(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<(String, String)> { 512 + fn read_header_entries( 513 + gc: &Gc<HeapObject>, 514 + shapes: &ShapeTable, 515 + obj_ref: GcRef, 516 + ) -> Vec<(String, String)> { 496 517 let mut entries = Vec::new(); 497 518 if let Some(HeapObject::Object(data)) = gc.get(obj_ref) { 498 519 let count = data 499 - .properties 500 - .get("__hdr_count__") 520 + .get_property("__hdr_count__", shapes) 501 521 .and_then(|p| { 502 - if let Value::Number(n) = &p.value { 503 - Some(*n as usize) 522 + if let Value::Number(n) = p.value { 523 + Some(n as usize) 504 524 } else { 505 525 None 506 526 } ··· 509 529 510 530 for i in 0..count { 511 531 let name = data 512 - .properties 513 - .get(&format!("__hdr_{i}_n__")) 532 + .get_property(&format!("__hdr_{i}_n__"), shapes) 514 533 .and_then(|p| { 515 - if let Value::String(s) = &p.value { 516 - Some(s.clone()) 534 + if let Value::String(s) = p.value { 535 + Some(s) 517 536 } else { 518 537 None 519 538 } 520 539 }) 521 540 .unwrap_or_default(); 522 541 let value = data 523 - .properties 524 - .get(&format!("__hdr_{i}_v__")) 542 + .get_property(&format!("__hdr_{i}_v__"), shapes) 525 543 .and_then(|p| { 526 - if let Value::String(s) = &p.value { 527 - Some(s.clone()) 544 + if let Value::String(s) = p.value { 545 + Some(s) 528 546 } else { 529 547 None 530 548 } ··· 547 565 Some(r) => r, 548 566 None => return Ok(Value::Null), 549 567 }; 550 - let entries = read_header_entries(ctx.gc, obj_ref); 568 + let entries = read_header_entries(ctx.gc, ctx.shapes, obj_ref); 551 569 for (n, v) in entries { 552 570 if n == name { 553 571 return Ok(Value::String(v)); ··· 567 585 Some(r) => r, 568 586 None => return Ok(Value::Boolean(false)), 569 587 }; 570 - let entries = read_header_entries(ctx.gc, obj_ref); 588 + let entries = read_header_entries(ctx.gc, ctx.shapes, obj_ref); 571 589 Ok(Value::Boolean(entries.iter().any(|(n, _)| n == &name))) 572 590 } 573 591 ··· 587 605 Some(r) => r, 588 606 None => return Ok(Value::Undefined), 589 607 }; 590 - let mut entries = read_header_entries(ctx.gc, obj_ref); 608 + let mut entries = read_header_entries(ctx.gc, ctx.shapes, obj_ref); 591 609 let mut found = false; 592 610 for entry in &mut entries { 593 611 if entry.0 == name { ··· 599 617 if !found { 600 618 entries.push((name, value)); 601 619 } 602 - write_header_entries(ctx.gc, obj_ref, &entries); 620 + write_header_entries(ctx.gc, ctx.shapes, obj_ref, &entries); 603 621 Ok(Value::Undefined) 604 622 } 605 623 ··· 614 632 Some(r) => r, 615 633 None => return Ok(Value::Undefined), 616 634 }; 617 - let mut entries = read_header_entries(ctx.gc, obj_ref); 635 + let mut entries = read_header_entries(ctx.gc, ctx.shapes, obj_ref); 618 636 entries.retain(|(n, _)| n != &name); 619 - write_header_entries(ctx.gc, obj_ref, &entries); 637 + write_header_entries(ctx.gc, ctx.shapes, obj_ref, &entries); 620 638 Ok(Value::Undefined) 621 639 } 622 640 623 641 /// Rewrite header entries into a Headers object. 624 - fn write_header_entries(gc: &mut Gc<HeapObject>, obj_ref: GcRef, entries: &[(String, String)]) { 642 + fn write_header_entries( 643 + gc: &mut Gc<HeapObject>, 644 + shapes: &mut ShapeTable, 645 + obj_ref: GcRef, 646 + entries: &[(String, String)], 647 + ) { 625 648 if let Some(HeapObject::Object(data)) = gc.get_mut(obj_ref) { 626 649 // Remove old header entries. 627 - data.properties.retain(|k, _| !k.starts_with("__hdr_")); 650 + data.retain_properties(shapes, |k, _| !k.starts_with("__hdr_")); 628 651 629 652 // Write new entries. 630 - data.properties.insert( 653 + data.insert_property( 631 654 "__hdr_count__".to_string(), 632 655 Property::builtin(Value::Number(entries.len() as f64)), 656 + shapes, 633 657 ); 634 658 for (i, (name, value)) in entries.iter().enumerate() { 635 - data.properties.insert( 659 + data.insert_property( 636 660 format!("__hdr_{i}_n__"), 637 661 Property::builtin(Value::String(name.clone())), 662 + shapes, 638 663 ); 639 - data.properties.insert( 664 + data.insert_property( 640 665 format!("__hdr_{i}_v__"), 641 666 Property::builtin(Value::String(value.clone())), 667 + shapes, 642 668 ); 643 669 } 644 670 } ··· 792 818 fn test_response_object_properties() { 793 819 // Test that create_response_object creates the right structure. 794 820 let mut gc = crate::gc::Gc::new(); 795 - builtins::init_promise_proto_for_test(&mut gc); 821 + let mut shapes = ShapeTable::new(); 822 + builtins::init_promise_proto_for_test(&mut gc, &mut shapes); 796 823 797 824 let result = FetchResult { 798 825 status: 200, ··· 805 832 url: "http://example.com/".to_string(), 806 833 }; 807 834 808 - let resp_ref = create_response_object(&mut gc, &result); 835 + let resp_ref = create_response_object(&mut gc, &mut shapes, &result); 809 836 let resp_obj = gc.get(resp_ref).unwrap(); 810 837 if let HeapObject::Object(data) = resp_obj { 811 838 // Check status 812 839 assert!(matches!( 813 - data.properties.get("status").map(|p| &p.value), 814 - Some(Value::Number(n)) if *n == 200.0 840 + data.get_property("status", &shapes).map(|p| p.value), 841 + Some(Value::Number(n)) if n == 200.0 815 842 )); 816 843 // Check ok 817 844 assert!(matches!( 818 - data.properties.get("ok").map(|p| &p.value), 845 + data.get_property("ok", &shapes).map(|p| p.value), 819 846 Some(Value::Boolean(true)) 820 847 )); 821 848 // Check statusText 822 849 assert!(matches!( 823 - data.properties.get("statusText").map(|p| &p.value), 850 + data.get_property("statusText", &shapes).map(|p| p.value), 824 851 Some(Value::String(s)) if s == "OK" 825 852 )); 826 853 // Check url 827 854 assert!(matches!( 828 - data.properties.get("url").map(|p| &p.value), 855 + data.get_property("url", &shapes).map(|p| p.value), 829 856 Some(Value::String(s)) if s == "http://example.com/" 830 857 )); 831 858 } else { ··· 836 863 #[test] 837 864 fn test_headers_get_case_insensitive() { 838 865 let mut gc = crate::gc::Gc::new(); 866 + let mut shapes = ShapeTable::new(); 839 867 let headers = vec![ 840 868 ("Content-Type".to_string(), "text/html".to_string()), 841 869 ("X-Foo".to_string(), "bar".to_string()), 842 870 ]; 843 - let hdr_ref = create_headers_object(&mut gc, &headers); 844 - let entries = read_header_entries(&gc, hdr_ref); 871 + let hdr_ref = create_headers_object(&mut gc, &mut shapes, &headers); 872 + let entries = read_header_entries(&gc, &shapes, hdr_ref); 845 873 assert_eq!(entries.len(), 2); 846 874 // All stored lowercased 847 875 assert!(entries ··· 853 881 #[test] 854 882 fn test_headers_duplicate_combining() { 855 883 let mut gc = crate::gc::Gc::new(); 884 + let mut shapes = ShapeTable::new(); 856 885 let headers = vec![ 857 886 ("Set-Cookie".to_string(), "a=1".to_string()), 858 887 ("Set-Cookie".to_string(), "b=2".to_string()), 859 888 ]; 860 - let hdr_ref = create_headers_object(&mut gc, &headers); 861 - let entries = read_header_entries(&gc, hdr_ref); 889 + let hdr_ref = create_headers_object(&mut gc, &mut shapes, &headers); 890 + let entries = read_header_entries(&gc, &shapes, hdr_ref); 862 891 assert_eq!(entries.len(), 1); 863 892 assert_eq!(entries[0].0, "set-cookie"); 864 893 assert_eq!(entries[0].1, "a=1, b=2");
+133 -67
crates/js/src/iframe_bridge.rs
··· 3 3 4 4 use crate::builtins::{make_native, set_builtin_prop}; 5 5 use crate::gc::{Gc, GcRef}; 6 + use crate::shape::ShapeTable; 6 7 use crate::vm::*; 7 8 use std::cell::RefCell; 8 9 use we_dom::NodeId; ··· 60 61 } 61 62 62 63 // Mark this as a window object with context ID and origin. 63 - data.properties.insert( 64 + data.insert_property( 64 65 WINDOW_CONTEXT_ID_KEY.to_string(), 65 66 Property::builtin(Value::String(context_id.to_string())), 67 + &mut vm.shapes, 66 68 ); 67 - data.properties.insert( 69 + data.insert_property( 68 70 WINDOW_ORIGIN_KEY.to_string(), 69 71 Property::builtin(Value::String(origin.to_string())), 72 + &mut vm.shapes, 70 73 ); 71 74 72 75 // window.self, window.window refer to window itself (set after alloc). 73 76 let window_ref = vm.gc.alloc(HeapObject::Object(data)); 74 77 75 - set_builtin_prop(&mut vm.gc, window_ref, "self", Value::Object(window_ref)); 76 - set_builtin_prop(&mut vm.gc, window_ref, "window", Value::Object(window_ref)); 78 + set_builtin_prop( 79 + &mut vm.gc, 80 + &mut vm.shapes, 81 + window_ref, 82 + "self", 83 + Value::Object(window_ref), 84 + ); 85 + set_builtin_prop( 86 + &mut vm.gc, 87 + &mut vm.shapes, 88 + window_ref, 89 + "window", 90 + Value::Object(window_ref), 91 + ); 77 92 78 93 // window.location (basic stub). 79 94 set_builtin_prop( 80 95 &mut vm.gc, 96 + &mut vm.shapes, 81 97 window_ref, 82 98 "location", 83 99 Value::Object(window_ref), ··· 86 102 // window.origin 87 103 set_builtin_prop( 88 104 &mut vm.gc, 105 + &mut vm.shapes, 89 106 window_ref, 90 107 "origin", 91 108 Value::String(origin.to_string()), ··· 95 112 let post_msg = make_native(&mut vm.gc, "postMessage", window_post_message); 96 113 set_builtin_prop( 97 114 &mut vm.gc, 115 + &mut vm.shapes, 98 116 window_ref, 99 117 "postMessage", 100 118 Value::Function(post_msg), ··· 118 136 ); 119 137 set_builtin_prop( 120 138 &mut vm.gc, 139 + &mut vm.shapes, 121 140 window_ref, 122 141 "addEventListener", 123 142 Value::Function(add_listener), 124 143 ); 125 144 set_builtin_prop( 126 145 &mut vm.gc, 146 + &mut vm.shapes, 127 147 window_ref, 128 148 "removeEventListener", 129 149 Value::Function(remove_listener), 130 150 ); 131 151 set_builtin_prop( 132 152 &mut vm.gc, 153 + &mut vm.shapes, 133 154 window_ref, 134 155 "dispatchEvent", 135 156 Value::Function(dispatch_event), ··· 140 161 141 162 // Also make document accessible from window if it exists. 142 163 if let Some(doc_val) = vm.get_global("document").cloned() { 143 - set_builtin_prop(&mut vm.gc, window_ref, "document", doc_val); 164 + set_builtin_prop(&mut vm.gc, &mut vm.shapes, window_ref, "document", doc_val); 144 165 } 145 166 146 167 // Register MessageEvent constructor. ··· 152 173 fn window_post_message(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 153 174 let message = args 154 175 .first() 155 - .map(|v| serialize_for_post_message(v, ctx.gc)) 176 + .map(|v| serialize_for_post_message(v, ctx.gc, ctx.shapes)) 156 177 .unwrap_or_default(); 157 178 158 179 let target_origin = args ··· 162 183 163 184 // Get the target window's context ID and sender's origin. 164 185 let (target_context_id, _sender_origin) = if let Value::Object(this_ref) = &ctx.this { 165 - let ctx_id = get_string_prop(ctx.gc, *this_ref, WINDOW_CONTEXT_ID_KEY).unwrap_or_default(); 166 - let origin = get_string_prop(ctx.gc, *this_ref, WINDOW_ORIGIN_KEY).unwrap_or_default(); 186 + let ctx_id = get_string_prop(ctx.gc, ctx.shapes, *this_ref, WINDOW_CONTEXT_ID_KEY) 187 + .unwrap_or_default(); 188 + let origin = 189 + get_string_prop(ctx.gc, ctx.shapes, *this_ref, WINDOW_ORIGIN_KEY).unwrap_or_default(); 167 190 (ctx_id, origin) 168 191 } else { 169 192 (String::new(), String::new()) ··· 189 212 } 190 213 191 214 /// Serialize a JS Value to a string for postMessage (simplified structured clone). 192 - fn serialize_for_post_message(value: &Value, gc: &Gc<HeapObject>) -> String { 215 + fn serialize_for_post_message(value: &Value, gc: &Gc<HeapObject>, shapes: &ShapeTable) -> String { 193 216 match value { 194 217 Value::Undefined => "undefined".to_string(), 195 218 Value::Null => "null".to_string(), ··· 207 230 Value::Object(r) => { 208 231 if let Some(HeapObject::Object(data)) = gc.get(*r) { 209 232 // Check if it's an array-like object. 210 - if data.properties.contains_key("length") { 233 + if data.contains_key("length", shapes) { 211 234 let len = data 212 - .properties 213 - .get("length") 235 + .get_property("length", shapes) 214 236 .map(|p| match &p.value { 215 237 Value::Number(n) => *n as usize, 216 238 _ => 0, ··· 218 240 .unwrap_or(0); 219 241 let mut items = Vec::new(); 220 242 for i in 0..len { 221 - if let Some(prop) = data.properties.get(&i.to_string()) { 222 - items.push(serialize_for_post_message(&prop.value, gc)); 243 + if let Some(prop) = data.get_property(&i.to_string(), shapes) { 244 + items.push(serialize_for_post_message(&prop.value, gc, shapes)); 223 245 } else { 224 246 items.push("null".to_string()); 225 247 } ··· 228 250 } 229 251 // Plain object: serialize enumerable properties. 230 252 let mut pairs = Vec::new(); 231 - for (k, prop) in &data.properties { 253 + for (k, prop) in data.property_entries(shapes) { 232 254 if prop.enumerable { 233 - let v = serialize_for_post_message(&prop.value, gc); 255 + let v = serialize_for_post_message(&prop.value, gc, shapes); 234 256 pairs.push(format!( 235 257 "\"{}\":{}", 236 258 k.replace('\\', "\\\\").replace('"', "\\\""), ··· 263 285 264 286 if let Some(Value::Object(opts_ref)) = args.get(1) { 265 287 if let Some(HeapObject::Object(opts)) = ctx.gc.get(*opts_ref) { 266 - if let Some(prop) = opts.properties.get("data") { 288 + if let Some(prop) = opts.get_property("data", ctx.shapes) { 267 289 data = prop.value.clone(); 268 290 } 269 - if let Some(prop) = opts.properties.get("origin") { 291 + if let Some(prop) = opts.get_property("origin", ctx.shapes) { 270 292 origin = prop.value.to_js_string(ctx.gc); 271 293 } 272 - if let Some(prop) = opts.properties.get("source") { 294 + if let Some(prop) = opts.get_property("source", ctx.shapes) { 273 295 source = prop.value.clone(); 274 296 } 275 297 } 276 298 } 277 299 278 - let event_ref = create_message_event(ctx.gc, &event_type, data, &origin, source); 300 + let event_ref = create_message_event(ctx.gc, ctx.shapes, &event_type, data, &origin, source); 279 301 Ok(Value::Object(event_ref)) 280 302 } 281 303 282 304 /// Create a MessageEvent object. 283 305 pub fn create_message_event( 284 306 gc: &mut Gc<HeapObject>, 307 + shapes: &mut ShapeTable, 285 308 event_type: &str, 286 309 data: Value, 287 310 origin: &str, ··· 290 313 let mut obj = ObjectData::new(); 291 314 292 315 // Standard Event properties. 293 - obj.properties.insert( 316 + obj.insert_property( 294 317 "type".to_string(), 295 318 Property::data(Value::String(event_type.to_string())), 319 + shapes, 296 320 ); 297 - obj.properties 298 - .insert("bubbles".to_string(), Property::data(Value::Boolean(false))); 299 - obj.properties.insert( 321 + obj.insert_property( 322 + "bubbles".to_string(), 323 + Property::data(Value::Boolean(false)), 324 + shapes, 325 + ); 326 + obj.insert_property( 300 327 "cancelable".to_string(), 301 328 Property::data(Value::Boolean(false)), 329 + shapes, 302 330 ); 303 - obj.properties.insert( 331 + obj.insert_property( 304 332 "defaultPrevented".to_string(), 305 333 Property::data(Value::Boolean(false)), 334 + shapes, 306 335 ); 307 - obj.properties 308 - .insert("eventPhase".to_string(), Property::data(Value::Number(0.0))); 309 - obj.properties 310 - .insert("target".to_string(), Property::data(Value::Null)); 311 - obj.properties 312 - .insert("currentTarget".to_string(), Property::data(Value::Null)); 313 - obj.properties 314 - .insert("timeStamp".to_string(), Property::data(Value::Number(0.0))); 336 + obj.insert_property( 337 + "eventPhase".to_string(), 338 + Property::data(Value::Number(0.0)), 339 + shapes, 340 + ); 341 + obj.insert_property("target".to_string(), Property::data(Value::Null), shapes); 342 + obj.insert_property( 343 + "currentTarget".to_string(), 344 + Property::data(Value::Null), 345 + shapes, 346 + ); 347 + obj.insert_property( 348 + "timeStamp".to_string(), 349 + Property::data(Value::Number(0.0)), 350 + shapes, 351 + ); 315 352 316 353 // Internal event state keys. 317 - obj.properties.insert( 354 + obj.insert_property( 318 355 "__event_type__".to_string(), 319 356 Property::builtin(Value::String(event_type.to_string())), 357 + shapes, 320 358 ); 321 - obj.properties.insert( 359 + obj.insert_property( 322 360 "__event_bubbles__".to_string(), 323 361 Property::builtin(Value::Boolean(false)), 362 + shapes, 324 363 ); 325 - obj.properties.insert( 364 + obj.insert_property( 326 365 "__event_cancelable__".to_string(), 327 366 Property::builtin(Value::Boolean(false)), 367 + shapes, 328 368 ); 329 - obj.properties.insert( 369 + obj.insert_property( 330 370 "__event_stop_prop__".to_string(), 331 371 Property::builtin(Value::Boolean(false)), 372 + shapes, 332 373 ); 333 - obj.properties.insert( 374 + obj.insert_property( 334 375 "__event_stop_immediate__".to_string(), 335 376 Property::builtin(Value::Boolean(false)), 377 + shapes, 336 378 ); 337 - obj.properties.insert( 379 + obj.insert_property( 338 380 "__event_default_prevented__".to_string(), 339 381 Property::builtin(Value::Boolean(false)), 382 + shapes, 340 383 ); 341 - obj.properties.insert( 384 + obj.insert_property( 342 385 "__event_phase__".to_string(), 343 386 Property::builtin(Value::Number(0.0)), 387 + shapes, 344 388 ); 345 389 346 390 // MessageEvent-specific properties. 347 - obj.properties 348 - .insert("data".to_string(), Property::data(data)); 349 - obj.properties.insert( 391 + obj.insert_property("data".to_string(), Property::data(data), shapes); 392 + obj.insert_property( 350 393 "origin".to_string(), 351 394 Property::data(Value::String(origin.to_string())), 395 + shapes, 352 396 ); 353 - obj.properties 354 - .insert("source".to_string(), Property::data(source)); 355 - obj.properties.insert( 397 + obj.insert_property("source".to_string(), Property::data(source), shapes); 398 + obj.insert_property( 356 399 "lastEventId".to_string(), 357 400 Property::data(Value::String(String::new())), 401 + shapes, 358 402 ); 359 - obj.properties 360 - .insert("ports".to_string(), Property::data(Value::Null)); 403 + obj.insert_property("ports".to_string(), Property::data(Value::Null), shapes); 361 404 362 405 gc.alloc(HeapObject::Object(obj)) 363 406 } ··· 366 409 /// Returns `Some(value)` for `contentWindow` and `contentDocument`. 367 410 pub fn resolve_iframe_property( 368 411 gc: &mut Gc<HeapObject>, 412 + shapes: &ShapeTable, 369 413 bridge: &DomBridge, 370 414 node_id: NodeId, 371 415 key: &str, ··· 400 444 if let Some(HeapObject::Object(data)) = gc.get(*window_ref) { 401 445 // Check origin. 402 446 let iframe_origin = data 403 - .properties 404 - .get(WINDOW_ORIGIN_KEY) 447 + .get_property(WINDOW_ORIGIN_KEY, shapes) 405 448 .and_then(|p| { 406 449 if let Value::String(s) = &p.value { 407 450 Some(s.clone()) ··· 415 458 416 459 if iframe_origin == parent_origin || iframe_origin.is_empty() { 417 460 // Same-origin: return the iframe's document. 418 - if let Some(prop) = data.properties.get("document") { 461 + if let Some(prop) = data.get_property("document", shapes) { 419 462 return Some(prop.value.clone()); 420 463 } 421 464 } ··· 456 499 } 457 500 458 501 /// Resolve `window.parent` and `window.top` properties. 459 - pub fn resolve_window_property(gc: &Gc<HeapObject>, gc_ref: GcRef, key: &str) -> Option<Value> { 502 + pub fn resolve_window_property( 503 + gc: &Gc<HeapObject>, 504 + shapes: &ShapeTable, 505 + gc_ref: GcRef, 506 + key: &str, 507 + ) -> Option<Value> { 460 508 // Check if this is a window object. 461 509 let is_window = if let Some(HeapObject::Object(data)) = gc.get(gc_ref) { 462 - data.properties.contains_key(WINDOW_CONTEXT_ID_KEY) 510 + data.contains_key(WINDOW_CONTEXT_ID_KEY, shapes) 463 511 } else { 464 512 false 465 513 }; ··· 472 520 "parent" => { 473 521 // Return the parent window. If not set, return self. 474 522 if let Some(HeapObject::Object(data)) = gc.get(gc_ref) { 475 - if let Some(prop) = data.properties.get("__parent_window__") { 523 + if let Some(prop) = data.get_property("__parent_window__", shapes) { 476 524 return Some(prop.value.clone()); 477 525 } 478 526 } ··· 483 531 let mut current = gc_ref; 484 532 loop { 485 533 if let Some(HeapObject::Object(data)) = gc.get(current) { 486 - if let Some(prop) = data.properties.get("__parent_window__") { 534 + if let Some(prop) = data.get_property("__parent_window__", shapes) { 487 535 if let Value::Object(parent_ref) = &prop.value { 488 536 if *parent_ref == current { 489 537 break; ··· 504 552 "length" => { 505 553 // Number of child frames. Read from __child_frames_count__ if set. 506 554 if let Some(HeapObject::Object(data)) = gc.get(gc_ref) { 507 - if let Some(prop) = data.properties.get("__child_frames_count__") { 555 + if let Some(prop) = data.get_property("__child_frames_count__", shapes) { 508 556 return Some(prop.value.clone()); 509 557 } 510 558 } ··· 515 563 } 516 564 517 565 /// Helper: get a string property from an object. 518 - fn get_string_prop(gc: &Gc<HeapObject>, gc_ref: GcRef, key: &str) -> Option<String> { 566 + fn get_string_prop( 567 + gc: &Gc<HeapObject>, 568 + shapes: &ShapeTable, 569 + gc_ref: GcRef, 570 + key: &str, 571 + ) -> Option<String> { 519 572 if let Some(HeapObject::Object(data)) = gc.get(gc_ref) { 520 - if let Some(prop) = data.properties.get(key) { 573 + if let Some(prop) = data.get_property(key, shapes) { 521 574 if let Value::String(s) = &prop.value { 522 575 return Some(s.clone()); 523 576 } ··· 534 587 #[test] 535 588 fn serialize_primitives() { 536 589 let gc = Gc::new(); 590 + let shapes = ShapeTable::new(); 537 591 assert_eq!( 538 - serialize_for_post_message(&Value::String("hello".to_string()), &gc), 592 + serialize_for_post_message(&Value::String("hello".to_string()), &gc, &shapes), 539 593 "\"hello\"" 540 594 ); 541 - assert_eq!(serialize_for_post_message(&Value::Number(42.0), &gc), "42"); 542 595 assert_eq!( 543 - serialize_for_post_message(&Value::Boolean(true), &gc), 596 + serialize_for_post_message(&Value::Number(42.0), &gc, &shapes), 597 + "42" 598 + ); 599 + assert_eq!( 600 + serialize_for_post_message(&Value::Boolean(true), &gc, &shapes), 544 601 "true" 545 602 ); 546 - assert_eq!(serialize_for_post_message(&Value::Null, &gc), "null"); 603 + assert_eq!( 604 + serialize_for_post_message(&Value::Null, &gc, &shapes), 605 + "null" 606 + ); 547 607 assert_eq!( 548 - serialize_for_post_message(&Value::Undefined, &gc), 608 + serialize_for_post_message(&Value::Undefined, &gc, &shapes), 549 609 "undefined" 550 610 ); 551 611 } ··· 553 613 #[test] 554 614 fn serialize_string_escapes() { 555 615 let gc = Gc::new(); 616 + let shapes = ShapeTable::new(); 556 617 assert_eq!( 557 - serialize_for_post_message(&Value::String("he\"llo".to_string()), &gc), 618 + serialize_for_post_message(&Value::String("he\"llo".to_string()), &gc, &shapes), 558 619 "\"he\\\"llo\"" 559 620 ); 560 621 } ··· 562 623 #[test] 563 624 fn message_event_creation() { 564 625 let mut gc = Gc::<HeapObject>::new(); 626 + let mut shapes = ShapeTable::new(); 565 627 let event_ref = create_message_event( 566 628 &mut gc, 629 + &mut shapes, 567 630 "message", 568 631 Value::String("test data".to_string()), 569 632 "https://example.com", ··· 571 634 ); 572 635 573 636 if let Some(HeapObject::Object(data)) = gc.get(event_ref) { 574 - match data.properties.get("type").map(|p| &p.value) { 637 + let type_val = data.get_property("type", &shapes); 638 + match type_val.as_ref().map(|p| &p.value) { 575 639 Some(Value::String(s)) => assert_eq!(s, "message"), 576 640 other => panic!("expected 'message', got {other:?}"), 577 641 } 578 - match data.properties.get("data").map(|p| &p.value) { 642 + let data_val = data.get_property("data", &shapes); 643 + match data_val.as_ref().map(|p| &p.value) { 579 644 Some(Value::String(s)) => assert_eq!(s, "test data"), 580 645 other => panic!("expected 'test data', got {other:?}"), 581 646 } 582 - match data.properties.get("origin").map(|p| &p.value) { 647 + let origin_val = data.get_property("origin", &shapes); 648 + match origin_val.as_ref().map(|p| &p.value) { 583 649 Some(Value::String(s)) => assert_eq!(s, "https://example.com"), 584 650 other => panic!("expected 'https://example.com', got {other:?}"), 585 651 }
+371 -184
crates/js/src/indexeddb.rs
··· 10 10 11 11 use crate::builtins::{make_native, set_builtin_prop}; 12 12 use crate::gc::{Gc, GcRef}; 13 + use crate::shape::ShapeTable; 13 14 use crate::vm::*; 14 15 15 16 // ── Structured clone value ────────────────────────────────────── ··· 60 61 } 61 62 62 63 /// Convert a JS `Value` into an `IdbValue` (structured clone). 63 - pub fn value_to_idb(gc: &Gc<HeapObject>, val: &Value) -> Result<IdbValue, String> { 64 + pub fn value_to_idb( 65 + gc: &Gc<HeapObject>, 66 + shapes: &ShapeTable, 67 + val: &Value, 68 + ) -> Result<IdbValue, String> { 64 69 match val { 65 70 Value::Undefined => Ok(IdbValue::Undefined), 66 71 Value::Null => Ok(IdbValue::Null), ··· 70 75 Value::Object(r) => { 71 76 match gc.get(*r) { 72 77 Some(HeapObject::Object(data)) => { 73 - if data.properties.contains_key("length") { 78 + if data.contains_key("length", shapes) { 74 79 // Array-like: extract indexed elements. 75 80 let len = data 76 - .properties 77 - .get("length") 81 + .get_property("length", shapes) 78 82 .map(|p| p.value.to_number() as usize) 79 83 .unwrap_or(0); 80 84 let mut items = Vec::with_capacity(len); 81 85 for i in 0..len { 82 86 let v = data 83 - .properties 84 - .get(&i.to_string()) 85 - .map(|p| &p.value) 86 - .unwrap_or(&Value::Undefined); 87 - items.push(value_to_idb(gc, v)?); 87 + .get_property(&i.to_string(), shapes) 88 + .map(|p| p.value) 89 + .unwrap_or(Value::Undefined); 90 + items.push(value_to_idb(gc, shapes, &v)?); 88 91 } 89 92 Ok(IdbValue::Array(items)) 90 93 } else { 91 94 // Plain object: extract enumerable properties. 92 95 let mut entries = Vec::new(); 93 - for (key, prop) in &data.properties { 96 + for (key, prop) in data.property_entries(shapes) { 94 97 if prop.enumerable && !key.starts_with("__") { 95 - entries.push((key.clone(), value_to_idb(gc, &prop.value)?)); 98 + entries.push((key.clone(), value_to_idb(gc, shapes, &prop.value)?)); 96 99 } 97 100 } 98 101 entries.sort_by(|a, b| a.0.cmp(&b.0)); ··· 107 110 } 108 111 109 112 /// Convert an `IdbValue` back into a JS `Value`. 110 - pub fn idb_to_value(gc: &mut Gc<HeapObject>, val: &IdbValue) -> Value { 113 + pub fn idb_to_value(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, val: &IdbValue) -> Value { 111 114 match val { 112 115 IdbValue::Undefined => Value::Undefined, 113 116 IdbValue::Null => Value::Null, ··· 117 120 IdbValue::Array(items) => { 118 121 let mut obj = ObjectData::new(); 119 122 for (i, item) in items.iter().enumerate() { 120 - obj.properties 121 - .insert(i.to_string(), Property::data(idb_to_value(gc, item))); 123 + obj.insert_property( 124 + i.to_string(), 125 + Property::data(idb_to_value(gc, shapes, item)), 126 + shapes, 127 + ); 122 128 } 123 - obj.properties.insert( 129 + obj.insert_property( 124 130 "length".to_string(), 125 131 Property { 126 132 value: Value::Number(items.len() as f64), ··· 128 134 enumerable: false, 129 135 configurable: false, 130 136 }, 137 + shapes, 131 138 ); 132 139 Value::Object(gc.alloc(HeapObject::Object(obj))) 133 140 } 134 141 IdbValue::Object(entries) => { 135 142 let mut obj = ObjectData::new(); 136 143 for (key, val) in entries { 137 - obj.properties 138 - .insert(key.clone(), Property::data(idb_to_value(gc, val))); 144 + obj.insert_property( 145 + key.clone(), 146 + Property::data(idb_to_value(gc, shapes, val)), 147 + shapes, 148 + ); 139 149 } 140 150 Value::Object(gc.alloc(HeapObject::Object(obj))) 141 151 } ··· 143 153 } 144 154 145 155 /// Convert a JS `Value` to an IDB key (Number or String only). 146 - fn value_to_key(gc: &Gc<HeapObject>, val: &Value) -> Option<IdbValue> { 156 + fn value_to_key(gc: &Gc<HeapObject>, shapes: &ShapeTable, val: &Value) -> Option<IdbValue> { 147 157 match val { 148 158 Value::Number(n) if !n.is_nan() => Some(IdbValue::Number(*n)), 149 159 Value::String(s) => Some(IdbValue::String(s.clone())), ··· 151 161 // Unwrap number/string objects. 152 162 match gc.get(*r) { 153 163 Some(HeapObject::Object(data)) => { 154 - if let Some(prop) = data.properties.get("valueOf") { 155 - value_to_key(gc, &prop.value) 164 + if let Some(prop) = data.get_property("valueOf", shapes) { 165 + value_to_key(gc, shapes, &prop.value) 156 166 } else { 157 167 None 158 168 } ··· 653 663 654 664 // ── Helper: read internal property ────────────────────────────── 655 665 656 - fn get_internal_str(gc: &Gc<HeapObject>, obj: GcRef, key: &str) -> Option<String> { 666 + fn get_internal_str( 667 + gc: &Gc<HeapObject>, 668 + shapes: &ShapeTable, 669 + obj: GcRef, 670 + key: &str, 671 + ) -> Option<String> { 657 672 match gc.get(obj) { 658 - Some(HeapObject::Object(data)) => match data.properties.get(key) { 673 + Some(HeapObject::Object(data)) => match data.get_property(key, shapes) { 659 674 Some(prop) => match &prop.value { 660 675 Value::String(s) => Some(s.clone()), 661 676 _ => None, ··· 666 681 } 667 682 } 668 683 669 - fn get_internal_bool(gc: &Gc<HeapObject>, obj: GcRef, key: &str) -> bool { 684 + fn get_internal_bool(gc: &Gc<HeapObject>, shapes: &ShapeTable, obj: GcRef, key: &str) -> bool { 670 685 match gc.get(obj) { 671 686 Some(HeapObject::Object(data)) => data 672 - .properties 673 - .get(key) 687 + .get_property(key, shapes) 674 688 .map(|p| p.value.to_boolean()) 675 689 .unwrap_or(false), 676 690 _ => false, 677 691 } 678 692 } 679 693 680 - fn get_obj_prop(gc: &Gc<HeapObject>, obj: GcRef, key: &str) -> Value { 694 + fn get_obj_prop(gc: &Gc<HeapObject>, shapes: &ShapeTable, obj: GcRef, key: &str) -> Value { 681 695 match gc.get(obj) { 682 696 Some(HeapObject::Object(data)) => data 683 - .properties 684 - .get(key) 697 + .get_property(key, shapes) 685 698 .map(|p| p.value.clone()) 686 699 .unwrap_or(Value::Undefined), 687 700 _ => Value::Undefined, ··· 690 703 691 704 // ── JS wrapper object creation ────────────────────────────────── 692 705 693 - fn create_idb_request(gc: &mut Gc<HeapObject>, object_proto: Option<GcRef>) -> GcRef { 706 + fn create_idb_request( 707 + gc: &mut Gc<HeapObject>, 708 + shapes: &mut ShapeTable, 709 + object_proto: Option<GcRef>, 710 + ) -> GcRef { 694 711 let mut data = ObjectData::new(); 695 712 data.prototype = object_proto; 696 - data.properties.insert( 713 + data.insert_property( 697 714 IDB_TYPE_KEY.to_string(), 698 715 Property::builtin(Value::String("request".to_string())), 716 + shapes, 699 717 ); 700 - data.properties 701 - .insert("result".to_string(), Property::data(Value::Undefined)); 702 - data.properties 703 - .insert("error".to_string(), Property::data(Value::Null)); 704 - data.properties.insert( 718 + data.insert_property( 719 + "result".to_string(), 720 + Property::data(Value::Undefined), 721 + shapes, 722 + ); 723 + data.insert_property("error".to_string(), Property::data(Value::Null), shapes); 724 + data.insert_property( 705 725 "readyState".to_string(), 706 726 Property::data(Value::String("pending".to_string())), 727 + shapes, 707 728 ); 708 - data.properties 709 - .insert("onsuccess".to_string(), Property::data(Value::Null)); 710 - data.properties 711 - .insert("onerror".to_string(), Property::data(Value::Null)); 712 - data.properties 713 - .insert("onupgradeneeded".to_string(), Property::data(Value::Null)); 729 + data.insert_property("onsuccess".to_string(), Property::data(Value::Null), shapes); 730 + data.insert_property("onerror".to_string(), Property::data(Value::Null), shapes); 731 + data.insert_property( 732 + "onupgradeneeded".to_string(), 733 + Property::data(Value::Null), 734 + shapes, 735 + ); 714 736 gc.alloc(HeapObject::Object(data)) 715 737 } 716 738 717 739 fn create_idb_database_wrapper( 718 740 gc: &mut Gc<HeapObject>, 741 + shapes: &mut ShapeTable, 719 742 name: &str, 720 743 version: u64, 721 744 store_names: &[String], ··· 723 746 ) -> GcRef { 724 747 let mut data = ObjectData::new(); 725 748 data.prototype = object_proto; 726 - data.properties.insert( 749 + data.insert_property( 727 750 IDB_TYPE_KEY.to_string(), 728 751 Property::builtin(Value::String("database".to_string())), 752 + shapes, 729 753 ); 730 - data.properties.insert( 754 + data.insert_property( 731 755 IDB_DB_NAME_KEY.to_string(), 732 756 Property::builtin(Value::String(name.to_string())), 757 + shapes, 733 758 ); 734 - data.properties.insert( 759 + data.insert_property( 735 760 "name".to_string(), 736 761 Property::data(Value::String(name.to_string())), 762 + shapes, 737 763 ); 738 - data.properties.insert( 764 + data.insert_property( 739 765 "version".to_string(), 740 766 Property::data(Value::Number(version as f64)), 767 + shapes, 741 768 ); 742 - data.properties.insert( 769 + data.insert_property( 743 770 IDB_UPGRADING_KEY.to_string(), 744 771 Property::builtin(Value::Boolean(false)), 772 + shapes, 745 773 ); 746 - data.properties 747 - .insert("onclose".to_string(), Property::data(Value::Null)); 774 + data.insert_property("onclose".to_string(), Property::data(Value::Null), shapes); 748 775 749 776 // Build objectStoreNames as an array-like object. 750 - let names_ref = make_string_list(gc, store_names); 751 - data.properties.insert( 777 + let names_ref = make_string_list(gc, shapes, store_names); 778 + data.insert_property( 752 779 "objectStoreNames".to_string(), 753 780 Property::data(Value::Object(names_ref)), 781 + shapes, 754 782 ); 755 783 756 784 let db_ref = gc.alloc(HeapObject::Object(data)); ··· 764 792 ]; 765 793 for &(name, callback) in methods { 766 794 let func = make_native(gc, name, callback); 767 - set_builtin_prop(gc, db_ref, name, Value::Function(func)); 795 + set_builtin_prop(gc, shapes, db_ref, name, Value::Function(func)); 768 796 } 769 797 770 798 db_ref ··· 772 800 773 801 fn create_idb_transaction_wrapper( 774 802 gc: &mut Gc<HeapObject>, 803 + shapes: &mut ShapeTable, 775 804 db_name: &str, 776 805 mode: &str, 777 806 db_ref: GcRef, ··· 779 808 ) -> GcRef { 780 809 let mut data = ObjectData::new(); 781 810 data.prototype = object_proto; 782 - data.properties.insert( 811 + data.insert_property( 783 812 IDB_TYPE_KEY.to_string(), 784 813 Property::builtin(Value::String("transaction".to_string())), 814 + shapes, 785 815 ); 786 - data.properties.insert( 816 + data.insert_property( 787 817 IDB_DB_NAME_KEY.to_string(), 788 818 Property::builtin(Value::String(db_name.to_string())), 819 + shapes, 789 820 ); 790 - data.properties.insert( 821 + data.insert_property( 791 822 IDB_TX_MODE_KEY.to_string(), 792 823 Property::builtin(Value::String(mode.to_string())), 824 + shapes, 793 825 ); 794 - data.properties.insert( 826 + data.insert_property( 795 827 "mode".to_string(), 796 828 Property::data(Value::String(mode.to_string())), 829 + shapes, 797 830 ); 798 - data.properties 799 - .insert("db".to_string(), Property::data(Value::Object(db_ref))); 800 - data.properties 801 - .insert("oncomplete".to_string(), Property::data(Value::Null)); 802 - data.properties 803 - .insert("onabort".to_string(), Property::data(Value::Null)); 804 - data.properties 805 - .insert("onerror".to_string(), Property::data(Value::Null)); 831 + data.insert_property( 832 + "db".to_string(), 833 + Property::data(Value::Object(db_ref)), 834 + shapes, 835 + ); 836 + data.insert_property( 837 + "oncomplete".to_string(), 838 + Property::data(Value::Null), 839 + shapes, 840 + ); 841 + data.insert_property("onabort".to_string(), Property::data(Value::Null), shapes); 842 + data.insert_property("onerror".to_string(), Property::data(Value::Null), shapes); 806 843 807 844 let tx_ref = gc.alloc(HeapObject::Object(data)); 808 845 ··· 812 849 ]; 813 850 for &(name, callback) in methods { 814 851 let func = make_native(gc, name, callback); 815 - set_builtin_prop(gc, tx_ref, name, Value::Function(func)); 852 + set_builtin_prop(gc, shapes, tx_ref, name, Value::Function(func)); 816 853 } 817 854 818 855 tx_ref ··· 820 857 821 858 fn create_idb_store_wrapper( 822 859 gc: &mut Gc<HeapObject>, 860 + shapes: &mut ShapeTable, 823 861 db_name: &str, 824 862 store_name: &str, 825 863 tx_ref: GcRef, ··· 827 865 ) -> GcRef { 828 866 let mut data = ObjectData::new(); 829 867 data.prototype = object_proto; 830 - data.properties.insert( 868 + data.insert_property( 831 869 IDB_TYPE_KEY.to_string(), 832 870 Property::builtin(Value::String("objectStore".to_string())), 871 + shapes, 833 872 ); 834 - data.properties.insert( 873 + data.insert_property( 835 874 IDB_DB_NAME_KEY.to_string(), 836 875 Property::builtin(Value::String(db_name.to_string())), 876 + shapes, 837 877 ); 838 - data.properties.insert( 878 + data.insert_property( 839 879 IDB_STORE_NAME_KEY.to_string(), 840 880 Property::builtin(Value::String(store_name.to_string())), 881 + shapes, 841 882 ); 842 - data.properties.insert( 883 + data.insert_property( 843 884 "name".to_string(), 844 885 Property::data(Value::String(store_name.to_string())), 886 + shapes, 845 887 ); 846 - data.properties.insert( 888 + data.insert_property( 847 889 "transaction".to_string(), 848 890 Property::data(Value::Object(tx_ref)), 891 + shapes, 849 892 ); 850 893 851 894 let store_ref = gc.alloc(HeapObject::Object(data)); ··· 861 904 ]; 862 905 for &(name, callback) in methods { 863 906 let func = make_native(gc, name, callback); 864 - set_builtin_prop(gc, store_ref, name, Value::Function(func)); 907 + set_builtin_prop(gc, shapes, store_ref, name, Value::Function(func)); 865 908 } 866 909 867 910 store_ref ··· 869 912 870 913 fn create_idb_index_wrapper( 871 914 gc: &mut Gc<HeapObject>, 915 + shapes: &mut ShapeTable, 872 916 db_name: &str, 873 917 store_name: &str, 874 918 index_name: &str, ··· 876 920 ) -> GcRef { 877 921 let mut data = ObjectData::new(); 878 922 data.prototype = object_proto; 879 - data.properties.insert( 923 + data.insert_property( 880 924 IDB_TYPE_KEY.to_string(), 881 925 Property::builtin(Value::String("index".to_string())), 926 + shapes, 882 927 ); 883 - data.properties.insert( 928 + data.insert_property( 884 929 IDB_DB_NAME_KEY.to_string(), 885 930 Property::builtin(Value::String(db_name.to_string())), 931 + shapes, 886 932 ); 887 - data.properties.insert( 933 + data.insert_property( 888 934 IDB_STORE_NAME_KEY.to_string(), 889 935 Property::builtin(Value::String(store_name.to_string())), 936 + shapes, 890 937 ); 891 - data.properties.insert( 938 + data.insert_property( 892 939 IDB_INDEX_NAME_KEY.to_string(), 893 940 Property::builtin(Value::String(index_name.to_string())), 941 + shapes, 894 942 ); 895 - data.properties.insert( 943 + data.insert_property( 896 944 "name".to_string(), 897 945 Property::data(Value::String(index_name.to_string())), 946 + shapes, 898 947 ); 899 948 900 949 let idx_ref = gc.alloc(HeapObject::Object(data)); ··· 902 951 let methods: &[NativeMethod] = &[("get", idb_index_get)]; 903 952 for &(name, callback) in methods { 904 953 let func = make_native(gc, name, callback); 905 - set_builtin_prop(gc, idx_ref, name, Value::Function(func)); 954 + set_builtin_prop(gc, shapes, idx_ref, name, Value::Function(func)); 906 955 } 907 956 908 957 idx_ref 909 958 } 910 959 911 960 /// Create an array-like DOMStringList object. 912 - fn make_string_list(gc: &mut Gc<HeapObject>, items: &[String]) -> GcRef { 961 + fn make_string_list(gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable, items: &[String]) -> GcRef { 913 962 let mut obj = ObjectData::new(); 914 963 for (i, s) in items.iter().enumerate() { 915 - obj.properties 916 - .insert(i.to_string(), Property::data(Value::String(s.clone()))); 964 + obj.insert_property( 965 + i.to_string(), 966 + Property::data(Value::String(s.clone())), 967 + shapes, 968 + ); 917 969 } 918 - obj.properties.insert( 970 + obj.insert_property( 919 971 "length".to_string(), 920 972 Property { 921 973 value: Value::Number(items.len() as f64), ··· 923 975 enumerable: false, 924 976 configurable: false, 925 977 }, 978 + shapes, 926 979 ); 927 980 gc.alloc(HeapObject::Object(obj)) 928 981 } ··· 930 983 /// Create an IDB event object for firing callbacks. 931 984 pub fn create_idb_event( 932 985 gc: &mut Gc<HeapObject>, 986 + shapes: &mut ShapeTable, 933 987 event_type: &str, 934 988 target: GcRef, 935 989 old_version: f64, 936 990 new_version: f64, 937 991 ) -> GcRef { 938 992 let mut data = ObjectData::new(); 939 - data.properties.insert( 993 + data.insert_property( 940 994 "type".to_string(), 941 995 Property::data(Value::String(event_type.to_string())), 996 + shapes, 942 997 ); 943 - data.properties 944 - .insert("target".to_string(), Property::data(Value::Object(target))); 945 - data.properties.insert( 998 + data.insert_property( 999 + "target".to_string(), 1000 + Property::data(Value::Object(target)), 1001 + shapes, 1002 + ); 1003 + data.insert_property( 946 1004 "oldVersion".to_string(), 947 1005 Property::data(Value::Number(old_version)), 1006 + shapes, 948 1007 ); 949 - data.properties.insert( 1008 + data.insert_property( 950 1009 "newVersion".to_string(), 951 1010 Property::data(Value::Number(new_version)), 1011 + shapes, 952 1012 ); 953 1013 gc.alloc(HeapObject::Object(data)) 954 1014 } ··· 980 1040 } 981 1041 }; 982 1042 983 - let request_ref = create_idb_request(ctx.gc, None); 1043 + let request_ref = create_idb_request(ctx.gc, ctx.shapes, None); 984 1044 985 1045 let mut idb = bridge.indexeddb.borrow_mut(); 986 1046 let old_version; ··· 993 1053 drop(idb); 994 1054 set_builtin_prop( 995 1055 ctx.gc, 1056 + ctx.shapes, 996 1057 request_ref, 997 1058 "readyState", 998 1059 Value::String("done".to_string()), 999 1060 ); 1000 1061 set_builtin_prop( 1001 1062 ctx.gc, 1063 + ctx.shapes, 1002 1064 request_ref, 1003 1065 "error", 1004 1066 Value::String( ··· 1033 1095 .unwrap_or_default(); 1034 1096 drop(idb); 1035 1097 1036 - let db_ref = create_idb_database_wrapper(ctx.gc, &name, requested_version, &store_names, None); 1098 + let db_ref = create_idb_database_wrapper( 1099 + ctx.gc, 1100 + ctx.shapes, 1101 + &name, 1102 + requested_version, 1103 + &store_names, 1104 + None, 1105 + ); 1037 1106 1038 1107 // Set request.result = db. 1039 - set_builtin_prop(ctx.gc, request_ref, "result", Value::Object(db_ref)); 1108 + set_builtin_prop( 1109 + ctx.gc, 1110 + ctx.shapes, 1111 + request_ref, 1112 + "result", 1113 + Value::Object(db_ref), 1114 + ); 1040 1115 1041 1116 if needs_upgrade { 1042 1117 // Mark database as in upgrade mode. 1043 - set_builtin_prop(ctx.gc, db_ref, IDB_UPGRADING_KEY, Value::Boolean(true)); 1118 + set_builtin_prop( 1119 + ctx.gc, 1120 + ctx.shapes, 1121 + db_ref, 1122 + IDB_UPGRADING_KEY, 1123 + Value::Boolean(true), 1124 + ); 1044 1125 1045 1126 // Create the version change transaction wrapper. 1046 - let tx_ref = create_idb_transaction_wrapper(ctx.gc, &name, "versionchange", db_ref, None); 1047 - set_builtin_prop(ctx.gc, request_ref, "transaction", Value::Object(tx_ref)); 1127 + let tx_ref = create_idb_transaction_wrapper( 1128 + ctx.gc, 1129 + ctx.shapes, 1130 + &name, 1131 + "versionchange", 1132 + db_ref, 1133 + None, 1134 + ); 1135 + set_builtin_prop( 1136 + ctx.gc, 1137 + ctx.shapes, 1138 + request_ref, 1139 + "transaction", 1140 + Value::Object(tx_ref), 1141 + ); 1048 1142 1049 1143 queue_idb_event(PendingIdbEvent { 1050 1144 target: request_ref, ··· 1076 1170 1077 1171 bridge.indexeddb.borrow_mut().databases.remove(&name); 1078 1172 1079 - let request_ref = create_idb_request(ctx.gc, None); 1080 - set_builtin_prop(ctx.gc, request_ref, "result", Value::Undefined); 1173 + let request_ref = create_idb_request(ctx.gc, ctx.shapes, None); 1174 + set_builtin_prop(ctx.gc, ctx.shapes, request_ref, "result", Value::Undefined); 1081 1175 1082 1176 queue_idb_event(PendingIdbEvent { 1083 1177 target: request_ref, ··· 1106 1200 }; 1107 1201 1108 1202 // Must be in upgrade mode. 1109 - if !get_internal_bool(ctx.gc, db_ref, IDB_UPGRADING_KEY) { 1203 + if !get_internal_bool(ctx.gc, ctx.shapes, db_ref, IDB_UPGRADING_KEY) { 1110 1204 return Err(RuntimeError { 1111 1205 kind: ErrorKind::Error, 1112 1206 message: "InvalidStateError: not in a version change transaction".to_string(), 1113 1207 }); 1114 1208 } 1115 1209 1116 - let db_name = get_internal_str(ctx.gc, db_ref, IDB_DB_NAME_KEY) 1210 + let db_name = get_internal_str(ctx.gc, ctx.shapes, db_ref, IDB_DB_NAME_KEY) 1117 1211 .ok_or_else(|| RuntimeError::type_error("invalid database object"))?; 1118 1212 1119 1213 let store_name = args ··· 1128 1222 let mut auto_increment = false; 1129 1223 if let Some(Value::Object(opts_ref)) = args.get(1) { 1130 1224 if let Some(HeapObject::Object(opts)) = ctx.gc.get(*opts_ref) { 1131 - if let Some(prop) = opts.properties.get("keyPath") { 1225 + if let Some(prop) = opts.get_property("keyPath", ctx.shapes) { 1132 1226 if let Value::String(kp) = &prop.value { 1133 1227 key_path = Some(kp.clone()); 1134 1228 } 1135 1229 } 1136 - if let Some(prop) = opts.properties.get("autoIncrement") { 1230 + if let Some(prop) = opts.get_property("autoIncrement", ctx.shapes) { 1137 1231 auto_increment = prop.value.to_boolean(); 1138 1232 } 1139 1233 } ··· 1168 1262 .get(&db_name) 1169 1263 .map(|db| db.stores.keys().cloned().collect()) 1170 1264 .unwrap_or_default(); 1171 - let names_ref = make_string_list(ctx.gc, &store_names); 1172 - set_builtin_prop(ctx.gc, db_ref, "objectStoreNames", Value::Object(names_ref)); 1265 + let names_ref = make_string_list(ctx.gc, ctx.shapes, &store_names); 1266 + set_builtin_prop( 1267 + ctx.gc, 1268 + ctx.shapes, 1269 + db_ref, 1270 + "objectStoreNames", 1271 + Value::Object(names_ref), 1272 + ); 1173 1273 1174 1274 // Return an IDBObjectStore wrapper. During upgrade, use versionchange tx. 1175 - let tx_ref = match get_obj_prop(ctx.gc, db_ref, "transaction") { 1275 + let tx_ref = match get_obj_prop(ctx.gc, ctx.shapes, db_ref, "transaction") { 1176 1276 Value::Object(r) => r, 1177 1277 _ => { 1178 1278 // Create a dummy transaction wrapper for the upgrade. 1179 - create_idb_transaction_wrapper(ctx.gc, &db_name, "versionchange", db_ref, None) 1279 + create_idb_transaction_wrapper( 1280 + ctx.gc, 1281 + ctx.shapes, 1282 + &db_name, 1283 + "versionchange", 1284 + db_ref, 1285 + None, 1286 + ) 1180 1287 } 1181 1288 }; 1182 1289 1183 1290 // Look for existing transaction property on the request. 1184 - let request_tx = get_obj_prop(ctx.gc, db_ref, "__upgrade_tx__"); 1291 + let request_tx = get_obj_prop(ctx.gc, ctx.shapes, db_ref, "__upgrade_tx__"); 1185 1292 let actual_tx = if let Value::Object(r) = request_tx { 1186 1293 r 1187 1294 } else { 1188 1295 tx_ref 1189 1296 }; 1190 1297 1191 - let store_ref = create_idb_store_wrapper(ctx.gc, &db_name, &store_name, actual_tx, None); 1298 + let store_ref = 1299 + create_idb_store_wrapper(ctx.gc, ctx.shapes, &db_name, &store_name, actual_tx, None); 1192 1300 Ok(Value::Object(store_ref)) 1193 1301 } 1194 1302 ··· 1210 1318 } 1211 1319 }; 1212 1320 1213 - if !get_internal_bool(ctx.gc, db_ref, IDB_UPGRADING_KEY) { 1321 + if !get_internal_bool(ctx.gc, ctx.shapes, db_ref, IDB_UPGRADING_KEY) { 1214 1322 return Err(RuntimeError { 1215 1323 kind: ErrorKind::Error, 1216 1324 message: "InvalidStateError: not in a version change transaction".to_string(), 1217 1325 }); 1218 1326 } 1219 1327 1220 - let db_name = get_internal_str(ctx.gc, db_ref, IDB_DB_NAME_KEY) 1328 + let db_name = get_internal_str(ctx.gc, ctx.shapes, db_ref, IDB_DB_NAME_KEY) 1221 1329 .ok_or_else(|| RuntimeError::type_error("invalid database object"))?; 1222 1330 1223 1331 let store_name = args ··· 1247 1355 .get(&db_name) 1248 1356 .map(|db| db.stores.keys().cloned().collect()) 1249 1357 .unwrap_or_default(); 1250 - let names_ref = make_string_list(ctx.gc, &store_names); 1251 - set_builtin_prop(ctx.gc, db_ref, "objectStoreNames", Value::Object(names_ref)); 1358 + let names_ref = make_string_list(ctx.gc, ctx.shapes, &store_names); 1359 + set_builtin_prop( 1360 + ctx.gc, 1361 + ctx.shapes, 1362 + db_ref, 1363 + "objectStoreNames", 1364 + Value::Object(names_ref), 1365 + ); 1252 1366 1253 1367 Ok(Value::Undefined) 1254 1368 } ··· 1264 1378 _ => return Err(RuntimeError::type_error("transaction on non-database")), 1265 1379 }; 1266 1380 1267 - let db_name = get_internal_str(ctx.gc, db_ref, IDB_DB_NAME_KEY) 1381 + let db_name = get_internal_str(ctx.gc, ctx.shapes, db_ref, IDB_DB_NAME_KEY) 1268 1382 .ok_or_else(|| RuntimeError::type_error("invalid database object"))?; 1269 1383 1270 1384 // Parse store names argument (string or array of strings). ··· 1274 1388 let mut names = Vec::new(); 1275 1389 if let Some(HeapObject::Object(data)) = ctx.gc.get(*r) { 1276 1390 let len = data 1277 - .properties 1278 - .get("length") 1391 + .get_property("length", ctx.shapes) 1279 1392 .map(|p| p.value.to_number() as usize) 1280 1393 .unwrap_or(0); 1281 1394 for i in 0..len { 1282 - if let Some(prop) = data.properties.get(&i.to_string()) { 1395 + if let Some(prop) = data.get_property(&i.to_string(), ctx.shapes) { 1283 1396 names.push(prop.value.to_js_string(ctx.gc)); 1284 1397 } 1285 1398 } ··· 1315 1428 } 1316 1429 } 1317 1430 1318 - let tx_ref = create_idb_transaction_wrapper(ctx.gc, &db_name, &mode, db_ref, None); 1431 + let tx_ref = create_idb_transaction_wrapper(ctx.gc, ctx.shapes, &db_name, &mode, db_ref, None); 1319 1432 1320 1433 // Queue auto-commit (complete event) for the transaction. 1321 1434 queue_idb_event(PendingIdbEvent { ··· 1332 1445 Value::Object(r) => *r, 1333 1446 _ => return Err(RuntimeError::type_error("close on non-database")), 1334 1447 }; 1335 - set_builtin_prop(ctx.gc, db_ref, "__closed__", Value::Boolean(true)); 1448 + set_builtin_prop( 1449 + ctx.gc, 1450 + ctx.shapes, 1451 + db_ref, 1452 + "__closed__", 1453 + Value::Boolean(true), 1454 + ); 1336 1455 Ok(Value::Undefined) 1337 1456 } 1338 1457 ··· 1347 1466 _ => return Err(RuntimeError::type_error("objectStore on non-transaction")), 1348 1467 }; 1349 1468 1350 - let db_name = get_internal_str(ctx.gc, tx_ref, IDB_DB_NAME_KEY) 1469 + let db_name = get_internal_str(ctx.gc, ctx.shapes, tx_ref, IDB_DB_NAME_KEY) 1351 1470 .ok_or_else(|| RuntimeError::type_error("invalid transaction object"))?; 1352 1471 1353 1472 let store_name = args ··· 1370 1489 } 1371 1490 } 1372 1491 1373 - let store_ref = create_idb_store_wrapper(ctx.gc, &db_name, &store_name, tx_ref, None); 1492 + let store_ref = 1493 + create_idb_store_wrapper(ctx.gc, ctx.shapes, &db_name, &store_name, tx_ref, None); 1374 1494 Ok(Value::Object(store_ref)) 1375 1495 } 1376 1496 ··· 1407 1527 }; 1408 1528 1409 1529 // Check transaction mode. 1410 - let tx_val = get_obj_prop(ctx.gc, store_ref, "transaction"); 1530 + let tx_val = get_obj_prop(ctx.gc, ctx.shapes, store_ref, "transaction"); 1411 1531 if let Value::Object(tx_ref) = &tx_val { 1412 - let mode = get_internal_str(ctx.gc, *tx_ref, IDB_TX_MODE_KEY); 1532 + let mode = get_internal_str(ctx.gc, ctx.shapes, *tx_ref, IDB_TX_MODE_KEY); 1413 1533 if mode.as_deref() == Some("readonly") { 1414 1534 return Err(RuntimeError { 1415 1535 kind: ErrorKind::Error, ··· 1418 1538 } 1419 1539 } 1420 1540 1421 - let db_name = get_internal_str(ctx.gc, store_ref, IDB_DB_NAME_KEY) 1541 + let db_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_DB_NAME_KEY) 1422 1542 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1423 - let store_name = get_internal_str(ctx.gc, store_ref, IDB_STORE_NAME_KEY) 1543 + let store_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_STORE_NAME_KEY) 1424 1544 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1425 1545 1426 1546 let value = args.first().cloned().unwrap_or(Value::Undefined); 1427 - let idb_value = value_to_idb(ctx.gc, &value).map_err(|e| RuntimeError::type_error(&e))?; 1547 + let idb_value = 1548 + value_to_idb(ctx.gc, ctx.shapes, &value).map_err(|e| RuntimeError::type_error(&e))?; 1428 1549 1429 - let explicit_key = args.get(1).and_then(|v| value_to_key(ctx.gc, v)); 1550 + let explicit_key = args 1551 + .get(1) 1552 + .and_then(|v| value_to_key(ctx.gc, ctx.shapes, v)); 1430 1553 1431 1554 let mut idb = bridge.indexeddb.borrow_mut(); 1432 1555 let db = idb ··· 1464 1587 IdbValue::String(s) => Value::String(s.clone()), 1465 1588 _ => Value::Undefined, 1466 1589 }; 1467 - let request_ref = create_idb_request(ctx.gc, None); 1468 - set_builtin_prop(ctx.gc, request_ref, "result", result_val); 1590 + let request_ref = create_idb_request(ctx.gc, ctx.shapes, None); 1591 + set_builtin_prop(ctx.gc, ctx.shapes, request_ref, "result", result_val); 1469 1592 set_builtin_prop( 1470 1593 ctx.gc, 1594 + ctx.shapes, 1471 1595 request_ref, 1472 1596 "readyState", 1473 1597 Value::String("done".to_string()), ··· 1492 1616 _ => return Err(RuntimeError::type_error("get on non-objectStore")), 1493 1617 }; 1494 1618 1495 - let db_name = get_internal_str(ctx.gc, store_ref, IDB_DB_NAME_KEY) 1619 + let db_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_DB_NAME_KEY) 1496 1620 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1497 - let store_name = get_internal_str(ctx.gc, store_ref, IDB_STORE_NAME_KEY) 1621 + let store_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_STORE_NAME_KEY) 1498 1622 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1499 1623 1500 1624 let key = args 1501 1625 .first() 1502 - .and_then(|v| value_to_key(ctx.gc, v)) 1626 + .and_then(|v| value_to_key(ctx.gc, ctx.shapes, v)) 1503 1627 .ok_or_else(|| RuntimeError::type_error("get requires a valid key"))?; 1504 1628 1505 1629 let idb = bridge.indexeddb.borrow(); ··· 1508 1632 .get(&db_name) 1509 1633 .and_then(|db| db.stores.get(&store_name)) 1510 1634 .and_then(|store| store.get(&key)) 1511 - .map(|v| idb_to_value(ctx.gc, v)) 1635 + .map(|v| idb_to_value(ctx.gc, ctx.shapes, v)) 1512 1636 .unwrap_or(Value::Undefined); 1513 1637 drop(idb); 1514 1638 1515 - let request_ref = create_idb_request(ctx.gc, None); 1516 - set_builtin_prop(ctx.gc, request_ref, "result", result); 1639 + let request_ref = create_idb_request(ctx.gc, ctx.shapes, None); 1640 + set_builtin_prop(ctx.gc, ctx.shapes, request_ref, "result", result); 1517 1641 set_builtin_prop( 1518 1642 ctx.gc, 1643 + ctx.shapes, 1519 1644 request_ref, 1520 1645 "readyState", 1521 1646 Value::String("done".to_string()), ··· 1541 1666 }; 1542 1667 1543 1668 // Check transaction mode. 1544 - let tx_val = get_obj_prop(ctx.gc, store_ref, "transaction"); 1669 + let tx_val = get_obj_prop(ctx.gc, ctx.shapes, store_ref, "transaction"); 1545 1670 if let Value::Object(tx_ref) = &tx_val { 1546 - let mode = get_internal_str(ctx.gc, *tx_ref, IDB_TX_MODE_KEY); 1671 + let mode = get_internal_str(ctx.gc, ctx.shapes, *tx_ref, IDB_TX_MODE_KEY); 1547 1672 if mode.as_deref() == Some("readonly") { 1548 1673 return Err(RuntimeError { 1549 1674 kind: ErrorKind::Error, ··· 1552 1677 } 1553 1678 } 1554 1679 1555 - let db_name = get_internal_str(ctx.gc, store_ref, IDB_DB_NAME_KEY) 1680 + let db_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_DB_NAME_KEY) 1556 1681 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1557 - let store_name = get_internal_str(ctx.gc, store_ref, IDB_STORE_NAME_KEY) 1682 + let store_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_STORE_NAME_KEY) 1558 1683 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1559 1684 1560 1685 let key = args 1561 1686 .first() 1562 - .and_then(|v| value_to_key(ctx.gc, v)) 1687 + .and_then(|v| value_to_key(ctx.gc, ctx.shapes, v)) 1563 1688 .ok_or_else(|| RuntimeError::type_error("delete requires a valid key"))?; 1564 1689 1565 1690 let mut idb = bridge.indexeddb.borrow_mut(); ··· 1570 1695 } 1571 1696 drop(idb); 1572 1697 1573 - let request_ref = create_idb_request(ctx.gc, None); 1574 - set_builtin_prop(ctx.gc, request_ref, "result", Value::Undefined); 1698 + let request_ref = create_idb_request(ctx.gc, ctx.shapes, None); 1699 + set_builtin_prop(ctx.gc, ctx.shapes, request_ref, "result", Value::Undefined); 1575 1700 set_builtin_prop( 1576 1701 ctx.gc, 1702 + ctx.shapes, 1577 1703 request_ref, 1578 1704 "readyState", 1579 1705 Value::String("done".to_string()), ··· 1599 1725 }; 1600 1726 1601 1727 // Check transaction mode. 1602 - let tx_val = get_obj_prop(ctx.gc, store_ref, "transaction"); 1728 + let tx_val = get_obj_prop(ctx.gc, ctx.shapes, store_ref, "transaction"); 1603 1729 if let Value::Object(tx_ref) = &tx_val { 1604 - let mode = get_internal_str(ctx.gc, *tx_ref, IDB_TX_MODE_KEY); 1730 + let mode = get_internal_str(ctx.gc, ctx.shapes, *tx_ref, IDB_TX_MODE_KEY); 1605 1731 if mode.as_deref() == Some("readonly") { 1606 1732 return Err(RuntimeError { 1607 1733 kind: ErrorKind::Error, ··· 1610 1736 } 1611 1737 } 1612 1738 1613 - let db_name = get_internal_str(ctx.gc, store_ref, IDB_DB_NAME_KEY) 1739 + let db_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_DB_NAME_KEY) 1614 1740 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1615 - let store_name = get_internal_str(ctx.gc, store_ref, IDB_STORE_NAME_KEY) 1741 + let store_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_STORE_NAME_KEY) 1616 1742 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1617 1743 1618 1744 let mut idb = bridge.indexeddb.borrow_mut(); ··· 1623 1749 } 1624 1750 drop(idb); 1625 1751 1626 - let request_ref = create_idb_request(ctx.gc, None); 1627 - set_builtin_prop(ctx.gc, request_ref, "result", Value::Undefined); 1752 + let request_ref = create_idb_request(ctx.gc, ctx.shapes, None); 1753 + set_builtin_prop(ctx.gc, ctx.shapes, request_ref, "result", Value::Undefined); 1628 1754 set_builtin_prop( 1629 1755 ctx.gc, 1756 + ctx.shapes, 1630 1757 request_ref, 1631 1758 "readyState", 1632 1759 Value::String("done".to_string()), ··· 1651 1778 _ => return Err(RuntimeError::type_error("count on non-objectStore")), 1652 1779 }; 1653 1780 1654 - let db_name = get_internal_str(ctx.gc, store_ref, IDB_DB_NAME_KEY) 1781 + let db_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_DB_NAME_KEY) 1655 1782 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1656 - let store_name = get_internal_str(ctx.gc, store_ref, IDB_STORE_NAME_KEY) 1783 + let store_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_STORE_NAME_KEY) 1657 1784 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1658 1785 1659 1786 let idb = bridge.indexeddb.borrow(); ··· 1665 1792 .unwrap_or(0); 1666 1793 drop(idb); 1667 1794 1668 - let request_ref = create_idb_request(ctx.gc, None); 1669 - set_builtin_prop(ctx.gc, request_ref, "result", Value::Number(count as f64)); 1795 + let request_ref = create_idb_request(ctx.gc, ctx.shapes, None); 1796 + set_builtin_prop( 1797 + ctx.gc, 1798 + ctx.shapes, 1799 + request_ref, 1800 + "result", 1801 + Value::Number(count as f64), 1802 + ); 1670 1803 set_builtin_prop( 1671 1804 ctx.gc, 1805 + ctx.shapes, 1672 1806 request_ref, 1673 1807 "readyState", 1674 1808 Value::String("done".to_string()), ··· 1693 1827 _ => return Err(RuntimeError::type_error("getAll on non-objectStore")), 1694 1828 }; 1695 1829 1696 - let db_name = get_internal_str(ctx.gc, store_ref, IDB_DB_NAME_KEY) 1830 + let db_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_DB_NAME_KEY) 1697 1831 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1698 - let store_name = get_internal_str(ctx.gc, store_ref, IDB_STORE_NAME_KEY) 1832 + let store_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_STORE_NAME_KEY) 1699 1833 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1700 1834 1701 1835 let idb = bridge.indexeddb.borrow(); ··· 1708 1842 drop(idb); 1709 1843 1710 1844 // Convert to JS array. 1711 - let js_values: Vec<Value> = values.iter().map(|v| idb_to_value(ctx.gc, v)).collect(); 1845 + let js_values: Vec<Value> = values 1846 + .iter() 1847 + .map(|v| idb_to_value(ctx.gc, ctx.shapes, v)) 1848 + .collect(); 1712 1849 let mut obj = ObjectData::new(); 1713 1850 for (i, v) in js_values.into_iter().enumerate() { 1714 - obj.properties.insert(i.to_string(), Property::data(v)); 1851 + obj.insert_property(i.to_string(), Property::data(v), ctx.shapes); 1715 1852 } 1716 - obj.properties.insert( 1853 + obj.insert_property( 1717 1854 "length".to_string(), 1718 1855 Property { 1719 1856 value: Value::Number(values.len() as f64), ··· 1721 1858 enumerable: false, 1722 1859 configurable: false, 1723 1860 }, 1861 + ctx.shapes, 1724 1862 ); 1725 1863 let arr_ref = ctx.gc.alloc(HeapObject::Object(obj)); 1726 1864 1727 - let request_ref = create_idb_request(ctx.gc, None); 1728 - set_builtin_prop(ctx.gc, request_ref, "result", Value::Object(arr_ref)); 1865 + let request_ref = create_idb_request(ctx.gc, ctx.shapes, None); 1729 1866 set_builtin_prop( 1730 1867 ctx.gc, 1868 + ctx.shapes, 1869 + request_ref, 1870 + "result", 1871 + Value::Object(arr_ref), 1872 + ); 1873 + set_builtin_prop( 1874 + ctx.gc, 1875 + ctx.shapes, 1731 1876 request_ref, 1732 1877 "readyState", 1733 1878 Value::String("done".to_string()), ··· 1752 1897 _ => return Err(RuntimeError::type_error("createIndex on non-objectStore")), 1753 1898 }; 1754 1899 1755 - let db_name = get_internal_str(ctx.gc, store_ref, IDB_DB_NAME_KEY) 1900 + let db_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_DB_NAME_KEY) 1756 1901 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1757 - let store_name = get_internal_str(ctx.gc, store_ref, IDB_STORE_NAME_KEY) 1902 + let store_name = get_internal_str(ctx.gc, ctx.shapes, store_ref, IDB_STORE_NAME_KEY) 1758 1903 .ok_or_else(|| RuntimeError::type_error("invalid store object"))?; 1759 1904 1760 1905 let index_name = args ··· 1776 1921 let mut unique = false; 1777 1922 if let Some(Value::Object(opts_ref)) = args.get(2) { 1778 1923 if let Some(HeapObject::Object(opts)) = ctx.gc.get(*opts_ref) { 1779 - if let Some(prop) = opts.properties.get("unique") { 1924 + if let Some(prop) = opts.get_property("unique", ctx.shapes) { 1780 1925 unique = prop.value.to_boolean(); 1781 1926 } 1782 1927 } ··· 1808 1953 ); 1809 1954 } 1810 1955 1811 - let idx_ref = create_idb_index_wrapper(ctx.gc, &db_name, &store_name, &index_name, None); 1956 + let idx_ref = 1957 + create_idb_index_wrapper(ctx.gc, ctx.shapes, &db_name, &store_name, &index_name, None); 1812 1958 Ok(Value::Object(idx_ref)) 1813 1959 } 1814 1960 ··· 1823 1969 _ => return Err(RuntimeError::type_error("get on non-index")), 1824 1970 }; 1825 1971 1826 - let db_name = get_internal_str(ctx.gc, idx_ref, IDB_DB_NAME_KEY) 1972 + let db_name = get_internal_str(ctx.gc, ctx.shapes, idx_ref, IDB_DB_NAME_KEY) 1827 1973 .ok_or_else(|| RuntimeError::type_error("invalid index object"))?; 1828 - let store_name = get_internal_str(ctx.gc, idx_ref, IDB_STORE_NAME_KEY) 1974 + let store_name = get_internal_str(ctx.gc, ctx.shapes, idx_ref, IDB_STORE_NAME_KEY) 1829 1975 .ok_or_else(|| RuntimeError::type_error("invalid index object"))?; 1830 - let index_name = get_internal_str(ctx.gc, idx_ref, IDB_INDEX_NAME_KEY) 1976 + let index_name = get_internal_str(ctx.gc, ctx.shapes, idx_ref, IDB_INDEX_NAME_KEY) 1831 1977 .ok_or_else(|| RuntimeError::type_error("invalid index object"))?; 1832 1978 1833 1979 let key = args 1834 1980 .first() 1835 - .and_then(|v| value_to_key(ctx.gc, v)) 1981 + .and_then(|v| value_to_key(ctx.gc, ctx.shapes, v)) 1836 1982 .ok_or_else(|| RuntimeError::type_error("get requires a valid key"))?; 1837 1983 1838 1984 let idb = bridge.indexeddb.borrow(); ··· 1841 1987 .get(&db_name) 1842 1988 .and_then(|db| db.stores.get(&store_name)) 1843 1989 .and_then(|store| store.index_get(&index_name, &key)) 1844 - .map(|v| idb_to_value(ctx.gc, v)) 1990 + .map(|v| idb_to_value(ctx.gc, ctx.shapes, v)) 1845 1991 .unwrap_or(Value::Undefined); 1846 1992 drop(idb); 1847 1993 1848 - let request_ref = create_idb_request(ctx.gc, None); 1849 - set_builtin_prop(ctx.gc, request_ref, "result", result); 1994 + let request_ref = create_idb_request(ctx.gc, ctx.shapes, None); 1995 + set_builtin_prop(ctx.gc, ctx.shapes, request_ref, "result", result); 1850 1996 set_builtin_prop( 1851 1997 ctx.gc, 1998 + ctx.shapes, 1852 1999 request_ref, 1853 2000 "readyState", 1854 2001 Value::String("done".to_string()), ··· 1871 2018 if let Some(proto) = vm.object_prototype { 1872 2019 data.prototype = Some(proto); 1873 2020 } 1874 - data.properties.insert( 2021 + data.insert_property( 1875 2022 IDB_TYPE_KEY.to_string(), 1876 2023 Property::builtin(Value::String("indexedDB".to_string())), 2024 + &mut vm.shapes, 1877 2025 ); 1878 2026 1879 2027 let idb_ref = vm.gc.alloc(HeapObject::Object(data)); ··· 1881 2029 let methods: &[NativeMethod] = &[("open", idb_open), ("deleteDatabase", idb_delete_database)]; 1882 2030 for &(name, callback) in methods { 1883 2031 let func = make_native(&mut vm.gc, name, callback); 1884 - set_builtin_prop(&mut vm.gc, idb_ref, name, Value::Function(func)); 2032 + set_builtin_prop( 2033 + &mut vm.gc, 2034 + &mut vm.shapes, 2035 + idb_ref, 2036 + name, 2037 + Value::Function(func), 2038 + ); 1885 2039 } 1886 2040 1887 2041 vm.set_global("indexedDB", Value::Object(idb_ref)); ··· 1901 2055 }; 1902 2056 1903 2057 // Look up the callback property on the target. 1904 - let callback = get_obj_prop(&vm.gc, event.target, callback_name); 2058 + let callback = get_obj_prop(&vm.gc, &vm.shapes, event.target, callback_name); 1905 2059 1906 2060 if let Value::Function(func_ref) = callback { 1907 2061 // Create event object. ··· 1912 2066 } => (*old_version, *new_version), 1913 2067 _ => (0.0, 0.0), 1914 2068 }; 1915 - let evt_ref = create_idb_event(&mut vm.gc, callback_name, event.target, old_v, new_v); 2069 + let evt_ref = create_idb_event( 2070 + &mut vm.gc, 2071 + &mut vm.shapes, 2072 + callback_name, 2073 + event.target, 2074 + old_v, 2075 + new_v, 2076 + ); 1916 2077 let _ = vm.call_function(func_ref, &[Value::Object(evt_ref)]); 1917 2078 vm.drain_microtasks()?; 1918 2079 } 1919 2080 1920 2081 // After upgradeneeded, clear the upgrade flag on the database. 1921 2082 if matches!(event.kind, IdbEventKind::UpgradeNeeded { .. }) { 1922 - let result = get_obj_prop(&vm.gc, event.target, "result"); 2083 + let result = get_obj_prop(&vm.gc, &vm.shapes, event.target, "result"); 1923 2084 if let Value::Object(db_ref) = result { 1924 - set_builtin_prop(&mut vm.gc, db_ref, IDB_UPGRADING_KEY, Value::Boolean(false)); 2085 + set_builtin_prop( 2086 + &mut vm.gc, 2087 + &mut vm.shapes, 2088 + db_ref, 2089 + IDB_UPGRADING_KEY, 2090 + Value::Boolean(false), 2091 + ); 1925 2092 // Update objectStoreNames on the database wrapper. 1926 - let db_name = get_internal_str(&vm.gc, db_ref, IDB_DB_NAME_KEY); 2093 + let db_name = get_internal_str(&vm.gc, &vm.shapes, db_ref, IDB_DB_NAME_KEY); 1927 2094 if let Some(db_name) = db_name { 1928 2095 if let Some(bridge) = &vm.dom_bridge { 1929 2096 let store_names: Vec<String> = bridge ··· 1933 2100 .get(&db_name) 1934 2101 .map(|db| db.stores.keys().cloned().collect()) 1935 2102 .unwrap_or_default(); 1936 - let names_ref = make_string_list(&mut vm.gc, &store_names); 2103 + let names_ref = make_string_list(&mut vm.gc, &mut vm.shapes, &store_names); 1937 2104 set_builtin_prop( 1938 2105 &mut vm.gc, 2106 + &mut vm.shapes, 1939 2107 db_ref, 1940 2108 "objectStoreNames", 1941 2109 Value::Object(names_ref), ··· 1949 2117 if matches!(event.kind, IdbEventKind::Success) { 1950 2118 set_builtin_prop( 1951 2119 &mut vm.gc, 2120 + &mut vm.shapes, 1952 2121 event.target, 1953 2122 "readyState", 1954 2123 Value::String("done".to_string()), ··· 2577 2746 #[test] 2578 2747 fn value_to_idb_primitives() { 2579 2748 let gc = &mut Gc::new(); 2749 + let shapes = &mut ShapeTable::new(); 2580 2750 assert_eq!( 2581 - value_to_idb(gc, &Value::Undefined).unwrap(), 2751 + value_to_idb(gc, shapes, &Value::Undefined).unwrap(), 2582 2752 IdbValue::Undefined 2583 2753 ); 2584 - assert_eq!(value_to_idb(gc, &Value::Null).unwrap(), IdbValue::Null); 2754 + assert_eq!( 2755 + value_to_idb(gc, shapes, &Value::Null).unwrap(), 2756 + IdbValue::Null 2757 + ); 2585 2758 assert_eq!( 2586 - value_to_idb(gc, &Value::Boolean(true)).unwrap(), 2759 + value_to_idb(gc, shapes, &Value::Boolean(true)).unwrap(), 2587 2760 IdbValue::Boolean(true) 2588 2761 ); 2589 2762 assert_eq!( 2590 - value_to_idb(gc, &Value::Number(42.0)).unwrap(), 2763 + value_to_idb(gc, shapes, &Value::Number(42.0)).unwrap(), 2591 2764 IdbValue::Number(42.0) 2592 2765 ); 2593 2766 assert_eq!( 2594 - value_to_idb(gc, &Value::String("hi".into())).unwrap(), 2767 + value_to_idb(gc, shapes, &Value::String("hi".into())).unwrap(), 2595 2768 IdbValue::String("hi".into()) 2596 2769 ); 2597 2770 } ··· 2599 2772 #[test] 2600 2773 fn value_to_idb_object_roundtrip() { 2601 2774 let gc = &mut Gc::new(); 2775 + let shapes = &mut ShapeTable::new(); 2602 2776 let mut obj = ObjectData::new(); 2603 - obj.properties.insert( 2777 + obj.insert_property( 2604 2778 "name".to_string(), 2605 2779 Property::data(Value::String("test".into())), 2780 + shapes, 2606 2781 ); 2607 - obj.properties 2608 - .insert("count".to_string(), Property::data(Value::Number(5.0))); 2782 + obj.insert_property( 2783 + "count".to_string(), 2784 + Property::data(Value::Number(5.0)), 2785 + shapes, 2786 + ); 2609 2787 let obj_ref = gc.alloc(HeapObject::Object(obj)); 2610 2788 2611 - let idb_val = value_to_idb(gc, &Value::Object(obj_ref)).unwrap(); 2612 - let js_val = idb_to_value(gc, &idb_val); 2789 + let idb_val = value_to_idb(gc, shapes, &Value::Object(obj_ref)).unwrap(); 2790 + let js_val = idb_to_value(gc, shapes, &idb_val); 2613 2791 2614 2792 match js_val { 2615 2793 Value::Object(r) => { 2616 2794 if let Some(HeapObject::Object(data)) = gc.get(r) { 2617 2795 assert_eq!( 2618 - data.properties.get("name").unwrap().value.to_js_string(gc), 2796 + data.get_property("name", shapes) 2797 + .unwrap() 2798 + .value 2799 + .to_js_string(gc), 2619 2800 "test" 2620 2801 ); 2621 - assert_eq!(data.properties.get("count").unwrap().value.to_number(), 5.0); 2802 + assert_eq!( 2803 + data.get_property("count", shapes) 2804 + .unwrap() 2805 + .value 2806 + .to_number(), 2807 + 5.0 2808 + ); 2622 2809 } else { 2623 2810 panic!("expected object"); 2624 2811 }
+1
crates/js/src/lib.rs
··· 12 12 pub mod lexer; 13 13 pub mod parser; 14 14 pub mod regex; 15 + pub mod shape; 15 16 pub mod storage; 16 17 pub mod timers; 17 18 pub mod vm;
+256
crates/js/src/shape.rs
··· 1 + //! Object shapes (hidden classes) for fast property access. 2 + //! 3 + //! A shape describes the property layout of an object: which property names 4 + //! exist at which slot indices, along with their attributes (writable, 5 + //! enumerable, configurable). Objects that are constructed identically share 6 + //! the same shape, enabling O(1) indexed property access once the shape is 7 + //! known. 8 + //! 9 + //! Shapes form a tree via parent pointers. Each non-root shape was created by 10 + //! adding one property to its parent shape. Transitions from a parent to a 11 + //! child are cached so that identical property additions reuse the same child 12 + //! shape. 13 + 14 + use std::collections::HashMap; 15 + 16 + /// Index into the global [`ShapeTable`] arena. 17 + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 18 + pub struct ShapeId(u32); 19 + 20 + impl ShapeId { 21 + /// The root (empty) shape — every fresh object starts here. 22 + pub const ROOT: ShapeId = ShapeId(0); 23 + } 24 + 25 + /// Property attributes stored per-property in the shape. 26 + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 27 + pub struct PropertyAttrs { 28 + pub writable: bool, 29 + pub enumerable: bool, 30 + pub configurable: bool, 31 + } 32 + 33 + impl PropertyAttrs { 34 + /// Default attributes for properties created by assignment. 35 + pub const DEFAULT: PropertyAttrs = PropertyAttrs { 36 + writable: true, 37 + enumerable: true, 38 + configurable: true, 39 + }; 40 + 41 + /// Attributes for built-in methods (non-enumerable, non-configurable). 42 + pub const BUILTIN: PropertyAttrs = PropertyAttrs { 43 + writable: true, 44 + enumerable: false, 45 + configurable: false, 46 + }; 47 + } 48 + 49 + /// Describes where a property lives and what attributes it has. 50 + #[derive(Debug, Clone, Copy)] 51 + pub struct SlotDescriptor { 52 + /// Index into the object's `slots` Vec. 53 + pub index: u32, 54 + /// Property attributes. 55 + pub attrs: PropertyAttrs, 56 + } 57 + 58 + /// A single shape node in the transition tree. 59 + /// 60 + /// The root shape has `parent = None` and `added_property = None`. 61 + /// Every other shape was created by adding exactly one property to its parent. 62 + pub struct Shape { 63 + /// Parent shape (before the last property was added). 64 + parent: Option<ShapeId>, 65 + /// The property that was added to transition from parent to this shape. 66 + added_property: Option<String>, 67 + /// Slot descriptor for the added property. 68 + added_slot: Option<SlotDescriptor>, 69 + /// Number of property slots in objects with this shape. 70 + pub slot_count: u32, 71 + /// Cached transitions: (property name, attributes) → child shape. 72 + transitions: HashMap<(String, PropertyAttrs), ShapeId>, 73 + } 74 + 75 + /// Arena of all shapes, shared by every object in the VM. 76 + pub struct ShapeTable { 77 + shapes: Vec<Shape>, 78 + } 79 + 80 + impl Default for ShapeTable { 81 + fn default() -> Self { 82 + Self::new() 83 + } 84 + } 85 + 86 + impl ShapeTable { 87 + pub fn new() -> Self { 88 + let root = Shape { 89 + parent: None, 90 + added_property: None, 91 + added_slot: None, 92 + slot_count: 0, 93 + transitions: HashMap::new(), 94 + }; 95 + Self { shapes: vec![root] } 96 + } 97 + 98 + /// Look up a property by walking the shape's transition chain. 99 + /// Returns the slot descriptor if found. 100 + pub fn lookup(&self, shape_id: ShapeId, name: &str) -> Option<SlotDescriptor> { 101 + let mut current = Some(shape_id); 102 + while let Some(id) = current { 103 + let shape = &self.shapes[id.0 as usize]; 104 + if shape.added_property.as_deref() == Some(name) { 105 + return shape.added_slot; 106 + } 107 + current = shape.parent; 108 + } 109 + None 110 + } 111 + 112 + /// Add a property to a shape, returning the (possibly cached) child shape. 113 + pub fn add_property(&mut self, shape_id: ShapeId, name: &str, attrs: PropertyAttrs) -> ShapeId { 114 + // Check for an existing transition with matching name. 115 + // (We accept attribute mismatches on the cached transition for simplicity; 116 + // the common case is that the same property is always added with the 117 + // same attributes.) 118 + let key = (name.to_string(), attrs); 119 + if let Some(&child_id) = self.shapes[shape_id.0 as usize].transitions.get(&key) { 120 + return child_id; 121 + } 122 + 123 + let slot_count = self.shapes[shape_id.0 as usize].slot_count; 124 + let new_shape = Shape { 125 + parent: Some(shape_id), 126 + added_property: Some(name.to_string()), 127 + added_slot: Some(SlotDescriptor { 128 + index: slot_count, 129 + attrs, 130 + }), 131 + slot_count: slot_count + 1, 132 + transitions: HashMap::new(), 133 + }; 134 + let new_id = ShapeId(self.shapes.len() as u32); 135 + self.shapes.push(new_shape); 136 + self.shapes[shape_id.0 as usize] 137 + .transitions 138 + .insert(key, new_id); 139 + new_id 140 + } 141 + 142 + /// Collect all property names and their slot descriptors for a shape, 143 + /// ordered from first-added to last-added. 144 + pub fn all_properties(&self, shape_id: ShapeId) -> Vec<(String, SlotDescriptor)> { 145 + let mut props = Vec::new(); 146 + let mut current = Some(shape_id); 147 + while let Some(id) = current { 148 + let shape = &self.shapes[id.0 as usize]; 149 + if let (Some(name), Some(slot)) = (&shape.added_property, shape.added_slot) { 150 + props.push((name.clone(), slot)); 151 + } 152 + current = shape.parent; 153 + } 154 + props.reverse(); // root-to-leaf order = insertion order 155 + props 156 + } 157 + 158 + /// Number of shapes allocated (for diagnostics). 159 + pub fn len(&self) -> usize { 160 + self.shapes.len() 161 + } 162 + 163 + /// Whether the shape table is empty (always false — root shape exists). 164 + pub fn is_empty(&self) -> bool { 165 + self.shapes.is_empty() 166 + } 167 + } 168 + 169 + #[cfg(test)] 170 + mod tests { 171 + use super::*; 172 + 173 + #[test] 174 + fn root_shape_is_empty() { 175 + let table = ShapeTable::new(); 176 + assert_eq!(table.shapes[ShapeId::ROOT.0 as usize].slot_count, 0); 177 + assert!(table.lookup(ShapeId::ROOT, "x").is_none()); 178 + } 179 + 180 + #[test] 181 + fn add_single_property() { 182 + let mut table = ShapeTable::new(); 183 + let s1 = table.add_property(ShapeId::ROOT, "x", PropertyAttrs::DEFAULT); 184 + assert_ne!(s1, ShapeId::ROOT); 185 + 186 + let desc = table.lookup(s1, "x").unwrap(); 187 + assert_eq!(desc.index, 0); 188 + assert!(desc.attrs.writable); 189 + assert!(desc.attrs.enumerable); 190 + assert!(desc.attrs.configurable); 191 + 192 + assert!(table.lookup(s1, "y").is_none()); 193 + } 194 + 195 + #[test] 196 + fn add_multiple_properties() { 197 + let mut table = ShapeTable::new(); 198 + let s1 = table.add_property(ShapeId::ROOT, "a", PropertyAttrs::DEFAULT); 199 + let s2 = table.add_property(s1, "b", PropertyAttrs::BUILTIN); 200 + let s3 = table.add_property(s2, "c", PropertyAttrs::DEFAULT); 201 + 202 + let a = table.lookup(s3, "a").unwrap(); 203 + let b = table.lookup(s3, "b").unwrap(); 204 + let c = table.lookup(s3, "c").unwrap(); 205 + assert_eq!(a.index, 0); 206 + assert_eq!(b.index, 1); 207 + assert_eq!(c.index, 2); 208 + assert!(!b.attrs.enumerable); // builtin 209 + } 210 + 211 + #[test] 212 + fn transitions_are_cached() { 213 + let mut table = ShapeTable::new(); 214 + let s1a = table.add_property(ShapeId::ROOT, "x", PropertyAttrs::DEFAULT); 215 + let s1b = table.add_property(ShapeId::ROOT, "x", PropertyAttrs::DEFAULT); 216 + assert_eq!(s1a, s1b); 217 + } 218 + 219 + #[test] 220 + fn different_properties_different_shapes() { 221 + let mut table = ShapeTable::new(); 222 + let s1 = table.add_property(ShapeId::ROOT, "x", PropertyAttrs::DEFAULT); 223 + let s2 = table.add_property(ShapeId::ROOT, "y", PropertyAttrs::DEFAULT); 224 + assert_ne!(s1, s2); 225 + } 226 + 227 + #[test] 228 + fn all_properties_returns_insertion_order() { 229 + let mut table = ShapeTable::new(); 230 + let s1 = table.add_property(ShapeId::ROOT, "a", PropertyAttrs::DEFAULT); 231 + let s2 = table.add_property(s1, "b", PropertyAttrs::DEFAULT); 232 + let s3 = table.add_property(s2, "c", PropertyAttrs::DEFAULT); 233 + 234 + let props = table.all_properties(s3); 235 + assert_eq!(props.len(), 3); 236 + assert_eq!(props[0].0, "a"); 237 + assert_eq!(props[1].0, "b"); 238 + assert_eq!(props[2].0, "c"); 239 + assert_eq!(props[0].1.index, 0); 240 + assert_eq!(props[1].1.index, 1); 241 + assert_eq!(props[2].1.index, 2); 242 + } 243 + 244 + #[test] 245 + fn shared_shapes_across_objects() { 246 + // Two objects adding properties in the same order share shapes. 247 + let mut table = ShapeTable::new(); 248 + let obj1_s1 = table.add_property(ShapeId::ROOT, "x", PropertyAttrs::DEFAULT); 249 + let obj1_s2 = table.add_property(obj1_s1, "y", PropertyAttrs::DEFAULT); 250 + 251 + let obj2_s1 = table.add_property(ShapeId::ROOT, "x", PropertyAttrs::DEFAULT); 252 + let obj2_s2 = table.add_property(obj2_s1, "y", PropertyAttrs::DEFAULT); 253 + 254 + assert_eq!(obj1_s2, obj2_s2); 255 + } 256 + }
+726 -221
crates/js/src/vm.rs
··· 7 7 8 8 use crate::bytecode::{Constant, Function, Op, Reg}; 9 9 use crate::gc::{Gc, GcRef, Traceable}; 10 + use crate::shape::{PropertyAttrs, ShapeId, ShapeTable}; 10 11 use std::cell::RefCell; 11 12 use std::collections::HashMap; 12 13 use std::fmt; ··· 59 60 fn trace(&self, visitor: &mut dyn FnMut(GcRef)) { 60 61 match self { 61 62 HeapObject::Object(data) => { 62 - for prop in data.properties.values() { 63 - if let Some(r) = prop.value.gc_ref() { 64 - visitor(r); 65 - } 66 - } 63 + data.trace_values(visitor); 67 64 if let Some(proto) = data.prototype { 68 65 visitor(proto); 69 66 } ··· 139 136 } 140 137 } 141 138 142 - /// A JS plain object (properties stored as a HashMap with descriptors). 139 + /// Result of a property removal attempt. 140 + pub enum RemoveResult { 141 + /// Property was removed successfully. 142 + Removed, 143 + /// Property was not found. 144 + NotFound, 145 + /// Property exists but is non-configurable. 146 + NonConfigurable, 147 + } 148 + 149 + /// Internal storage representation for object properties. 150 + pub enum ObjectStorage { 151 + /// Fast mode: shape-based indexed slots. 152 + Shaped { shape: ShapeId, slots: Vec<Value> }, 153 + /// Dictionary mode: fallback after property deletion or attribute changes. 154 + Dictionary(HashMap<String, Property>), 155 + } 156 + 157 + /// A JS plain object. Properties are stored either in shape-indexed slots 158 + /// (fast path) or a HashMap (dictionary mode, after deletion/freeze/seal). 143 159 pub struct ObjectData { 144 - pub properties: HashMap<String, Property>, 160 + pub storage: ObjectStorage, 145 161 pub prototype: Option<GcRef>, 146 162 /// Whether new properties can be added (Object.preventExtensions). 147 163 pub extensible: bool, ··· 150 166 impl ObjectData { 151 167 pub fn new() -> Self { 152 168 Self { 153 - properties: HashMap::new(), 169 + storage: ObjectStorage::Shaped { 170 + shape: ShapeId::ROOT, 171 + slots: Vec::new(), 172 + }, 154 173 prototype: None, 155 174 extensible: true, 156 175 } 157 176 } 177 + 178 + // ── Core property operations ────────────────────────────── 179 + 180 + /// Get a property descriptor (cloned) by name. 181 + pub fn get_property(&self, key: &str, shapes: &ShapeTable) -> Option<Property> { 182 + match &self.storage { 183 + ObjectStorage::Shaped { shape, slots } => { 184 + let desc = shapes.lookup(*shape, key)?; 185 + Some(Property { 186 + value: slots[desc.index as usize].clone(), 187 + writable: desc.attrs.writable, 188 + enumerable: desc.attrs.enumerable, 189 + configurable: desc.attrs.configurable, 190 + }) 191 + } 192 + ObjectStorage::Dictionary(map) => map.get(key).cloned(), 193 + } 194 + } 195 + 196 + /// Check if the object has an own property with the given name. 197 + pub fn contains_key(&self, key: &str, shapes: &ShapeTable) -> bool { 198 + match &self.storage { 199 + ObjectStorage::Shaped { shape, .. } => shapes.lookup(*shape, key).is_some(), 200 + ObjectStorage::Dictionary(map) => map.contains_key(key), 201 + } 202 + } 203 + 204 + /// Insert or overwrite a property. In shaped mode, if the property already 205 + /// exists, updates its slot value (ignoring attribute differences). If it 206 + /// does not exist, transitions to a child shape. 207 + pub fn insert_property(&mut self, key: String, prop: Property, shapes: &mut ShapeTable) { 208 + let attrs = PropertyAttrs { 209 + writable: prop.writable, 210 + enumerable: prop.enumerable, 211 + configurable: prop.configurable, 212 + }; 213 + match &mut self.storage { 214 + ObjectStorage::Shaped { shape, slots } => { 215 + if let Some(desc) = shapes.lookup(*shape, &key) { 216 + // Property exists — update value in-place. 217 + slots[desc.index as usize] = prop.value; 218 + } else { 219 + // New property — transition shape. 220 + let new_shape = shapes.add_property(*shape, &key, attrs); 221 + *shape = new_shape; 222 + slots.push(prop.value); 223 + } 224 + } 225 + ObjectStorage::Dictionary(map) => { 226 + map.insert(key, prop); 227 + } 228 + } 229 + } 230 + 231 + /// Update the value of an existing property if it is writable. 232 + /// Returns true if the update happened. 233 + pub fn update_value(&mut self, key: &str, val: Value, shapes: &ShapeTable) -> bool { 234 + match &mut self.storage { 235 + ObjectStorage::Shaped { shape, slots } => { 236 + if let Some(desc) = shapes.lookup(*shape, key) { 237 + if desc.attrs.writable { 238 + slots[desc.index as usize] = val; 239 + return true; 240 + } 241 + } 242 + false 243 + } 244 + ObjectStorage::Dictionary(map) => { 245 + if let Some(prop) = map.get_mut(key) { 246 + if prop.writable { 247 + prop.value = val; 248 + return true; 249 + } 250 + } 251 + false 252 + } 253 + } 254 + } 255 + 256 + /// Set a property: update existing value if writable, or insert new. 257 + pub fn set_property(&mut self, key: String, val: Value, shapes: &mut ShapeTable) { 258 + match &mut self.storage { 259 + ObjectStorage::Shaped { shape, slots } => { 260 + if let Some(desc) = shapes.lookup(*shape, &key) { 261 + if desc.attrs.writable { 262 + slots[desc.index as usize] = val; 263 + } 264 + } else { 265 + let new_shape = shapes.add_property(*shape, &key, PropertyAttrs::DEFAULT); 266 + *shape = new_shape; 267 + slots.push(val); 268 + } 269 + } 270 + ObjectStorage::Dictionary(map) => { 271 + if let Some(prop) = map.get_mut(&key) { 272 + if prop.writable { 273 + prop.value = val; 274 + } 275 + } else { 276 + map.insert(key, Property::data(val)); 277 + } 278 + } 279 + } 280 + } 281 + 282 + /// Remove a property. Transitions to dictionary mode in shaped mode. 283 + /// Returns `None` if not found, `Some(property)` if removed. 284 + /// Returns `Some` with configurable=false if removal was blocked. 285 + pub fn remove_property(&mut self, key: &str, shapes: &ShapeTable) -> RemoveResult { 286 + match &mut self.storage { 287 + ObjectStorage::Shaped { shape, .. } => { 288 + let desc = match shapes.lookup(*shape, key) { 289 + Some(d) => d, 290 + None => return RemoveResult::NotFound, 291 + }; 292 + if !desc.attrs.configurable { 293 + return RemoveResult::NonConfigurable; 294 + } 295 + // Convert to dictionary mode, then remove. 296 + self.to_dictionary(shapes); 297 + if let ObjectStorage::Dictionary(map) = &mut self.storage { 298 + map.remove(key); 299 + } 300 + RemoveResult::Removed 301 + } 302 + ObjectStorage::Dictionary(map) => { 303 + // Check if the property exists and is configurable. 304 + let configurable = match map.get(key) { 305 + Some(prop) => { 306 + if !prop.configurable { 307 + return RemoveResult::NonConfigurable; 308 + } 309 + true 310 + } 311 + None => return RemoveResult::NotFound, 312 + }; 313 + if configurable { 314 + map.remove(key); 315 + } 316 + RemoveResult::Removed 317 + } 318 + } 319 + } 320 + 321 + // ── Iteration ───────────────────────────────────────────── 322 + 323 + /// Collect all own properties as (name, Property) pairs. 324 + pub fn property_entries(&self, shapes: &ShapeTable) -> Vec<(String, Property)> { 325 + match &self.storage { 326 + ObjectStorage::Shaped { shape, slots } => shapes 327 + .all_properties(*shape) 328 + .into_iter() 329 + .map(|(name, desc)| { 330 + ( 331 + name, 332 + Property { 333 + value: slots[desc.index as usize].clone(), 334 + writable: desc.attrs.writable, 335 + enumerable: desc.attrs.enumerable, 336 + configurable: desc.attrs.configurable, 337 + }, 338 + ) 339 + }) 340 + .collect(), 341 + ObjectStorage::Dictionary(map) => { 342 + map.iter().map(|(k, v)| (k.clone(), v.clone())).collect() 343 + } 344 + } 345 + } 346 + 347 + /// Collect all own property names. 348 + pub fn property_keys(&self, shapes: &ShapeTable) -> Vec<String> { 349 + match &self.storage { 350 + ObjectStorage::Shaped { shape, .. } => shapes 351 + .all_properties(*shape) 352 + .into_iter() 353 + .map(|(name, _)| name) 354 + .collect(), 355 + ObjectStorage::Dictionary(map) => map.keys().cloned().collect(), 356 + } 357 + } 358 + 359 + /// Number of own properties. 360 + pub fn property_count(&self) -> usize { 361 + match &self.storage { 362 + ObjectStorage::Shaped { slots, .. } => slots.len(), 363 + ObjectStorage::Dictionary(map) => map.len(), 364 + } 365 + } 366 + 367 + /// Whether the object has no own properties. 368 + pub fn is_empty(&self) -> bool { 369 + self.property_count() == 0 370 + } 371 + 372 + // ── Dictionary mode transitions ─────────────────────────── 373 + 374 + /// Convert from shaped mode to dictionary mode. 375 + pub fn to_dictionary(&mut self, shapes: &ShapeTable) { 376 + if let ObjectStorage::Shaped { shape, slots } = &self.storage { 377 + let props = shapes.all_properties(*shape); 378 + let mut map = HashMap::with_capacity(props.len()); 379 + for (name, desc) in props { 380 + map.insert( 381 + name, 382 + Property { 383 + value: slots[desc.index as usize].clone(), 384 + writable: desc.attrs.writable, 385 + enumerable: desc.attrs.enumerable, 386 + configurable: desc.attrs.configurable, 387 + }, 388 + ); 389 + } 390 + self.storage = ObjectStorage::Dictionary(map); 391 + } 392 + } 393 + 394 + /// Apply a function to every property (transitions to dict mode if shaped). 395 + pub fn modify_all_properties(&mut self, shapes: &ShapeTable, mut f: impl FnMut(&mut Property)) { 396 + self.to_dictionary(shapes); 397 + if let ObjectStorage::Dictionary(map) = &mut self.storage { 398 + for prop in map.values_mut() { 399 + f(prop); 400 + } 401 + } 402 + } 403 + 404 + /// Retain only properties for which the predicate returns true. 405 + pub fn retain_properties( 406 + &mut self, 407 + shapes: &ShapeTable, 408 + f: impl FnMut(&str, &Property) -> bool, 409 + ) { 410 + self.to_dictionary(shapes); 411 + if let ObjectStorage::Dictionary(map) = &mut self.storage { 412 + let mut f = f; 413 + map.retain(|k, v| f(k, v)); 414 + } 415 + } 416 + 417 + // ── GC tracing ──────────────────────────────────────────── 418 + 419 + /// Trace all values reachable from this object's properties. 420 + pub fn trace_values(&self, visitor: &mut dyn FnMut(GcRef)) { 421 + match &self.storage { 422 + ObjectStorage::Shaped { slots, .. } => { 423 + for val in slots { 424 + if let Some(r) = val.gc_ref() { 425 + visitor(r); 426 + } 427 + } 428 + } 429 + ObjectStorage::Dictionary(map) => { 430 + for prop in map.values() { 431 + if let Some(r) = prop.value.gc_ref() { 432 + visitor(r); 433 + } 434 + } 435 + } 436 + } 437 + } 158 438 } 159 439 160 440 impl Default for ObjectData { ··· 258 538 /// Context passed to native functions, providing GC access and `this` binding. 259 539 pub struct NativeContext<'a> { 260 540 pub gc: &'a mut Gc<HeapObject>, 541 + pub shapes: &'a mut ShapeTable, 261 542 pub this: Value, 262 543 pub console_output: &'a dyn ConsoleOutput, 263 544 pub dom_bridge: Option<&'a DomBridge>, ··· 497 778 } 498 779 499 780 /// Convert to a JS Value (an error object). Allocates through the GC. 500 - pub fn to_value(&self, gc: &mut Gc<HeapObject>) -> Value { 781 + pub fn to_value(&self, gc: &mut Gc<HeapObject>, shapes: &mut ShapeTable) -> Value { 501 782 let mut obj = ObjectData::new(); 502 - obj.properties.insert( 783 + obj.insert_property( 503 784 "message".to_string(), 504 785 Property::data(Value::String(self.message.clone())), 786 + shapes, 505 787 ); 506 788 let name = match self.kind { 507 789 ErrorKind::TypeError => "TypeError", ··· 510 792 ErrorKind::SyntaxError => "SyntaxError", 511 793 ErrorKind::Error => "Error", 512 794 }; 513 - obj.properties.insert( 795 + obj.insert_property( 514 796 "name".to_string(), 515 797 Property::data(Value::String(name.to_string())), 798 + shapes, 516 799 ); 517 800 Value::Object(gc.alloc(HeapObject::Object(obj))) 518 801 } ··· 521 804 // ── Property access helpers ────────────────────────────────── 522 805 523 806 /// Get a property from an object, walking the prototype chain. 524 - fn gc_get_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str) -> Value { 807 + fn gc_get_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str, shapes: &ShapeTable) -> Value { 525 808 let proto = { 526 809 match gc.get(obj_ref) { 527 810 Some(HeapObject::Object(data)) => { 528 - if let Some(prop) = data.properties.get(key) { 811 + if let Some(prop) = data.get_property(key, shapes) { 529 812 return prop.value.clone(); 530 813 } 531 814 data.prototype ··· 548 831 } 549 832 }; 550 833 if let Some(proto_ref) = proto { 551 - gc_get_property(gc, proto_ref, key) 834 + gc_get_property(gc, proto_ref, key, shapes) 552 835 } else { 553 836 Value::Undefined 554 837 } 555 838 } 556 839 557 840 /// Check if an object has a property (own or inherited). 558 - fn gc_has_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str) -> bool { 841 + fn gc_has_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str, shapes: &ShapeTable) -> bool { 559 842 let proto = { 560 843 match gc.get(obj_ref) { 561 844 Some(HeapObject::Object(data)) => { 562 - if data.properties.contains_key(key) { 845 + if data.contains_key(key, shapes) { 563 846 return true; 564 847 } 565 848 data.prototype ··· 574 857 } 575 858 }; 576 859 if let Some(proto_ref) = proto { 577 - gc_has_property(gc, proto_ref, key) 860 + gc_has_property(gc, proto_ref, key, shapes) 578 861 } else { 579 862 false 580 863 } ··· 582 865 583 866 /// Collect all enumerable string keys of an object (own + inherited), in proper order. 584 867 /// Integer indices first (sorted numerically), then string keys in insertion order. 585 - fn gc_enumerate_keys(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> { 868 + fn gc_enumerate_keys(gc: &Gc<HeapObject>, obj_ref: GcRef, shapes: &ShapeTable) -> Vec<String> { 586 869 let mut seen = std::collections::HashSet::new(); 587 870 let mut integer_keys: Vec<(u32, String)> = Vec::new(); 588 871 let mut string_keys: Vec<String> = Vec::new(); ··· 591 874 while let Some(cur_ref) = current { 592 875 match gc.get(cur_ref) { 593 876 Some(HeapObject::Object(data)) => { 594 - for (key, prop) in &data.properties { 877 + for (key, prop) in data.property_entries(shapes) { 595 878 if prop.enumerable && seen.insert(key.clone()) { 596 879 if let Ok(idx) = key.parse::<u32>() { 597 880 integer_keys.push((idx, key.clone())); ··· 788 1071 globals: HashMap<String, Value>, 789 1072 /// Garbage collector managing heap objects. 790 1073 pub gc: Gc<HeapObject>, 1074 + /// Shape table for object property layout tracking. 1075 + pub shapes: ShapeTable, 791 1076 /// Optional instruction limit. If set, the VM will return an error after 792 1077 /// executing this many instructions (prevents infinite loops). 793 1078 instruction_limit: Option<u64>, ··· 827 1112 frames: Vec::new(), 828 1113 globals: HashMap::new(), 829 1114 gc: Gc::new(), 1115 + shapes: ShapeTable::new(), 830 1116 instruction_limit: None, 831 1117 instructions_executed: 0, 832 1118 object_prototype: None, ··· 1039 1325 let dom_ref = self.dom_bridge.as_deref(); 1040 1326 let mut ctx = NativeContext { 1041 1327 gc: &mut self.gc, 1328 + shapes: &mut self.shapes, 1042 1329 this, 1043 1330 console_output: &*self.console_output, 1044 1331 dom_bridge: dom_ref, ··· 1048 1335 // Check for generator resume marker. 1049 1336 if let Value::Object(r) = &result { 1050 1337 let is_gen_resume = matches!( 1051 - gc_get_property(&self.gc, *r, "__generator_resume__"), 1338 + gc_get_property(&self.gc, *r, "__generator_resume__", &self.shapes), 1052 1339 Value::Boolean(true) 1053 1340 ); 1054 1341 if is_gen_resume { 1055 - let gen_ref = match gc_get_property(&self.gc, *r, "__gen_ref__") { 1056 - Value::Object(gr) => gr, 1057 - _ => return Ok(Value::Undefined), 1058 - }; 1059 - let send_val = gc_get_property(&self.gc, *r, "__send_value__"); 1060 - let kind_str = match gc_get_property(&self.gc, *r, "__resume_kind__") { 1061 - Value::String(s) => s, 1062 - _ => "next".to_string(), 1063 - }; 1342 + let gen_ref = 1343 + match gc_get_property(&self.gc, *r, "__gen_ref__", &self.shapes) { 1344 + Value::Object(gr) => gr, 1345 + _ => return Ok(Value::Undefined), 1346 + }; 1347 + let send_val = 1348 + gc_get_property(&self.gc, *r, "__send_value__", &self.shapes); 1349 + let kind_str = 1350 + match gc_get_property(&self.gc, *r, "__resume_kind__", &self.shapes) { 1351 + Value::String(s) => s, 1352 + _ => "next".to_string(), 1353 + }; 1064 1354 return match kind_str.as_str() { 1065 1355 "next" => self.run_generator(gen_ref, send_val), 1066 1356 "return" => { ··· 1081 1371 1082 1372 // Check for async resume marker. 1083 1373 let is_async_resume = matches!( 1084 - gc_get_property(&self.gc, *r, "__async_resume__"), 1374 + gc_get_property(&self.gc, *r, "__async_resume__", &self.shapes), 1085 1375 Value::Boolean(true) 1086 1376 ); 1087 1377 if is_async_resume { 1088 - let gen_ref = match gc_get_property(&self.gc, *r, "__gen_ref__") { 1089 - Value::Object(gr) => gr, 1090 - _ => return Ok(Value::Undefined), 1091 - }; 1378 + let gen_ref = 1379 + match gc_get_property(&self.gc, *r, "__gen_ref__", &self.shapes) { 1380 + Value::Object(gr) => gr, 1381 + _ => return Ok(Value::Undefined), 1382 + }; 1092 1383 let result_promise = 1093 - match gc_get_property(&self.gc, *r, "__result_promise__") { 1384 + match gc_get_property(&self.gc, *r, "__result_promise__", &self.shapes) 1385 + { 1094 1386 Value::Object(pr) => pr, 1095 1387 _ => return Ok(Value::Undefined), 1096 1388 }; 1097 1389 let is_throw = matches!( 1098 - gc_get_property(&self.gc, *r, "__is_throw__"), 1390 + gc_get_property(&self.gc, *r, "__is_throw__", &self.shapes), 1099 1391 Value::Boolean(true) 1100 1392 ); 1101 - let value = gc_get_property(&self.gc, *r, "__value__"); 1393 + let value = gc_get_property(&self.gc, *r, "__value__", &self.shapes); 1102 1394 self.drive_async_step(gen_ref, result_promise, value, is_throw); 1103 1395 return Ok(Value::Undefined); 1104 1396 } 1105 1397 1106 1398 // Check for async generator resume marker. 1107 1399 let is_ag_resume = matches!( 1108 - gc_get_property(&self.gc, *r, "__async_generator_resume__"), 1400 + gc_get_property(&self.gc, *r, "__async_generator_resume__", &self.shapes), 1109 1401 Value::Boolean(true) 1110 1402 ); 1111 1403 if is_ag_resume { 1112 - let gen_ref = match gc_get_property(&self.gc, *r, "__gen_ref__") { 1113 - Value::Object(gr) => gr, 1114 - _ => return Ok(Value::Undefined), 1115 - }; 1116 - let send_val = gc_get_property(&self.gc, *r, "__send_value__"); 1117 - let kind_str = match gc_get_property(&self.gc, *r, "__resume_kind__") { 1118 - Value::String(s) => s, 1119 - _ => "next".to_string(), 1120 - }; 1404 + let gen_ref = 1405 + match gc_get_property(&self.gc, *r, "__gen_ref__", &self.shapes) { 1406 + Value::Object(gr) => gr, 1407 + _ => return Ok(Value::Undefined), 1408 + }; 1409 + let send_val = 1410 + gc_get_property(&self.gc, *r, "__send_value__", &self.shapes); 1411 + let kind_str = 1412 + match gc_get_property(&self.gc, *r, "__resume_kind__", &self.shapes) { 1413 + Value::String(s) => s, 1414 + _ => "next".to_string(), 1415 + }; 1121 1416 1122 1417 // Create a promise for the result. 1123 - let promise = crate::builtins::create_promise_object_pub(&mut self.gc); 1418 + let promise = crate::builtins::create_promise_object_pub( 1419 + &mut self.gc, 1420 + &mut self.shapes, 1421 + ); 1124 1422 1125 1423 match kind_str.as_str() { 1126 1424 "next" => match self.run_generator(gen_ref, send_val) { 1127 1425 Ok(iter_result) => { 1128 1426 crate::builtins::resolve_promise_internal( 1129 1427 &mut self.gc, 1428 + &mut self.shapes, 1130 1429 promise, 1131 1430 iter_result, 1132 1431 ); 1133 1432 } 1134 1433 Err(err) => { 1135 - let reason = err.to_value(&mut self.gc); 1434 + let reason = err.to_value(&mut self.gc, &mut self.shapes); 1136 1435 crate::builtins::reject_promise_internal( 1137 1436 &mut self.gc, 1437 + &mut self.shapes, 1138 1438 promise, 1139 1439 reason, 1140 1440 ); ··· 1147 1447 let result = self.make_iterator_result(send_val, true); 1148 1448 crate::builtins::resolve_promise_internal( 1149 1449 &mut self.gc, 1450 + &mut self.shapes, 1150 1451 promise, 1151 1452 result, 1152 1453 ); ··· 1158 1459 1159 1460 // Check for event dispatch marker. 1160 1461 let is_event_dispatch = matches!( 1161 - gc_get_property(&self.gc, *r, "__event_dispatch__"), 1462 + gc_get_property(&self.gc, *r, "__event_dispatch__", &self.shapes), 1162 1463 Value::Boolean(true) 1163 1464 ); 1164 1465 if is_event_dispatch { 1165 - let target_idx = match gc_get_property(&self.gc, *r, "__target_id__") { 1166 - Value::Number(n) => n as usize, 1167 - _ => return Ok(Value::Boolean(true)), 1168 - }; 1169 - let evt_ref = match gc_get_property(&self.gc, *r, "__event_ref__") { 1170 - Value::Object(er) => er, 1171 - _ => return Ok(Value::Boolean(true)), 1172 - }; 1466 + let target_idx = 1467 + match gc_get_property(&self.gc, *r, "__target_id__", &self.shapes) { 1468 + Value::Number(n) => n as usize, 1469 + _ => return Ok(Value::Boolean(true)), 1470 + }; 1471 + let evt_ref = 1472 + match gc_get_property(&self.gc, *r, "__event_ref__", &self.shapes) { 1473 + Value::Object(er) => er, 1474 + _ => return Ok(Value::Boolean(true)), 1475 + }; 1173 1476 return Ok(crate::dom_bridge::run_event_dispatch( 1174 1477 self, target_idx, evt_ref, 1175 1478 )); ··· 1184 1487 // Async function: create generator + promise, drive async. 1185 1488 if callee_func.is_async && !callee_func.is_generator { 1186 1489 let gen_ref = self.create_raw_generator(callee_func, upvalues, args); 1187 - let result_promise = crate::builtins::create_promise_object_pub(&mut self.gc); 1490 + let result_promise = 1491 + crate::builtins::create_promise_object_pub(&mut self.gc, &mut self.shapes); 1188 1492 self.drive_async_step(gen_ref, result_promise, Value::Undefined, false); 1189 1493 return Ok(Value::Object(result_promise)); 1190 1494 } ··· 1264 1568 let is_finally = matches!( 1265 1569 crate::builtins::promise_get_prop_pub( 1266 1570 &self.gc, 1571 + &self.shapes, 1267 1572 chained, 1268 1573 "__finally__" 1269 1574 ), ··· 1276 1581 Ok(_) => { 1277 1582 let parent = crate::builtins::promise_get_prop_pub( 1278 1583 &self.gc, 1584 + &self.shapes, 1279 1585 chained, 1280 1586 "__finally_parent__", 1281 1587 ); 1282 1588 if let Some(parent_ref) = parent.gc_ref() { 1283 1589 let parent_state = crate::builtins::promise_state_pub( 1284 - &self.gc, parent_ref, 1590 + &self.gc, 1591 + &self.shapes, 1592 + parent_ref, 1285 1593 ); 1286 1594 let parent_result = 1287 1595 crate::builtins::promise_get_prop_pub( 1288 1596 &self.gc, 1597 + &self.shapes, 1289 1598 parent_ref, 1290 1599 crate::builtins::PROMISE_RESULT_KEY, 1291 1600 ); 1292 1601 if parent_state == crate::builtins::PROMISE_FULFILLED { 1293 1602 crate::builtins::resolve_promise_internal( 1294 1603 &mut self.gc, 1604 + &mut self.shapes, 1295 1605 chained, 1296 1606 parent_result, 1297 1607 ); 1298 1608 } else { 1299 1609 crate::builtins::reject_promise_internal( 1300 1610 &mut self.gc, 1611 + &mut self.shapes, 1301 1612 chained, 1302 1613 parent_result, 1303 1614 ); ··· 1305 1616 } 1306 1617 } 1307 1618 Err(err) => { 1308 - let err_val = err.to_value(&mut self.gc); 1619 + let err_val = err.to_value(&mut self.gc, &mut self.shapes); 1309 1620 crate::builtins::reject_promise_internal( 1310 1621 &mut self.gc, 1622 + &mut self.shapes, 1311 1623 chained, 1312 1624 err_val, 1313 1625 ); ··· 1317 1629 match result { 1318 1630 Ok(val) => { 1319 1631 // If result is a promise, chain it. 1320 - if crate::builtins::is_promise_pub(&self.gc, &val) { 1632 + if crate::builtins::is_promise_pub( 1633 + &self.gc, 1634 + &self.shapes, 1635 + &val, 1636 + ) { 1321 1637 if let Some(val_ref) = val.gc_ref() { 1322 1638 let state = crate::builtins::promise_state_pub( 1323 - &self.gc, val_ref, 1639 + &self.gc, 1640 + &self.shapes, 1641 + val_ref, 1324 1642 ); 1325 1643 if state == crate::builtins::PROMISE_FULFILLED { 1326 1644 let r = crate::builtins::promise_get_prop_pub( 1327 1645 &self.gc, 1646 + &self.shapes, 1328 1647 val_ref, 1329 1648 crate::builtins::PROMISE_RESULT_KEY, 1330 1649 ); 1331 1650 crate::builtins::resolve_promise_internal( 1332 1651 &mut self.gc, 1652 + &mut self.shapes, 1333 1653 chained, 1334 1654 r, 1335 1655 ); ··· 1337 1657 { 1338 1658 let r = crate::builtins::promise_get_prop_pub( 1339 1659 &self.gc, 1660 + &self.shapes, 1340 1661 val_ref, 1341 1662 crate::builtins::PROMISE_RESULT_KEY, 1342 1663 ); 1343 1664 crate::builtins::reject_promise_internal( 1344 1665 &mut self.gc, 1666 + &mut self.shapes, 1345 1667 chained, 1346 1668 r, 1347 1669 ); 1348 1670 } else { 1349 1671 crate::builtins::chain_promise_pub( 1350 1672 &mut self.gc, 1673 + &mut self.shapes, 1351 1674 val_ref, 1352 1675 chained, 1353 1676 ); ··· 1356 1679 } else { 1357 1680 crate::builtins::resolve_promise_internal( 1358 1681 &mut self.gc, 1682 + &mut self.shapes, 1359 1683 chained, 1360 1684 val, 1361 1685 ); 1362 1686 } 1363 1687 } 1364 1688 Err(err) => { 1365 - let err_val = err.to_value(&mut self.gc); 1689 + let err_val = err.to_value(&mut self.gc, &mut self.shapes); 1366 1690 crate::builtins::reject_promise_internal( 1367 1691 &mut self.gc, 1692 + &mut self.shapes, 1368 1693 chained, 1369 1694 err_val, 1370 1695 ); ··· 1379 1704 if task.is_fulfillment { 1380 1705 crate::builtins::resolve_promise_internal( 1381 1706 &mut self.gc, 1707 + &mut self.shapes, 1382 1708 chained, 1383 1709 task.value, 1384 1710 ); 1385 1711 } else { 1386 1712 crate::builtins::reject_promise_internal( 1387 1713 &mut self.gc, 1714 + &mut self.shapes, 1388 1715 chained, 1389 1716 task.value, 1390 1717 ); ··· 1419 1746 for fetch in completed { 1420 1747 match fetch.result { 1421 1748 Ok(result) => { 1422 - let response = crate::fetch::create_response_object(&mut self.gc, &result); 1749 + let response = crate::fetch::create_response_object( 1750 + &mut self.gc, 1751 + &mut self.shapes, 1752 + &result, 1753 + ); 1423 1754 crate::builtins::resolve_promise_internal( 1424 1755 &mut self.gc, 1756 + &mut self.shapes, 1425 1757 fetch.promise, 1426 1758 Value::Object(response), 1427 1759 ); 1428 1760 } 1429 1761 Err(err_msg) => { 1430 1762 let err_val = Value::String(err_msg); 1431 - crate::builtins::reject_promise_internal(&mut self.gc, fetch.promise, err_val); 1763 + crate::builtins::reject_promise_internal( 1764 + &mut self.gc, 1765 + &mut self.shapes, 1766 + fetch.promise, 1767 + err_val, 1768 + ); 1432 1769 } 1433 1770 } 1434 1771 self.drain_microtasks()?; ··· 1544 1881 obj.prototype = self.object_prototype; 1545 1882 1546 1883 // Store the generator GcRef so methods can find it. 1547 - obj.properties.insert( 1884 + obj.insert_property( 1548 1885 "__gen__".to_string(), 1549 1886 Property { 1550 1887 value: Value::Object(gen_ref), ··· 1552 1889 enumerable: false, 1553 1890 configurable: false, 1554 1891 }, 1892 + &mut self.shapes, 1555 1893 ); 1556 1894 1557 1895 // next() method ··· 1564 1902 properties: HashMap::new(), 1565 1903 upvalues: Vec::new(), 1566 1904 }))); 1567 - obj.properties.insert( 1905 + obj.insert_property( 1568 1906 "next".to_string(), 1569 1907 Property::builtin(Value::Function(next_fn)), 1908 + &mut self.shapes, 1570 1909 ); 1571 1910 1572 1911 // return() method ··· 1579 1918 properties: HashMap::new(), 1580 1919 upvalues: Vec::new(), 1581 1920 }))); 1582 - obj.properties.insert( 1921 + obj.insert_property( 1583 1922 "return".to_string(), 1584 1923 Property::builtin(Value::Function(return_fn)), 1924 + &mut self.shapes, 1585 1925 ); 1586 1926 1587 1927 // throw() method ··· 1594 1934 properties: HashMap::new(), 1595 1935 upvalues: Vec::new(), 1596 1936 }))); 1597 - obj.properties.insert( 1937 + obj.insert_property( 1598 1938 "throw".to_string(), 1599 1939 Property::builtin(Value::Function(throw_fn)), 1940 + &mut self.shapes, 1600 1941 ); 1601 1942 1602 1943 // @@iterator method (generators are iterable - returns self) ··· 1609 1950 properties: HashMap::new(), 1610 1951 upvalues: Vec::new(), 1611 1952 }))); 1612 - obj.properties.insert( 1953 + obj.insert_property( 1613 1954 "@@iterator".to_string(), 1614 1955 Property::builtin(Value::Function(iter_fn)), 1956 + &mut self.shapes, 1615 1957 ); 1616 1958 1617 1959 self.gc.alloc(HeapObject::Object(obj)) ··· 1650 1992 fn make_iterator_result(&mut self, value: Value, done: bool) -> Value { 1651 1993 let mut obj = ObjectData::new(); 1652 1994 obj.prototype = self.object_prototype; 1653 - obj.properties 1654 - .insert("value".to_string(), Property::data(value)); 1655 - obj.properties 1656 - .insert("done".to_string(), Property::data(Value::Boolean(done))); 1995 + obj.insert_property("value".to_string(), Property::data(value), &mut self.shapes); 1996 + obj.insert_property( 1997 + "done".to_string(), 1998 + Property::data(Value::Boolean(done)), 1999 + &mut self.shapes, 2000 + ); 1657 2001 let gc_ref = self.gc.alloc(HeapObject::Object(obj)); 1658 2002 Value::Object(gc_ref) 1659 2003 } ··· 1901 2245 // Get the @@iterator property. 1902 2246 let iter_fn = match iterable { 1903 2247 Value::Object(gc_ref) | Value::Function(gc_ref) => { 1904 - gc_get_property(&self.gc, *gc_ref, "@@iterator") 2248 + gc_get_property(&self.gc, *gc_ref, "@@iterator", &self.shapes) 1905 2249 } 1906 2250 Value::String(_) => { 1907 2251 // Strings have @@iterator on their prototype. 1908 2252 self.string_prototype 1909 - .map(|p| gc_get_property(&self.gc, p, "@@iterator")) 2253 + .map(|p| gc_get_property(&self.gc, p, "@@iterator", &self.shapes)) 1910 2254 .unwrap_or(Value::Undefined) 1911 2255 } 1912 2256 _ => Value::Undefined, ··· 1940 2284 _ => return Err(RuntimeError::type_error("iterator is not an object")), 1941 2285 }; 1942 2286 1943 - let next_fn = gc_get_property(&self.gc, iter_ref, "next"); 2287 + let next_fn = gc_get_property(&self.gc, iter_ref, "next", &self.shapes); 1944 2288 let next_fn_ref = match next_fn { 1945 2289 Value::Function(r) => r, 1946 2290 _ => return Err(RuntimeError::type_error("iterator.next is not a function")), ··· 1958 2302 // Extract value and done from the result object. 1959 2303 let (value, done) = match result { 1960 2304 Value::Object(r) => { 1961 - let val = gc_get_property(&self.gc, r, "value"); 1962 - let d = gc_get_property(&self.gc, r, "done"); 2305 + let val = gc_get_property(&self.gc, r, "value", &self.shapes); 2306 + let d = gc_get_property(&self.gc, r, "done", &self.shapes); 1963 2307 (val, d.to_boolean()) 1964 2308 } 1965 2309 _ => (Value::Undefined, true), ··· 1993 2337 // Extract {value, done} from the iterator result. 1994 2338 let (value, done) = match &iter_result { 1995 2339 Value::Object(r) => { 1996 - let val = gc_get_property(&self.gc, *r, "value"); 1997 - let d = gc_get_property(&self.gc, *r, "done"); 2340 + let val = gc_get_property(&self.gc, *r, "value", &self.shapes); 2341 + let d = gc_get_property(&self.gc, *r, "done", &self.shapes); 1998 2342 (val, d.to_boolean()) 1999 2343 } 2000 2344 _ => (Value::Undefined, true), ··· 2002 2346 2003 2347 if done { 2004 2348 // Async function returned — resolve the result promise. 2005 - crate::builtins::resolve_promise_internal(&mut self.gc, result_promise, value); 2349 + crate::builtins::resolve_promise_internal( 2350 + &mut self.gc, 2351 + &mut self.shapes, 2352 + result_promise, 2353 + value, 2354 + ); 2006 2355 } else { 2007 2356 // Async function awaited — set up promise chain to resume. 2008 2357 self.setup_async_resume(gen_ref, result_promise, value); ··· 2016 2365 let reason = if err.kind == ErrorKind::Error { 2017 2366 Value::String(err.message.clone()) 2018 2367 } else { 2019 - err.to_value(&mut self.gc) 2368 + err.to_value(&mut self.gc, &mut self.shapes) 2020 2369 }; 2021 - crate::builtins::reject_promise_internal(&mut self.gc, result_promise, reason); 2370 + crate::builtins::reject_promise_internal( 2371 + &mut self.gc, 2372 + &mut self.shapes, 2373 + result_promise, 2374 + reason, 2375 + ); 2022 2376 } 2023 2377 } 2024 2378 } ··· 2065 2419 }))); 2066 2420 2067 2421 // If the awaited value is a promise, react to it. 2068 - if crate::builtins::is_promise_pub(&self.gc, &awaited_value) { 2422 + if crate::builtins::is_promise_pub(&self.gc, &self.shapes, &awaited_value) { 2069 2423 let val_ref = awaited_value.gc_ref().unwrap(); 2070 - let state = crate::builtins::promise_state_pub(&self.gc, val_ref); 2424 + let state = crate::builtins::promise_state_pub(&self.gc, &self.shapes, val_ref); 2071 2425 if state == crate::builtins::PROMISE_FULFILLED { 2072 2426 let r = crate::builtins::promise_get_prop_pub( 2073 2427 &self.gc, 2428 + &self.shapes, 2074 2429 val_ref, 2075 2430 crate::builtins::PROMISE_RESULT_KEY, 2076 2431 ); ··· 2083 2438 } else if state == crate::builtins::PROMISE_REJECTED { 2084 2439 let r = crate::builtins::promise_get_prop_pub( 2085 2440 &self.gc, 2441 + &self.shapes, 2086 2442 val_ref, 2087 2443 crate::builtins::PROMISE_RESULT_KEY, 2088 2444 ); ··· 2096 2452 // Pending promise: add reactions. 2097 2453 crate::builtins::add_reaction_pub( 2098 2454 &mut self.gc, 2455 + &mut self.shapes, 2099 2456 val_ref, 2100 2457 Value::Function(fulfill_fn), 2101 2458 Value::Function(reject_fn), ··· 2120 2477 is_throw: bool, 2121 2478 ) -> GcRef { 2122 2479 let mut data = ObjectData::new(); 2123 - data.properties.insert( 2480 + data.insert_property( 2124 2481 "__gen_ref__".to_string(), 2125 2482 Property::builtin(Value::Object(gen_ref)), 2483 + &mut self.shapes, 2126 2484 ); 2127 - data.properties.insert( 2485 + data.insert_property( 2128 2486 "__result_promise__".to_string(), 2129 2487 Property::builtin(Value::Object(result_promise)), 2488 + &mut self.shapes, 2130 2489 ); 2131 - data.properties.insert( 2490 + data.insert_property( 2132 2491 "__is_throw__".to_string(), 2133 2492 Property::builtin(Value::Boolean(is_throw)), 2493 + &mut self.shapes, 2134 2494 ); 2135 2495 self.gc.alloc(HeapObject::Object(data)) 2136 2496 } ··· 2140 2500 let mut obj = ObjectData::new(); 2141 2501 obj.prototype = self.object_prototype; 2142 2502 2143 - obj.properties.insert( 2503 + obj.insert_property( 2144 2504 "__gen__".to_string(), 2145 2505 Property { 2146 2506 value: Value::Object(gen_ref), ··· 2148 2508 enumerable: false, 2149 2509 configurable: false, 2150 2510 }, 2511 + &mut self.shapes, 2151 2512 ); 2152 2513 2153 2514 // next() method — returns a Promise for the next iteration result. ··· 2160 2521 properties: HashMap::new(), 2161 2522 upvalues: Vec::new(), 2162 2523 }))); 2163 - obj.properties.insert( 2524 + obj.insert_property( 2164 2525 "next".to_string(), 2165 2526 Property::builtin(Value::Function(next_fn)), 2527 + &mut self.shapes, 2166 2528 ); 2167 2529 2168 2530 // return() method ··· 2175 2537 properties: HashMap::new(), 2176 2538 upvalues: Vec::new(), 2177 2539 }))); 2178 - obj.properties.insert( 2540 + obj.insert_property( 2179 2541 "return".to_string(), 2180 2542 Property::builtin(Value::Function(return_fn)), 2543 + &mut self.shapes, 2181 2544 ); 2182 2545 2183 2546 // @@asyncIterator method — returns self. ··· 2190 2553 properties: HashMap::new(), 2191 2554 upvalues: Vec::new(), 2192 2555 }))); 2193 - obj.properties.insert( 2556 + obj.insert_property( 2194 2557 "@@asyncIterator".to_string(), 2195 2558 Property::builtin(Value::Function(iter_fn)), 2559 + &mut self.shapes, 2196 2560 ); 2197 2561 2198 2562 self.gc.alloc(HeapObject::Object(obj)) ··· 2203 2567 fn resolve_dom_property(&mut self, gc_ref: GcRef, key: &str) -> Option<Value> { 2204 2568 let bridge = Rc::clone(self.dom_bridge.as_ref()?); 2205 2569 // Try Storage proxy properties first. 2206 - if let Some(val) = crate::dom_bridge::resolve_storage_get(&self.gc, &bridge, gc_ref, key) { 2570 + if let Some(val) = 2571 + crate::dom_bridge::resolve_storage_get(&self.gc, &self.shapes, &bridge, gc_ref, key) 2572 + { 2207 2573 return Some(val); 2208 2574 } 2209 2575 // Try window properties (parent, top, frames, length). 2210 - if let Some(val) = crate::iframe_bridge::resolve_window_property(&self.gc, gc_ref, key) { 2576 + if let Some(val) = 2577 + crate::iframe_bridge::resolve_window_property(&self.gc, &self.shapes, gc_ref, key) 2578 + { 2211 2579 return Some(val); 2212 2580 } 2213 2581 // Try iframe element properties (contentWindow, contentDocument). 2214 - if let Some(node_id) = crate::dom_bridge::get_node_id_pub(&self.gc, gc_ref) { 2215 - if let Some(val) = 2216 - crate::iframe_bridge::resolve_iframe_property(&mut self.gc, &bridge, node_id, key) 2217 - { 2582 + if let Some(node_id) = crate::dom_bridge::get_node_id_pub(&self.gc, &self.shapes, gc_ref) { 2583 + if let Some(val) = crate::iframe_bridge::resolve_iframe_property( 2584 + &mut self.gc, 2585 + &self.shapes, 2586 + &bridge, 2587 + node_id, 2588 + key, 2589 + ) { 2218 2590 return Some(val); 2219 2591 } 2220 2592 } 2221 2593 // Try node wrapper properties. 2222 - if let Some(val) = crate::dom_bridge::resolve_dom_get(&mut self.gc, &bridge, gc_ref, key) { 2594 + if let Some(val) = 2595 + crate::dom_bridge::resolve_dom_get(&mut self.gc, &mut self.shapes, &bridge, gc_ref, key) 2596 + { 2223 2597 return Some(val); 2224 2598 } 2225 2599 // Try document-level dynamic properties (e.g. document.cookie). 2226 - crate::dom_bridge::resolve_document_get(&self.gc, &bridge, gc_ref, key) 2600 + crate::dom_bridge::resolve_document_get(&self.gc, &self.shapes, &bridge, gc_ref, key) 2227 2601 } 2228 2602 2229 2603 /// Handle a DOM property set on a wrapper object. ··· 2231 2605 fn handle_dom_property_set(&mut self, gc_ref: GcRef, key: &str, val: &Value) -> bool { 2232 2606 if let Some(bridge) = self.dom_bridge.clone() { 2233 2607 // Check for Storage proxy sets (localStorage["key"] = "val"). 2234 - if crate::dom_bridge::handle_storage_set(&bridge, gc_ref, key, val, &self.gc) { 2608 + if crate::dom_bridge::handle_storage_set( 2609 + &bridge, 2610 + gc_ref, 2611 + key, 2612 + val, 2613 + &self.gc, 2614 + &self.shapes, 2615 + ) { 2235 2616 return true; 2236 2617 } 2237 2618 // Check for document-level dynamic properties (e.g. document.cookie). 2238 - if crate::dom_bridge::handle_document_set(&bridge, gc_ref, key, val, &self.gc) { 2619 + if crate::dom_bridge::handle_document_set( 2620 + &bridge, 2621 + gc_ref, 2622 + key, 2623 + val, 2624 + &self.gc, 2625 + &self.shapes, 2626 + ) { 2239 2627 return true; 2240 2628 } 2241 2629 // Check for style proxy objects. 2242 - if crate::dom_bridge::handle_style_set(&mut self.gc, &bridge, gc_ref, key, val) { 2630 + if crate::dom_bridge::handle_style_set( 2631 + &mut self.gc, 2632 + &mut self.shapes, 2633 + &bridge, 2634 + gc_ref, 2635 + key, 2636 + val, 2637 + ) { 2243 2638 return true; 2244 2639 } 2245 2640 // Then check for DOM node wrapper sets. 2246 - crate::dom_bridge::handle_dom_set(&mut self.gc, &bridge, gc_ref, key, val) 2641 + crate::dom_bridge::handle_dom_set( 2642 + &mut self.gc, 2643 + &mut self.shapes, 2644 + &bridge, 2645 + gc_ref, 2646 + key, 2647 + val, 2648 + ) 2247 2649 } else { 2248 2650 false 2249 2651 } ··· 2612 3014 let key = self.registers[base + key_r as usize].to_js_string(&self.gc); 2613 3015 let result = match self.registers[base + obj_r as usize] { 2614 3016 Value::Object(gc_ref) => { 2615 - Value::Boolean(gc_has_property(&self.gc, gc_ref, &key)) 3017 + Value::Boolean(gc_has_property(&self.gc, gc_ref, &key, &self.shapes)) 2616 3018 } 2617 3019 _ => { 2618 3020 return Err(RuntimeError::type_error( ··· 2674 3076 let desc = 2675 3077 self.registers[base + func_r as usize].to_js_string(&self.gc); 2676 3078 let err = RuntimeError::type_error(format!("{desc} is not a function")); 2677 - let err_val = err.to_value(&mut self.gc); 3079 + let err_val = err.to_value(&mut self.gc, &mut self.shapes); 2678 3080 if !self.handle_exception(err_val) { 2679 3081 return Err(err); 2680 3082 } ··· 2699 3101 }, 2700 3102 _ => { 2701 3103 let err = RuntimeError::type_error("not a function"); 2702 - let err_val = err.to_value(&mut self.gc); 3104 + let err_val = err.to_value(&mut self.gc, &mut self.shapes); 2703 3105 if !self.handle_exception(err_val) { 2704 3106 return Err(err); 2705 3107 } ··· 2727 3129 let dom_ref = self.dom_bridge.as_deref(); 2728 3130 let mut ctx = NativeContext { 2729 3131 gc: &mut self.gc, 3132 + shapes: &mut self.shapes, 2730 3133 this, 2731 3134 console_output: &*self.console_output, 2732 3135 dom_bridge: dom_ref, ··· 2736 3139 // Check if this is a generator resume request. 2737 3140 if let Value::Object(r) = &val { 2738 3141 let is_resume = matches!( 2739 - gc_get_property(&self.gc, *r, "__generator_resume__"), 3142 + gc_get_property( 3143 + &self.gc, 3144 + *r, 3145 + "__generator_resume__", 3146 + &self.shapes 3147 + ), 2740 3148 Value::Boolean(true) 2741 3149 ); 2742 3150 if is_resume { ··· 2744 3152 &self.gc, 2745 3153 *r, 2746 3154 "__gen_ref__", 3155 + &self.shapes, 2747 3156 ) { 2748 3157 Value::Object(gr) => gr, 2749 3158 _ => { ··· 2752 3161 continue; 2753 3162 } 2754 3163 }; 2755 - let send_val = 2756 - gc_get_property(&self.gc, *r, "__send_value__"); 3164 + let send_val = gc_get_property( 3165 + &self.gc, 3166 + *r, 3167 + "__send_value__", 3168 + &self.shapes, 3169 + ); 2757 3170 let kind = match gc_get_property( 2758 3171 &self.gc, 2759 3172 *r, 2760 3173 "__resume_kind__", 3174 + &self.shapes, 2761 3175 ) { 2762 3176 Value::String(s) => s, 2763 3177 _ => "next".to_string(), ··· 2771 3185 result; 2772 3186 } 2773 3187 Err(err) => { 2774 - let err_val = 2775 - err.to_value(&mut self.gc); 3188 + let err_val = err.to_value( 3189 + &mut self.gc, 3190 + &mut self.shapes, 3191 + ); 2776 3192 if !self.handle_exception(err_val) { 2777 3193 return Err(err); 2778 3194 } ··· 2813 3229 2814 3230 // Check for async resume marker. 2815 3231 let is_async_resume = matches!( 2816 - gc_get_property(&self.gc, *r, "__async_resume__"), 3232 + gc_get_property( 3233 + &self.gc, 3234 + *r, 3235 + "__async_resume__", 3236 + &self.shapes 3237 + ), 2817 3238 Value::Boolean(true) 2818 3239 ); 2819 3240 if is_async_resume { ··· 2821 3242 &self.gc, 2822 3243 *r, 2823 3244 "__gen_ref__", 3245 + &self.shapes, 2824 3246 ) { 2825 3247 Value::Object(gr) => gr, 2826 3248 _ => { ··· 2833 3255 &self.gc, 2834 3256 *r, 2835 3257 "__result_promise__", 3258 + &self.shapes, 2836 3259 ) { 2837 3260 Value::Object(pr) => pr, 2838 3261 _ => { ··· 2842 3265 } 2843 3266 }; 2844 3267 let ar_throw = matches!( 2845 - gc_get_property(&self.gc, *r, "__is_throw__"), 3268 + gc_get_property( 3269 + &self.gc, 3270 + *r, 3271 + "__is_throw__", 3272 + &self.shapes 3273 + ), 2846 3274 Value::Boolean(true) 2847 3275 ); 2848 - let ar_value = 2849 - gc_get_property(&self.gc, *r, "__value__"); 3276 + let ar_value = gc_get_property( 3277 + &self.gc, 3278 + *r, 3279 + "__value__", 3280 + &self.shapes, 3281 + ); 2850 3282 self.drive_async_step( 2851 3283 ar_gen, ar_promise, ar_value, ar_throw, 2852 3284 ); ··· 2859 3291 gc_get_property( 2860 3292 &self.gc, 2861 3293 *r, 2862 - "__async_generator_resume__" 3294 + "__async_generator_resume__", 3295 + &self.shapes, 2863 3296 ), 2864 3297 Value::Boolean(true) 2865 3298 ); ··· 2868 3301 &self.gc, 2869 3302 *r, 2870 3303 "__gen_ref__", 3304 + &self.shapes, 2871 3305 ) { 2872 3306 Value::Object(gr) => gr, 2873 3307 _ => { ··· 2876 3310 continue; 2877 3311 } 2878 3312 }; 2879 - let ag_send = 2880 - gc_get_property(&self.gc, *r, "__send_value__"); 3313 + let ag_send = gc_get_property( 3314 + &self.gc, 3315 + *r, 3316 + "__send_value__", 3317 + &self.shapes, 3318 + ); 2881 3319 let ag_kind = match gc_get_property( 2882 3320 &self.gc, 2883 3321 *r, 2884 3322 "__resume_kind__", 3323 + &self.shapes, 2885 3324 ) { 2886 3325 Value::String(s) => s, 2887 3326 _ => "next".to_string(), ··· 2890 3329 let promise = 2891 3330 crate::builtins::create_promise_object_pub( 2892 3331 &mut self.gc, 3332 + &mut self.shapes, 2893 3333 ); 2894 3334 2895 3335 match ag_kind.as_str() { ··· 2898 3338 Ok(iter_result) => { 2899 3339 crate::builtins::resolve_promise_internal( 2900 3340 &mut self.gc, 3341 + &mut self.shapes, 2901 3342 promise, 2902 3343 iter_result, 2903 3344 ); 2904 3345 } 2905 3346 Err(err) => { 2906 - let reason = err.to_value(&mut self.gc); 3347 + let reason = err.to_value( 3348 + &mut self.gc, 3349 + &mut self.shapes, 3350 + ); 2907 3351 crate::builtins::reject_promise_internal( 2908 3352 &mut self.gc, 3353 + &mut self.shapes, 2909 3354 promise, 2910 3355 reason, 2911 3356 ); ··· 2922 3367 self.make_iterator_result(ag_send, true); 2923 3368 crate::builtins::resolve_promise_internal( 2924 3369 &mut self.gc, 3370 + &mut self.shapes, 2925 3371 promise, 2926 3372 result, 2927 3373 ); ··· 2935 3381 2936 3382 // Check for event dispatch marker. 2937 3383 let is_event_dispatch = matches!( 2938 - gc_get_property(&self.gc, *r, "__event_dispatch__"), 3384 + gc_get_property( 3385 + &self.gc, 3386 + *r, 3387 + "__event_dispatch__", 3388 + &self.shapes 3389 + ), 2939 3390 Value::Boolean(true) 2940 3391 ); 2941 3392 if is_event_dispatch { ··· 2943 3394 &self.gc, 2944 3395 *r, 2945 3396 "__target_id__", 3397 + &self.shapes, 2946 3398 ) { 2947 3399 Value::Number(n) => n as usize, 2948 3400 _ => { ··· 2955 3407 &self.gc, 2956 3408 *r, 2957 3409 "__event_ref__", 3410 + &self.shapes, 2958 3411 ) { 2959 3412 Value::Object(er) => er, 2960 3413 _ => { ··· 2973 3426 self.registers[base + dst as usize] = val; 2974 3427 } 2975 3428 Err(err) => { 2976 - let err_val = err.to_value(&mut self.gc); 3429 + let err_val = err.to_value(&mut self.gc, &mut self.shapes); 2977 3430 if !self.handle_exception(err_val) { 2978 3431 return Err(err); 2979 3432 } ··· 2985 3438 if callee_func.is_async && !callee_func.is_generator { 2986 3439 let gen_ref = 2987 3440 self.create_raw_generator(callee_func, callee_upvalues, &args); 2988 - let result_promise = 2989 - crate::builtins::create_promise_object_pub(&mut self.gc); 3441 + let result_promise = crate::builtins::create_promise_object_pub( 3442 + &mut self.gc, 3443 + &mut self.shapes, 3444 + ); 2990 3445 self.drive_async_step( 2991 3446 gen_ref, 2992 3447 result_promise, ··· 3020 3475 if self.frames.len() >= MAX_CALL_DEPTH { 3021 3476 let err = 3022 3477 RuntimeError::range_error("Maximum call stack size exceeded"); 3023 - let err_val = err.to_value(&mut self.gc); 3478 + let err_val = err.to_value(&mut self.gc, &mut self.shapes); 3024 3479 if !self.handle_exception(err_val) { 3025 3480 return Err(err); 3026 3481 } ··· 3118 3573 }))); 3119 3574 // Set .prototype.constructor = this function. 3120 3575 if let Some(HeapObject::Object(data)) = self.gc.get_mut(proto_obj) { 3121 - data.properties.insert( 3576 + data.insert_property( 3122 3577 "constructor".to_string(), 3123 3578 Property { 3124 3579 value: Value::Function(gc_ref), ··· 3126 3581 enumerable: false, 3127 3582 configurable: true, 3128 3583 }, 3584 + &mut self.shapes, 3129 3585 ); 3130 3586 } 3131 3587 self.registers[base + dst as usize] = Value::Function(gc_ref); ··· 3151 3607 }; 3152 3608 let val = match self.registers[base + obj_r as usize] { 3153 3609 Value::Object(gc_ref) | Value::Function(gc_ref) => { 3154 - gc_get_property(&self.gc, gc_ref, &key) 3610 + gc_get_property(&self.gc, gc_ref, &key, &self.shapes) 3155 3611 } 3156 3612 Value::String(ref s) => { 3157 3613 let v = string_get_property(s, &key); 3158 3614 if matches!(v, Value::Undefined) { 3159 3615 self.string_prototype 3160 - .map(|p| gc_get_property(&self.gc, p, &key)) 3616 + .map(|p| gc_get_property(&self.gc, p, &key, &self.shapes)) 3161 3617 .unwrap_or(Value::Undefined) 3162 3618 } else { 3163 3619 v ··· 3165 3621 } 3166 3622 Value::Number(_) => self 3167 3623 .number_prototype 3168 - .map(|p| gc_get_property(&self.gc, p, &key)) 3624 + .map(|p| gc_get_property(&self.gc, p, &key, &self.shapes)) 3169 3625 .unwrap_or(Value::Undefined), 3170 3626 Value::Boolean(_) => self 3171 3627 .boolean_prototype 3172 - .map(|p| gc_get_property(&self.gc, p, &key)) 3628 + .map(|p| gc_get_property(&self.gc, p, &key, &self.shapes)) 3173 3629 .unwrap_or(Value::Undefined), 3174 3630 _ => Value::Undefined, 3175 3631 }; ··· 3202 3658 if let Some(gc_ref) = obj_gc { 3203 3659 match self.gc.get_mut(gc_ref) { 3204 3660 Some(HeapObject::Object(data)) => { 3205 - if let Some(prop) = data.properties.get_mut(&key) { 3206 - if prop.writable { 3207 - prop.value = val; 3208 - } 3209 - } else { 3210 - data.properties.insert(key, Property::data(val)); 3211 - } 3661 + data.set_property(key, val, &mut self.shapes); 3212 3662 } 3213 3663 Some(HeapObject::Function(fdata)) => { 3214 3664 if let Some(prop) = fdata.properties.get_mut(&key) { ··· 3242 3692 let base = self.frames[fi].base; 3243 3693 let mut obj = ObjectData::new(); 3244 3694 obj.prototype = self.array_prototype; 3245 - obj.properties.insert( 3695 + obj.insert_property( 3246 3696 "length".to_string(), 3247 3697 Property { 3248 3698 value: Value::Number(0.0), ··· 3250 3700 enumerable: false, 3251 3701 configurable: false, 3252 3702 }, 3703 + &mut self.shapes, 3253 3704 ); 3254 3705 let gc_ref = self.gc.alloc(HeapObject::Object(obj)); 3255 3706 self.registers[base + dst as usize] = Value::Object(gc_ref); ··· 3271 3722 }; 3272 3723 let val = match self.registers[base + obj_r as usize] { 3273 3724 Value::Object(gc_ref) | Value::Function(gc_ref) => { 3274 - gc_get_property(&self.gc, gc_ref, &key) 3725 + gc_get_property(&self.gc, gc_ref, &key, &self.shapes) 3275 3726 } 3276 3727 Value::String(ref s) => { 3277 3728 let v = string_get_property(s, &key); 3278 3729 if matches!(v, Value::Undefined) { 3279 3730 self.string_prototype 3280 - .map(|p| gc_get_property(&self.gc, p, &key)) 3731 + .map(|p| gc_get_property(&self.gc, p, &key, &self.shapes)) 3281 3732 .unwrap_or(Value::Undefined) 3282 3733 } else { 3283 3734 v ··· 3285 3736 } 3286 3737 Value::Number(_) => self 3287 3738 .number_prototype 3288 - .map(|p| gc_get_property(&self.gc, p, &key)) 3739 + .map(|p| gc_get_property(&self.gc, p, &key, &self.shapes)) 3289 3740 .unwrap_or(Value::Undefined), 3290 3741 Value::Boolean(_) => self 3291 3742 .boolean_prototype 3292 - .map(|p| gc_get_property(&self.gc, p, &key)) 3743 + .map(|p| gc_get_property(&self.gc, p, &key, &self.shapes)) 3293 3744 .unwrap_or(Value::Undefined), 3294 3745 _ => Value::Undefined, 3295 3746 }; ··· 3321 3772 if let Some(gc_ref) = obj_gc { 3322 3773 match self.gc.get_mut(gc_ref) { 3323 3774 Some(HeapObject::Object(data)) => { 3324 - if let Some(prop) = data.properties.get_mut(&key) { 3325 - if prop.writable { 3326 - prop.value = val; 3327 - } 3328 - } else { 3329 - data.properties.insert(key, Property::data(val)); 3330 - } 3775 + data.set_property(key, val, &mut self.shapes); 3331 3776 } 3332 3777 Some(HeapObject::Function(fdata)) => { 3333 3778 if let Some(prop) = fdata.properties.get_mut(&key) { ··· 3354 3799 let result = 3355 3800 if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] { 3356 3801 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) { 3357 - match data.properties.get(&key) { 3358 - Some(prop) if !prop.configurable => false, 3359 - Some(_) => { 3360 - data.properties.remove(&key); 3361 - true 3362 - } 3363 - None => true, 3802 + match data.remove_property(&key, &self.shapes) { 3803 + RemoveResult::NonConfigurable => false, 3804 + RemoveResult::Removed | RemoveResult::NotFound => true, 3364 3805 } 3365 3806 } else { 3366 3807 true ··· 3375 3816 let obj_r = Self::read_u8(&mut self.frames[fi]); 3376 3817 let base = self.frames[fi].base; 3377 3818 let keys = match self.registers[base + obj_r as usize] { 3378 - Value::Object(gc_ref) => gc_enumerate_keys(&self.gc, gc_ref), 3819 + Value::Object(gc_ref) => gc_enumerate_keys(&self.gc, gc_ref, &self.shapes), 3379 3820 _ => Vec::new(), 3380 3821 }; 3381 3822 // Store keys as an array object. 3382 3823 let mut arr = ObjectData::new(); 3383 3824 for (i, key) in keys.iter().enumerate() { 3384 - arr.properties 3385 - .insert(i.to_string(), Property::data(Value::String(key.clone()))); 3825 + arr.insert_property( 3826 + i.to_string(), 3827 + Property::data(Value::String(key.clone())), 3828 + &mut self.shapes, 3829 + ); 3386 3830 } 3387 - arr.properties.insert( 3831 + arr.insert_property( 3388 3832 "length".to_string(), 3389 3833 Property { 3390 3834 value: Value::Number(keys.len() as f64), ··· 3392 3836 enumerable: false, 3393 3837 configurable: false, 3394 3838 }, 3839 + &mut self.shapes, 3395 3840 ); 3396 3841 let gc_ref = self.gc.alloc(HeapObject::Object(arr)); 3397 3842 self.registers[base + dst as usize] = Value::Object(gc_ref); ··· 3405 3850 let idx = self.registers[base + idx_r as usize].to_number() as usize; 3406 3851 let len = match self.registers[base + keys_r as usize] { 3407 3852 Value::Object(gc_ref) => { 3408 - gc_get_property(&self.gc, gc_ref, "length").to_number() as usize 3853 + gc_get_property(&self.gc, gc_ref, "length", &self.shapes).to_number() 3854 + as usize 3409 3855 } 3410 3856 _ => 0, 3411 3857 }; ··· 3415 3861 } else { 3416 3862 let key_str = idx.to_string(); 3417 3863 let key = match self.registers[base + keys_r as usize] { 3418 - Value::Object(gc_ref) => gc_get_property(&self.gc, gc_ref, &key_str), 3864 + Value::Object(gc_ref) => { 3865 + gc_get_property(&self.gc, gc_ref, &key_str, &self.shapes) 3866 + } 3419 3867 _ => Value::Undefined, 3420 3868 }; 3421 3869 self.registers[base + dst_val as usize] = key; ··· 3656 4104 _ => break, 3657 4105 }; 3658 4106 if let Some(HeapObject::Object(data)) = self.gc.get_mut(dst_ref) { 3659 - let len = match data.properties.get("length") { 4107 + let len = match data.get_property("length", &self.shapes) { 3660 4108 Some(prop) => prop.value.to_number() as usize, 3661 4109 None => 0, 3662 4110 }; 3663 - data.properties 3664 - .insert(len.to_string(), Property::data(value)); 3665 - data.properties.insert( 4111 + data.insert_property( 4112 + len.to_string(), 4113 + Property::data(value), 4114 + &mut self.shapes, 4115 + ); 4116 + data.insert_property( 3666 4117 "length".to_string(), 3667 4118 Property { 3668 4119 value: Value::Number((len + 1) as f64), ··· 3670 4121 enumerable: false, 3671 4122 configurable: false, 3672 4123 }, 4124 + &mut self.shapes, 3673 4125 ); 3674 4126 } 3675 4127 } ··· 3757 4209 fn generator_next(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3758 4210 // The generator wrapper stores the actual generator as __gen__. 3759 4211 let gen_ref = match &ctx.this { 3760 - Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") { 4212 + Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__", ctx.shapes) { 3761 4213 Value::Object(gen_r) => gen_r, 3762 4214 _ => return Err(RuntimeError::type_error("not a generator")), 3763 4215 }, ··· 3777 4229 // Store gen_ref and send_value for the VM to process. 3778 4230 // We use a special object with __generator_resume__ marker. 3779 4231 let mut obj = ObjectData::new(); 3780 - obj.properties.insert( 4232 + obj.insert_property( 3781 4233 "__generator_resume__".to_string(), 3782 4234 Property::builtin(Value::Boolean(true)), 4235 + ctx.shapes, 3783 4236 ); 3784 - obj.properties.insert( 4237 + obj.insert_property( 3785 4238 "__gen_ref__".to_string(), 3786 4239 Property::builtin(Value::Object(gen_ref)), 4240 + ctx.shapes, 3787 4241 ); 3788 - obj.properties 3789 - .insert("__send_value__".to_string(), Property::builtin(send_value)); 3790 - obj.properties.insert( 4242 + obj.insert_property( 4243 + "__send_value__".to_string(), 4244 + Property::builtin(send_value), 4245 + ctx.shapes, 4246 + ); 4247 + obj.insert_property( 3791 4248 "__resume_kind__".to_string(), 3792 4249 Property::builtin(Value::String("next".to_string())), 4250 + ctx.shapes, 3793 4251 ); 3794 4252 let r = ctx.gc.alloc(HeapObject::Object(obj)); 3795 4253 Ok(Value::Object(r)) ··· 3798 4256 /// Native callback for generator.return(value). 3799 4257 fn generator_return(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3800 4258 let gen_ref = match &ctx.this { 3801 - Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") { 4259 + Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__", ctx.shapes) { 3802 4260 Value::Object(gen_r) => gen_r, 3803 4261 _ => return Err(RuntimeError::type_error("not a generator")), 3804 4262 }, ··· 3808 4266 let return_value = args.first().cloned().unwrap_or(Value::Undefined); 3809 4267 3810 4268 let mut obj = ObjectData::new(); 3811 - obj.properties.insert( 4269 + obj.insert_property( 3812 4270 "__generator_resume__".to_string(), 3813 4271 Property::builtin(Value::Boolean(true)), 4272 + ctx.shapes, 3814 4273 ); 3815 - obj.properties.insert( 4274 + obj.insert_property( 3816 4275 "__gen_ref__".to_string(), 3817 4276 Property::builtin(Value::Object(gen_ref)), 4277 + ctx.shapes, 3818 4278 ); 3819 - obj.properties.insert( 4279 + obj.insert_property( 3820 4280 "__send_value__".to_string(), 3821 4281 Property::builtin(return_value), 4282 + ctx.shapes, 3822 4283 ); 3823 - obj.properties.insert( 4284 + obj.insert_property( 3824 4285 "__resume_kind__".to_string(), 3825 4286 Property::builtin(Value::String("return".to_string())), 4287 + ctx.shapes, 3826 4288 ); 3827 4289 let r = ctx.gc.alloc(HeapObject::Object(obj)); 3828 4290 Ok(Value::Object(r)) ··· 3831 4293 /// Native callback for generator.throw(error). 3832 4294 fn generator_throw(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3833 4295 let gen_ref = match &ctx.this { 3834 - Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") { 4296 + Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__", ctx.shapes) { 3835 4297 Value::Object(gen_r) => gen_r, 3836 4298 _ => return Err(RuntimeError::type_error("not a generator")), 3837 4299 }, ··· 3841 4303 let error_value = args.first().cloned().unwrap_or(Value::Undefined); 3842 4304 3843 4305 let mut obj = ObjectData::new(); 3844 - obj.properties.insert( 4306 + obj.insert_property( 3845 4307 "__generator_resume__".to_string(), 3846 4308 Property::builtin(Value::Boolean(true)), 4309 + ctx.shapes, 3847 4310 ); 3848 - obj.properties.insert( 4311 + obj.insert_property( 3849 4312 "__gen_ref__".to_string(), 3850 4313 Property::builtin(Value::Object(gen_ref)), 4314 + ctx.shapes, 3851 4315 ); 3852 - obj.properties 3853 - .insert("__send_value__".to_string(), Property::builtin(error_value)); 3854 - obj.properties.insert( 4316 + obj.insert_property( 4317 + "__send_value__".to_string(), 4318 + Property::builtin(error_value), 4319 + ctx.shapes, 4320 + ); 4321 + obj.insert_property( 3855 4322 "__resume_kind__".to_string(), 3856 4323 Property::builtin(Value::String("throw".to_string())), 4324 + ctx.shapes, 3857 4325 ); 3858 4326 let r = ctx.gc.alloc(HeapObject::Object(obj)); 3859 4327 Ok(Value::Object(r)) ··· 3880 4348 let value = args.first().cloned().unwrap_or(Value::Undefined); 3881 4349 let data_ref = ASYNC_RESUME_DATA.with(|cell| cell.take()); 3882 4350 if let Some(data) = data_ref { 3883 - let gen_ref = match gc_get_property(ctx.gc, data, "__gen_ref__") { 4351 + let gen_ref = match gc_get_property(ctx.gc, data, "__gen_ref__", ctx.shapes) { 3884 4352 Value::Object(r) => r, 3885 4353 _ => return Ok(value), 3886 4354 }; 3887 - let result_promise = match gc_get_property(ctx.gc, data, "__result_promise__") { 4355 + let result_promise = match gc_get_property(ctx.gc, data, "__result_promise__", ctx.shapes) { 3888 4356 Value::Object(r) => r, 3889 4357 _ => return Ok(value), 3890 4358 }; 3891 4359 let is_throw = matches!( 3892 - gc_get_property(ctx.gc, data, "__is_throw__"), 4360 + gc_get_property(ctx.gc, data, "__is_throw__", ctx.shapes), 3893 4361 Value::Boolean(true) 3894 4362 ); 3895 4363 3896 4364 // Return a marker for the VM to detect. 3897 4365 let mut obj = ObjectData::new(); 3898 - obj.properties.insert( 4366 + obj.insert_property( 3899 4367 "__async_resume__".to_string(), 3900 4368 Property::builtin(Value::Boolean(true)), 4369 + ctx.shapes, 3901 4370 ); 3902 - obj.properties.insert( 4371 + obj.insert_property( 3903 4372 "__gen_ref__".to_string(), 3904 4373 Property::builtin(Value::Object(gen_ref)), 4374 + ctx.shapes, 3905 4375 ); 3906 - obj.properties.insert( 4376 + obj.insert_property( 3907 4377 "__result_promise__".to_string(), 3908 4378 Property::builtin(Value::Object(result_promise)), 4379 + ctx.shapes, 3909 4380 ); 3910 - obj.properties.insert( 4381 + obj.insert_property( 3911 4382 "__is_throw__".to_string(), 3912 4383 Property::builtin(Value::Boolean(is_throw)), 4384 + ctx.shapes, 3913 4385 ); 3914 - obj.properties 3915 - .insert("__value__".to_string(), Property::builtin(value)); 4386 + obj.insert_property( 4387 + "__value__".to_string(), 4388 + Property::builtin(value), 4389 + ctx.shapes, 4390 + ); 3916 4391 let r = ctx.gc.alloc(HeapObject::Object(obj)); 3917 4392 Ok(Value::Object(r)) 3918 4393 } else { ··· 3927 4402 /// Native callback for async generator `.next(value)`. 3928 4403 fn async_generator_next(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3929 4404 let gen_ref = match &ctx.this { 3930 - Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") { 4405 + Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__", ctx.shapes) { 3931 4406 Value::Object(gen_r) => gen_r, 3932 4407 _ => return Err(RuntimeError::type_error("not an async generator")), 3933 4408 }, ··· 3938 4413 3939 4414 // Return a marker for the VM to handle. 3940 4415 let mut obj = ObjectData::new(); 3941 - obj.properties.insert( 4416 + obj.insert_property( 3942 4417 "__async_generator_resume__".to_string(), 3943 4418 Property::builtin(Value::Boolean(true)), 4419 + ctx.shapes, 3944 4420 ); 3945 - obj.properties.insert( 4421 + obj.insert_property( 3946 4422 "__gen_ref__".to_string(), 3947 4423 Property::builtin(Value::Object(gen_ref)), 4424 + ctx.shapes, 3948 4425 ); 3949 - obj.properties 3950 - .insert("__send_value__".to_string(), Property::builtin(send_value)); 3951 - obj.properties.insert( 4426 + obj.insert_property( 4427 + "__send_value__".to_string(), 4428 + Property::builtin(send_value), 4429 + ctx.shapes, 4430 + ); 4431 + obj.insert_property( 3952 4432 "__resume_kind__".to_string(), 3953 4433 Property::builtin(Value::String("next".to_string())), 4434 + ctx.shapes, 3954 4435 ); 3955 4436 let r = ctx.gc.alloc(HeapObject::Object(obj)); 3956 4437 Ok(Value::Object(r)) ··· 3959 4440 /// Native callback for async generator `.return(value)`. 3960 4441 fn async_generator_return(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3961 4442 let gen_ref = match &ctx.this { 3962 - Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") { 4443 + Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__", ctx.shapes) { 3963 4444 Value::Object(gen_r) => gen_r, 3964 4445 _ => return Err(RuntimeError::type_error("not an async generator")), 3965 4446 }, ··· 3969 4450 let return_value = args.first().cloned().unwrap_or(Value::Undefined); 3970 4451 3971 4452 let mut obj = ObjectData::new(); 3972 - obj.properties.insert( 4453 + obj.insert_property( 3973 4454 "__async_generator_resume__".to_string(), 3974 4455 Property::builtin(Value::Boolean(true)), 4456 + ctx.shapes, 3975 4457 ); 3976 - obj.properties.insert( 4458 + obj.insert_property( 3977 4459 "__gen_ref__".to_string(), 3978 4460 Property::builtin(Value::Object(gen_ref)), 4461 + ctx.shapes, 3979 4462 ); 3980 - obj.properties.insert( 4463 + obj.insert_property( 3981 4464 "__send_value__".to_string(), 3982 4465 Property::builtin(return_value), 4466 + ctx.shapes, 3983 4467 ); 3984 - obj.properties.insert( 4468 + obj.insert_property( 3985 4469 "__resume_kind__".to_string(), 3986 4470 Property::builtin(Value::String("return".to_string())), 4471 + ctx.shapes, 3987 4472 ); 3988 4473 let r = ctx.gc.alloc(HeapObject::Object(obj)); 3989 4474 Ok(Value::Object(r)) ··· 4632 5117 fn test_delete_non_configurable() { 4633 5118 // Array length is non-configurable, delete should return false. 4634 5119 let mut gc: Gc<HeapObject> = Gc::new(); 5120 + let mut shapes = ShapeTable::new(); 4635 5121 let mut obj = ObjectData::new(); 4636 - obj.properties.insert( 5122 + obj.insert_property( 4637 5123 "x".to_string(), 4638 5124 Property { 4639 5125 value: Value::Number(1.0), ··· 4641 5127 enumerable: true, 4642 5128 configurable: false, 4643 5129 }, 5130 + &mut shapes, 4644 5131 ); 4645 5132 let obj_ref = gc.alloc(HeapObject::Object(obj)); 4646 5133 4647 5134 // Try to delete the non-configurable property. 4648 5135 match gc.get_mut(obj_ref) { 4649 5136 Some(HeapObject::Object(data)) => { 4650 - let prop = data.properties.get("x").unwrap(); 5137 + let prop = data.get_property("x", &shapes).unwrap(); 4651 5138 assert!(!prop.configurable); 4652 5139 // The property should still be there. 4653 - assert!(data.properties.contains_key("x")); 5140 + assert!(data.contains_key("x", &shapes)); 4654 5141 } 4655 5142 _ => panic!("expected object"), 4656 5143 } ··· 4660 5147 fn test_property_writable_flag() { 4661 5148 // Setting a non-writable property should silently fail. 4662 5149 let mut gc: Gc<HeapObject> = Gc::new(); 5150 + let mut shapes = ShapeTable::new(); 4663 5151 let mut obj = ObjectData::new(); 4664 - obj.properties.insert( 5152 + obj.insert_property( 4665 5153 "frozen".to_string(), 4666 5154 Property { 4667 5155 value: Value::Number(42.0), ··· 4669 5157 enumerable: true, 4670 5158 configurable: false, 4671 5159 }, 5160 + &mut shapes, 4672 5161 ); 4673 5162 let obj_ref = gc.alloc(HeapObject::Object(obj)); 4674 5163 ··· 4676 5165 match gc.get(obj_ref) { 4677 5166 Some(HeapObject::Object(data)) => { 4678 5167 assert_eq!( 4679 - data.properties.get("frozen").unwrap().value.to_number(), 5168 + data.get_property("frozen", &shapes) 5169 + .unwrap() 5170 + .value 5171 + .to_number(), 4680 5172 42.0 4681 5173 ); 4682 5174 } ··· 4779 5271 fn test_gc_enumerate_keys_order() { 4780 5272 // Integer keys should come first, sorted numerically. 4781 5273 let mut gc: Gc<HeapObject> = Gc::new(); 5274 + let mut shapes = ShapeTable::new(); 4782 5275 let mut obj = ObjectData::new(); 4783 - obj.properties 4784 - .insert("b".to_string(), Property::data(Value::Number(2.0))); 4785 - obj.properties 4786 - .insert("2".to_string(), Property::data(Value::Number(3.0))); 4787 - obj.properties 4788 - .insert("a".to_string(), Property::data(Value::Number(1.0))); 4789 - obj.properties 4790 - .insert("0".to_string(), Property::data(Value::Number(0.0))); 5276 + obj.insert_property( 5277 + "b".to_string(), 5278 + Property::data(Value::Number(2.0)), 5279 + &mut shapes, 5280 + ); 5281 + obj.insert_property( 5282 + "2".to_string(), 5283 + Property::data(Value::Number(3.0)), 5284 + &mut shapes, 5285 + ); 5286 + obj.insert_property( 5287 + "a".to_string(), 5288 + Property::data(Value::Number(1.0)), 5289 + &mut shapes, 5290 + ); 5291 + obj.insert_property( 5292 + "0".to_string(), 5293 + Property::data(Value::Number(0.0)), 5294 + &mut shapes, 5295 + ); 4791 5296 let obj_ref = gc.alloc(HeapObject::Object(obj)); 4792 5297 4793 - let keys = gc_enumerate_keys(&gc, obj_ref); 4794 - // Integer keys first (sorted), then string keys. 5298 + let keys = gc_enumerate_keys(&gc, obj_ref, &shapes); 5299 + // Integer keys first (sorted), then string keys in insertion order. 4795 5300 let int_part: Vec<&str> = keys.iter().take(2).map(|s| s.as_str()).collect(); 4796 5301 assert_eq!(int_part, vec!["0", "2"]); 4797 - // Remaining keys are string keys (order depends on HashMap iteration). 5302 + // Remaining keys are string keys in insertion order. 4798 5303 let str_part: Vec<&str> = keys.iter().skip(2).map(|s| s.as_str()).collect(); 4799 5304 assert!(str_part.contains(&"a")); 4800 5305 assert!(str_part.contains(&"b"));