diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp index ddf0a53da9..e1b5f5d2d0 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp @@ -48,6 +48,16 @@ ArrayBuffer::~ArrayBuffer() { } +// 1.1.5 IsResizableArrayBuffer ( arrayBuffer ), https://tc39.es/proposal-resizablearraybuffer/#sec-isresizablearraybuffer +bool ArrayBuffer::is_resizable_array_buffer() const +{ + // 1. Assert: Type(arrayBuffer) is Object and arrayBuffer has an [[ArrayBufferData]] internal slot. + + // 2. If buffer has an [[ArrayBufferMaxByteLength]] internal slot, return true. + // 3. Return false. + return m_max_byte_length.has_value(); +} + void ArrayBuffer::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h index 3ef6c8df53..bca09128b8 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h +++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h @@ -34,6 +34,7 @@ public: virtual ~ArrayBuffer() override; size_t byte_length() const { return buffer_impl().size(); } + size_t max_byte_length() const { return m_max_byte_length.value(); } // Will VERIFY() that it has value ByteBuffer& buffer() { return buffer_impl(); } const ByteBuffer& buffer() const { return buffer_impl(); } @@ -47,6 +48,8 @@ public: void detach_buffer() { m_buffer = Empty {}; } bool is_detached() const { return m_buffer.has(); } + bool is_resizable_array_buffer() const; + enum Order { SeqCst, Unordered diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp index 768272f4d8..f9fda290d9 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2020, Linus Groh * Copyright (c) 2021-2022, Jamie Mansfield * Copyright (c) 2021, Idan Horowitz + * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ @@ -26,6 +27,8 @@ void ArrayBufferPrototype::initialize(GlobalObject& global_object) u8 attr = Attribute::Writable | Attribute::Configurable; define_native_function(vm.names.slice, slice, 2, attr); define_native_accessor(vm.names.byteLength, byte_length_getter, {}, Attribute::Configurable); + define_native_accessor(vm.names.maxByteLength, max_byte_length_getter, {}, Attribute::Configurable); + define_native_accessor(vm.names.resizable, resizable_getter, {}, Attribute::Configurable); // 25.1.5.4 ArrayBuffer.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-arraybuffer.prototype-@@tostringtag define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, vm.names.ArrayBuffer.as_string()), Attribute::Configurable); @@ -145,4 +148,44 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::byte_length_getter) return Value(length); } +// 1.3.2 get ArrayBuffer.prototype.maxByteLength, https://tc39.es/proposal-resizablearraybuffer/#sec-get-arraybuffer.prototype.maxbytelength +JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::max_byte_length_getter) +{ + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + auto* array_buffer_object = TRY(typed_this_value(global_object)); + + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + // FIXME: Check for shared buffer + + // 4. If IsDetachedBuffer(O) is true, return +0𝔽. + if (array_buffer_object->is_detached()) + return Value(0); + + // 5. If IsResizableArrayBuffer(O) is true, then + if (array_buffer_object->is_resizable_array_buffer()) { + // a. Let length be O.[[ArrayBufferMaxByteLength]]. + return array_buffer_object->max_byte_length(); + } + + // 6. Else + // a. Let length be O.[[ArrayBufferByteLength]]. + // 7. Return 𝔽(length). + return array_buffer_object->byte_length(); +} + +// 1.3.3 get ArrayBuffer.prototype.resizable, https://tc39.es/proposal-resizablearraybuffer/#sec-get-arraybuffer.prototype.resizable +JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::resizable_getter) +{ + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + auto* array_buffer_object = TRY(typed_this_value(global_object)); + + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + // FIXME: Check for shared buffer + + // 4. Return IsResizableArrayBuffer(O). + return array_buffer_object->is_resizable_array_buffer(); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.h b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.h index 7dead0a111..eb50c333ab 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.h @@ -22,6 +22,8 @@ public: private: JS_DECLARE_NATIVE_FUNCTION(slice); JS_DECLARE_NATIVE_FUNCTION(byte_length_getter); + JS_DECLARE_NATIVE_FUNCTION(max_byte_length_getter); + JS_DECLARE_NATIVE_FUNCTION(resizable_getter); }; } diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 7444be9ddc..53f078d299 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -380,6 +380,7 @@ namespace JS { P(reject) \ P(relativeTo) \ P(repeat) \ + P(resizable) \ P(resolve) \ P(resolvedOptions) \ P(reverse) \ diff --git a/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.js b/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.js index 8fb05a250c..193d2e47aa 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.js +++ b/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.js @@ -11,3 +11,57 @@ test("ArrayBuffer constructor must be invoked with 'new'", () => { ArrayBuffer(); }).toThrowWithMessage(TypeError, "ArrayBuffer constructor must be called with 'new'"); }); + +describe("resizable array buffer", () => { + test("construct with options", () => { + expect(new ArrayBuffer(5, { maxByteLength: 5 })).toBeInstanceOf(ArrayBuffer); + }); + + test("resizable when provided max byte length", () => { + expect(new ArrayBuffer(1).resizable).toEqual(false); + expect(new ArrayBuffer(1, {}).resizable).toEqual(false); + expect(new ArrayBuffer(1, { maxByteLength: undefined }).resizable).toEqual(false); + expect(new ArrayBuffer(1, { maxByteLength: 1 }).resizable).toEqual(true); + }); + + test("byte length must be shorter than max byte length", () => { + expect(() => { + new ArrayBuffer(1, { maxByteLength: 0 }); + }).toThrowWithMessage(RangeError, "Byte length exceeds maxByteLength option"); + }); + + test("max byte length cannot be too large", () => { + expect(() => { + new ArrayBuffer(0, { maxByteLength: 9007199254740992 }); + }).toThrowWithMessage(RangeError, "Index must be a positive integer"); + }); + + test("max byte length cannot be negative", () => { + expect(() => { + new ArrayBuffer(0, { maxByteLength: -1 }); + }).toThrowWithMessage(RangeError, "Index must be a positive integer"); + }); + + test("invalid max byte length object", () => { + expect(() => { + new ArrayBuffer(0, { + maxByteLength: { + toString: function () { + return {}; + }, + valueOf: function () { + return {}; + }, + }, + }); + }).toThrowWithMessage(TypeError, "Cannot convert object to number"); + + expect(() => { + new ArrayBuffer(0, { + get maxByteLength() { + throw "Exception"; + }, + }); + }).toThrow(); + }); +});