1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-15 10:04:59 +00:00
serenity/Userland/Libraries/LibWeb/Streams/ReadableStreamDefaultReader.cpp
Andreas Kling bfd354492e LibWeb: Put most LibWeb GC objects in type-specific heap blocks
With this change, we now have ~1200 CellAllocators across both LibJS and
LibWeb in a normal WebContent instance.

This gives us a minimum heap size of 4.7 MiB in the scenario where we
only have one cell allocated per type. Of course, in practice there will
be many more of each type, so the effective overhead is quite a bit
smaller than that in practice.

I left a few types unconverted to this mechanism because I got tired of
doing this. :^)
2023-11-19 22:00:48 +01:00

235 lines
8.4 KiB
C++

/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Heap/Heap.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ReadableStreamDefaultReaderPrototype.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/ReadableStream.h>
#include <LibWeb/Streams/ReadableStreamDefaultReader.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(ReadableStreamDefaultReader);
void ReadLoopReadRequest::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_realm);
visitor.visit(m_reader);
}
// https://streams.spec.whatwg.org/#default-reader-constructor
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStreamDefaultReader>> ReadableStreamDefaultReader::construct_impl(JS::Realm& realm, JS::NonnullGCPtr<ReadableStream> stream)
{
auto reader = realm.heap().allocate<ReadableStreamDefaultReader>(realm, realm);
// 1. Perform ? SetUpReadableStreamDefaultReader(this, stream);
TRY(set_up_readable_stream_default_reader(reader, *stream));
return reader;
}
ReadableStreamDefaultReader::ReadableStreamDefaultReader(JS::Realm& realm)
: Bindings::PlatformObject(realm)
, ReadableStreamGenericReaderMixin(realm)
{
}
void ReadableStreamDefaultReader::initialize(JS::Realm& realm)
{
Base::initialize(realm);
set_prototype(&Bindings::ensure_web_prototype<Bindings::ReadableStreamDefaultReaderPrototype>(realm, "ReadableStreamDefaultReader"));
}
void ReadableStreamDefaultReader::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
ReadableStreamGenericReaderMixin::visit_edges(visitor);
for (auto& request : m_read_requests)
visitor.visit(request);
}
// https://streams.spec.whatwg.org/#read-loop
ReadLoopReadRequest::ReadLoopReadRequest(JS::VM& vm, JS::Realm& realm, ReadableStreamDefaultReader& reader, SuccessSteps success_steps, FailureSteps failure_steps)
: m_vm(vm)
, m_realm(realm)
, m_reader(reader)
, m_success_steps(move(success_steps))
, m_failure_steps(move(failure_steps))
{
}
// chunk steps, given chunk
void ReadLoopReadRequest::on_chunk(JS::Value chunk)
{
// 1. If chunk is not a Uint8Array object, call failureSteps with a TypeError and abort these steps.
if (!chunk.is_object() || !is<JS::Uint8Array>(chunk.as_object())) {
m_failure_steps(JS::TypeError::create(m_realm, "Chunk data is not Uint8Array"sv));
return;
}
auto const& array = static_cast<JS::Uint8Array const&>(chunk.as_object());
auto const& buffer = array.viewed_array_buffer()->buffer();
// 2. Append the bytes represented by chunk to bytes.
m_bytes.append(buffer);
// FIXME: As the spec suggests, implement this non-recursively - instead of directly. It is not too big of a deal currently
// as we enqueue the entire blob buffer in one go, meaning that we only recurse a single time. Once we begin queuing
// up more than one chunk at a time, we may run into stack overflow problems.
//
// 3. Read-loop given reader, bytes, successSteps, and failureSteps.
auto maybe_error = readable_stream_default_reader_read(m_reader, *this);
if (maybe_error.is_exception()) {
auto throw_completion = Bindings::dom_exception_to_throw_completion(m_vm, maybe_error.exception());
m_failure_steps(*throw_completion.release_error().value());
}
}
// close steps
void ReadLoopReadRequest::on_close()
{
// 1. Call successSteps with bytes.
m_success_steps(m_bytes);
}
// error steps, given e
void ReadLoopReadRequest::on_error(JS::Value error)
{
// 1. Call failureSteps with e.
m_failure_steps(error);
}
class DefaultReaderReadRequest final : public ReadRequest {
JS_CELL(DefaultReaderReadRequest, Cell);
public:
DefaultReaderReadRequest(JS::Realm& realm, WebIDL::Promise& promise)
: m_realm(realm)
, m_promise(promise)
{
}
virtual void on_chunk(JS::Value chunk) override
{
WebIDL::resolve_promise(m_realm, m_promise, JS::create_iterator_result_object(m_realm.vm(), chunk, false));
}
virtual void on_close() override
{
WebIDL::resolve_promise(m_realm, m_promise, JS::create_iterator_result_object(m_realm.vm(), JS::js_undefined(), true));
}
virtual void on_error(JS::Value error) override
{
WebIDL::reject_promise(m_realm, m_promise, error);
}
private:
virtual void visit_edges(Visitor& visitor) override
{
Base::visit_edges(visitor);
visitor.visit(m_realm);
visitor.visit(m_promise);
}
JS::Realm& m_realm;
WebIDL::Promise& m_promise;
};
// https://streams.spec.whatwg.org/#default-reader-read
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> ReadableStreamDefaultReader::read()
{
auto& realm = this->realm();
// 1. If this.[[stream]] is undefined, return a promise rejected with a TypeError exception.
if (!m_stream) {
auto exception = JS::TypeError::create(realm, "Cannot read from an empty stream"sv);
auto promise_capability = WebIDL::create_rejected_promise(realm, exception);
return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise_capability->promise()) };
}
// 2. Let promise be a new promise.
auto promise_capability = WebIDL::create_promise(realm);
// 3. Let readRequest be a new read request with the following items:
// chunk steps, given chunk
// Resolve promise with «[ "value" → chunk, "done" → false ]».
// close steps
// Resolve promise with «[ "value" → undefined, "done" → true ]».
// error steps, given e
// Reject promise with e.
auto read_request = heap().allocate_without_realm<DefaultReaderReadRequest>(realm, promise_capability);
// 4. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).
TRY(readable_stream_default_reader_read(*this, read_request));
// 5. Return promise.
return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise_capability->promise()) };
}
// https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
WebIDL::ExceptionOr<void> ReadableStreamDefaultReader::read_all_bytes(ReadLoopReadRequest::SuccessSteps success_steps, ReadLoopReadRequest::FailureSteps failure_steps)
{
auto& realm = this->realm();
auto& vm = realm.vm();
// 1. Let readRequest be a new read request with the following items:
// NOTE: items and steps in ReadLoopReadRequest.
auto read_request = heap().allocate_without_realm<ReadLoopReadRequest>(vm, realm, *this, move(success_steps), move(failure_steps));
// 2. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).
TRY(readable_stream_default_reader_read(*this, read_request));
return {};
}
// FIXME: This function is a promise-based wrapper around "read all bytes". The spec changed this function to not use promises
// in https://github.com/whatwg/streams/commit/f894acdd417926a2121710803cef593e15127964 - however, it seems that the
// FileAPI blob specification has not been updated to match, see: https://github.com/w3c/FileAPI/issues/187.
WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::Promise>> ReadableStreamDefaultReader::read_all_bytes_deprecated()
{
auto& realm = this->realm();
auto promise = WebIDL::create_promise(realm);
auto success_steps = [promise, &realm](ByteBuffer bytes) {
auto buffer = JS::ArrayBuffer::create(realm, move(bytes));
WebIDL::resolve_promise(realm, promise, buffer);
};
auto failure_steps = [promise, &realm](JS::Value error) {
WebIDL::reject_promise(realm, promise, error);
};
TRY(read_all_bytes(move(success_steps), move(failure_steps)));
return promise;
}
// https://streams.spec.whatwg.org/#default-reader-release-lock
WebIDL::ExceptionOr<void> ReadableStreamDefaultReader::release_lock()
{
// 1. If this.[[stream]] is undefined, return.
if (!m_stream)
return {};
// 2. Perform ! ReadableStreamDefaultReaderRelease(this).
return readable_stream_default_reader_release(*this);
}
}