Files
ladybird/Libraries/LibJS/Runtime/PromiseResolvingElementFunctions.cpp
Andreas Kling 277551c40f LibJS: Account promise storage as external memory
Add shared helpers for estimating external string and container storage.
Use them for symbols, bound function arguments, promise reactions,
and promise combinator value lists.
2026-05-07 10:03:09 +02:00

220 lines
9.1 KiB
C++

/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/AggregateError.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ExternalMemory.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibJS/Runtime/PromiseResolvingElementFunctions.h>
namespace JS {
GC_DEFINE_ALLOCATOR(RemainingElements);
GC_DEFINE_ALLOCATOR(PromiseValueList);
GC_DEFINE_ALLOCATOR(PromiseResolvingElementFunction);
GC_DEFINE_ALLOCATOR(PromiseAllResolveElementFunction);
GC_DEFINE_ALLOCATOR(PromiseAllSettledResolveElementFunction);
GC_DEFINE_ALLOCATOR(PromiseAllSettledRejectElementFunction);
GC_DEFINE_ALLOCATOR(PromiseAnyRejectElementFunction);
void PromiseValueList::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_values);
}
size_t PromiseValueList::external_memory_size() const
{
return vector_external_memory_size(m_values);
}
PromiseResolvingElementFunction::PromiseResolvingElementFunction(size_t index, PromiseValueList& values, GC::Ref<PromiseCapability const> capability, RemainingElements& remaining_elements, Object& prototype)
: NativeFunction(prototype)
, m_index(index)
, m_values(values)
, m_capability(capability)
, m_remaining_elements(remaining_elements)
{
}
void PromiseResolvingElementFunction::initialize(Realm& realm)
{
Base::initialize(realm);
define_direct_property(vm().names.length, Value(1), Attribute::Configurable);
}
ThrowCompletionOr<Value> PromiseResolvingElementFunction::call()
{
if (m_already_called)
return js_undefined();
m_already_called = true;
return resolve_element();
}
void PromiseResolvingElementFunction::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_values);
visitor.visit(m_capability);
visitor.visit(m_remaining_elements);
}
GC::Ref<PromiseAllResolveElementFunction> PromiseAllResolveElementFunction::create(Realm& realm, size_t index, PromiseValueList& values, GC::Ref<PromiseCapability const> capability, RemainingElements& remaining_elements)
{
return realm.create<PromiseAllResolveElementFunction>(index, values, capability, remaining_elements, realm.intrinsics().function_prototype());
}
PromiseAllResolveElementFunction::PromiseAllResolveElementFunction(size_t index, PromiseValueList& values, GC::Ref<PromiseCapability const> capability, RemainingElements& remaining_elements, Object& prototype)
: PromiseResolvingElementFunction(index, values, capability, remaining_elements, prototype)
{
}
ThrowCompletionOr<Value> PromiseAllResolveElementFunction::resolve_element()
{
auto& vm = this->vm();
auto& realm = *vm.current_realm();
// 8. Set values[index] to x.
m_values->values()[m_index] = vm.argument(0);
// 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
// 10. If remainingElementsCount.[[Value]] is 0, then
if (--m_remaining_elements->value == 0) {
// a. Let valuesArray be CreateArrayFromList(values).
auto values_array = Array::create_from(realm, m_values->values());
// b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »).
return JS::call(vm, *m_capability->resolve(), js_undefined(), values_array);
}
// 11. Return undefined.
return js_undefined();
}
GC::Ref<PromiseAllSettledResolveElementFunction> PromiseAllSettledResolveElementFunction::create(Realm& realm, size_t index, PromiseValueList& values, GC::Ref<PromiseCapability const> capability, RemainingElements& remaining_elements)
{
return realm.create<PromiseAllSettledResolveElementFunction>(index, values, capability, remaining_elements, realm.intrinsics().function_prototype());
}
PromiseAllSettledResolveElementFunction::PromiseAllSettledResolveElementFunction(size_t index, PromiseValueList& values, GC::Ref<PromiseCapability const> capability, RemainingElements& remaining_elements, Object& prototype)
: PromiseResolvingElementFunction(index, values, capability, remaining_elements, prototype)
{
}
ThrowCompletionOr<Value> PromiseAllSettledResolveElementFunction::resolve_element()
{
auto& vm = this->vm();
auto& realm = *vm.current_realm();
// 9. Let obj be OrdinaryObjectCreate(%Object.prototype%).
auto object = Object::create(realm, realm.intrinsics().object_prototype());
// 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled").
MUST(object->create_data_property_or_throw(vm.names.status, PrimitiveString::create(vm, "fulfilled"_string)));
// 11. Perform ! CreateDataPropertyOrThrow(obj, "value", x).
MUST(object->create_data_property_or_throw(vm.names.value, vm.argument(0)));
// 12. Set values[index] to obj.
m_values->values()[m_index] = object;
// 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
// 14. If remainingElementsCount.[[Value]] is 0, then
if (--m_remaining_elements->value == 0) {
// a. Let valuesArray be CreateArrayFromList(values).
auto values_array = Array::create_from(realm, m_values->values());
// b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »).
return JS::call(vm, *m_capability->resolve(), js_undefined(), values_array);
}
// 15. Return undefined.
return js_undefined();
}
GC::Ref<PromiseAllSettledRejectElementFunction> PromiseAllSettledRejectElementFunction::create(Realm& realm, size_t index, PromiseValueList& values, GC::Ref<PromiseCapability const> capability, RemainingElements& remaining_elements)
{
return realm.create<PromiseAllSettledRejectElementFunction>(index, values, capability, remaining_elements, realm.intrinsics().function_prototype());
}
PromiseAllSettledRejectElementFunction::PromiseAllSettledRejectElementFunction(size_t index, PromiseValueList& values, GC::Ref<PromiseCapability const> capability, RemainingElements& remaining_elements, Object& prototype)
: PromiseResolvingElementFunction(index, values, capability, remaining_elements, prototype)
{
}
ThrowCompletionOr<Value> PromiseAllSettledRejectElementFunction::resolve_element()
{
auto& vm = this->vm();
auto& realm = *vm.current_realm();
// 9. Let obj be OrdinaryObjectCreate(%Object.prototype%).
auto object = Object::create(realm, realm.intrinsics().object_prototype());
// 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected").
MUST(object->create_data_property_or_throw(vm.names.status, PrimitiveString::create(vm, "rejected"_string)));
// 11. Perform ! CreateDataPropertyOrThrow(obj, "reason", x).
MUST(object->create_data_property_or_throw(vm.names.reason, vm.argument(0)));
// 12. Set values[index] to obj.
m_values->values()[m_index] = object;
// 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
// 14. If remainingElementsCount.[[Value]] is 0, then
if (--m_remaining_elements->value == 0) {
// a. Let valuesArray be CreateArrayFromList(values).
auto values_array = Array::create_from(realm, m_values->values());
// b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »).
return JS::call(vm, *m_capability->resolve(), js_undefined(), values_array);
}
// 15. Return undefined.
return js_undefined();
}
GC::Ref<PromiseAnyRejectElementFunction> PromiseAnyRejectElementFunction::create(Realm& realm, size_t index, PromiseValueList& errors, GC::Ref<PromiseCapability const> capability, RemainingElements& remaining_elements)
{
return realm.create<PromiseAnyRejectElementFunction>(index, errors, capability, remaining_elements, realm.intrinsics().function_prototype());
}
PromiseAnyRejectElementFunction::PromiseAnyRejectElementFunction(size_t index, PromiseValueList& errors, GC::Ref<PromiseCapability const> capability, RemainingElements& remaining_elements, Object& prototype)
: PromiseResolvingElementFunction(index, errors, capability, remaining_elements, prototype)
{
}
ThrowCompletionOr<Value> PromiseAnyRejectElementFunction::resolve_element()
{
auto& vm = this->vm();
auto& realm = *vm.current_realm();
// 8. Set errors[index] to x.
m_values->values()[m_index] = vm.argument(0);
// 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
// 10. If remainingElementsCount.[[Value]] is 0, then
if (--m_remaining_elements->value == 0) {
// a. Let error be a newly created AggregateError object.
auto error = AggregateError::create(realm);
// b. Perform ! DefinePropertyOrThrow(error, "errors", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: CreateArrayFromList(errors) }).
auto errors_array = Array::create_from(realm, m_values->values());
PropertyDescriptor descriptor { .value = errors_array, .writable = true, .enumerable = false, .configurable = true };
MUST(error->define_property_or_throw(vm.names.errors, descriptor));
// c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »).
return JS::call(vm, *m_capability->reject(), js_undefined(), error);
}
return js_undefined();
}
}