ladybird/Libraries/LibWeb/WebIDL/Buffers.cpp
Shannon Booth 97abc707c7 LibWeb/Bindings: Generate buffer typedefs as variants
Represent BufferSource and ArrayBufferView as ordinary IDL typedefs over
their underlying union types, instead of special casing in the IDL
generator. This allows the union conversion/return machinery handle
these types consistently with other typedefs, which removes buffer
specific paths from the IDL generator.

This necessitates changing the WebIDL::BufferSource and
WebIDL::ArrayBufferView classes as views over these variants. This
replaces the old GC backed BufferableObject wrapper structure and
provide convenience helpers to determine things such as the byte length,
byte offset, backing buffer, and typed-array APIs.
2026-05-30 11:22:08 +02:00

191 lines
6.4 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/DataView.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/WebIDL/Buffers.h>
namespace Web::WebIDL {
ArrayBufferViewVariant ArrayBufferView::from_object(GC::Ref<JS::Object> object)
{
if (is<JS::DataView>(*object))
return GC::Ref { static_cast<JS::DataView&>(*object) };
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \
if (is<JS::ClassName>(*object)) \
return GC::Ref { static_cast<JS::ClassName&>(*object) };
JS_ENUMERATE_TYPED_ARRAYS
#undef __JS_ENUMERATE
VERIFY_NOT_REACHED();
}
BufferSourceVariant BufferSource::from_object(GC::Ref<JS::Object> object)
{
if (is<JS::ArrayBuffer>(*object))
return GC::Ref { static_cast<JS::ArrayBuffer&>(*object) };
return ArrayBufferView::from_object(object);
}
BufferSource::BufferSource(BufferSourceVariant const& source)
: m_buffer_source(source)
{
}
BufferSource::BufferSource(ArrayBufferViewVariant const& source)
: BufferSource(source.visit([](auto const& view) -> BufferSourceVariant { return view; }))
{
}
BufferSource::BufferSource(ArrayBufferView const& source)
: BufferSource(source.array_buffer_view())
{
}
u32 BufferSource::byte_length() const
{
return m_buffer_source.visit(
[](GC::Ref<JS::DataView> data_view) {
auto view_record = JS::make_data_view_with_buffer_witness_record(data_view, JS::ArrayBuffer::Order::SeqCst);
return JS::get_view_byte_length(view_record);
},
[](GC::Ref<JS::ArrayBuffer> array_buffer) {
return static_cast<u32>(array_buffer->byte_length());
},
[](auto const& typed_array) {
auto typed_array_record = JS::make_typed_array_with_buffer_witness_record(*typed_array, JS::ArrayBuffer::Order::SeqCst);
return JS::typed_array_byte_length(typed_array_record);
});
}
u32 BufferSource::byte_offset() const
{
return m_buffer_source.visit(
[](GC::Ref<JS::ArrayBuffer>) -> u32 { return 0; },
[](auto const& view) -> u32 { return static_cast<u32>(view->byte_offset()); });
}
u32 BufferSource::element_size() const
{
return m_buffer_source.visit(
[](GC::Ref<JS::DataView>) -> u32 {
return 1;
},
[](GC::Ref<JS::ArrayBuffer>) -> u32 {
return 1;
},
[](auto const& typed_array) -> u32 {
return typed_array->element_size();
});
}
GC::Ptr<JS::ArrayBuffer> BufferSource::viewed_array_buffer() const
{
return m_buffer_source.visit(
[](GC::Ref<JS::ArrayBuffer> array_buffer) -> GC::Ptr<JS::ArrayBuffer> { return array_buffer; },
[](auto const& view) -> GC::Ptr<JS::ArrayBuffer> { return view->viewed_array_buffer(); });
}
GC::Ptr<JS::TypedArrayBase> BufferSource::typed_array_base() const
{
return m_buffer_source.visit(
[](GC::Ref<JS::DataView>) -> GC::Ptr<JS::TypedArrayBase> { return nullptr; },
[](GC::Ref<JS::ArrayBuffer>) -> GC::Ptr<JS::TypedArrayBase> { return nullptr; },
[](auto const& typed_array) -> GC::Ptr<JS::TypedArrayBase> { return &static_cast<JS::TypedArrayBase&>(*typed_array); });
}
bool BufferSource::is_data_view() const
{
return m_buffer_source.has<GC::Ref<JS::DataView>>();
}
bool BufferSource::is_array_buffer() const
{
return m_buffer_source.has<GC::Ref<JS::ArrayBuffer>>();
}
ArrayBufferView::ArrayBufferView(ArrayBufferViewVariant const& view)
: m_array_buffer_view(view)
{
}
u32 ArrayBufferView::byte_length() const
{
return BufferSource { m_array_buffer_view }.byte_length();
}
u32 ArrayBufferView::byte_offset() const
{
return BufferSource { m_array_buffer_view }.byte_offset();
}
u32 ArrayBufferView::element_size() const
{
return BufferSource { m_array_buffer_view }.element_size();
}
GC::Ptr<JS::ArrayBuffer> ArrayBufferView::viewed_array_buffer() const
{
return BufferSource { m_array_buffer_view }.viewed_array_buffer();
}
GC::Ptr<JS::TypedArrayBase> ArrayBufferView::typed_array_base() const
{
return BufferSource { m_array_buffer_view }.typed_array_base();
}
// https://webidl.spec.whatwg.org/#arraybufferview-write
void ArrayBufferView::write(ReadonlyBytes bytes, u32 starting_offset)
{
// 1. Let jsView be the result of converting view to a JavaScript value.
// 2. Assert: bytess length ≤ jsView.[[ByteLength]] startingOffset.
VERIFY(bytes.size() <= byte_length() - starting_offset);
// 3. Assert: if view is not a DataView, then bytess length modulo the element size of views type is 0.
if (!m_array_buffer_view.has<GC::Ref<JS::DataView>>()) {
auto element_size = typed_array_base()->element_size();
VERIFY(bytes.size() % element_size == 0);
}
// 4. Let arrayBuffer be the result of converting jsView.[[ViewedArrayBuffer]] to an IDL value of type ArrayBuffer.
auto array_buffer = viewed_array_buffer();
// 5. Write bytes into arrayBuffer with startingOffset set to jsView.[[ByteOffset]] + startingOffset.
array_buffer->overwrite(byte_offset() + starting_offset, bytes.data(), bytes.size());
}
// https://webidl.spec.whatwg.org/#buffersource-detached
bool BufferSource::is_detached(JS::Value const& buffer_source)
{
// A buffer source type instance bufferSource is detached if the following steps return true:
// 1. Let jsArrayBuffer be the result of converting bufferSource to a JavaScript value.
// 2. If jsArrayBuffer has a [[ViewedArrayBuffer]] internal slot, then set jsArrayBuffer to jsArrayBuffer.[[ViewedArrayBuffer]].
if (!buffer_source.is_object())
return false;
JS::ArrayBuffer const* js_array_buffer = nullptr;
auto const& array_buffer_object = buffer_source.as_object();
if (auto const* array_buffer = as_if<JS::ArrayBuffer>(array_buffer_object)) {
js_array_buffer = array_buffer;
} else if (auto const* typed_array_base = as_if<JS::TypedArrayBase>(array_buffer_object)) {
js_array_buffer = typed_array_base->viewed_array_buffer();
} else if (auto const* data_view = as_if<JS::DataView>(array_buffer_object)) {
js_array_buffer = data_view->viewed_array_buffer();
} else {
return false;
}
// 3. Return IsDetachedBuffer(jsArrayBuffer).
return js_array_buffer->is_detached();
}
}