mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 03:25:13 +00:00
LibJS: Implement the AsyncDisposableStack interface
This is very similar to the DisposableStack interface, except disposal of resources is promise-based.
This commit is contained in:
parent
5ea0aa5f08
commit
26c2484c2f
Notes:
github-actions[bot]
2025-01-17 19:47:28 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/26c2484c2f7 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3290
22 changed files with 873 additions and 0 deletions
|
@ -41,6 +41,9 @@ set(SOURCES
|
|||
Runtime/ArrayIterator.cpp
|
||||
Runtime/ArrayIteratorPrototype.cpp
|
||||
Runtime/ArrayPrototype.cpp
|
||||
Runtime/AsyncDisposableStack.cpp
|
||||
Runtime/AsyncDisposableStackConstructor.cpp
|
||||
Runtime/AsyncDisposableStackPrototype.cpp
|
||||
Runtime/AsyncFromSyncIterator.cpp
|
||||
Runtime/AsyncFromSyncIteratorPrototype.cpp
|
||||
Runtime/AsyncFunctionConstructor.cpp
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
__JS_ENUMERATE(AggregateError, aggregate_error, AggregateErrorPrototype, AggregateErrorConstructor, void) \
|
||||
__JS_ENUMERATE(Array, array, ArrayPrototype, ArrayConstructor, void) \
|
||||
__JS_ENUMERATE(ArrayBuffer, array_buffer, ArrayBufferPrototype, ArrayBufferConstructor, void) \
|
||||
__JS_ENUMERATE(AsyncDisposableStack, async_disposable_stack, AsyncDisposableStackPrototype, AsyncDisposableStackConstructor, void) \
|
||||
__JS_ENUMERATE(AsyncFunction, async_function, AsyncFunctionPrototype, AsyncFunctionConstructor, void) \
|
||||
__JS_ENUMERATE(AsyncGeneratorFunction, async_generator_function, AsyncGeneratorFunctionPrototype, AsyncGeneratorFunctionConstructor, void) \
|
||||
__JS_ENUMERATE(BigIntObject, bigint, BigIntPrototype, BigIntConstructor, void) \
|
||||
|
|
25
Libraries/LibJS/Runtime/AsyncDisposableStack.cpp
Normal file
25
Libraries/LibJS/Runtime/AsyncDisposableStack.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AsyncDisposableStack.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(AsyncDisposableStack);
|
||||
|
||||
AsyncDisposableStack::AsyncDisposableStack(DisposeCapability dispose_capability, Object& prototype)
|
||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||
, m_dispose_capability(move(dispose_capability))
|
||||
{
|
||||
}
|
||||
|
||||
void AsyncDisposableStack::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
m_dispose_capability.visit_edges(visitor);
|
||||
}
|
||||
|
||||
}
|
41
Libraries/LibJS/Runtime/AsyncDisposableStack.h
Normal file
41
Libraries/LibJS/Runtime/AsyncDisposableStack.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
class AsyncDisposableStack final : public Object {
|
||||
JS_OBJECT(AsyncDisposableStack, Object);
|
||||
GC_DECLARE_ALLOCATOR(AsyncDisposableStack);
|
||||
|
||||
public:
|
||||
virtual ~AsyncDisposableStack() override = default;
|
||||
|
||||
enum class AsyncDisposableState {
|
||||
Pending,
|
||||
Disposed
|
||||
};
|
||||
|
||||
[[nodiscard]] AsyncDisposableState async_disposable_state() const { return m_async_disposable_state; }
|
||||
void set_disposed() { m_async_disposable_state = AsyncDisposableState::Disposed; }
|
||||
|
||||
[[nodiscard]] DisposeCapability const& dispose_capability() const { return m_dispose_capability; }
|
||||
[[nodiscard]] DisposeCapability& dispose_capability() { return m_dispose_capability; }
|
||||
|
||||
private:
|
||||
AsyncDisposableStack(DisposeCapability, Object& prototype);
|
||||
|
||||
virtual void visit_edges(Visitor& visitor) override;
|
||||
|
||||
DisposeCapability m_dispose_capability;
|
||||
AsyncDisposableState m_async_disposable_state { AsyncDisposableState::Pending };
|
||||
};
|
||||
|
||||
}
|
52
Libraries/LibJS/Runtime/AsyncDisposableStackConstructor.cpp
Normal file
52
Libraries/LibJS/Runtime/AsyncDisposableStackConstructor.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/AsyncDisposableStack.h>
|
||||
#include <LibJS/Runtime/AsyncDisposableStackConstructor.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(AsyncDisposableStackConstructor);
|
||||
|
||||
AsyncDisposableStackConstructor::AsyncDisposableStackConstructor(Realm& realm)
|
||||
: NativeFunction(realm.vm().names.AsyncDisposableStack.as_string(), realm.intrinsics().function_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
void AsyncDisposableStackConstructor::initialize(Realm& realm)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
Base::initialize(realm);
|
||||
|
||||
// 12.4.2.1 AsyncDisposableStack.prototype, https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype
|
||||
define_direct_property(vm.names.prototype, realm.intrinsics().async_disposable_stack_prototype(), 0);
|
||||
|
||||
define_direct_property(vm.names.length, Value(0), Attribute::Configurable);
|
||||
}
|
||||
|
||||
// 12.4.1.1 AsyncDisposableStack ( ), https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack
|
||||
ThrowCompletionOr<Value> AsyncDisposableStackConstructor::call()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, vm.names.AsyncDisposableStack);
|
||||
}
|
||||
|
||||
// 12.4.1.1 AsyncDisposableStack ( ), https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack
|
||||
ThrowCompletionOr<GC::Ref<Object>> AsyncDisposableStackConstructor::construct(FunctionObject& new_target)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 2. Let asyncDisposableStack be ? OrdinaryCreateFromConstructor(NewTarget, "%AsyncDisposableStack.prototype%", « [[AsyncDisposableState]], [[DisposeCapability]] »).
|
||||
// 3. Set asyncDisposableStack.[[AsyncDisposableState]] to pending.
|
||||
// 4. Set asyncDisposableStack.[[DisposeCapability]] to NewDisposeCapability().
|
||||
// 5. Return asyncDisposableStack.
|
||||
return TRY(ordinary_create_from_constructor<AsyncDisposableStack>(vm, new_target, &Intrinsics::async_disposable_stack_prototype, new_dispose_capability()));
|
||||
}
|
||||
|
||||
}
|
30
Libraries/LibJS/Runtime/AsyncDisposableStackConstructor.h
Normal file
30
Libraries/LibJS/Runtime/AsyncDisposableStackConstructor.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
class AsyncDisposableStackConstructor final : public NativeFunction {
|
||||
JS_OBJECT(AsyncDisposableStackConstructor, NativeFunction);
|
||||
GC_DECLARE_ALLOCATOR(AsyncDisposableStackConstructor);
|
||||
|
||||
public:
|
||||
virtual void initialize(Realm&) override;
|
||||
virtual ~AsyncDisposableStackConstructor() override = default;
|
||||
|
||||
virtual ThrowCompletionOr<Value> call() override;
|
||||
virtual ThrowCompletionOr<GC::Ref<Object>> construct(FunctionObject&) override;
|
||||
|
||||
private:
|
||||
explicit AsyncDisposableStackConstructor(Realm&);
|
||||
|
||||
virtual bool has_constructor() const override { return true; }
|
||||
};
|
||||
|
||||
}
|
213
Libraries/LibJS/Runtime/AsyncDisposableStackPrototype.cpp
Normal file
213
Libraries/LibJS/Runtime/AsyncDisposableStackPrototype.cpp
Normal file
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/AsyncDisposableStackConstructor.h>
|
||||
#include <LibJS/Runtime/AsyncDisposableStackPrototype.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
#include <LibJS/Runtime/PromiseCapability.h>
|
||||
#include <LibJS/Runtime/PromiseConstructor.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(AsyncDisposableStackPrototype);
|
||||
|
||||
AsyncDisposableStackPrototype::AsyncDisposableStackPrototype(Realm& realm)
|
||||
: PrototypeObject(realm.intrinsics().object_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
void AsyncDisposableStackPrototype::initialize(Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
|
||||
auto& vm = this->vm();
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.adopt, adopt, 2, attr);
|
||||
define_native_function(realm, vm.names.defer, defer, 1, attr);
|
||||
define_native_function(realm, vm.names.disposeAsync, dispose_async, 0, attr);
|
||||
define_native_accessor(realm, vm.names.disposed, disposed_getter, {}, attr);
|
||||
define_native_function(realm, vm.names.move, move_, 0, attr);
|
||||
define_native_function(realm, vm.names.use, use, 1, attr);
|
||||
|
||||
// 12.4.3.7 AsyncDisposableStack.prototype [ @@asyncDispose ] (), https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype-@@asyncDispose
|
||||
define_direct_property(vm.well_known_symbol_async_dispose(), get_without_side_effects(vm.names.disposeAsync), attr);
|
||||
|
||||
// 12.4.3.8 AsyncDisposableStack.prototype [ @@toStringTag ], https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype-@@toStringTag
|
||||
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, vm.names.AsyncDisposableStack.as_string()), Attribute::Configurable);
|
||||
}
|
||||
|
||||
// 12.4.3.1 AsyncDisposableStack.prototype.adopt( value, onDisposeAsync ), https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.adopt
|
||||
JS_DEFINE_NATIVE_FUNCTION(AsyncDisposableStackPrototype::adopt)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
auto value = vm.argument(0);
|
||||
auto on_dispose_async = vm.argument(1);
|
||||
|
||||
// 1. Let asyncDisposableStack be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(asyncDisposableStack, [[AsyncDisposableState]]).
|
||||
auto async_disposable_stack = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. If asyncDisposableStack.[[AsyncDisposableState]] is disposed, throw a ReferenceError exception.
|
||||
if (async_disposable_stack->async_disposable_state() == AsyncDisposableStack::AsyncDisposableState::Disposed)
|
||||
return vm.throw_completion<ReferenceError>(ErrorType::AsyncDisposableStackAlreadyDisposed);
|
||||
|
||||
// 4. If IsCallable(onDisposeAsync) is false, throw a TypeError exception.
|
||||
if (!on_dispose_async.is_function())
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, on_dispose_async);
|
||||
|
||||
// 5. Let closure be a new Abstract Closure with no parameters that captures value and onDisposeAsync and performs the following steps when called:
|
||||
auto closure = [value, on_dispose_async](VM& vm) mutable -> ThrowCompletionOr<Value> {
|
||||
// a. Return ? Call(onDisposeAsync, undefined, « value »).
|
||||
return TRY(call(vm, on_dispose_async.as_function(), js_undefined(), value));
|
||||
};
|
||||
|
||||
// 6. Let F be CreateBuiltinFunction(closure, 0, "", « »).
|
||||
auto function = NativeFunction::create(realm, move(closure), 0, "");
|
||||
|
||||
// 7. Perform ? AddDisposableResource(asyncDisposableStack.[[DisposeCapability]], undefined, async-dispose, F).
|
||||
TRY(add_disposable_resource(vm, async_disposable_stack->dispose_capability(), js_undefined(), Environment::InitializeBindingHint::AsyncDispose, function));
|
||||
|
||||
// 8. Return value.
|
||||
return value;
|
||||
}
|
||||
|
||||
// 12.4.3.2 AsyncDisposableStack.prototype.defer( onDisposeAsync ), https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.defer
|
||||
JS_DEFINE_NATIVE_FUNCTION(AsyncDisposableStackPrototype::defer)
|
||||
{
|
||||
auto on_dispose_async = vm.argument(0);
|
||||
|
||||
// 1. Let asyncDisposableStack be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(asyncDisposableStack, [[AsyncDisposableState]]).
|
||||
auto async_disposable_stack = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. If asyncDisposableStack.[[AsyncDisposableState]] is disposed, throw a ReferenceError exception.
|
||||
if (async_disposable_stack->async_disposable_state() == AsyncDisposableStack::AsyncDisposableState::Disposed)
|
||||
return vm.throw_completion<ReferenceError>(ErrorType::AsyncDisposableStackAlreadyDisposed);
|
||||
|
||||
// 4. If IsCallable(onDisposeAsync) is false, throw a TypeError exception.
|
||||
if (!on_dispose_async.is_function())
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, on_dispose_async);
|
||||
|
||||
// 5. Perform ? AddDisposableResource(asyncDisposableStack.[[DisposeCapability]], undefined, async-dispose, onDisposeAsync).
|
||||
TRY(add_disposable_resource(vm, async_disposable_stack->dispose_capability(), js_undefined(), Environment::InitializeBindingHint::AsyncDispose, on_dispose_async.as_function()));
|
||||
|
||||
// 6. Return undefined.
|
||||
return js_undefined();
|
||||
}
|
||||
|
||||
// 12.4.3.3 AsyncDisposableStack.prototype.disposeAsync(), https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.disposeAsync
|
||||
JS_DEFINE_NATIVE_FUNCTION(AsyncDisposableStackPrototype::dispose_async)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// 1. Let asyncDisposableStack be the this value.
|
||||
auto async_disposable_stack_value = vm.this_value();
|
||||
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor()));
|
||||
|
||||
// 3. If asyncDisposableStack does not have an [[AsyncDisposableState]] internal slot, then
|
||||
if (!async_disposable_stack_value.is_object() || !is<AsyncDisposableStack>(async_disposable_stack_value.as_object())) {
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
|
||||
auto error = TypeError::create(realm, MUST(String::formatted(ErrorType::NotAnObjectOfType.message(), display_name())));
|
||||
MUST(call(vm, *promise_capability->reject(), js_undefined(), error));
|
||||
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return promise_capability->promise();
|
||||
}
|
||||
|
||||
auto& async_disposable_stack = static_cast<AsyncDisposableStack&>(async_disposable_stack_value.as_object());
|
||||
|
||||
// 4. If asyncDisposableStack.[[AsyncDisposableState]] is disposed, then
|
||||
if (async_disposable_stack.async_disposable_state() == AsyncDisposableStack::AsyncDisposableState::Disposed) {
|
||||
// a. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »).
|
||||
MUST(call(vm, *promise_capability->resolve(), js_undefined(), js_undefined()));
|
||||
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return promise_capability->promise();
|
||||
}
|
||||
|
||||
// 5. Set asyncDisposableStack.[[AsyncDisposableState]] to disposed.
|
||||
async_disposable_stack.set_disposed();
|
||||
|
||||
// 6. Let result be DisposeResources(asyncDisposableStack.[[DisposeCapability]], NormalCompletion(undefined)).
|
||||
// 7. IfAbruptRejectPromise(result, promiseCapability).
|
||||
auto result = TRY_OR_REJECT(vm, promise_capability, dispose_resources(vm, async_disposable_stack.dispose_capability(), normal_completion(js_undefined())));
|
||||
|
||||
// 8. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result »).
|
||||
MUST(call(vm, *promise_capability->resolve(), js_undefined(), *result));
|
||||
|
||||
// 9. Return promiseCapability.[[Promise]].
|
||||
return promise_capability->promise();
|
||||
}
|
||||
|
||||
// 12.4.3.4 get AsyncDisposableStack.prototype.disposed, https://tc39.es/proposal-explicit-resource-management/#sec-get-asyncdisposablestack.prototype.disposed
|
||||
JS_DEFINE_NATIVE_FUNCTION(AsyncDisposableStackPrototype::disposed_getter)
|
||||
{
|
||||
// 1. Let asyncDisposableStack be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(asyncDisposableStack, [[AsyncDisposableState]]).
|
||||
auto async_disposable_stack = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. If asyncDisposableStack.[[AsyncDisposableState]] is disposed, return true.
|
||||
if (async_disposable_stack->async_disposable_state() == AsyncDisposableStack::AsyncDisposableState::Disposed)
|
||||
return true;
|
||||
|
||||
// 4. Otherwise, return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 12.4.3.5 AsyncDisposableStack.prototype.move(), https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.move
|
||||
JS_DEFINE_NATIVE_FUNCTION(AsyncDisposableStackPrototype::move_)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// 1. Let asyncDisposableStack be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(asyncDisposableStack, [[AsyncDisposableState]]).
|
||||
auto async_disposable_stack = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. If asyncDisposableStack.[[AsyncDisposableState]] is disposed, throw a ReferenceError exception.
|
||||
if (async_disposable_stack->async_disposable_state() == AsyncDisposableStack::AsyncDisposableState::Disposed)
|
||||
return vm.throw_completion<ReferenceError>(ErrorType::AsyncDisposableStackAlreadyDisposed);
|
||||
|
||||
// 4. Let newAsyncDisposableStack be ? OrdinaryCreateFromConstructor(%AsyncDisposableStack%, "%AsyncDisposableStack.prototype%", « [[AsyncDisposableState]], [[DisposeCapability]] »).
|
||||
// 5. Set newAsyncDisposableStack.[[AsyncDisposableState]] to pending.
|
||||
// 6. Set newAsyncDisposableStack.[[DisposeCapability]] to asyncDisposableStack.[[DisposeCapability]].
|
||||
auto new_async_disposable_stack = TRY(ordinary_create_from_constructor<AsyncDisposableStack>(vm, realm.intrinsics().async_disposable_stack_constructor(), &Intrinsics::async_disposable_stack_prototype, move(async_disposable_stack->dispose_capability())));
|
||||
|
||||
// 7. Set asyncDisposableStack.[[DisposeCapability]] to NewDisposeCapability().
|
||||
async_disposable_stack->dispose_capability() = new_dispose_capability();
|
||||
|
||||
// 8. Set asyncDisposableStack.[[AsyncDisposableState]] to disposed.
|
||||
async_disposable_stack->set_disposed();
|
||||
|
||||
// 9. Return newAsyncDisposableStack.
|
||||
return new_async_disposable_stack;
|
||||
}
|
||||
|
||||
// 12.4.3.6 AsyncDisposableStack.prototype.use( value ), https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.use
|
||||
JS_DEFINE_NATIVE_FUNCTION(AsyncDisposableStackPrototype::use)
|
||||
{
|
||||
auto value = vm.argument(0);
|
||||
|
||||
// 1. Let asyncDisposableStack be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(asyncDisposableStack, [[AsyncDisposableState]]).
|
||||
auto async_disposable_stack = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. If asyncDisposableStack.[[AsyncDisposableState]] is disposed, throw a ReferenceError exception.
|
||||
if (async_disposable_stack->async_disposable_state() == AsyncDisposableStack::AsyncDisposableState::Disposed)
|
||||
return vm.throw_completion<ReferenceError>(ErrorType::AsyncDisposableStackAlreadyDisposed);
|
||||
|
||||
// 4. Perform ? AddDisposableResource(asyncDisposableStack.[[DisposeCapability]], value, async-dispose).
|
||||
TRY(add_disposable_resource(vm, async_disposable_stack->dispose_capability(), value, Environment::InitializeBindingHint::AsyncDispose));
|
||||
|
||||
// 5. Return value.
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
33
Libraries/LibJS/Runtime/AsyncDisposableStackPrototype.h
Normal file
33
Libraries/LibJS/Runtime/AsyncDisposableStackPrototype.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/AsyncDisposableStack.h>
|
||||
#include <LibJS/Runtime/PrototypeObject.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
class AsyncDisposableStackPrototype final : public PrototypeObject<AsyncDisposableStackPrototype, AsyncDisposableStack> {
|
||||
JS_PROTOTYPE_OBJECT(AsyncDisposableStackPrototype, AsyncDisposableStack, AsyncDisposableStack);
|
||||
GC_DECLARE_ALLOCATOR(AsyncDisposableStackPrototype);
|
||||
|
||||
public:
|
||||
virtual void initialize(Realm&) override;
|
||||
virtual ~AsyncDisposableStackPrototype() override = default;
|
||||
|
||||
private:
|
||||
explicit AsyncDisposableStackPrototype(Realm&);
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(adopt);
|
||||
JS_DECLARE_NATIVE_FUNCTION(defer);
|
||||
JS_DECLARE_NATIVE_FUNCTION(dispose_async);
|
||||
JS_DECLARE_NATIVE_FUNCTION(disposed_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(move_);
|
||||
JS_DECLARE_NATIVE_FUNCTION(use);
|
||||
};
|
||||
|
||||
}
|
|
@ -127,6 +127,7 @@ namespace JS {
|
|||
P(direction) \
|
||||
P(disabledFeatures) \
|
||||
P(disambiguation) \
|
||||
P(disposeAsync) \
|
||||
P(disposed) \
|
||||
P(done) \
|
||||
P(dotAll) \
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
M(AccessorValueOrWritable, "Accessor property descriptor cannot specify a value or writable key") \
|
||||
M(AgentCannotSuspend, "Agent is not allowed to suspend") \
|
||||
M(ArrayMaxSize, "Maximum array size exceeded") \
|
||||
M(AsyncDisposableStackAlreadyDisposed, "AsyncDisposableStack is already disposed") \
|
||||
M(BadArgCountMany, "{}() needs {} arguments") \
|
||||
M(BadArgCountOne, "{}() needs one argument") \
|
||||
M(BigIntBadOperator, "Cannot use {} operator with BigInt") \
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <LibJS/Runtime/ArrayBufferConstructor.h>
|
||||
#include <LibJS/Runtime/ArrayConstructor.h>
|
||||
#include <LibJS/Runtime/ArrayPrototype.h>
|
||||
#include <LibJS/Runtime/AsyncDisposableStackConstructor.h>
|
||||
#include <LibJS/Runtime/AsyncFunctionConstructor.h>
|
||||
#include <LibJS/Runtime/AsyncGeneratorFunctionConstructor.h>
|
||||
#include <LibJS/Runtime/AsyncGeneratorPrototype.h>
|
||||
|
@ -123,6 +124,7 @@ void set_default_global_bindings(Realm& realm)
|
|||
global.define_intrinsic_accessor(vm.names.AggregateError, attr, [](auto& realm) -> Value { return realm.intrinsics().aggregate_error_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.Array, attr, [](auto& realm) -> Value { return realm.intrinsics().array_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.ArrayBuffer, attr, [](auto& realm) -> Value { return realm.intrinsics().array_buffer_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.AsyncDisposableStack, attr, [](auto& realm) -> Value { return realm.intrinsics().async_disposable_stack_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.BigInt, attr, [](auto& realm) -> Value { return realm.intrinsics().bigint_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.BigInt64Array, attr, [](auto& realm) -> Value { return realm.intrinsics().big_int64_array_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.BigUint64Array, attr, [](auto& realm) -> Value { return realm.intrinsics().big_uint64_array_constructor(); });
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include <LibJS/Runtime/ArrayConstructor.h>
|
||||
#include <LibJS/Runtime/ArrayIteratorPrototype.h>
|
||||
#include <LibJS/Runtime/ArrayPrototype.h>
|
||||
#include <LibJS/Runtime/AsyncDisposableStackConstructor.h>
|
||||
#include <LibJS/Runtime/AsyncDisposableStackPrototype.h>
|
||||
#include <LibJS/Runtime/AsyncFromSyncIteratorPrototype.h>
|
||||
#include <LibJS/Runtime/AsyncFunctionConstructor.h>
|
||||
#include <LibJS/Runtime/AsyncFunctionPrototype.h>
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
test("constructor properties", () => {
|
||||
expect(AsyncDisposableStack).toHaveLength(0);
|
||||
expect(AsyncDisposableStack.name).toBe("AsyncDisposableStack");
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("called without new", () => {
|
||||
expect(() => {
|
||||
AsyncDisposableStack();
|
||||
}).toThrowWithMessage(
|
||||
TypeError,
|
||||
"AsyncDisposableStack constructor must be called with 'new'"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("typeof", () => {
|
||||
expect(typeof new AsyncDisposableStack()).toBe("object");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
test("length is 0", () => {
|
||||
expect(AsyncDisposableStack.prototype[Symbol.asyncDispose]).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("is the same as disposeAsync", () => {
|
||||
expect(AsyncDisposableStack.prototype[Symbol.asyncDispose]).toBe(
|
||||
AsyncDisposableStack.prototype.disposeAsync
|
||||
);
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
test("basic functionality", () => {
|
||||
expect(AsyncDisposableStack.prototype[Symbol.toStringTag]).toBe("AsyncDisposableStack");
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
test("length is 2", () => {
|
||||
expect(AsyncDisposableStack.prototype.adopt).toHaveLength(2);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("adopted dispose method gets called when stack is disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposedCalled = 0;
|
||||
let disposeArgument = undefined;
|
||||
expect(disposedCalled).toBe(0);
|
||||
const result = stack.adopt(null, arg => {
|
||||
disposeArgument = arg;
|
||||
++disposedCalled;
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
|
||||
expect(disposedCalled).toBe(0);
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
expect(disposeArgument).toBeNull();
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
});
|
||||
|
||||
test("can adopt any value", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
const disposed = [];
|
||||
function dispose(value) {
|
||||
disposed.push(value);
|
||||
}
|
||||
|
||||
const values = [null, undefined, 1, "a", Symbol.dispose, () => {}, new WeakMap(), [], {}];
|
||||
|
||||
values.forEach(value => {
|
||||
stack.adopt(value, dispose);
|
||||
});
|
||||
|
||||
await stack.disposeAsync();
|
||||
|
||||
expect(disposed).toEqual(values.reverse());
|
||||
});
|
||||
|
||||
test("adopted stack is already disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.adopt(stack, value => {
|
||||
expect(stack).toBe(value);
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
await stack.disposeAsync();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("if call back is not a function throws type error", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
[
|
||||
1,
|
||||
1n,
|
||||
"a",
|
||||
Symbol.dispose,
|
||||
NaN,
|
||||
0,
|
||||
{},
|
||||
[],
|
||||
{ f() {} },
|
||||
{ [Symbol.dispose]() {} },
|
||||
{
|
||||
get [Symbol.dispose]() {
|
||||
return () => {};
|
||||
},
|
||||
},
|
||||
].forEach(value => {
|
||||
expect(() => stack.adopt(null, value)).toThrowWithMessage(TypeError, "not a function");
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("adopt throws if stack is already disposed (over type errors)", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
[{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
|
||||
expect(() => stack.adopt(value, () => {})).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"AsyncDisposableStack already disposed values"
|
||||
);
|
||||
expect(() => stack.adopt(null, value)).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"AsyncDisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
test("length is 1", () => {
|
||||
expect(AsyncDisposableStack.prototype.defer).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("deferred function gets called when stack is disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposedCalled = 0;
|
||||
expect(disposedCalled).toBe(0);
|
||||
const result = stack.defer((...args) => {
|
||||
expect(args.length).toBe(0);
|
||||
++disposedCalled;
|
||||
});
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
expect(disposedCalled).toBe(0);
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
});
|
||||
|
||||
test("deferred stack is already disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.defer(() => {
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
await stack.disposeAsync();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("if call back is not a function throws type error", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
[
|
||||
1,
|
||||
1n,
|
||||
"a",
|
||||
Symbol.dispose,
|
||||
NaN,
|
||||
0,
|
||||
{},
|
||||
[],
|
||||
{ f() {} },
|
||||
{ [Symbol.dispose]() {} },
|
||||
{
|
||||
get [Symbol.dispose]() {
|
||||
return () => {};
|
||||
},
|
||||
},
|
||||
].forEach(value => {
|
||||
expect(() => stack.defer(value)).toThrowWithMessage(TypeError, "not a function");
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("defer throws if stack is already disposed (over type errors)", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
[{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
|
||||
expect(() => stack.defer(value)).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"AsyncDisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,88 @@
|
|||
test("length is 0", () => {
|
||||
expect(AsyncDisposableStack.prototype.disposeAsync).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("make the stack marked as disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
const result = await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("call dispose on objects in stack when called", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposedCalled = false;
|
||||
stack.use({
|
||||
[Symbol.asyncDispose]() {
|
||||
disposedCalled = true;
|
||||
},
|
||||
});
|
||||
|
||||
expect(disposedCalled).toBeFalse();
|
||||
const result = await stack.disposeAsync();
|
||||
expect(disposedCalled).toBeTrue();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("disposed the objects added to the stack in reverse order", async () => {
|
||||
const disposed = [];
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.use({
|
||||
[Symbol.asyncDispose]() {
|
||||
disposed.push("a");
|
||||
},
|
||||
});
|
||||
stack.use({
|
||||
[Symbol.asyncDispose]() {
|
||||
disposed.push("b");
|
||||
},
|
||||
});
|
||||
|
||||
expect(disposed).toEqual([]);
|
||||
const result = await stack.disposeAsync();
|
||||
expect(disposed).toEqual(["b", "a"]);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("does not dispose anything if already disposed", async () => {
|
||||
const disposed = [];
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.use({
|
||||
[Symbol.asyncDispose]() {
|
||||
disposed.push("a");
|
||||
},
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
expect(disposed).toEqual([]);
|
||||
|
||||
let result = await stack.disposeAsync();
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(disposed).toEqual(["a"]);
|
||||
|
||||
result = await stack.disposeAsync();
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(disposed).toEqual(["a"]);
|
||||
});
|
||||
|
||||
test("throws if dispose method throws", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposedCalled = false;
|
||||
stack.use({
|
||||
[Symbol.asyncDispose]() {
|
||||
disposedCalled = true;
|
||||
expect().fail("fail in dispose");
|
||||
},
|
||||
});
|
||||
|
||||
expect(async () => await stack.disposeAsync()).toThrowWithMessage(
|
||||
ExpectationError,
|
||||
"fail in dispose"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
test("is getter without setter", () => {
|
||||
const property = Object.getOwnPropertyDescriptor(AsyncDisposableStack.prototype, "disposed");
|
||||
expect(property.get).not.toBeUndefined();
|
||||
expect(property.set).toBeUndefined();
|
||||
expect(property.value).toBeUndefined();
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("is not a property on the object itself", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
expect(Object.hasOwn(stack, "disposed")).toBeFalse();
|
||||
});
|
||||
|
||||
test("starts off as false", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
expect(stack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("becomes true after being disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
test("length is 0", () => {
|
||||
expect(AsyncDisposableStack.prototype.move).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("stack is disposed after moving", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
|
||||
const newStack = stack.move();
|
||||
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(newStack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("move does not dispose resource but only move them", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposeCalled = false;
|
||||
stack.defer(() => {
|
||||
disposeCalled = true;
|
||||
});
|
||||
|
||||
expect(disposeCalled).toBeFalse();
|
||||
expect(stack.disposed).toBeFalse();
|
||||
|
||||
const newStack = stack.move();
|
||||
|
||||
expect(disposeCalled).toBeFalse();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(newStack.disposed).toBeFalse();
|
||||
|
||||
await stack.disposeAsync();
|
||||
|
||||
expect(disposeCalled).toBeFalse();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(newStack.disposed).toBeFalse();
|
||||
|
||||
await newStack.disposeAsync();
|
||||
|
||||
expect(disposeCalled).toBeTrue();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(newStack.disposed).toBeTrue();
|
||||
});
|
||||
|
||||
test("can add stack to itself", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.move(stack);
|
||||
await stack.disposeAsync();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("move throws if stack is already disposed (over type errors)", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
expect(() => stack.move()).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"AsyncDisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
test("length is 1", () => {
|
||||
expect(AsyncDisposableStack.prototype.use).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("added objects dispose method gets when stack is disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposedCalled = 0;
|
||||
const obj = {
|
||||
[Symbol.dispose]() {
|
||||
++disposedCalled;
|
||||
},
|
||||
};
|
||||
expect(disposedCalled).toBe(0);
|
||||
const result = stack.use(obj);
|
||||
expect(result).toBe(obj);
|
||||
|
||||
expect(disposedCalled).toBe(0);
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
});
|
||||
|
||||
test("can add null and undefined", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
|
||||
expect(stack.use(null)).toBeNull();
|
||||
expect(stack.use(undefined)).toBeUndefined();
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
|
||||
test("can add stack to itself", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.use(stack);
|
||||
await stack.disposeAsync();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("if added value is not an object or null or undefined throws type error", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
[1, 1n, "a", Symbol.dispose, NaN, 0].forEach(value => {
|
||||
expect(() => stack.use(value)).toThrowWithMessage(TypeError, "not an object");
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("if added object does not have a dispose method throws type error", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
[{}, [], { f() {} }].forEach(value => {
|
||||
expect(() => stack.use(value)).toThrowWithMessage(
|
||||
TypeError,
|
||||
"does not have dispose method"
|
||||
);
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("if added object has non function dispose method it throws type error", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let calledGetter = false;
|
||||
[
|
||||
{ [Symbol.dispose]: 1 },
|
||||
{
|
||||
get [Symbol.dispose]() {
|
||||
calledGetter = true;
|
||||
return 1;
|
||||
},
|
||||
},
|
||||
].forEach(value => {
|
||||
expect(() => stack.use(value)).toThrowWithMessage(TypeError, "is not a function");
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
expect(calledGetter).toBeTrue();
|
||||
});
|
||||
|
||||
test("use throws if stack is already disposed (over type errors)", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
[{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
|
||||
expect(() => stack.use(value)).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"AsyncDisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -10,6 +10,7 @@ AnimationPlaybackEvent
|
|||
AnimationTimeline
|
||||
Array
|
||||
ArrayBuffer
|
||||
AsyncDisposableStack
|
||||
Attr
|
||||
Audio
|
||||
AudioBuffer
|
||||
|
|
Loading…
Add table
Reference in a new issue