1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-23 09:37:34 +00:00

LibJS: Add the DataView built-in object

This commit is contained in:
Idan Horowitz 2021-06-14 01:47:08 +03:00 committed by Linus Groh
parent 5b2255291e
commit e4d267d4fb
15 changed files with 351 additions and 0 deletions

View file

@ -37,6 +37,9 @@ set(SOURCES
Runtime/BooleanPrototype.cpp Runtime/BooleanPrototype.cpp
Runtime/BoundFunction.cpp Runtime/BoundFunction.cpp
Runtime/ConsoleObject.cpp Runtime/ConsoleObject.cpp
Runtime/DataView.cpp
Runtime/DataViewConstructor.cpp
Runtime/DataViewPrototype.cpp
Runtime/DateConstructor.cpp Runtime/DateConstructor.cpp
Runtime/Date.cpp Runtime/Date.cpp
Runtime/DatePrototype.cpp Runtime/DatePrototype.cpp

View file

@ -31,6 +31,7 @@
__JS_ENUMERATE(ArrayBuffer, array_buffer, ArrayBufferPrototype, ArrayBufferConstructor, void) \ __JS_ENUMERATE(ArrayBuffer, array_buffer, ArrayBufferPrototype, ArrayBufferConstructor, void) \
__JS_ENUMERATE(BigIntObject, bigint, BigIntPrototype, BigIntConstructor, void) \ __JS_ENUMERATE(BigIntObject, bigint, BigIntPrototype, BigIntConstructor, void) \
__JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor, void) \ __JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor, void) \
__JS_ENUMERATE(DataView, data_view, DataViewPrototype, DataViewConstructor, void) \
__JS_ENUMERATE(Date, date, DatePrototype, DateConstructor, void) \ __JS_ENUMERATE(Date, date, DatePrototype, DateConstructor, void) \
__JS_ENUMERATE(Error, error, ErrorPrototype, ErrorConstructor, void) \ __JS_ENUMERATE(Error, error, ErrorPrototype, ErrorConstructor, void) \
__JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor, void) \ __JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor, void) \

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/DataView.h>
namespace JS {
DataView* DataView::create(GlobalObject& global_object, ArrayBuffer* viewed_buffer, size_t byte_length, size_t byte_offset)
{
return global_object.heap().allocate<DataView>(global_object, *global_object.data_view_prototype(), viewed_buffer, byte_length, byte_offset);
}
DataView::DataView(Object& prototype, ArrayBuffer* viewed_buffer, size_t byte_length, size_t byte_offset)
: Object(prototype)
, m_viewed_array_buffer(viewed_buffer)
, m_byte_length(byte_length)
, m_byte_offset(byte_offset)
{
}
DataView::~DataView()
{
}
void DataView::visit_edges(Visitor& visitor)
{
Object::visit_edges(visitor);
visitor.visit(m_viewed_array_buffer);
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Object.h>
namespace JS {
class DataView : public Object {
JS_OBJECT(DataView, Object);
public:
static DataView* create(GlobalObject&, ArrayBuffer*, size_t byte_length, size_t byte_offset);
explicit DataView(Object& prototype, ArrayBuffer*, size_t byte_length, size_t byte_offset);
virtual ~DataView() override;
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
size_t byte_length() const { return m_byte_length; }
size_t byte_offset() const { return m_byte_offset; }
private:
virtual void visit_edges(Visitor& visitor) override;
ArrayBuffer* m_viewed_array_buffer { nullptr };
size_t m_byte_length { 0 };
size_t m_byte_offset { 0 };
};
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/DataView.h>
#include <LibJS/Runtime/DataViewConstructor.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
namespace JS {
DataViewConstructor::DataViewConstructor(GlobalObject& global_object)
: NativeFunction(vm().names.DataView, *global_object.function_prototype())
{
}
void DataViewConstructor::initialize(GlobalObject& global_object)
{
auto& vm = this->vm();
NativeFunction::initialize(global_object);
// 25.3.3.1 DataView.prototype, https://tc39.es/ecma262/#sec-dataview.prototype
define_property(vm.names.prototype, global_object.data_view_prototype(), 0);
define_property(vm.names.length, Value(1), Attribute::Configurable);
}
DataViewConstructor::~DataViewConstructor()
{
}
// 25.3.2.1 DataView ( buffer [ , byteOffset [ , byteLength ] ] ), https://tc39.es/ecma262/#sec-dataview-buffer-byteoffset-bytelength
Value DataViewConstructor::call()
{
auto& vm = this->vm();
vm.throw_exception<TypeError>(global_object(), ErrorType::ConstructorWithoutNew, vm.names.DataView);
return {};
}
// 25.3.2.1 DataView ( buffer [ , byteOffset [ , byteLength ] ] ), https://tc39.es/ecma262/#sec-dataview-buffer-byteoffset-bytelength
Value DataViewConstructor::construct(Function&)
{
auto& vm = this->vm();
auto buffer = vm.argument(0);
if (!buffer.is_object() || !is<ArrayBuffer>(buffer.as_object())) {
vm.throw_exception<TypeError>(global_object(), ErrorType::IsNotAn, buffer.to_string_without_side_effects(), vm.names.ArrayBuffer);
return {};
}
auto& array_buffer = static_cast<ArrayBuffer&>(buffer.as_object());
auto offset = vm.argument(1).to_index(global_object());
if (vm.exception())
return {};
if (array_buffer.is_detached()) {
vm.throw_exception<TypeError>(global_object(), ErrorType::DetachedArrayBuffer);
return {};
}
auto buffer_byte_length = array_buffer.byte_length();
if (offset > buffer_byte_length) {
vm.throw_exception<RangeError>(global_object(), ErrorType::DataViewOutOfRangeByteOffset, offset, buffer_byte_length);
return {};
}
size_t view_byte_length;
if (vm.argument(2).is_undefined()) {
view_byte_length = buffer_byte_length - offset;
} else {
view_byte_length = vm.argument(2).to_index(global_object());
if (vm.exception())
return {};
if (offset + view_byte_length > buffer_byte_length) {
vm.throw_exception<RangeError>(global_object(), ErrorType::InvalidLength, vm.names.DataView);
return {};
}
}
// FIXME: Use OrdinaryCreateFromConstructor(newTarget, "%DataView.prototype%")
if (array_buffer.is_detached()) {
vm.throw_exception<TypeError>(global_object(), ErrorType::DetachedArrayBuffer);
return {};
}
return DataView::create(global_object(), &array_buffer, view_byte_length, offset);
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/NativeFunction.h>
namespace JS {
class DataViewConstructor final : public NativeFunction {
JS_OBJECT(DataViewConstructor, NativeFunction);
public:
explicit DataViewConstructor(GlobalObject&);
virtual void initialize(GlobalObject&) override;
virtual ~DataViewConstructor() override;
virtual Value call() override;
virtual Value construct(Function&) override;
private:
virtual bool has_constructor() const override { return true; }
};
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/DataViewPrototype.h>
namespace JS {
DataViewPrototype::DataViewPrototype(GlobalObject& global_object)
: Object(*global_object.object_prototype())
{
}
void DataViewPrototype::initialize(GlobalObject& global_object)
{
auto& vm = this->vm();
Object::initialize(global_object);
define_native_accessor(vm.names.buffer, buffer_getter, {}, Attribute::Configurable);
define_native_accessor(vm.names.byteLength, byte_length_getter, {}, Attribute::Configurable);
define_native_accessor(vm.names.byteOffset, byte_offset_getter, {}, Attribute::Configurable);
// 25.3.4.25 DataView.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-dataview.prototype-@@tostringtag
define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), vm.names.DataView.as_string()), Attribute::Configurable);
}
DataViewPrototype::~DataViewPrototype()
{
}
static DataView* typed_this(VM& vm, GlobalObject& global_object)
{
auto this_value = vm.this_value(global_object);
if (!this_value.is_object() || !is<DataView>(this_value.as_object())) {
vm.throw_exception<TypeError>(global_object, ErrorType::NotA, vm.names.DataView);
return nullptr;
}
return static_cast<DataView*>(&this_value.as_object());
}
// 25.3.4.1 get DataView.prototype.buffer, https://tc39.es/ecma262/#sec-get-dataview.prototype.buffer
JS_DEFINE_NATIVE_GETTER(DataViewPrototype::buffer_getter)
{
auto* data_view = typed_this(vm, global_object);
if (!data_view)
return {};
return data_view->viewed_array_buffer();
}
// 25.3.4.2 get DataView.prototype.byteLength, https://tc39.es/ecma262/#sec-get-dataview.prototype.bytelength
JS_DEFINE_NATIVE_GETTER(DataViewPrototype::byte_length_getter)
{
auto* data_view = typed_this(vm, global_object);
if (!data_view)
return {};
if (data_view->viewed_array_buffer()->is_detached()) {
vm.throw_exception<TypeError>(global_object, ErrorType::DetachedArrayBuffer);
return {};
}
return Value(data_view->byte_length());
}
// 25.3.4.3 get DataView.prototype.byteOffset, https://tc39.es/ecma262/#sec-get-dataview.prototype.byteoffset
JS_DEFINE_NATIVE_GETTER(DataViewPrototype::byte_offset_getter)
{
auto* data_view = typed_this(vm, global_object);
if (!data_view)
return {};
if (data_view->viewed_array_buffer()->is_detached()) {
vm.throw_exception<TypeError>(global_object, ErrorType::DetachedArrayBuffer);
return {};
}
return Value(data_view->byte_offset());
}
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/DataView.h>
namespace JS {
class DataViewPrototype final : public Object {
JS_OBJECT(DataViewPrototype, Object);
public:
DataViewPrototype(GlobalObject&);
virtual void initialize(GlobalObject&) override;
virtual ~DataViewPrototype() override;
private:
JS_DECLARE_NATIVE_GETTER(buffer_getter);
JS_DECLARE_NATIVE_GETTER(byte_length_getter);
JS_DECLARE_NATIVE_GETTER(byte_offset_getter);
};
}

View file

@ -21,6 +21,7 @@
M(ClassIsAbstract, "Abstract class {} cannot be constructed directly") \ M(ClassIsAbstract, "Abstract class {} cannot be constructed directly") \
M(ConstructorWithoutNew, "{} constructor must be called with 'new'") \ M(ConstructorWithoutNew, "{} constructor must be called with 'new'") \
M(Convert, "Cannot convert {} to {}") \ M(Convert, "Cannot convert {} to {}") \
M(DataViewOutOfRangeByteOffset, "Data view byte offset {} is out of range for buffer with length {}") \
M(DescChangeNonConfigurable, "Cannot change attributes of non-configurable property '{}'") \ M(DescChangeNonConfigurable, "Cannot change attributes of non-configurable property '{}'") \
M(DescWriteNonWritable, "Cannot write to non-writable property '{}'") \ M(DescWriteNonWritable, "Cannot write to non-writable property '{}'") \
M(DetachedArrayBuffer, "ArrayBuffer is detached") \ M(DetachedArrayBuffer, "ArrayBuffer is detached") \
@ -36,6 +37,7 @@
M(InvalidTimeValue, "Invalid time value") \ M(InvalidTimeValue, "Invalid time value") \
M(InvalidRadix, "Radix must be an integer no less than 2, and no greater than 36") \ M(InvalidRadix, "Radix must be an integer no less than 2, and no greater than 36") \
M(IsNotA, "{} is not a {}") \ M(IsNotA, "{} is not a {}") \
M(IsNotAn, "{} is not an {}") \
M(IsNotAEvaluatedFrom, "{} is not a {} (evaluated from '{}')") \ M(IsNotAEvaluatedFrom, "{} is not a {} (evaluated from '{}')") \
M(IterableNextBadReturn, "iterator.next() returned a non-object value") \ M(IterableNextBadReturn, "iterator.next() returned a non-object value") \
M(IterableNextNotAFunction, "'next' property on returned object from Symbol.iterator method is " \ M(IterableNextNotAFunction, "'next' property on returned object from Symbol.iterator method is " \

View file

@ -27,6 +27,8 @@
#include <LibJS/Runtime/BooleanConstructor.h> #include <LibJS/Runtime/BooleanConstructor.h>
#include <LibJS/Runtime/BooleanPrototype.h> #include <LibJS/Runtime/BooleanPrototype.h>
#include <LibJS/Runtime/ConsoleObject.h> #include <LibJS/Runtime/ConsoleObject.h>
#include <LibJS/Runtime/DataViewConstructor.h>
#include <LibJS/Runtime/DataViewPrototype.h>
#include <LibJS/Runtime/DateConstructor.h> #include <LibJS/Runtime/DateConstructor.h>
#include <LibJS/Runtime/DatePrototype.h> #include <LibJS/Runtime/DatePrototype.h>
#include <LibJS/Runtime/ErrorConstructor.h> #include <LibJS/Runtime/ErrorConstructor.h>
@ -150,6 +152,7 @@ void GlobalObject::initialize_global_object()
add_constructor(vm.names.ArrayBuffer, m_array_buffer_constructor, m_array_buffer_prototype); add_constructor(vm.names.ArrayBuffer, m_array_buffer_constructor, m_array_buffer_prototype);
add_constructor(vm.names.BigInt, m_bigint_constructor, m_bigint_prototype); add_constructor(vm.names.BigInt, m_bigint_constructor, m_bigint_prototype);
add_constructor(vm.names.Boolean, m_boolean_constructor, m_boolean_prototype); add_constructor(vm.names.Boolean, m_boolean_constructor, m_boolean_prototype);
add_constructor(vm.names.DataView, m_data_view_constructor, m_data_view_prototype);
add_constructor(vm.names.Date, m_date_constructor, m_date_prototype); add_constructor(vm.names.Date, m_date_constructor, m_date_prototype);
add_constructor(vm.names.Error, m_error_constructor, m_error_prototype); add_constructor(vm.names.Error, m_error_constructor, m_error_prototype);
add_constructor(vm.names.Function, m_function_constructor, m_function_prototype); add_constructor(vm.names.Function, m_function_constructor, m_function_prototype);

View file

@ -0,0 +1,15 @@
test("basic functionality", () => {
expect(DataView).toHaveLength(1);
expect(DataView.name).toBe("DataView");
expect(DataView.prototype.constructor).toBe(DataView);
const buffer = new ArrayBuffer();
expect(new DataView(buffer)).toBeInstanceOf(DataView);
expect(typeof new DataView(buffer)).toBe("object");
});
test("DataView constructor must be invoked with 'new'", () => {
expect(() => {
DataView();
}).toThrowWithMessage(TypeError, "DataView constructor must be called with 'new'");
});

View file

@ -0,0 +1,4 @@
test("basic functionality", () => {
const buffer = new ArrayBuffer();
expect(new DataView(buffer).buffer).toBe(buffer);
});

View file

@ -0,0 +1,7 @@
test("basic functionality", () => {
const buffer = new ArrayBuffer(124);
expect(new DataView(buffer).byteLength).toBe(124);
expect(new DataView(buffer, 0, 1).byteLength).toBe(1);
expect(new DataView(buffer, 0, 64).byteLength).toBe(64);
expect(new DataView(buffer, 0, 123).byteLength).toBe(123);
});

View file

@ -0,0 +1,7 @@
test("basic functionality", () => {
const buffer = new ArrayBuffer(124);
expect(new DataView(buffer).byteOffset).toBe(0);
expect(new DataView(buffer, 1).byteOffset).toBe(1);
expect(new DataView(buffer, 64).byteOffset).toBe(64);
expect(new DataView(buffer, 123).byteOffset).toBe(123);
});

View file

@ -23,6 +23,7 @@
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayBuffer.h> #include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/BooleanObject.h> #include <LibJS/Runtime/BooleanObject.h>
#include <LibJS/Runtime/DataView.h>
#include <LibJS/Runtime/Date.h> #include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Function.h> #include <LibJS/Runtime/Function.h>
@ -397,6 +398,19 @@ static void print_typed_array(const JS::Object& object, HashTable<JS::Object*>&
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
static void print_data_view(const JS::Object& object, HashTable<JS::Object*>& seen_objects)
{
auto& data_view = static_cast<const JS::DataView&>(object);
print_type("DataView");
out("\n byteLength: ");
print_value(JS::Value(data_view.byte_length()), seen_objects);
out("\n byteOffset: ");
print_value(JS::Value(data_view.byte_offset()), seen_objects);
out("\n buffer: ");
print_type("ArrayBuffer");
out(" @ {:p}", data_view.viewed_array_buffer());
}
static void print_primitive_wrapper_object(const FlyString& name, const JS::Object& object, HashTable<JS::Object*>& seen_objects) static void print_primitive_wrapper_object(const FlyString& name, const JS::Object& object, HashTable<JS::Object*>& seen_objects)
{ {
// BooleanObject, NumberObject, StringObject // BooleanObject, NumberObject, StringObject
@ -438,6 +452,8 @@ static void print_value(JS::Value value, HashTable<JS::Object*>& seen_objects)
return print_map(object, seen_objects); return print_map(object, seen_objects);
if (is<JS::Set>(object)) if (is<JS::Set>(object))
return print_set(object, seen_objects); return print_set(object, seen_objects);
if (is<JS::DataView>(object))
return print_data_view(object, seen_objects);
if (is<JS::ProxyObject>(object)) if (is<JS::ProxyObject>(object))
return print_proxy_object(object, seen_objects); return print_proxy_object(object, seen_objects);
if (is<JS::Promise>(object)) if (is<JS::Promise>(object))