summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordavidot <davidot@serenityos.org>2022-12-21 11:48:14 +0100
committerLinus Groh <mail@linusgroh.de>2023-01-23 09:56:50 +0000
commit6255ca4a422754d7c29124be1e57d96cee69f4e5 (patch)
tree35472f6e3b56fb537bfdcc4940a7b647e29d15cb
parentbff038411a71047cb02bc49d4c8518d06096a6ef (diff)
downloadserenity-6255ca4a422754d7c29124be1e57d96cee69f4e5.zip
LibJS: Add DisposableStack{, Prototype, Constructor}
Since the async parts of the spec are not stage 3 at this point we don't add AsyncDisposableStack.
-rw-r--r--.prettierignore1
-rw-r--r--Userland/Libraries/LibJS/CMakeLists.txt3
-rw-r--r--Userland/Libraries/LibJS/Forward.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h5
-rw-r--r--Userland/Libraries/LibJS/Runtime/DisposableStack.cpp26
-rw-r--r--Userland/Libraries/LibJS/Runtime/DisposableStack.h40
-rw-r--r--Userland/Libraries/LibJS/Runtime/DisposableStackConstructor.cpp50
-rw-r--r--Userland/Libraries/LibJS/Runtime/DisposableStackConstructor.h29
-rw-r--r--Userland/Libraries/LibJS/Runtime/DisposableStackPrototype.cpp206
-rw-r--r--Userland/Libraries/LibJS/Runtime/DisposableStackPrototype.h32
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorTypes.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/GlobalObject.cpp2
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intrinsics.cpp2
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.js18
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@dispose.js19
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@toStringTag.js3
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.adopt.js95
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.defer.js70
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.dispose.js83
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.disposed.js24
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.move.js62
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.use.js96
22 files changed, 868 insertions, 0 deletions
diff --git a/.prettierignore b/.prettierignore
index 3f5155996b..0d31829d6e 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -4,6 +4,7 @@ Userland/Libraries/LibJS/Tests/unicode-identifier-escape.js
Userland/Libraries/LibJS/Tests/modules/failing.mjs
# FIXME: Remove once prettier is updated to support using declarations.
+Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@dispose.js
Userland/Libraries/LibJS/Tests/modules/top-level-dispose.mjs
Userland/Libraries/LibJS/Tests/using-declaration.js
Userland/Libraries/LibJS/Tests/using-for-loops.js
diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt
index 9650dc2082..e7b0ccdceb 100644
--- a/Userland/Libraries/LibJS/CMakeLists.txt
+++ b/Userland/Libraries/LibJS/CMakeLists.txt
@@ -75,6 +75,9 @@ set(SOURCES
Runtime/DateConstructor.cpp
Runtime/DatePrototype.cpp
Runtime/DeclarativeEnvironment.cpp
+ Runtime/DisposableStack.cpp
+ Runtime/DisposableStackConstructor.cpp
+ Runtime/DisposableStackPrototype.cpp
Runtime/ECMAScriptFunctionObject.cpp
Runtime/Environment.cpp
Runtime/Error.cpp
diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h
index c5938e98fc..2df9320ee4 100644
--- a/Userland/Libraries/LibJS/Forward.h
+++ b/Userland/Libraries/LibJS/Forward.h
@@ -27,6 +27,7 @@
__JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor, void) \
__JS_ENUMERATE(DataView, data_view, DataViewPrototype, DataViewConstructor, void) \
__JS_ENUMERATE(Date, date, DatePrototype, DateConstructor, void) \
+ __JS_ENUMERATE(DisposableStack, disposable_stack, DisposableStackPrototype, DisposableStackConstructor, void) \
__JS_ENUMERATE(Error, error, ErrorPrototype, ErrorConstructor, void) \
__JS_ENUMERATE(FinalizationRegistry, finalization_registry, FinalizationRegistryPrototype, FinalizationRegistryConstructor, void) \
__JS_ENUMERATE(FunctionObject, function, FunctionPrototype, FunctionConstructor, void) \
diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
index ed742088a0..90c2449a98 100644
--- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
+++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
@@ -65,6 +65,7 @@ namespace JS {
P(acos) \
P(acosh) \
P(add) \
+ P(adopt) \
P(all) \
P(allSettled) \
P(anchor) \
@@ -143,6 +144,7 @@ namespace JS {
P(debug) \
P(decodeURI) \
P(decodeURIComponent) \
+ P(defer) \
P(defineProperties) \
P(defineProperty) \
P(deleteProperty) \
@@ -151,6 +153,7 @@ namespace JS {
P(difference) \
P(direction) \
P(disambiguation) \
+ P(disposed) \
P(done) \
P(dotAll) \
P(encodeURI) \
@@ -365,6 +368,7 @@ namespace JS {
P(months) \
P(monthsDisplay) \
P(monthsInYear) \
+ P(move) \
P(multiline) \
P(name) \
P(nanosecond) \
@@ -555,6 +559,7 @@ namespace JS {
P(unshift) \
P(until) \
P(usage) \
+ P(use) \
P(useGrouping) \
P(value) \
P(valueOf) \
diff --git a/Userland/Libraries/LibJS/Runtime/DisposableStack.cpp b/Userland/Libraries/LibJS/Runtime/DisposableStack.cpp
new file mode 100644
index 0000000000..e54ad354b0
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/DisposableStack.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/DisposableStack.h>
+
+namespace JS {
+
+DisposableStack::DisposableStack(Vector<DisposableResource> stack, Object& prototype)
+ : Object(ConstructWithPrototypeTag::Tag, prototype)
+ , m_disposable_resource_stack(move(stack))
+{
+}
+
+void DisposableStack::visit_edges(Cell::Visitor& visitor)
+{
+ Base::visit_edges(visitor);
+ for (auto& resource : m_disposable_resource_stack) {
+ visitor.visit(resource.resource_value);
+ visitor.visit(resource.dispose_method);
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/DisposableStack.h b/Userland/Libraries/LibJS/Runtime/DisposableStack.h
new file mode 100644
index 0000000000..77b846f559
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/DisposableStack.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/AbstractOperations.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class DisposableStack final : public Object {
+ JS_OBJECT(DisposableStack, Object);
+
+public:
+ virtual ~DisposableStack() override = default;
+
+ enum class DisposableState {
+ Pending,
+ Disposed
+ };
+
+ [[nodiscard]] DisposableState disposable_state() const { return m_state; }
+ [[nodiscard]] Vector<DisposableResource> const& disposable_resource_stack() const { return m_disposable_resource_stack; }
+ [[nodiscard]] Vector<DisposableResource>& disposable_resource_stack() { return m_disposable_resource_stack; }
+
+ void set_disposed() { m_state = DisposableState::Disposed; }
+
+private:
+ DisposableStack(Vector<DisposableResource> stack, Object& prototype);
+
+ virtual void visit_edges(Visitor& visitor) override;
+
+ Vector<DisposableResource> m_disposable_resource_stack;
+ DisposableState m_state { DisposableState::Pending };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/DisposableStackConstructor.cpp b/Userland/Libraries/LibJS/Runtime/DisposableStackConstructor.cpp
new file mode 100644
index 0000000000..d70786d5a6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/DisposableStackConstructor.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/AbstractOperations.h>
+#include <LibJS/Runtime/DisposableStack.h>
+#include <LibJS/Runtime/DisposableStackConstructor.h>
+
+namespace JS {
+
+DisposableStackConstructor::DisposableStackConstructor(Realm& realm)
+ : NativeFunction(realm.vm().names.DisposableStack.as_string(), *realm.intrinsics().function_prototype())
+{
+}
+
+void DisposableStackConstructor::initialize(Realm& realm)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(realm);
+
+ // 26.2.2.1 DisposableStack.prototype, https://tc39.es/ecma262/#sec-finalization-registry.prototype
+ define_direct_property(vm.names.prototype, realm.intrinsics().disposable_stack_prototype(), 0);
+
+ define_direct_property(vm.names.length, Value(0), Attribute::Configurable);
+}
+
+// 11.3.1.1 DisposableStack ( ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack
+ThrowCompletionOr<Value> DisposableStackConstructor::call()
+{
+ auto& vm = this->vm();
+
+ // 1. If NewTarget is undefined, throw a TypeError exception.
+ return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, vm.names.DisposableStack);
+}
+
+// 11.3.1.1 DisposableStack ( ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack
+ThrowCompletionOr<NonnullGCPtr<Object>> DisposableStackConstructor::construct(FunctionObject& new_target)
+{
+ auto& vm = this->vm();
+
+ // 2. Let disposableStack be ? OrdinaryCreateFromConstructor(NewTarget, "%DisposableStack.prototype%", « [[DisposableState]], [[DisposableResourceStack]] »).
+ // 3. Set disposableStack.[[DisposableState]] to pending.
+ // 4. Set disposableStack.[[DisposableResourceStack]] to a new empty List.
+ // 5. Return disposableStack.
+ return TRY(ordinary_create_from_constructor<DisposableStack>(vm, new_target, &Intrinsics::disposable_stack_prototype, Vector<DisposableResource> {}));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/DisposableStackConstructor.h b/Userland/Libraries/LibJS/Runtime/DisposableStackConstructor.h
new file mode 100644
index 0000000000..b512657a36
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/DisposableStackConstructor.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class DisposableStackConstructor final : public NativeFunction {
+ JS_OBJECT(DisposableStackConstructor, NativeFunction);
+
+public:
+ virtual void initialize(Realm&) override;
+ virtual ~DisposableStackConstructor() override = default;
+
+ virtual ThrowCompletionOr<Value> call() override;
+ virtual ThrowCompletionOr<NonnullGCPtr<Object>> construct(FunctionObject&) override;
+
+private:
+ explicit DisposableStackConstructor(Realm&);
+
+ virtual bool has_constructor() const override { return true; }
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/DisposableStackPrototype.cpp b/Userland/Libraries/LibJS/Runtime/DisposableStackPrototype.cpp
new file mode 100644
index 0000000000..9ba270ce15
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/DisposableStackPrototype.cpp
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/AbstractOperations.h>
+#include <LibJS/Runtime/DisposableStack.h>
+#include <LibJS/Runtime/DisposableStackConstructor.h>
+#include <LibJS/Runtime/DisposableStackPrototype.h>
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+DisposableStackPrototype::DisposableStackPrototype(Realm& realm)
+ : PrototypeObject(*realm.intrinsics().object_prototype())
+{
+}
+
+void DisposableStackPrototype::initialize(Realm& realm)
+{
+ auto& vm = this->vm();
+ Object::initialize(realm);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+
+ define_native_accessor(realm, vm.names.disposed, disposed_getter, {}, attr);
+ define_native_function(realm, vm.names.dispose, dispose, 0, attr);
+ define_native_function(realm, vm.names.use, use, 1, attr);
+ 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.move, move_, 0, attr);
+
+ // 11.3.3.7 DisposableStack.prototype [ @@dispose ] (), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype-@@dispose
+ define_direct_property(*vm.well_known_symbol_dispose(), get_without_side_effects(vm.names.dispose), attr);
+
+ // 11.3.3.8 DisposableStack.prototype [ @@toStringTag ], https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype-@@toStringTag
+ define_direct_property(*vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, vm.names.DisposableStack.as_string()), Attribute::Configurable);
+}
+
+// 11.3.3.1 get DisposableStack.prototype.disposed, https://tc39.es/proposal-explicit-resource-management/#sec-get-disposablestack.prototype.disposed
+JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::disposed_getter)
+{
+ // 1. Let disposableStack be the this value.
+ // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]).
+ auto* disposable_stack = TRY(typed_this_object(vm));
+
+ // 3. If disposableStack.[[DisposableState]] is disposed, return true.
+ if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed)
+ return Value(true);
+
+ // 4. Otherwise, return false.
+ return Value(false);
+}
+
+// 11.3.3.2 DisposableStack.prototype.dispose (), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.dispose
+JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::dispose)
+{
+ // 1. Let disposableStack be the this value.
+ // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]).
+ auto* disposable_stack = TRY(typed_this_object(vm));
+
+ // 3. If disposableStack.[[DisposableState]] is disposed, return undefined.
+ if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed)
+ return js_undefined();
+
+ // 4. Set disposableStack.[[DisposableState]] to disposed.
+ disposable_stack->set_disposed();
+
+ // 5. Return DisposeResources(disposableStack, NormalCompletion(undefined)).
+ return TRY(dispose_resources(vm, disposable_stack->disposable_resource_stack(), Completion { js_undefined() }));
+}
+
+// 11.3.3.3 DisposableStack.prototype.use( value ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.use
+JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::use)
+{
+ auto value = vm.argument(0);
+
+ // 1. Let disposableStack be the this value.
+ // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]).
+ auto* disposable_stack = TRY(typed_this_object(vm));
+
+ // 3. If disposableStack.[[DisposableState]] is disposed, throw a ReferenceError exception.
+ if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed)
+ return vm.throw_completion<ReferenceError>(ErrorType::DisposableStackAlreadyDisposed);
+
+ // 4. If value is neither null nor undefined, then
+ if (!value.is_nullish()) {
+ // a. If Type(value) is not Object, throw a TypeError exception.
+ if (!value.is_object())
+ return vm.throw_completion<TypeError>(ErrorType::NotAnObject, value.to_string_without_side_effects());
+
+ // FIXME: This should be TRY in the spec
+ // b. Let method be GetDisposeMethod(value, sync-dispose).
+ auto method = TRY(get_dispose_method(vm, value, Environment::InitializeBindingHint::SyncDispose));
+
+ // c. If method is undefined, then
+ if (!method.ptr()) {
+ // i. Throw a TypeError exception.
+ return vm.throw_completion<TypeError>(ErrorType::NoDisposeMethod, value.to_string_without_side_effects());
+ }
+ // d. Else,
+ // i. Perform ? AddDisposableResource(disposableStack, value, sync-dispose, method).
+ add_disposable_resource(vm, disposable_stack->disposable_resource_stack(), value, Environment::InitializeBindingHint::SyncDispose, method);
+ }
+
+ // 5. Return value.
+ return value;
+}
+
+// 11.3.3.4 DisposableStack.prototype.adopt( value, onDispose ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.adopt
+JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::adopt)
+{
+ auto& realm = *vm.current_realm();
+
+ auto value = vm.argument(0);
+ auto on_dispose = vm.argument(1);
+
+ // 1. Let disposableStack be the this value.
+ // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]).
+ auto* disposable_stack = TRY(typed_this_object(vm));
+
+ // 3. If disposableStack.[[DisposableState]] is disposed, throw a ReferenceError exception.
+ if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed)
+ return vm.throw_completion<ReferenceError>(ErrorType::DisposableStackAlreadyDisposed);
+
+ // 4. If IsCallable(onDispose) is false, throw a TypeError exception.
+ if (!on_dispose.is_function())
+ return vm.throw_completion<TypeError>(ErrorType::NotAFunction, on_dispose.to_string_without_side_effects());
+
+ // 5. Let F be a new built-in function object as defined in 11.3.3.4.1.
+ // 6. Set F.[[Argument]] to value.
+ // 7. Set F.[[OnDisposeCallback]] to onDispose.
+ // 11.3.3.4.1 DisposableStack Adopt Callback Functions, https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack-adopt-callback-functions
+ // A DisposableStack adopt callback function is an anonymous built-in function object that has [[Argument]] and [[OnDisposeCallback]] internal slots.
+ auto function = NativeFunction::create(
+ realm, [argument = make_handle(value), callback = make_handle(on_dispose)](VM& vm) {
+ // When a DisposableStack adopt callback function is called, the following steps are taken:
+ // 1. Let F be the active function object.
+ // 2. Assert: IsCallable(F.[[OnDisposeCallback]]) is true.
+ VERIFY(callback.value().is_function());
+
+ // 3. Return Call(F.[[OnDisposeCallback]], undefined, « F.[[Argument]] »).
+ return call(vm, callback.value(), js_undefined(), argument.value());
+ },
+ 0, "");
+
+ // 8. Perform ? AddDisposableResource(disposableStack, undefined, sync-dispose, F).
+ TRY(add_disposable_resource(vm, disposable_stack->disposable_resource_stack(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, function));
+
+ // 9. Return value.
+ return value;
+}
+
+// 11.3.3.5 DisposableStack.prototype.defer( onDispose ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.defer
+JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::defer)
+{
+ auto on_dispose = vm.argument(0);
+
+ // 1. Let disposableStack be the this value.
+ // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]).
+ auto* disposable_stack = TRY(typed_this_object(vm));
+
+ // 3. If disposableStack.[[DisposableState]] is disposed, throw a ReferenceError exception.
+ if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed)
+ return vm.throw_completion<ReferenceError>(ErrorType::DisposableStackAlreadyDisposed);
+
+ // 4. If IsCallable(onDispose) is false, throw a TypeError exception.
+ if (!on_dispose.is_function())
+ return vm.throw_completion<TypeError>(ErrorType::NotAFunction, on_dispose.to_string_without_side_effects());
+
+ // 5. Perform ? AddDisposableResource(disposableStack, undefined, sync-dispose, onDispose).
+ TRY(add_disposable_resource(vm, disposable_stack->disposable_resource_stack(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, &on_dispose.as_function()));
+
+ // 6. Return undefined.
+ return js_undefined();
+}
+
+// 11.3.3.6 DisposableStack.prototype.move(), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.move
+JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::move_)
+{
+ // 1. Let disposableStack be the this value.
+ // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]).
+ auto* disposable_stack = TRY(typed_this_object(vm));
+
+ // 3. If disposableStack.[[DisposableState]] is disposed, throw a ReferenceError exception.
+ if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed)
+ return vm.throw_completion<ReferenceError>(ErrorType::DisposableStackAlreadyDisposed);
+
+ // 4. Let newDisposableStack be ? OrdinaryCreateFromConstructor(%DisposableStack%, "%DisposableStack.prototype%", « [[DisposableState]], [[DisposableResourceStack]] »).
+ auto new_disposable_stack = TRY(ordinary_create_from_constructor<DisposableStack>(vm, *vm.current_realm()->intrinsics().disposable_stack_constructor(), &Intrinsics::disposable_stack_prototype, disposable_stack->disposable_resource_stack()));
+
+ // 5. Set newDisposableStack.[[DisposableState]] to pending.
+ // 6. Set newDisposableStack.[[DisposableResourceStack]] to disposableStack.[[DisposableResourceStack]].
+ // NOTE: Already done in the constructor
+
+ // 7. Set disposableStack.[[DisposableResourceStack]] to a new empty List.
+ disposable_stack->disposable_resource_stack().clear();
+
+ // 8. Set disposableStack.[[DisposableState]] to disposed.
+ disposable_stack->set_disposed();
+
+ // 9. Return newDisposableStack.
+ return new_disposable_stack;
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/DisposableStackPrototype.h b/Userland/Libraries/LibJS/Runtime/DisposableStackPrototype.h
new file mode 100644
index 0000000000..6b093a12aa
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/DisposableStackPrototype.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/FinalizationRegistry.h>
+#include <LibJS/Runtime/PrototypeObject.h>
+
+namespace JS {
+
+class DisposableStackPrototype final : public PrototypeObject<DisposableStackPrototype, DisposableStack> {
+ JS_PROTOTYPE_OBJECT(DisposableStackPrototype, DisposableStack, DisposableStack);
+
+public:
+ virtual void initialize(Realm&) override;
+ virtual ~DisposableStackPrototype() override = default;
+
+private:
+ explicit DisposableStackPrototype(Realm&);
+
+ JS_DECLARE_NATIVE_FUNCTION(disposed_getter);
+ JS_DECLARE_NATIVE_FUNCTION(dispose);
+ JS_DECLARE_NATIVE_FUNCTION(use);
+ JS_DECLARE_NATIVE_FUNCTION(adopt);
+ JS_DECLARE_NATIVE_FUNCTION(defer);
+ JS_DECLARE_NATIVE_FUNCTION(move_);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
index 6e2e07d2e0..3c169ccca4 100644
--- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
+++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
@@ -32,6 +32,7 @@
M(DescWriteNonWritable, "Cannot write to non-writable property '{}'") \
M(DetachedArrayBuffer, "ArrayBuffer is detached") \
M(DetachKeyMismatch, "Provided detach key {} does not match the ArrayBuffer's detach key {}") \
+ M(DisposableStackAlreadyDisposed, "DisposableStack already disposed values") \
M(DivisionByZero, "Division by zero") \
M(DynamicImportNotAllowed, "Dynamic Imports are not allowed") \
M(FinalizationRegistrySameTargetAndValue, "Target and held value must not be the same") \
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
index 73a925d3f1..42b47836f7 100644
--- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
+++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
@@ -28,6 +28,7 @@
#include <LibJS/Runtime/ConsoleObject.h>
#include <LibJS/Runtime/DataViewConstructor.h>
#include <LibJS/Runtime/DateConstructor.h>
+#include <LibJS/Runtime/DisposableStackConstructor.h>
#include <LibJS/Runtime/ErrorConstructor.h>
#include <LibJS/Runtime/FinalizationRegistryConstructor.h>
#include <LibJS/Runtime/FinalizationRegistryPrototype.h>
@@ -135,6 +136,7 @@ Object& set_default_global_bindings(Realm& realm)
global.define_intrinsic_accessor(vm.names.Boolean, attr, [](auto& realm) -> Value { return realm.intrinsics().boolean_constructor(); });
global.define_intrinsic_accessor(vm.names.DataView, attr, [](auto& realm) -> Value { return realm.intrinsics().data_view_constructor(); });
global.define_intrinsic_accessor(vm.names.Date, attr, [](auto& realm) -> Value { return realm.intrinsics().date_constructor(); });
+ global.define_intrinsic_accessor(vm.names.DisposableStack, attr, [](auto& realm) -> Value { return realm.intrinsics().disposable_stack_constructor(); });
global.define_intrinsic_accessor(vm.names.Error, attr, [](auto& realm) -> Value { return realm.intrinsics().error_constructor(); });
global.define_intrinsic_accessor(vm.names.EvalError, attr, [](auto& realm) -> Value { return realm.intrinsics().eval_error_constructor(); });
global.define_intrinsic_accessor(vm.names.FinalizationRegistry, attr, [](auto& realm) -> Value { return realm.intrinsics().finalization_registry_constructor(); });
diff --git a/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp b/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp
index 3d229eaa5e..feac0524d4 100644
--- a/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp
@@ -28,6 +28,8 @@
#include <LibJS/Runtime/DataViewPrototype.h>
#include <LibJS/Runtime/DateConstructor.h>
#include <LibJS/Runtime/DatePrototype.h>
+#include <LibJS/Runtime/DisposableStackConstructor.h>
+#include <LibJS/Runtime/DisposableStackPrototype.h>
#include <LibJS/Runtime/ErrorConstructor.h>
#include <LibJS/Runtime/ErrorPrototype.h>
#include <LibJS/Runtime/FinalizationRegistryConstructor.h>
diff --git a/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.js b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.js
new file mode 100644
index 0000000000..652ffab02f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.js
@@ -0,0 +1,18 @@
+test("constructor properties", () => {
+ expect(DisposableStack).toHaveLength(0);
+ expect(DisposableStack.name).toBe("DisposableStack");
+});
+
+describe("errors", () => {
+ test("called without new", () => {
+ expect(() => {
+ DisposableStack();
+ }).toThrowWithMessage(TypeError, "DisposableStack constructor must be called with 'new'");
+ });
+});
+
+describe("normal behavior", () => {
+ test("typeof", () => {
+ expect(typeof new DisposableStack()).toBe("object");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@dispose.js b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@dispose.js
new file mode 100644
index 0000000000..62c76f8e77
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@dispose.js
@@ -0,0 +1,19 @@
+test("length is 0", () => {
+ expect(DisposableStack.prototype[Symbol.dispose]).toHaveLength(0);
+});
+
+test("is the same as dispose", () => {
+ expect(DisposableStack.prototype[Symbol.dispose]).toBe(DisposableStack.prototype.dispose);
+});
+
+describe("used in using functionality", () => {
+ test("make the stack marked as disposed", () => {
+ let innerStack;
+ {
+ using stack = new DisposableStack();
+ innerStack = stack;
+ expect(stack.disposed).toBeFalse();
+ }
+ expect(innerStack.disposed).toBeTrue();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@toStringTag.js b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@toStringTag.js
new file mode 100644
index 0000000000..1578a6f556
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@toStringTag.js
@@ -0,0 +1,3 @@
+test("basic functionality", () => {
+ expect(DisposableStack.prototype[Symbol.toStringTag]).toBe("DisposableStack");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.adopt.js b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.adopt.js
new file mode 100644
index 0000000000..e881daea9a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.adopt.js
@@ -0,0 +1,95 @@
+test("length is 2", () => {
+ expect(DisposableStack.prototype.adopt).toHaveLength(2);
+});
+
+describe("basic functionality", () => {
+ test("adopted dispose method gets called when stack is disposed", () => {
+ const stack = new DisposableStack();
+ 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);
+ stack.dispose();
+ expect(disposedCalled).toBe(1);
+ expect(disposeArgument).toBeNull();
+ stack.dispose();
+ expect(disposedCalled).toBe(1);
+ });
+
+ test("can adopt any value", () => {
+ const stack = new DisposableStack();
+ 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);
+ });
+
+ stack.dispose();
+
+ expect(disposed).toEqual(values.reverse());
+ });
+
+ test("adopted stack is already disposed", () => {
+ const stack = new DisposableStack();
+ stack.adopt(stack, value => {
+ expect(stack).toBe(value);
+ expect(stack.disposed).toBeTrue();
+ });
+ stack.dispose();
+ });
+});
+
+describe("throws errors", () => {
+ test("if call back is not a function throws type error", () => {
+ const stack = new DisposableStack();
+ [
+ 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)", () => {
+ const stack = new DisposableStack();
+ stack.dispose();
+ expect(stack.disposed).toBeTrue();
+
+ [{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
+ expect(() => stack.adopt(value, () => {})).toThrowWithMessage(
+ ReferenceError,
+ "DisposableStack already disposed values"
+ );
+ expect(() => stack.adopt(null, value)).toThrowWithMessage(
+ ReferenceError,
+ "DisposableStack already disposed values"
+ );
+ });
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.defer.js b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.defer.js
new file mode 100644
index 0000000000..452b66c296
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.defer.js
@@ -0,0 +1,70 @@
+test("length is 1", () => {
+ expect(DisposableStack.prototype.defer).toHaveLength(1);
+});
+
+describe("basic functionality", () => {
+ test("deferred function gets called when stack is disposed", () => {
+ const stack = new DisposableStack();
+ 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);
+ stack.dispose();
+ expect(disposedCalled).toBe(1);
+ stack.dispose();
+ expect(disposedCalled).toBe(1);
+ });
+
+ test("deferred stack is already disposed", () => {
+ const stack = new DisposableStack();
+ stack.defer(() => {
+ expect(stack.disposed).toBeTrue();
+ });
+ stack.dispose();
+ });
+});
+
+describe("throws errors", () => {
+ test("if call back is not a function throws type error", () => {
+ const stack = new DisposableStack();
+ [
+ 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)", () => {
+ const stack = new DisposableStack();
+ stack.dispose();
+ expect(stack.disposed).toBeTrue();
+
+ [{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
+ expect(() => stack.defer(value)).toThrowWithMessage(
+ ReferenceError,
+ "DisposableStack already disposed values"
+ );
+ });
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.dispose.js b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.dispose.js
new file mode 100644
index 0000000000..5229caa474
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.dispose.js
@@ -0,0 +1,83 @@
+test("length is 0", () => {
+ expect(DisposableStack.prototype.dispose).toHaveLength(0);
+});
+
+describe("basic functionality", () => {
+ test("make the stack marked as disposed", () => {
+ const stack = new DisposableStack();
+ const result = stack.dispose();
+ expect(stack.disposed).toBeTrue();
+ expect(result).toBeUndefined();
+ });
+
+ test("call dispose on objects in stack when called", () => {
+ const stack = new DisposableStack();
+ let disposedCalled = false;
+ stack.use({
+ [Symbol.dispose]() {
+ disposedCalled = true;
+ },
+ });
+
+ expect(disposedCalled).toBeFalse();
+ const result = stack.dispose();
+ expect(disposedCalled).toBeTrue();
+ expect(result).toBeUndefined();
+ });
+
+ test("disposed the objects added to the stack in reverse order", () => {
+ const disposed = [];
+ const stack = new DisposableStack();
+ stack.use({
+ [Symbol.dispose]() {
+ disposed.push("a");
+ },
+ });
+ stack.use({
+ [Symbol.dispose]() {
+ disposed.push("b");
+ },
+ });
+
+ expect(disposed).toEqual([]);
+ const result = stack.dispose();
+ expect(disposed).toEqual(["b", "a"]);
+ expect(result).toBeUndefined();
+ });
+
+ test("does not dispose anything if already disposed", () => {
+ const disposed = [];
+ const stack = new DisposableStack();
+ stack.use({
+ [Symbol.dispose]() {
+ disposed.push("a");
+ },
+ });
+
+ expect(stack.disposed).toBeFalse();
+ expect(disposed).toEqual([]);
+
+ expect(stack.dispose()).toBeUndefined();
+
+ expect(stack.disposed).toBeTrue();
+ expect(disposed).toEqual(["a"]);
+
+ expect(stack.dispose()).toBeUndefined();
+
+ expect(stack.disposed).toBeTrue();
+ expect(disposed).toEqual(["a"]);
+ });
+
+ test("throws if dispose method throws", () => {
+ const stack = new DisposableStack();
+ let disposedCalled = false;
+ stack.use({
+ [Symbol.dispose]() {
+ disposedCalled = true;
+ expect().fail("fail in dispose");
+ },
+ });
+
+ expect(() => stack.dispose()).toThrowWithMessage(ExpectationError, "fail in dispose");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.disposed.js b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.disposed.js
new file mode 100644
index 0000000000..e123274915
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.disposed.js
@@ -0,0 +1,24 @@
+test("is getter without setter", () => {
+ const property = Object.getOwnPropertyDescriptor(DisposableStack.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 DisposableStack();
+ expect(Object.hasOwn(stack, "disposed")).toBeFalse();
+ });
+
+ test("starts off as false", () => {
+ const stack = new DisposableStack();
+ expect(stack.disposed).toBeFalse();
+ });
+
+ test("becomes true after being disposed", () => {
+ const stack = new DisposableStack();
+ stack.dispose();
+ expect(stack.disposed).toBeTrue();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.move.js b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.move.js
new file mode 100644
index 0000000000..9ed2a1f140
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.move.js
@@ -0,0 +1,62 @@
+test("length is 0", () => {
+ expect(DisposableStack.prototype.move).toHaveLength(0);
+});
+
+describe("basic functionality", () => {
+ test("stack is disposed after moving", () => {
+ const stack = new DisposableStack();
+
+ const newStack = stack.move();
+
+ expect(stack.disposed).toBeTrue();
+ expect(newStack.disposed).toBeFalse();
+ });
+
+ test("move does not dispose resource but only move them", () => {
+ const stack = new DisposableStack();
+ 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();
+
+ stack.dispose();
+
+ expect(disposeCalled).toBeFalse();
+ expect(stack.disposed).toBeTrue();
+ expect(newStack.disposed).toBeFalse();
+
+ newStack.dispose();
+
+ expect(disposeCalled).toBeTrue();
+ expect(stack.disposed).toBeTrue();
+ expect(newStack.disposed).toBeTrue();
+ });
+
+ test("can add stack to itself", () => {
+ const stack = new DisposableStack();
+ stack.move(stack);
+ stack.dispose();
+ });
+});
+
+describe("throws errors", () => {
+ test("move throws if stack is already disposed (over type errors)", () => {
+ const stack = new DisposableStack();
+ stack.dispose();
+ expect(stack.disposed).toBeTrue();
+
+ expect(() => stack.move()).toThrowWithMessage(
+ ReferenceError,
+ "DisposableStack already disposed values"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.use.js b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.use.js
new file mode 100644
index 0000000000..77224b130c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.use.js
@@ -0,0 +1,96 @@
+test("length is 1", () => {
+ expect(DisposableStack.prototype.use).toHaveLength(1);
+});
+
+describe("basic functionality", () => {
+ test("added objects dispose method gets when stack is disposed", () => {
+ const stack = new DisposableStack();
+ 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);
+ stack.dispose();
+ expect(disposedCalled).toBe(1);
+ stack.dispose();
+ expect(disposedCalled).toBe(1);
+ });
+
+ test("can add null and undefined", () => {
+ const stack = new DisposableStack();
+
+ expect(stack.use(null)).toBeNull();
+ expect(stack.use(undefined)).toBeUndefined();
+
+ expect(stack.disposed).toBeFalse();
+ stack.dispose();
+ expect(stack.disposed).toBeTrue();
+ });
+
+ test("can add stack to itself", () => {
+ const stack = new DisposableStack();
+ stack.use(stack);
+ stack.dispose();
+ });
+});
+
+describe("throws errors", () => {
+ test("if added value is not an object or null or undefined throws type error", () => {
+ const stack = new DisposableStack();
+ [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 DisposableStack();
+ [{}, [], { 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 DisposableStack();
+ 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)", () => {
+ const stack = new DisposableStack();
+ stack.dispose();
+ expect(stack.disposed).toBeTrue();
+
+ [{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
+ expect(() => stack.use(value)).toThrowWithMessage(
+ ReferenceError,
+ "DisposableStack already disposed values"
+ );
+ });
+ });
+});