mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 21:12:43 +00:00 
			
		
		
		
	 de3e6cc75c
			
		
	
	
		de3e6cc75c
		
	
	
	
	
		
			
			The combination of template + auto&& parameter + constexpr if statements allowed one caller to pass in a GlobalObject, without the compiler complaining.
		
			
				
	
	
		
			320 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			320 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | ||
|  * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
 | ||
|  *
 | ||
|  * SPDX-License-Identifier: BSD-2-Clause
 | ||
|  */
 | ||
| 
 | ||
| #include <LibJS/Runtime/GlobalObject.h>
 | ||
| #include <LibWeb/Bindings/ExceptionOrUtils.h>
 | ||
| #include <LibWeb/Bindings/LegacyPlatformObject.h>
 | ||
| 
 | ||
| namespace Web::Bindings {
 | ||
| 
 | ||
| LegacyPlatformObject::LegacyPlatformObject(JS::Object& prototype)
 | ||
|     : PlatformObject(prototype)
 | ||
| {
 | ||
| }
 | ||
| 
 | ||
| LegacyPlatformObject::~LegacyPlatformObject() = default;
 | ||
| 
 | ||
| JS::ThrowCompletionOr<bool> LegacyPlatformObject::is_named_property_exposed_on_object(JS::PropertyKey const& property_key) const
 | ||
| {
 | ||
|     [[maybe_unused]] auto& vm = this->vm();
 | ||
| 
 | ||
|     // The spec doesn't say anything about the type of the property name here.
 | ||
|     // Numbers can be converted to a string, which is fine and what other engines do.
 | ||
|     // However, since a symbol cannot be converted to a string, it cannot be a supported property name. Return early if it's a symbol.
 | ||
|     if (property_key.is_symbol())
 | ||
|         return false;
 | ||
| 
 | ||
|     // 1. If P is not a supported property name of O, then return false.
 | ||
|     // NOTE: This is in it's own variable to enforce the type.
 | ||
|     // FIXME: Can this throw?
 | ||
|     Vector<String> supported_property_names = this->supported_property_names();
 | ||
|     auto property_key_string = property_key.to_string();
 | ||
|     if (!supported_property_names.contains_slow(property_key_string))
 | ||
|         return false;
 | ||
| 
 | ||
|     // 2. If O has an own property named P, then return false.
 | ||
|     // NOTE: This has to be done manually instead of using Object::has_own_property, as that would use the overridden internal_get_own_property.
 | ||
|     auto own_property_named_p = MUST(Object::internal_get_own_property(property_key));
 | ||
| 
 | ||
|     if (own_property_named_p.has_value())
 | ||
|         return false;
 | ||
| 
 | ||
|     // NOTE: Step 3 is not here as the interface doesn't have the LegacyOverrideBuiltIns extended attribute.
 | ||
|     // 4. Let prototype be O.[[GetPrototypeOf]]().
 | ||
|     auto* prototype = TRY(internal_get_prototype_of());
 | ||
| 
 | ||
|     // 5. While prototype is not null:
 | ||
|     while (prototype) {
 | ||
|         // FIXME: 1. If prototype is not a named properties object, and prototype has an own property named P, then return false.
 | ||
|         //           (It currently does not check for named property objects)
 | ||
|         bool prototype_has_own_property_named_p = TRY(prototype->has_own_property(property_key));
 | ||
|         if (prototype_has_own_property_named_p)
 | ||
|             return false;
 | ||
| 
 | ||
|         // 2. Set prototype to prototype.[[GetPrototypeOf]]().
 | ||
|         prototype = TRY(prototype->internal_get_prototype_of());
 | ||
|     }
 | ||
| 
 | ||
|     // 6. Return true.
 | ||
|     return true;
 | ||
| }
 | ||
| 
 | ||
| JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> LegacyPlatformObject::legacy_platform_object_get_own_property_for_get_own_property_slot(JS::PropertyKey const& property_name) const
 | ||
| {
 | ||
|     auto& vm = this->vm();
 | ||
| 
 | ||
|     if (property_name.is_number()) {
 | ||
|         // 1. Let index be the result of calling ToUint32(P).
 | ||
|         u32 index = property_name.as_number();
 | ||
| 
 | ||
|         // 2. If index is a supported property index, then:
 | ||
|         // FIXME: Can this throw?
 | ||
|         if (is_supported_property_index(index)) {
 | ||
| 
 | ||
|             auto value = TRY(throw_dom_exception_if_needed(vm, [&] { return item_value(index); }));
 | ||
| 
 | ||
|             // 5. Let desc be a newly created Property Descriptor with no fields.
 | ||
|             JS::PropertyDescriptor descriptor;
 | ||
| 
 | ||
|             // 6. Set desc.[[Value]] to the result of converting value to an ECMAScript value.
 | ||
|             descriptor.value = value;
 | ||
| 
 | ||
|             descriptor.writable = false;
 | ||
| 
 | ||
|             // 8. Set desc.[[Enumerable]] and desc.[[Configurable]] to true.
 | ||
|             descriptor.enumerable = true;
 | ||
|             descriptor.configurable = true;
 | ||
| 
 | ||
|             // 9. Return desc.
 | ||
|             return descriptor;
 | ||
|         }
 | ||
| 
 | ||
|         // 3. Set ignoreNamedProps to true.
 | ||
|         return TRY(Object::internal_get_own_property(property_name));
 | ||
|     }
 | ||
| 
 | ||
|     // 1. If the result of running the named property visibility algorithm with property name P and object O is true, then:
 | ||
|     if (TRY(is_named_property_exposed_on_object(property_name))) {
 | ||
|         // FIXME: It's unfortunate that this is done twice, once in is_named_property_exposed_on_object and here.
 | ||
|         auto property_name_string = property_name.to_string();
 | ||
| 
 | ||
|         auto value = TRY(throw_dom_exception_if_needed(vm, [&] { return named_item_value(property_name_string); }));
 | ||
| 
 | ||
|         // 5. Let desc be a newly created Property Descriptor with no fields.
 | ||
|         JS::PropertyDescriptor descriptor;
 | ||
| 
 | ||
|         // 6. Set desc.[[Value]] to the result of converting value to an ECMAScript value.
 | ||
|         descriptor.value = value;
 | ||
| 
 | ||
|         descriptor.writable = false;
 | ||
| 
 | ||
|         descriptor.enumerable = false;
 | ||
| 
 | ||
|         // 9. Set desc.[[Configurable]] to true.
 | ||
|         descriptor.configurable = true;
 | ||
| 
 | ||
|         // 10. Return desc.
 | ||
|         return descriptor;
 | ||
|     }
 | ||
| 
 | ||
|     return TRY(Object::internal_get_own_property(property_name));
 | ||
| }
 | ||
| 
 | ||
| JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> LegacyPlatformObject::legacy_platform_object_get_own_property_for_set_slot(JS::PropertyKey const& property_name) const
 | ||
| {
 | ||
|     if (!property_name.is_number())
 | ||
|         return TRY(Object::internal_get_own_property(property_name));
 | ||
| 
 | ||
|     // 1. Let index be the result of calling ToUint32(P).
 | ||
|     u32 index = property_name.as_number();
 | ||
| 
 | ||
|     // 2. If index is a supported property index, then:
 | ||
|     // FIXME: Can this throw?
 | ||
|     if (is_supported_property_index(index)) {
 | ||
| 
 | ||
|         auto value = TRY(throw_dom_exception_if_needed(vm(), [&] { return item_value(index); }));
 | ||
| 
 | ||
|         // 5. Let desc be a newly created Property Descriptor with no fields.
 | ||
|         JS::PropertyDescriptor descriptor;
 | ||
| 
 | ||
|         // 6. Set desc.[[Value]] to the result of converting value to an ECMAScript value.
 | ||
|         descriptor.value = value;
 | ||
| 
 | ||
|         descriptor.writable = false;
 | ||
| 
 | ||
|         // 8. Set desc.[[Enumerable]] and desc.[[Configurable]] to true.
 | ||
|         descriptor.enumerable = true;
 | ||
|         descriptor.configurable = true;
 | ||
| 
 | ||
|         // 9. Return desc.
 | ||
|         return descriptor;
 | ||
|     }
 | ||
| 
 | ||
|     // 3. Set ignoreNamedProps to true.
 | ||
|     return TRY(Object::internal_get_own_property(property_name));
 | ||
| }
 | ||
| 
 | ||
| JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> LegacyPlatformObject::internal_get_own_property(JS::PropertyKey const& property_name) const
 | ||
| {
 | ||
|     // 1. Return LegacyPlatformObjectGetOwnProperty(O, P, false).
 | ||
|     return TRY(legacy_platform_object_get_own_property_for_get_own_property_slot(property_name));
 | ||
| }
 | ||
| 
 | ||
| JS::ThrowCompletionOr<bool> LegacyPlatformObject::internal_set(JS::PropertyKey const& property_name, JS::Value value, JS::Value receiver)
 | ||
| {
 | ||
|     [[maybe_unused]] auto& global_object = this->global_object();
 | ||
| 
 | ||
|     // 2. Let ownDesc be LegacyPlatformObjectGetOwnProperty(O, P, true).
 | ||
|     auto own_descriptor = TRY(legacy_platform_object_get_own_property_for_set_slot(property_name));
 | ||
| 
 | ||
|     // 3. Perform ? OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).
 | ||
|     // NOTE: The spec says "perform" instead of "return", meaning nothing will be returned on this path according to the spec, which isn't possible to do.
 | ||
|     //       Let's treat it as though it says "return" instead of "perform".
 | ||
|     return ordinary_set_with_own_descriptor(property_name, value, receiver, own_descriptor);
 | ||
| }
 | ||
| 
 | ||
| JS::ThrowCompletionOr<bool> LegacyPlatformObject::internal_define_own_property(JS::PropertyKey const& property_name, JS::PropertyDescriptor const& property_descriptor)
 | ||
| {
 | ||
|     [[maybe_unused]] auto& vm = this->vm();
 | ||
|     [[maybe_unused]] auto& global_object = this->global_object();
 | ||
| 
 | ||
|     if (property_name.is_number()) {
 | ||
|         // 1. If the result of calling IsDataDescriptor(Desc) is false, then return false.
 | ||
|         if (!property_descriptor.is_data_descriptor())
 | ||
|             return false;
 | ||
| 
 | ||
|         return false;
 | ||
|     }
 | ||
| 
 | ||
|     if (property_name.is_string()) {
 | ||
|         auto& property_name_as_string = property_name.as_string();
 | ||
| 
 | ||
|         // 1. Let creating be true if P is not a supported property name, and false otherwise.
 | ||
|         // NOTE: This is in it's own variable to enforce the type.
 | ||
|         // FIXME: Can this throw?
 | ||
|         Vector<String> supported_property_names = this->supported_property_names();
 | ||
|         [[maybe_unused]] bool creating = !supported_property_names.contains_slow(property_name_as_string);
 | ||
| 
 | ||
|         // NOTE: This has to be done manually instead of using Object::has_own_property, as that would use the overridden internal_get_own_property.
 | ||
|         auto own_property_named_p = TRY(Object::internal_get_own_property(property_name));
 | ||
| 
 | ||
|         if (!own_property_named_p.has_value()) {
 | ||
| 
 | ||
|             if (!creating)
 | ||
|                 return false;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // property_descriptor is a const&, thus we need to create a copy here to set [[Configurable]]
 | ||
|     JS::PropertyDescriptor descriptor_copy(property_descriptor);
 | ||
|     descriptor_copy.configurable = true;
 | ||
| 
 | ||
|     // 4. Return OrdinaryDefineOwnProperty(O, P, Desc).
 | ||
|     return Object::internal_define_own_property(property_name, descriptor_copy);
 | ||
| }
 | ||
| 
 | ||
| JS::ThrowCompletionOr<bool> LegacyPlatformObject::internal_delete(JS::PropertyKey const& property_name)
 | ||
| {
 | ||
|     [[maybe_unused]] auto& global_object = this->global_object();
 | ||
| 
 | ||
|     if (property_name.is_number()) {
 | ||
|         // 1. Let index be the result of calling ToUint32(P).
 | ||
|         u32 index = property_name.as_number();
 | ||
| 
 | ||
|         // 2. If index is not a supported property index, then return true.
 | ||
|         // FIXME: Can this throw?
 | ||
|         if (!is_supported_property_index(index))
 | ||
|             return true;
 | ||
| 
 | ||
|         // 3. Return false.
 | ||
|         return false;
 | ||
|     }
 | ||
| 
 | ||
|     if (TRY(is_named_property_exposed_on_object(property_name))) {
 | ||
| 
 | ||
|         return false;
 | ||
|     }
 | ||
| 
 | ||
|     // 3. If O has an own property with name P, then:
 | ||
|     auto own_property_named_p_descriptor = TRY(Object::internal_get_own_property(property_name));
 | ||
| 
 | ||
|     if (own_property_named_p_descriptor.has_value()) {
 | ||
|         // 1. If the property is not configurable, then return false.
 | ||
|         // 2. Otherwise, remove the property from O.
 | ||
|         if (*own_property_named_p_descriptor->configurable)
 | ||
|             storage_delete(property_name);
 | ||
|         else
 | ||
|             return false;
 | ||
|     }
 | ||
| 
 | ||
|     // 4. Return true.
 | ||
|     return true;
 | ||
| }
 | ||
| 
 | ||
| JS::ThrowCompletionOr<bool> LegacyPlatformObject::internal_prevent_extensions()
 | ||
| {
 | ||
|     // 1. Return false.
 | ||
|     return false;
 | ||
| }
 | ||
| 
 | ||
| JS::ThrowCompletionOr<JS::MarkedVector<JS::Value>> LegacyPlatformObject::internal_own_property_keys() const
 | ||
| {
 | ||
|     auto& vm = this->vm();
 | ||
| 
 | ||
|     // 1. Let keys be a new empty list of ECMAScript String and Symbol values.
 | ||
|     JS::MarkedVector<JS::Value> keys { heap() };
 | ||
| 
 | ||
|     for (u64 index = 0; index <= NumericLimits<u32>::max(); ++index) {
 | ||
|         if (is_supported_property_index(index))
 | ||
|             keys.append(js_string(vm, String::number(index)));
 | ||
|         else
 | ||
|             break;
 | ||
|     }
 | ||
| 
 | ||
|     for (auto& named_property : supported_property_names()) {
 | ||
|         if (TRY(is_named_property_exposed_on_object(named_property)))
 | ||
|             keys.append(js_string(vm, named_property));
 | ||
|     }
 | ||
| 
 | ||
|     // 4. For each P of O’s own property keys that is a String, in ascending chronological order of property creation, append P to keys.
 | ||
|     for (auto& it : shape().property_table_ordered()) {
 | ||
|         if (it.key.is_string())
 | ||
|             keys.append(it.key.to_value(vm));
 | ||
|     }
 | ||
| 
 | ||
|     // 5. For each P of O’s own property keys that is a Symbol, in ascending chronological order of property creation, append P to keys.
 | ||
|     for (auto& it : shape().property_table_ordered()) {
 | ||
|         if (it.key.is_symbol())
 | ||
|             keys.append(it.key.to_value(vm));
 | ||
|     }
 | ||
| 
 | ||
|     // FIXME: 6. Assert: keys has no duplicate items.
 | ||
| 
 | ||
|     // 7. Return keys.
 | ||
|     return { move(keys) };
 | ||
| }
 | ||
| 
 | ||
| JS::Value LegacyPlatformObject::item_value(size_t) const
 | ||
| {
 | ||
|     return JS::js_undefined();
 | ||
| }
 | ||
| 
 | ||
| JS::Value LegacyPlatformObject::named_item_value(FlyString const&) const
 | ||
| {
 | ||
|     return JS::js_undefined();
 | ||
| }
 | ||
| 
 | ||
| Vector<String> LegacyPlatformObject::supported_property_names() const
 | ||
| {
 | ||
|     return {};
 | ||
| }
 | ||
| 
 | ||
| bool LegacyPlatformObject::is_supported_property_index(u32) const
 | ||
| {
 | ||
|     return false;
 | ||
| }
 | ||
| 
 | ||
| }
 |