diff --git a/Libraries/LibJS/Runtime/Array.cpp b/Libraries/LibJS/Runtime/Array.cpp index 9fced8923ce..4cb10429406 100644 --- a/Libraries/LibJS/Runtime/Array.cpp +++ b/Libraries/LibJS/Runtime/Array.cpp @@ -35,7 +35,7 @@ ThrowCompletionOr> Array::create(Realm& realm, u64 length, Object // 3. Let A be MakeBasicObject(« [[Prototype]], [[Extensible]] »). // 4. Set A.[[Prototype]] to proto. // 5. Set A.[[DefineOwnProperty]] as specified in 10.4.2.1. - auto array = realm.create(*prototype); + auto array = realm.create(realm, *prototype); // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). MUST(array->internal_define_own_property(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = false })); @@ -63,12 +63,19 @@ GC::Ref Array::create_from(Realm& realm, ReadonlySpan elements) return array; } -Array::Array(Object& prototype) +Array::Array(Realm& realm, Object& prototype) : Object(ConstructWithPrototypeTag::Tag, prototype) + , m_realm(realm) { m_has_magical_length_property = true; } +void Array::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_realm); +} + // 10.4.2.4 ArraySetLength ( A, Desc ), https://tc39.es/ecma262/#sec-arraysetlength ThrowCompletionOr Array::set_length(PropertyDescriptor const& property_descriptor) { @@ -276,6 +283,72 @@ ThrowCompletionOr> Array::internal_get_own_property return Object::internal_get_own_property(property_key); } +ThrowCompletionOr Array::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) +{ + auto& vm = this->vm(); + + auto default_prototype_chain_intact = [&] { + auto const& intrinsics = m_realm->intrinsics(); + auto* array_prototype = shape().prototype(); + if (!array_prototype) + return false; + if (!array_prototype->indexed_properties().is_empty()) + return false; + auto& array_prototype_shape = shape().prototype()->shape(); + if (intrinsics.default_array_prototype_shape().ptr() != &array_prototype_shape) + return false; + + auto* object_prototype = array_prototype_shape.prototype(); + if (!object_prototype) + return false; + if (!object_prototype->indexed_properties().is_empty()) + return false; + auto& object_prototype_shape = array_prototype_shape.prototype()->shape(); + if (intrinsics.default_object_prototype_shape().ptr() != &object_prototype_shape) + return false; + if (object_prototype_shape.prototype()) + return false; + + return true; + }; + + VERIFY(receiver.is_object()); + auto& receiver_object = receiver.as_object(); + + // Fast path for arrays with intact prototype chain + if (&receiver_object == this && !m_is_proxy_target && default_prototype_chain_intact()) { + if (property_key.is_number()) { + auto index = property_key.as_number(); + auto property_descriptor = TRY(internal_get_own_property(property_key)); + if (!property_descriptor.has_value()) { + if (!TRY(is_extensible())) + return false; + PropertyAttributes attributes; + attributes.set_writable(true); + attributes.set_enumerable(true); + attributes.set_configurable(true); + indexed_properties().put(index, value, attributes); + return true; + } + if (property_descriptor->is_data_descriptor()) { + if (property_descriptor->writable.has_value() && !*property_descriptor->writable) + return false; + auto attributes = property_descriptor->attributes(); + indexed_properties().put(index, value, attributes); + return true; + } + } else if (property_key == vm.names.length) { + auto property_descriptor = TRY(internal_get_own_property(property_key)); + if (property_descriptor->writable.has_value() && !*property_descriptor->writable) + return false; + property_descriptor->value = value; + return TRY(set_length(*property_descriptor)); + } + } + + return Object::internal_set(property_key, value, receiver, cacheable_metadata, phase); +} + // 10.4.2.1 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc ThrowCompletionOr Array::internal_define_own_property(PropertyKey const& property_key, PropertyDescriptor const& property_descriptor, Optional* precomputed_get_own_property) { diff --git a/Libraries/LibJS/Runtime/Array.h b/Libraries/LibJS/Runtime/Array.h index e30df2335e5..cf936272709 100644 --- a/Libraries/LibJS/Runtime/Array.h +++ b/Libraries/LibJS/Runtime/Array.h @@ -48,21 +48,28 @@ public: virtual ~Array() override = default; virtual ThrowCompletionOr> internal_get_own_property(PropertyKey const&) const override final; + virtual ThrowCompletionOr internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) override; virtual ThrowCompletionOr internal_define_own_property(PropertyKey const&, PropertyDescriptor const&, Optional* precomputed_get_own_property = nullptr) override final; virtual ThrowCompletionOr internal_delete(PropertyKey const&) override; virtual ThrowCompletionOr> internal_own_property_keys() const override final; [[nodiscard]] bool length_is_writable() const { return m_length_writable; } + void set_is_proxy_target(bool is_proxy_target) { m_is_proxy_target = is_proxy_target; } + + virtual void visit_edges(Cell::Visitor& visitor) override; + protected: - explicit Array(Object& prototype); + explicit Array(Realm& realm, Object& prototype); private: virtual bool is_array_exotic_object() const final { return true; } ThrowCompletionOr set_length(PropertyDescriptor const&); + GC::Ref m_realm; bool m_length_writable { true }; + bool m_is_proxy_target { false }; }; template<> diff --git a/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Libraries/LibJS/Runtime/ArrayPrototype.cpp index defc5df7f41..f174886d40b 100644 --- a/Libraries/LibJS/Runtime/ArrayPrototype.cpp +++ b/Libraries/LibJS/Runtime/ArrayPrototype.cpp @@ -32,7 +32,7 @@ GC_DEFINE_ALLOCATOR(ArrayPrototype); static HashTable> s_array_join_seen_objects; ArrayPrototype::ArrayPrototype(Realm& realm) - : Array(realm.intrinsics().object_prototype()) + : Array(realm, realm.intrinsics().object_prototype()) { } diff --git a/Libraries/LibJS/Runtime/Intrinsics.cpp b/Libraries/LibJS/Runtime/Intrinsics.cpp index 5e3ed956002..885de9e2aed 100644 --- a/Libraries/LibJS/Runtime/Intrinsics.cpp +++ b/Libraries/LibJS/Runtime/Intrinsics.cpp @@ -316,6 +316,13 @@ void Intrinsics::initialize_intrinsics(Realm& realm) m_json_parse_function = &json_object()->get_without_side_effects(vm.names.parse).as_function(); m_json_stringify_function = &json_object()->get_without_side_effects(vm.names.stringify).as_function(); m_object_prototype_to_string_function = &object_prototype()->get_without_side_effects(vm.names.toString).as_function(); + + array_prototype()->convert_to_prototype_if_needed(); + m_default_array_prototype_shape = array_prototype()->shape(); + m_default_object_prototype_shape = object_prototype()->shape(); + + VERIFY(array_prototype()->indexed_properties().is_empty()); + VERIFY(object_prototype()->indexed_properties().is_empty()); } template @@ -416,6 +423,8 @@ void Intrinsics::visit_edges(Visitor& visitor) visitor.visit(m_native_function_shape); visitor.visit(m_unmapped_arguments_object_shape); visitor.visit(m_mapped_arguments_object_shape); + visitor.visit(m_default_array_prototype_shape); + visitor.visit(m_default_object_prototype_shape); visitor.visit(m_proxy_constructor); visitor.visit(m_async_from_sync_iterator_prototype); visitor.visit(m_async_generator_prototype); diff --git a/Libraries/LibJS/Runtime/Intrinsics.h b/Libraries/LibJS/Runtime/Intrinsics.h index f98a2694420..3f69abfa3f0 100644 --- a/Libraries/LibJS/Runtime/Intrinsics.h +++ b/Libraries/LibJS/Runtime/Intrinsics.h @@ -50,6 +50,9 @@ public: [[nodiscard]] u32 mapped_arguments_object_well_known_symbol_iterator_offset() const { return m_mapped_arguments_object_well_known_symbol_iterator_offset; } [[nodiscard]] u32 mapped_arguments_object_callee_offset() const { return m_mapped_arguments_object_callee_offset; } + [[nodiscard]] GC::Ref default_array_prototype_shape() const { return *m_default_array_prototype_shape; } + [[nodiscard]] GC::Ref default_object_prototype_shape() const { return *m_default_object_prototype_shape; } + [[nodiscard]] GC::Ref throw_type_error_accessor() { return *m_throw_type_error_accessor; } // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype @@ -179,6 +182,9 @@ private: u32 m_mapped_arguments_object_well_known_symbol_iterator_offset { 0 }; u32 m_mapped_arguments_object_callee_offset { 0 }; + GC::Ptr m_default_array_prototype_shape; + GC::Ptr m_default_object_prototype_shape; + GC::Ptr m_throw_type_error_accessor; // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype diff --git a/Libraries/LibJS/Runtime/ProxyObject.cpp b/Libraries/LibJS/Runtime/ProxyObject.cpp index 5b882904b15..ab8cd25a4d5 100644 --- a/Libraries/LibJS/Runtime/ProxyObject.cpp +++ b/Libraries/LibJS/Runtime/ProxyObject.cpp @@ -46,6 +46,10 @@ ProxyObject::ProxyObject(Object& target, Object& handler, Object& prototype) , m_target(target) , m_handler(handler) { + if (target.is_array_exotic_object()) { + auto& array = static_cast(target); + array.set_is_proxy_target(true); + } } static Value property_key_to_value(VM& vm, PropertyKey const& property_key) diff --git a/Libraries/LibJS/Tests/builtins/Array/array-as-proxy-target.js b/Libraries/LibJS/Tests/builtins/Array/array-as-proxy-target.js new file mode 100644 index 00000000000..63eae6951e6 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/Array/array-as-proxy-target.js @@ -0,0 +1,26 @@ +test("proxy traps should be invoked in the correct order", () => { + var log = []; + var target = []; + var proxy = new Proxy( + target, + new Proxy( + {}, + { + get(t, pk, r) { + log.push(pk); + }, + } + ) + ); + proxy.push(1); + + expect(log.length, 8); + expect(log[0]).toBe("get"); + expect(log[1]).toBe("get"); + expect(log[2]).toBe("set"); + expect(log[3]).toBe("getOwnPropertyDescriptor"); + expect(log[4]).toBe("defineProperty"); + expect(log[5]).toBe("set"); + expect(log[6]).toBe("getOwnPropertyDescriptor"); + expect(log[7]).toBe("defineProperty"); +}); diff --git a/Libraries/LibWeb/WebIDL/ObservableArray.cpp b/Libraries/LibWeb/WebIDL/ObservableArray.cpp index ebd7a26d924..516b5d77ed1 100644 --- a/Libraries/LibWeb/WebIDL/ObservableArray.cpp +++ b/Libraries/LibWeb/WebIDL/ObservableArray.cpp @@ -14,11 +14,11 @@ GC_DEFINE_ALLOCATOR(ObservableArray); GC::Ref ObservableArray::create(JS::Realm& realm) { auto prototype = realm.intrinsics().array_prototype(); - return realm.create(prototype); + return realm.create(realm, prototype); } -ObservableArray::ObservableArray(Object& prototype) - : JS::Array(prototype) +ObservableArray::ObservableArray(JS::Realm& realm, Object& prototype) + : JS::Array(realm, prototype) { } diff --git a/Libraries/LibWeb/WebIDL/ObservableArray.h b/Libraries/LibWeb/WebIDL/ObservableArray.h index ccb00c7553f..6cea86495e3 100644 --- a/Libraries/LibWeb/WebIDL/ObservableArray.h +++ b/Libraries/LibWeb/WebIDL/ObservableArray.h @@ -43,7 +43,7 @@ public: } } - explicit ObservableArray(Object& prototype); + explicit ObservableArray(JS::Realm&, Object& prototype); virtual void visit_edges(JS::Cell::Visitor&) override;