Serenity Operating System
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

LibJS: Add DisposableStack{, Prototype, Constructor}

Since the async parts of the spec are not stage 3 at this point we don't
add AsyncDisposableStack.

authored by

davidot and committed by
Linus Groh
6255ca4a bff03841

+868
+1
.prettierignore
··· 4 4 Userland/Libraries/LibJS/Tests/modules/failing.mjs 5 5 6 6 # FIXME: Remove once prettier is updated to support using declarations. 7 + Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@dispose.js 7 8 Userland/Libraries/LibJS/Tests/modules/top-level-dispose.mjs 8 9 Userland/Libraries/LibJS/Tests/using-declaration.js 9 10 Userland/Libraries/LibJS/Tests/using-for-loops.js
+3
Userland/Libraries/LibJS/CMakeLists.txt
··· 75 75 Runtime/DateConstructor.cpp 76 76 Runtime/DatePrototype.cpp 77 77 Runtime/DeclarativeEnvironment.cpp 78 + Runtime/DisposableStack.cpp 79 + Runtime/DisposableStackConstructor.cpp 80 + Runtime/DisposableStackPrototype.cpp 78 81 Runtime/ECMAScriptFunctionObject.cpp 79 82 Runtime/Environment.cpp 80 83 Runtime/Error.cpp
+1
Userland/Libraries/LibJS/Forward.h
··· 27 27 __JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor, void) \ 28 28 __JS_ENUMERATE(DataView, data_view, DataViewPrototype, DataViewConstructor, void) \ 29 29 __JS_ENUMERATE(Date, date, DatePrototype, DateConstructor, void) \ 30 + __JS_ENUMERATE(DisposableStack, disposable_stack, DisposableStackPrototype, DisposableStackConstructor, void) \ 30 31 __JS_ENUMERATE(Error, error, ErrorPrototype, ErrorConstructor, void) \ 31 32 __JS_ENUMERATE(FinalizationRegistry, finalization_registry, FinalizationRegistryPrototype, FinalizationRegistryConstructor, void) \ 32 33 __JS_ENUMERATE(FunctionObject, function, FunctionPrototype, FunctionConstructor, void) \
+5
Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
··· 65 65 P(acos) \ 66 66 P(acosh) \ 67 67 P(add) \ 68 + P(adopt) \ 68 69 P(all) \ 69 70 P(allSettled) \ 70 71 P(anchor) \ ··· 143 144 P(debug) \ 144 145 P(decodeURI) \ 145 146 P(decodeURIComponent) \ 147 + P(defer) \ 146 148 P(defineProperties) \ 147 149 P(defineProperty) \ 148 150 P(deleteProperty) \ ··· 151 153 P(difference) \ 152 154 P(direction) \ 153 155 P(disambiguation) \ 156 + P(disposed) \ 154 157 P(done) \ 155 158 P(dotAll) \ 156 159 P(encodeURI) \ ··· 365 368 P(months) \ 366 369 P(monthsDisplay) \ 367 370 P(monthsInYear) \ 371 + P(move) \ 368 372 P(multiline) \ 369 373 P(name) \ 370 374 P(nanosecond) \ ··· 555 559 P(unshift) \ 556 560 P(until) \ 557 561 P(usage) \ 562 + P(use) \ 558 563 P(useGrouping) \ 559 564 P(value) \ 560 565 P(valueOf) \
+26
Userland/Libraries/LibJS/Runtime/DisposableStack.cpp
··· 1 + /* 2 + * Copyright (c) 2022, David Tuin <davidot@serenityos.org> 3 + * 4 + * SPDX-License-Identifier: BSD-2-Clause 5 + */ 6 + 7 + #include <LibJS/Runtime/DisposableStack.h> 8 + 9 + namespace JS { 10 + 11 + DisposableStack::DisposableStack(Vector<DisposableResource> stack, Object& prototype) 12 + : Object(ConstructWithPrototypeTag::Tag, prototype) 13 + , m_disposable_resource_stack(move(stack)) 14 + { 15 + } 16 + 17 + void DisposableStack::visit_edges(Cell::Visitor& visitor) 18 + { 19 + Base::visit_edges(visitor); 20 + for (auto& resource : m_disposable_resource_stack) { 21 + visitor.visit(resource.resource_value); 22 + visitor.visit(resource.dispose_method); 23 + } 24 + } 25 + 26 + }
+40
Userland/Libraries/LibJS/Runtime/DisposableStack.h
··· 1 + /* 2 + * Copyright (c) 2022, David Tuin <davidot@serenityos.org> 3 + * 4 + * SPDX-License-Identifier: BSD-2-Clause 5 + */ 6 + 7 + #pragma once 8 + 9 + #include <LibJS/Runtime/AbstractOperations.h> 10 + #include <LibJS/Runtime/Object.h> 11 + 12 + namespace JS { 13 + 14 + class DisposableStack final : public Object { 15 + JS_OBJECT(DisposableStack, Object); 16 + 17 + public: 18 + virtual ~DisposableStack() override = default; 19 + 20 + enum class DisposableState { 21 + Pending, 22 + Disposed 23 + }; 24 + 25 + [[nodiscard]] DisposableState disposable_state() const { return m_state; } 26 + [[nodiscard]] Vector<DisposableResource> const& disposable_resource_stack() const { return m_disposable_resource_stack; } 27 + [[nodiscard]] Vector<DisposableResource>& disposable_resource_stack() { return m_disposable_resource_stack; } 28 + 29 + void set_disposed() { m_state = DisposableState::Disposed; } 30 + 31 + private: 32 + DisposableStack(Vector<DisposableResource> stack, Object& prototype); 33 + 34 + virtual void visit_edges(Visitor& visitor) override; 35 + 36 + Vector<DisposableResource> m_disposable_resource_stack; 37 + DisposableState m_state { DisposableState::Pending }; 38 + }; 39 + 40 + }
+50
Userland/Libraries/LibJS/Runtime/DisposableStackConstructor.cpp
··· 1 + /* 2 + * Copyright (c) 2022, David Tuin <davidot@serenityos.org> 3 + * 4 + * SPDX-License-Identifier: BSD-2-Clause 5 + */ 6 + 7 + #include <LibJS/Runtime/AbstractOperations.h> 8 + #include <LibJS/Runtime/DisposableStack.h> 9 + #include <LibJS/Runtime/DisposableStackConstructor.h> 10 + 11 + namespace JS { 12 + 13 + DisposableStackConstructor::DisposableStackConstructor(Realm& realm) 14 + : NativeFunction(realm.vm().names.DisposableStack.as_string(), *realm.intrinsics().function_prototype()) 15 + { 16 + } 17 + 18 + void DisposableStackConstructor::initialize(Realm& realm) 19 + { 20 + auto& vm = this->vm(); 21 + NativeFunction::initialize(realm); 22 + 23 + // 26.2.2.1 DisposableStack.prototype, https://tc39.es/ecma262/#sec-finalization-registry.prototype 24 + define_direct_property(vm.names.prototype, realm.intrinsics().disposable_stack_prototype(), 0); 25 + 26 + define_direct_property(vm.names.length, Value(0), Attribute::Configurable); 27 + } 28 + 29 + // 11.3.1.1 DisposableStack ( ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack 30 + ThrowCompletionOr<Value> DisposableStackConstructor::call() 31 + { 32 + auto& vm = this->vm(); 33 + 34 + // 1. If NewTarget is undefined, throw a TypeError exception. 35 + return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, vm.names.DisposableStack); 36 + } 37 + 38 + // 11.3.1.1 DisposableStack ( ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack 39 + ThrowCompletionOr<NonnullGCPtr<Object>> DisposableStackConstructor::construct(FunctionObject& new_target) 40 + { 41 + auto& vm = this->vm(); 42 + 43 + // 2. Let disposableStack be ? OrdinaryCreateFromConstructor(NewTarget, "%DisposableStack.prototype%", « [[DisposableState]], [[DisposableResourceStack]] »). 44 + // 3. Set disposableStack.[[DisposableState]] to pending. 45 + // 4. Set disposableStack.[[DisposableResourceStack]] to a new empty List. 46 + // 5. Return disposableStack. 47 + return TRY(ordinary_create_from_constructor<DisposableStack>(vm, new_target, &Intrinsics::disposable_stack_prototype, Vector<DisposableResource> {})); 48 + } 49 + 50 + }
+29
Userland/Libraries/LibJS/Runtime/DisposableStackConstructor.h
··· 1 + /* 2 + * Copyright (c) 2022, David Tuin <davidot@serenityos.org> 3 + * 4 + * SPDX-License-Identifier: BSD-2-Clause 5 + */ 6 + 7 + #pragma once 8 + 9 + #include <LibJS/Runtime/NativeFunction.h> 10 + 11 + namespace JS { 12 + 13 + class DisposableStackConstructor final : public NativeFunction { 14 + JS_OBJECT(DisposableStackConstructor, NativeFunction); 15 + 16 + public: 17 + virtual void initialize(Realm&) override; 18 + virtual ~DisposableStackConstructor() override = default; 19 + 20 + virtual ThrowCompletionOr<Value> call() override; 21 + virtual ThrowCompletionOr<NonnullGCPtr<Object>> construct(FunctionObject&) override; 22 + 23 + private: 24 + explicit DisposableStackConstructor(Realm&); 25 + 26 + virtual bool has_constructor() const override { return true; } 27 + }; 28 + 29 + }
+206
Userland/Libraries/LibJS/Runtime/DisposableStackPrototype.cpp
··· 1 + /* 2 + * Copyright (c) 2022, David Tuin <davidot@serenityos.org> 3 + * 4 + * SPDX-License-Identifier: BSD-2-Clause 5 + */ 6 + 7 + #include <LibJS/Runtime/AbstractOperations.h> 8 + #include <LibJS/Runtime/DisposableStack.h> 9 + #include <LibJS/Runtime/DisposableStackConstructor.h> 10 + #include <LibJS/Runtime/DisposableStackPrototype.h> 11 + #include <LibJS/Runtime/NativeFunction.h> 12 + 13 + namespace JS { 14 + 15 + DisposableStackPrototype::DisposableStackPrototype(Realm& realm) 16 + : PrototypeObject(*realm.intrinsics().object_prototype()) 17 + { 18 + } 19 + 20 + void DisposableStackPrototype::initialize(Realm& realm) 21 + { 22 + auto& vm = this->vm(); 23 + Object::initialize(realm); 24 + u8 attr = Attribute::Writable | Attribute::Configurable; 25 + 26 + define_native_accessor(realm, vm.names.disposed, disposed_getter, {}, attr); 27 + define_native_function(realm, vm.names.dispose, dispose, 0, attr); 28 + define_native_function(realm, vm.names.use, use, 1, attr); 29 + define_native_function(realm, vm.names.adopt, adopt, 2, attr); 30 + define_native_function(realm, vm.names.defer, defer, 1, attr); 31 + define_native_function(realm, vm.names.move, move_, 0, attr); 32 + 33 + // 11.3.3.7 DisposableStack.prototype [ @@dispose ] (), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype-@@dispose 34 + define_direct_property(*vm.well_known_symbol_dispose(), get_without_side_effects(vm.names.dispose), attr); 35 + 36 + // 11.3.3.8 DisposableStack.prototype [ @@toStringTag ], https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype-@@toStringTag 37 + define_direct_property(*vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, vm.names.DisposableStack.as_string()), Attribute::Configurable); 38 + } 39 + 40 + // 11.3.3.1 get DisposableStack.prototype.disposed, https://tc39.es/proposal-explicit-resource-management/#sec-get-disposablestack.prototype.disposed 41 + JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::disposed_getter) 42 + { 43 + // 1. Let disposableStack be the this value. 44 + // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). 45 + auto* disposable_stack = TRY(typed_this_object(vm)); 46 + 47 + // 3. If disposableStack.[[DisposableState]] is disposed, return true. 48 + if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) 49 + return Value(true); 50 + 51 + // 4. Otherwise, return false. 52 + return Value(false); 53 + } 54 + 55 + // 11.3.3.2 DisposableStack.prototype.dispose (), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.dispose 56 + JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::dispose) 57 + { 58 + // 1. Let disposableStack be the this value. 59 + // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). 60 + auto* disposable_stack = TRY(typed_this_object(vm)); 61 + 62 + // 3. If disposableStack.[[DisposableState]] is disposed, return undefined. 63 + if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) 64 + return js_undefined(); 65 + 66 + // 4. Set disposableStack.[[DisposableState]] to disposed. 67 + disposable_stack->set_disposed(); 68 + 69 + // 5. Return DisposeResources(disposableStack, NormalCompletion(undefined)). 70 + return TRY(dispose_resources(vm, disposable_stack->disposable_resource_stack(), Completion { js_undefined() })); 71 + } 72 + 73 + // 11.3.3.3 DisposableStack.prototype.use( value ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.use 74 + JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::use) 75 + { 76 + auto value = vm.argument(0); 77 + 78 + // 1. Let disposableStack be the this value. 79 + // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). 80 + auto* disposable_stack = TRY(typed_this_object(vm)); 81 + 82 + // 3. If disposableStack.[[DisposableState]] is disposed, throw a ReferenceError exception. 83 + if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) 84 + return vm.throw_completion<ReferenceError>(ErrorType::DisposableStackAlreadyDisposed); 85 + 86 + // 4. If value is neither null nor undefined, then 87 + if (!value.is_nullish()) { 88 + // a. If Type(value) is not Object, throw a TypeError exception. 89 + if (!value.is_object()) 90 + return vm.throw_completion<TypeError>(ErrorType::NotAnObject, value.to_string_without_side_effects()); 91 + 92 + // FIXME: This should be TRY in the spec 93 + // b. Let method be GetDisposeMethod(value, sync-dispose). 94 + auto method = TRY(get_dispose_method(vm, value, Environment::InitializeBindingHint::SyncDispose)); 95 + 96 + // c. If method is undefined, then 97 + if (!method.ptr()) { 98 + // i. Throw a TypeError exception. 99 + return vm.throw_completion<TypeError>(ErrorType::NoDisposeMethod, value.to_string_without_side_effects()); 100 + } 101 + // d. Else, 102 + // i. Perform ? AddDisposableResource(disposableStack, value, sync-dispose, method). 103 + add_disposable_resource(vm, disposable_stack->disposable_resource_stack(), value, Environment::InitializeBindingHint::SyncDispose, method); 104 + } 105 + 106 + // 5. Return value. 107 + return value; 108 + } 109 + 110 + // 11.3.3.4 DisposableStack.prototype.adopt( value, onDispose ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.adopt 111 + JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::adopt) 112 + { 113 + auto& realm = *vm.current_realm(); 114 + 115 + auto value = vm.argument(0); 116 + auto on_dispose = vm.argument(1); 117 + 118 + // 1. Let disposableStack be the this value. 119 + // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). 120 + auto* disposable_stack = TRY(typed_this_object(vm)); 121 + 122 + // 3. If disposableStack.[[DisposableState]] is disposed, throw a ReferenceError exception. 123 + if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) 124 + return vm.throw_completion<ReferenceError>(ErrorType::DisposableStackAlreadyDisposed); 125 + 126 + // 4. If IsCallable(onDispose) is false, throw a TypeError exception. 127 + if (!on_dispose.is_function()) 128 + return vm.throw_completion<TypeError>(ErrorType::NotAFunction, on_dispose.to_string_without_side_effects()); 129 + 130 + // 5. Let F be a new built-in function object as defined in 11.3.3.4.1. 131 + // 6. Set F.[[Argument]] to value. 132 + // 7. Set F.[[OnDisposeCallback]] to onDispose. 133 + // 11.3.3.4.1 DisposableStack Adopt Callback Functions, https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack-adopt-callback-functions 134 + // A DisposableStack adopt callback function is an anonymous built-in function object that has [[Argument]] and [[OnDisposeCallback]] internal slots. 135 + auto function = NativeFunction::create( 136 + realm, [argument = make_handle(value), callback = make_handle(on_dispose)](VM& vm) { 137 + // When a DisposableStack adopt callback function is called, the following steps are taken: 138 + // 1. Let F be the active function object. 139 + // 2. Assert: IsCallable(F.[[OnDisposeCallback]]) is true. 140 + VERIFY(callback.value().is_function()); 141 + 142 + // 3. Return Call(F.[[OnDisposeCallback]], undefined, « F.[[Argument]] »). 143 + return call(vm, callback.value(), js_undefined(), argument.value()); 144 + }, 145 + 0, ""); 146 + 147 + // 8. Perform ? AddDisposableResource(disposableStack, undefined, sync-dispose, F). 148 + TRY(add_disposable_resource(vm, disposable_stack->disposable_resource_stack(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, function)); 149 + 150 + // 9. Return value. 151 + return value; 152 + } 153 + 154 + // 11.3.3.5 DisposableStack.prototype.defer( onDispose ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.defer 155 + JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::defer) 156 + { 157 + auto on_dispose = vm.argument(0); 158 + 159 + // 1. Let disposableStack be the this value. 160 + // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). 161 + auto* disposable_stack = TRY(typed_this_object(vm)); 162 + 163 + // 3. If disposableStack.[[DisposableState]] is disposed, throw a ReferenceError exception. 164 + if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) 165 + return vm.throw_completion<ReferenceError>(ErrorType::DisposableStackAlreadyDisposed); 166 + 167 + // 4. If IsCallable(onDispose) is false, throw a TypeError exception. 168 + if (!on_dispose.is_function()) 169 + return vm.throw_completion<TypeError>(ErrorType::NotAFunction, on_dispose.to_string_without_side_effects()); 170 + 171 + // 5. Perform ? AddDisposableResource(disposableStack, undefined, sync-dispose, onDispose). 172 + TRY(add_disposable_resource(vm, disposable_stack->disposable_resource_stack(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, &on_dispose.as_function())); 173 + 174 + // 6. Return undefined. 175 + return js_undefined(); 176 + } 177 + 178 + // 11.3.3.6 DisposableStack.prototype.move(), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.move 179 + JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::move_) 180 + { 181 + // 1. Let disposableStack be the this value. 182 + // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). 183 + auto* disposable_stack = TRY(typed_this_object(vm)); 184 + 185 + // 3. If disposableStack.[[DisposableState]] is disposed, throw a ReferenceError exception. 186 + if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) 187 + return vm.throw_completion<ReferenceError>(ErrorType::DisposableStackAlreadyDisposed); 188 + 189 + // 4. Let newDisposableStack be ? OrdinaryCreateFromConstructor(%DisposableStack%, "%DisposableStack.prototype%", « [[DisposableState]], [[DisposableResourceStack]] »). 190 + 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())); 191 + 192 + // 5. Set newDisposableStack.[[DisposableState]] to pending. 193 + // 6. Set newDisposableStack.[[DisposableResourceStack]] to disposableStack.[[DisposableResourceStack]]. 194 + // NOTE: Already done in the constructor 195 + 196 + // 7. Set disposableStack.[[DisposableResourceStack]] to a new empty List. 197 + disposable_stack->disposable_resource_stack().clear(); 198 + 199 + // 8. Set disposableStack.[[DisposableState]] to disposed. 200 + disposable_stack->set_disposed(); 201 + 202 + // 9. Return newDisposableStack. 203 + return new_disposable_stack; 204 + } 205 + 206 + }
+32
Userland/Libraries/LibJS/Runtime/DisposableStackPrototype.h
··· 1 + /* 2 + * Copyright (c) 2022, David Tuin <davidot@serenityos.org> 3 + * 4 + * SPDX-License-Identifier: BSD-2-Clause 5 + */ 6 + 7 + #pragma once 8 + 9 + #include <LibJS/Runtime/FinalizationRegistry.h> 10 + #include <LibJS/Runtime/PrototypeObject.h> 11 + 12 + namespace JS { 13 + 14 + class DisposableStackPrototype final : public PrototypeObject<DisposableStackPrototype, DisposableStack> { 15 + JS_PROTOTYPE_OBJECT(DisposableStackPrototype, DisposableStack, DisposableStack); 16 + 17 + public: 18 + virtual void initialize(Realm&) override; 19 + virtual ~DisposableStackPrototype() override = default; 20 + 21 + private: 22 + explicit DisposableStackPrototype(Realm&); 23 + 24 + JS_DECLARE_NATIVE_FUNCTION(disposed_getter); 25 + JS_DECLARE_NATIVE_FUNCTION(dispose); 26 + JS_DECLARE_NATIVE_FUNCTION(use); 27 + JS_DECLARE_NATIVE_FUNCTION(adopt); 28 + JS_DECLARE_NATIVE_FUNCTION(defer); 29 + JS_DECLARE_NATIVE_FUNCTION(move_); 30 + }; 31 + 32 + }
+1
Userland/Libraries/LibJS/Runtime/ErrorTypes.h
··· 32 32 M(DescWriteNonWritable, "Cannot write to non-writable property '{}'") \ 33 33 M(DetachedArrayBuffer, "ArrayBuffer is detached") \ 34 34 M(DetachKeyMismatch, "Provided detach key {} does not match the ArrayBuffer's detach key {}") \ 35 + M(DisposableStackAlreadyDisposed, "DisposableStack already disposed values") \ 35 36 M(DivisionByZero, "Division by zero") \ 36 37 M(DynamicImportNotAllowed, "Dynamic Imports are not allowed") \ 37 38 M(FinalizationRegistrySameTargetAndValue, "Target and held value must not be the same") \
+2
Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
··· 28 28 #include <LibJS/Runtime/ConsoleObject.h> 29 29 #include <LibJS/Runtime/DataViewConstructor.h> 30 30 #include <LibJS/Runtime/DateConstructor.h> 31 + #include <LibJS/Runtime/DisposableStackConstructor.h> 31 32 #include <LibJS/Runtime/ErrorConstructor.h> 32 33 #include <LibJS/Runtime/FinalizationRegistryConstructor.h> 33 34 #include <LibJS/Runtime/FinalizationRegistryPrototype.h> ··· 135 136 global.define_intrinsic_accessor(vm.names.Boolean, attr, [](auto& realm) -> Value { return realm.intrinsics().boolean_constructor(); }); 136 137 global.define_intrinsic_accessor(vm.names.DataView, attr, [](auto& realm) -> Value { return realm.intrinsics().data_view_constructor(); }); 137 138 global.define_intrinsic_accessor(vm.names.Date, attr, [](auto& realm) -> Value { return realm.intrinsics().date_constructor(); }); 139 + global.define_intrinsic_accessor(vm.names.DisposableStack, attr, [](auto& realm) -> Value { return realm.intrinsics().disposable_stack_constructor(); }); 138 140 global.define_intrinsic_accessor(vm.names.Error, attr, [](auto& realm) -> Value { return realm.intrinsics().error_constructor(); }); 139 141 global.define_intrinsic_accessor(vm.names.EvalError, attr, [](auto& realm) -> Value { return realm.intrinsics().eval_error_constructor(); }); 140 142 global.define_intrinsic_accessor(vm.names.FinalizationRegistry, attr, [](auto& realm) -> Value { return realm.intrinsics().finalization_registry_constructor(); });
+2
Userland/Libraries/LibJS/Runtime/Intrinsics.cpp
··· 28 28 #include <LibJS/Runtime/DataViewPrototype.h> 29 29 #include <LibJS/Runtime/DateConstructor.h> 30 30 #include <LibJS/Runtime/DatePrototype.h> 31 + #include <LibJS/Runtime/DisposableStackConstructor.h> 32 + #include <LibJS/Runtime/DisposableStackPrototype.h> 31 33 #include <LibJS/Runtime/ErrorConstructor.h> 32 34 #include <LibJS/Runtime/ErrorPrototype.h> 33 35 #include <LibJS/Runtime/FinalizationRegistryConstructor.h>
+18
Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.js
··· 1 + test("constructor properties", () => { 2 + expect(DisposableStack).toHaveLength(0); 3 + expect(DisposableStack.name).toBe("DisposableStack"); 4 + }); 5 + 6 + describe("errors", () => { 7 + test("called without new", () => { 8 + expect(() => { 9 + DisposableStack(); 10 + }).toThrowWithMessage(TypeError, "DisposableStack constructor must be called with 'new'"); 11 + }); 12 + }); 13 + 14 + describe("normal behavior", () => { 15 + test("typeof", () => { 16 + expect(typeof new DisposableStack()).toBe("object"); 17 + }); 18 + });
+19
Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@dispose.js
··· 1 + test("length is 0", () => { 2 + expect(DisposableStack.prototype[Symbol.dispose]).toHaveLength(0); 3 + }); 4 + 5 + test("is the same as dispose", () => { 6 + expect(DisposableStack.prototype[Symbol.dispose]).toBe(DisposableStack.prototype.dispose); 7 + }); 8 + 9 + describe("used in using functionality", () => { 10 + test("make the stack marked as disposed", () => { 11 + let innerStack; 12 + { 13 + using stack = new DisposableStack(); 14 + innerStack = stack; 15 + expect(stack.disposed).toBeFalse(); 16 + } 17 + expect(innerStack.disposed).toBeTrue(); 18 + }); 19 + });
+3
Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.@@toStringTag.js
··· 1 + test("basic functionality", () => { 2 + expect(DisposableStack.prototype[Symbol.toStringTag]).toBe("DisposableStack"); 3 + });
+95
Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.adopt.js
··· 1 + test("length is 2", () => { 2 + expect(DisposableStack.prototype.adopt).toHaveLength(2); 3 + }); 4 + 5 + describe("basic functionality", () => { 6 + test("adopted dispose method gets called when stack is disposed", () => { 7 + const stack = new DisposableStack(); 8 + let disposedCalled = 0; 9 + let disposeArgument = undefined; 10 + expect(disposedCalled).toBe(0); 11 + const result = stack.adopt(null, arg => { 12 + disposeArgument = arg; 13 + ++disposedCalled; 14 + }); 15 + expect(result).toBeNull(); 16 + 17 + expect(disposedCalled).toBe(0); 18 + stack.dispose(); 19 + expect(disposedCalled).toBe(1); 20 + expect(disposeArgument).toBeNull(); 21 + stack.dispose(); 22 + expect(disposedCalled).toBe(1); 23 + }); 24 + 25 + test("can adopt any value", () => { 26 + const stack = new DisposableStack(); 27 + const disposed = []; 28 + function dispose(value) { 29 + disposed.push(value); 30 + } 31 + 32 + const values = [null, undefined, 1, "a", Symbol.dispose, () => {}, new WeakMap(), [], {}]; 33 + 34 + values.forEach(value => { 35 + stack.adopt(value, dispose); 36 + }); 37 + 38 + stack.dispose(); 39 + 40 + expect(disposed).toEqual(values.reverse()); 41 + }); 42 + 43 + test("adopted stack is already disposed", () => { 44 + const stack = new DisposableStack(); 45 + stack.adopt(stack, value => { 46 + expect(stack).toBe(value); 47 + expect(stack.disposed).toBeTrue(); 48 + }); 49 + stack.dispose(); 50 + }); 51 + }); 52 + 53 + describe("throws errors", () => { 54 + test("if call back is not a function throws type error", () => { 55 + const stack = new DisposableStack(); 56 + [ 57 + 1, 58 + 1n, 59 + "a", 60 + Symbol.dispose, 61 + NaN, 62 + 0, 63 + {}, 64 + [], 65 + { f() {} }, 66 + { [Symbol.dispose]() {} }, 67 + { 68 + get [Symbol.dispose]() { 69 + return () => {}; 70 + }, 71 + }, 72 + ].forEach(value => { 73 + expect(() => stack.adopt(null, value)).toThrowWithMessage(TypeError, "not a function"); 74 + }); 75 + 76 + expect(stack.disposed).toBeFalse(); 77 + }); 78 + 79 + test("adopt throws if stack is already disposed (over type errors)", () => { 80 + const stack = new DisposableStack(); 81 + stack.dispose(); 82 + expect(stack.disposed).toBeTrue(); 83 + 84 + [{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => { 85 + expect(() => stack.adopt(value, () => {})).toThrowWithMessage( 86 + ReferenceError, 87 + "DisposableStack already disposed values" 88 + ); 89 + expect(() => stack.adopt(null, value)).toThrowWithMessage( 90 + ReferenceError, 91 + "DisposableStack already disposed values" 92 + ); 93 + }); 94 + }); 95 + });
+70
Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.defer.js
··· 1 + test("length is 1", () => { 2 + expect(DisposableStack.prototype.defer).toHaveLength(1); 3 + }); 4 + 5 + describe("basic functionality", () => { 6 + test("deferred function gets called when stack is disposed", () => { 7 + const stack = new DisposableStack(); 8 + let disposedCalled = 0; 9 + expect(disposedCalled).toBe(0); 10 + const result = stack.defer((...args) => { 11 + expect(args.length).toBe(0); 12 + ++disposedCalled; 13 + }); 14 + expect(result).toBeUndefined(); 15 + 16 + expect(disposedCalled).toBe(0); 17 + stack.dispose(); 18 + expect(disposedCalled).toBe(1); 19 + stack.dispose(); 20 + expect(disposedCalled).toBe(1); 21 + }); 22 + 23 + test("deferred stack is already disposed", () => { 24 + const stack = new DisposableStack(); 25 + stack.defer(() => { 26 + expect(stack.disposed).toBeTrue(); 27 + }); 28 + stack.dispose(); 29 + }); 30 + }); 31 + 32 + describe("throws errors", () => { 33 + test("if call back is not a function throws type error", () => { 34 + const stack = new DisposableStack(); 35 + [ 36 + 1, 37 + 1n, 38 + "a", 39 + Symbol.dispose, 40 + NaN, 41 + 0, 42 + {}, 43 + [], 44 + { f() {} }, 45 + { [Symbol.dispose]() {} }, 46 + { 47 + get [Symbol.dispose]() { 48 + return () => {}; 49 + }, 50 + }, 51 + ].forEach(value => { 52 + expect(() => stack.defer(value)).toThrowWithMessage(TypeError, "not a function"); 53 + }); 54 + 55 + expect(stack.disposed).toBeFalse(); 56 + }); 57 + 58 + test("defer throws if stack is already disposed (over type errors)", () => { 59 + const stack = new DisposableStack(); 60 + stack.dispose(); 61 + expect(stack.disposed).toBeTrue(); 62 + 63 + [{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => { 64 + expect(() => stack.defer(value)).toThrowWithMessage( 65 + ReferenceError, 66 + "DisposableStack already disposed values" 67 + ); 68 + }); 69 + }); 70 + });
+83
Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.dispose.js
··· 1 + test("length is 0", () => { 2 + expect(DisposableStack.prototype.dispose).toHaveLength(0); 3 + }); 4 + 5 + describe("basic functionality", () => { 6 + test("make the stack marked as disposed", () => { 7 + const stack = new DisposableStack(); 8 + const result = stack.dispose(); 9 + expect(stack.disposed).toBeTrue(); 10 + expect(result).toBeUndefined(); 11 + }); 12 + 13 + test("call dispose on objects in stack when called", () => { 14 + const stack = new DisposableStack(); 15 + let disposedCalled = false; 16 + stack.use({ 17 + [Symbol.dispose]() { 18 + disposedCalled = true; 19 + }, 20 + }); 21 + 22 + expect(disposedCalled).toBeFalse(); 23 + const result = stack.dispose(); 24 + expect(disposedCalled).toBeTrue(); 25 + expect(result).toBeUndefined(); 26 + }); 27 + 28 + test("disposed the objects added to the stack in reverse order", () => { 29 + const disposed = []; 30 + const stack = new DisposableStack(); 31 + stack.use({ 32 + [Symbol.dispose]() { 33 + disposed.push("a"); 34 + }, 35 + }); 36 + stack.use({ 37 + [Symbol.dispose]() { 38 + disposed.push("b"); 39 + }, 40 + }); 41 + 42 + expect(disposed).toEqual([]); 43 + const result = stack.dispose(); 44 + expect(disposed).toEqual(["b", "a"]); 45 + expect(result).toBeUndefined(); 46 + }); 47 + 48 + test("does not dispose anything if already disposed", () => { 49 + const disposed = []; 50 + const stack = new DisposableStack(); 51 + stack.use({ 52 + [Symbol.dispose]() { 53 + disposed.push("a"); 54 + }, 55 + }); 56 + 57 + expect(stack.disposed).toBeFalse(); 58 + expect(disposed).toEqual([]); 59 + 60 + expect(stack.dispose()).toBeUndefined(); 61 + 62 + expect(stack.disposed).toBeTrue(); 63 + expect(disposed).toEqual(["a"]); 64 + 65 + expect(stack.dispose()).toBeUndefined(); 66 + 67 + expect(stack.disposed).toBeTrue(); 68 + expect(disposed).toEqual(["a"]); 69 + }); 70 + 71 + test("throws if dispose method throws", () => { 72 + const stack = new DisposableStack(); 73 + let disposedCalled = false; 74 + stack.use({ 75 + [Symbol.dispose]() { 76 + disposedCalled = true; 77 + expect().fail("fail in dispose"); 78 + }, 79 + }); 80 + 81 + expect(() => stack.dispose()).toThrowWithMessage(ExpectationError, "fail in dispose"); 82 + }); 83 + });
+24
Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.disposed.js
··· 1 + test("is getter without setter", () => { 2 + const property = Object.getOwnPropertyDescriptor(DisposableStack.prototype, "disposed"); 3 + expect(property.get).not.toBeUndefined(); 4 + expect(property.set).toBeUndefined(); 5 + expect(property.value).toBeUndefined(); 6 + }); 7 + 8 + describe("basic functionality", () => { 9 + test("is not a property on the object itself", () => { 10 + const stack = new DisposableStack(); 11 + expect(Object.hasOwn(stack, "disposed")).toBeFalse(); 12 + }); 13 + 14 + test("starts off as false", () => { 15 + const stack = new DisposableStack(); 16 + expect(stack.disposed).toBeFalse(); 17 + }); 18 + 19 + test("becomes true after being disposed", () => { 20 + const stack = new DisposableStack(); 21 + stack.dispose(); 22 + expect(stack.disposed).toBeTrue(); 23 + }); 24 + });
+62
Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.move.js
··· 1 + test("length is 0", () => { 2 + expect(DisposableStack.prototype.move).toHaveLength(0); 3 + }); 4 + 5 + describe("basic functionality", () => { 6 + test("stack is disposed after moving", () => { 7 + const stack = new DisposableStack(); 8 + 9 + const newStack = stack.move(); 10 + 11 + expect(stack.disposed).toBeTrue(); 12 + expect(newStack.disposed).toBeFalse(); 13 + }); 14 + 15 + test("move does not dispose resource but only move them", () => { 16 + const stack = new DisposableStack(); 17 + let disposeCalled = false; 18 + stack.defer(() => { 19 + disposeCalled = true; 20 + }); 21 + 22 + expect(disposeCalled).toBeFalse(); 23 + expect(stack.disposed).toBeFalse(); 24 + 25 + const newStack = stack.move(); 26 + 27 + expect(disposeCalled).toBeFalse(); 28 + expect(stack.disposed).toBeTrue(); 29 + expect(newStack.disposed).toBeFalse(); 30 + 31 + stack.dispose(); 32 + 33 + expect(disposeCalled).toBeFalse(); 34 + expect(stack.disposed).toBeTrue(); 35 + expect(newStack.disposed).toBeFalse(); 36 + 37 + newStack.dispose(); 38 + 39 + expect(disposeCalled).toBeTrue(); 40 + expect(stack.disposed).toBeTrue(); 41 + expect(newStack.disposed).toBeTrue(); 42 + }); 43 + 44 + test("can add stack to itself", () => { 45 + const stack = new DisposableStack(); 46 + stack.move(stack); 47 + stack.dispose(); 48 + }); 49 + }); 50 + 51 + describe("throws errors", () => { 52 + test("move throws if stack is already disposed (over type errors)", () => { 53 + const stack = new DisposableStack(); 54 + stack.dispose(); 55 + expect(stack.disposed).toBeTrue(); 56 + 57 + expect(() => stack.move()).toThrowWithMessage( 58 + ReferenceError, 59 + "DisposableStack already disposed values" 60 + ); 61 + }); 62 + });
+96
Userland/Libraries/LibJS/Tests/builtins/DisposableStack/DisposableStack.prototype.use.js
··· 1 + test("length is 1", () => { 2 + expect(DisposableStack.prototype.use).toHaveLength(1); 3 + }); 4 + 5 + describe("basic functionality", () => { 6 + test("added objects dispose method gets when stack is disposed", () => { 7 + const stack = new DisposableStack(); 8 + let disposedCalled = 0; 9 + const obj = { 10 + [Symbol.dispose]() { 11 + ++disposedCalled; 12 + }, 13 + }; 14 + expect(disposedCalled).toBe(0); 15 + const result = stack.use(obj); 16 + expect(result).toBe(obj); 17 + 18 + expect(disposedCalled).toBe(0); 19 + stack.dispose(); 20 + expect(disposedCalled).toBe(1); 21 + stack.dispose(); 22 + expect(disposedCalled).toBe(1); 23 + }); 24 + 25 + test("can add null and undefined", () => { 26 + const stack = new DisposableStack(); 27 + 28 + expect(stack.use(null)).toBeNull(); 29 + expect(stack.use(undefined)).toBeUndefined(); 30 + 31 + expect(stack.disposed).toBeFalse(); 32 + stack.dispose(); 33 + expect(stack.disposed).toBeTrue(); 34 + }); 35 + 36 + test("can add stack to itself", () => { 37 + const stack = new DisposableStack(); 38 + stack.use(stack); 39 + stack.dispose(); 40 + }); 41 + }); 42 + 43 + describe("throws errors", () => { 44 + test("if added value is not an object or null or undefined throws type error", () => { 45 + const stack = new DisposableStack(); 46 + [1, 1n, "a", Symbol.dispose, NaN, 0].forEach(value => { 47 + expect(() => stack.use(value)).toThrowWithMessage(TypeError, "not an object"); 48 + }); 49 + 50 + expect(stack.disposed).toBeFalse(); 51 + }); 52 + 53 + test("if added object does not have a dispose method throws type error", () => { 54 + const stack = new DisposableStack(); 55 + [{}, [], { f() {} }].forEach(value => { 56 + expect(() => stack.use(value)).toThrowWithMessage( 57 + TypeError, 58 + "does not have dispose method" 59 + ); 60 + }); 61 + 62 + expect(stack.disposed).toBeFalse(); 63 + }); 64 + 65 + test("if added object has non function dispose method it throws type error", () => { 66 + const stack = new DisposableStack(); 67 + let calledGetter = false; 68 + [ 69 + { [Symbol.dispose]: 1 }, 70 + { 71 + get [Symbol.dispose]() { 72 + calledGetter = true; 73 + return 1; 74 + }, 75 + }, 76 + ].forEach(value => { 77 + expect(() => stack.use(value)).toThrowWithMessage(TypeError, "is not a function"); 78 + }); 79 + 80 + expect(stack.disposed).toBeFalse(); 81 + expect(calledGetter).toBeTrue(); 82 + }); 83 + 84 + test("use throws if stack is already disposed (over type errors)", () => { 85 + const stack = new DisposableStack(); 86 + stack.dispose(); 87 + expect(stack.disposed).toBeTrue(); 88 + 89 + [{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => { 90 + expect(() => stack.use(value)).toThrowWithMessage( 91 + ReferenceError, 92 + "DisposableStack already disposed values" 93 + ); 94 + }); 95 + }); 96 + });