Coverage Report

Created: 2026-06-07 07:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/Streams/ReadableStreamDefaultReader.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
3
 * Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <LibJS/Heap/Heap.h>
9
#include <LibJS/Runtime/ArrayBuffer.h>
10
#include <LibJS/Runtime/Error.h>
11
#include <LibJS/Runtime/Iterator.h>
12
#include <LibJS/Runtime/PromiseCapability.h>
13
#include <LibJS/Runtime/Realm.h>
14
#include <LibJS/Runtime/TypedArray.h>
15
#include <LibWeb/Bindings/ExceptionOrUtils.h>
16
#include <LibWeb/Bindings/Intrinsics.h>
17
#include <LibWeb/Bindings/ReadableStreamDefaultReaderPrototype.h>
18
#include <LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.h>
19
#include <LibWeb/Streams/AbstractOperations.h>
20
#include <LibWeb/Streams/ReadableStream.h>
21
#include <LibWeb/Streams/ReadableStreamDefaultReader.h>
22
#include <LibWeb/WebIDL/ExceptionOr.h>
23
#include <LibWeb/WebIDL/Promise.h>
24
25
namespace Web::Streams {
26
27
JS_DEFINE_ALLOCATOR(ReadableStreamDefaultReader);
28
JS_DEFINE_ALLOCATOR(ReadLoopReadRequest);
29
30
void ReadLoopReadRequest::visit_edges(Visitor& visitor)
31
0
{
32
0
    Base::visit_edges(visitor);
33
0
    visitor.visit(m_realm);
34
0
    visitor.visit(m_reader);
35
0
    visitor.visit(m_success_steps);
36
0
    visitor.visit(m_failure_steps);
37
0
    visitor.visit(m_chunk_steps);
38
0
}
39
40
// https://streams.spec.whatwg.org/#default-reader-constructor
41
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStreamDefaultReader>> ReadableStreamDefaultReader::construct_impl(JS::Realm& realm, JS::NonnullGCPtr<ReadableStream> stream)
42
0
{
43
0
    auto reader = realm.heap().allocate<ReadableStreamDefaultReader>(realm, realm);
44
45
    // 1. Perform ? SetUpReadableStreamDefaultReader(this, stream);
46
0
    TRY(set_up_readable_stream_default_reader(reader, *stream));
47
48
0
    return reader;
49
0
}
50
51
ReadableStreamDefaultReader::ReadableStreamDefaultReader(JS::Realm& realm)
52
0
    : Bindings::PlatformObject(realm)
53
0
    , ReadableStreamGenericReaderMixin(realm)
54
0
{
55
0
}
56
57
void ReadableStreamDefaultReader::initialize(JS::Realm& realm)
58
0
{
59
0
    Base::initialize(realm);
60
0
    WEB_SET_PROTOTYPE_FOR_INTERFACE(ReadableStreamDefaultReader);
61
0
}
62
63
void ReadableStreamDefaultReader::visit_edges(Cell::Visitor& visitor)
64
0
{
65
0
    Base::visit_edges(visitor);
66
0
    ReadableStreamGenericReaderMixin::visit_edges(visitor);
67
0
    for (auto& request : m_read_requests)
68
0
        visitor.visit(request);
69
0
}
70
71
// https://streams.spec.whatwg.org/#read-loop
72
ReadLoopReadRequest::ReadLoopReadRequest(JS::VM& vm, JS::Realm& realm, ReadableStreamDefaultReader& reader, JS::NonnullGCPtr<SuccessSteps> success_steps, JS::NonnullGCPtr<FailureSteps> failure_steps, JS::GCPtr<ChunkSteps> chunk_steps)
73
0
    : m_vm(vm)
74
0
    , m_realm(realm)
75
0
    , m_reader(reader)
76
0
    , m_success_steps(success_steps)
77
0
    , m_failure_steps(failure_steps)
78
0
    , m_chunk_steps(chunk_steps)
79
0
{
80
0
}
81
82
// chunk steps, given chunk
83
void ReadLoopReadRequest::on_chunk(JS::Value chunk)
84
0
{
85
    // 1. If chunk is not a Uint8Array object, call failureSteps with a TypeError and abort these steps.
86
0
    if (!chunk.is_object() || !is<JS::Uint8Array>(chunk.as_object())) {
87
0
        m_failure_steps->function()(JS::TypeError::create(m_realm, "Chunk data is not Uint8Array"sv));
88
0
        return;
89
0
    }
90
91
0
    auto const& array = static_cast<JS::Uint8Array const&>(chunk.as_object());
92
0
    auto const& buffer = array.viewed_array_buffer()->buffer();
93
94
    // 2. Append the bytes represented by chunk to bytes.
95
0
    m_bytes.append(buffer);
96
97
0
    if (m_chunk_steps) {
98
        // FIXME: Can we move the buffer out of the `chunk`? Unclear if that is safe.
99
0
        m_chunk_steps->function()(MUST(ByteBuffer::copy(buffer)));
100
0
    }
101
102
    // FIXME: As the spec suggests, implement this non-recursively - instead of directly. It is not too big of a deal currently
103
    //        as we enqueue the entire blob buffer in one go, meaning that we only recurse a single time. Once we begin queuing
104
    //        up more than one chunk at a time, we may run into stack overflow problems.
105
    //
106
    // 3. Read-loop given reader, bytes, successSteps, and failureSteps.
107
0
    readable_stream_default_reader_read(m_reader, *this);
108
0
}
109
110
// close steps
111
void ReadLoopReadRequest::on_close()
112
0
{
113
    // 1. Call successSteps with bytes.
114
0
    m_success_steps->function()(move(m_bytes));
115
0
}
116
117
// error steps, given e
118
void ReadLoopReadRequest::on_error(JS::Value error)
119
0
{
120
    // 1. Call failureSteps with e.
121
0
    m_failure_steps->function()(error);
122
0
}
123
124
class DefaultReaderReadRequest final : public ReadRequest {
125
    JS_CELL(DefaultReaderReadRequest, ReadRequest);
126
    JS_DECLARE_ALLOCATOR(DefaultReaderReadRequest);
127
128
public:
129
    DefaultReaderReadRequest(JS::Realm& realm, WebIDL::Promise& promise)
130
0
        : m_realm(realm)
131
0
        , m_promise(promise)
132
0
    {
133
0
    }
134
135
    virtual void on_chunk(JS::Value chunk) override
136
0
    {
137
0
        WebIDL::resolve_promise(m_realm, m_promise, JS::create_iterator_result_object(m_realm->vm(), chunk, false));
138
0
    }
139
140
    virtual void on_close() override
141
0
    {
142
0
        WebIDL::resolve_promise(m_realm, m_promise, JS::create_iterator_result_object(m_realm->vm(), JS::js_undefined(), true));
143
0
    }
144
145
    virtual void on_error(JS::Value error) override
146
0
    {
147
0
        WebIDL::reject_promise(m_realm, m_promise, error);
148
0
    }
149
150
private:
151
    virtual void visit_edges(Visitor& visitor) override
152
0
    {
153
0
        Base::visit_edges(visitor);
154
0
        visitor.visit(m_realm);
155
0
        visitor.visit(m_promise);
156
0
    }
157
158
    JS::NonnullGCPtr<JS::Realm> m_realm;
159
    JS::NonnullGCPtr<WebIDL::Promise> m_promise;
160
};
161
162
JS_DEFINE_ALLOCATOR(DefaultReaderReadRequest);
163
164
// https://streams.spec.whatwg.org/#default-reader-read
165
JS::NonnullGCPtr<JS::Promise> ReadableStreamDefaultReader::read()
166
0
{
167
0
    auto& realm = this->realm();
168
169
    // 1. If this.[[stream]] is undefined, return a promise rejected with a TypeError exception.
170
0
    if (!m_stream) {
171
0
        WebIDL::SimpleException exception { WebIDL::SimpleExceptionType::TypeError, "Cannot read from an empty stream"sv };
172
0
        return WebIDL::create_rejected_promise_from_exception(realm, move(exception));
173
0
    }
174
175
    // 2. Let promise be a new promise.
176
0
    auto promise_capability = WebIDL::create_promise(realm);
177
178
    // 3. Let readRequest be a new read request with the following items:
179
    //    chunk steps, given chunk
180
    //        Resolve promise with «[ "value" → chunk, "done" → false ]».
181
    //    close steps
182
    //        Resolve promise with «[ "value" → undefined, "done" → true ]».
183
    //    error steps, given e
184
    //        Reject promise with e.
185
0
    auto read_request = heap().allocate_without_realm<DefaultReaderReadRequest>(realm, promise_capability);
186
187
    // 4. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).
188
0
    readable_stream_default_reader_read(*this, read_request);
189
190
    // 5. Return promise.
191
0
    return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise_capability->promise()) };
192
0
}
193
194
void ReadableStreamDefaultReader::read_a_chunk(Fetch::Infrastructure::IncrementalReadLoopReadRequest& read_request)
195
0
{
196
    // To read a chunk from a ReadableStreamDefaultReader reader, given a read request readRequest,
197
    // perform ! ReadableStreamDefaultReaderRead(reader, readRequest).
198
0
    readable_stream_default_reader_read(*this, read_request);
199
0
}
200
201
// https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
202
void ReadableStreamDefaultReader::read_all_bytes(JS::NonnullGCPtr<ReadLoopReadRequest::SuccessSteps> success_steps, JS::NonnullGCPtr<ReadLoopReadRequest::FailureSteps> failure_steps)
203
0
{
204
0
    auto& realm = this->realm();
205
0
    auto& vm = realm.vm();
206
207
    // 1. Let readRequest be a new read request with the following items:
208
    //    NOTE: items and steps in ReadLoopReadRequest.
209
0
    auto read_request = heap().allocate_without_realm<ReadLoopReadRequest>(vm, realm, *this, success_steps, failure_steps);
210
211
    // 2. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).
212
0
    readable_stream_default_reader_read(*this, read_request);
213
0
}
214
215
void ReadableStreamDefaultReader::read_all_chunks(JS::NonnullGCPtr<ReadLoopReadRequest::ChunkSteps> chunk_steps, JS::NonnullGCPtr<ReadLoopReadRequest::SuccessSteps> success_steps, JS::NonnullGCPtr<ReadLoopReadRequest::FailureSteps> failure_steps)
216
0
{
217
    // AD-HOC: Some spec steps direct us to "read all chunks" from a stream, but there isn't an AO defined to do that.
218
    //         We implement those steps by using the "read all bytes" definition, with a custom callback to receive
219
    //         each chunk that is read.
220
0
    auto& realm = this->realm();
221
0
    auto& vm = realm.vm();
222
223
    // 1. Let readRequest be a new read request with the following items:
224
    //    NOTE: items and steps in ReadLoopReadRequest.
225
0
    auto read_request = heap().allocate_without_realm<ReadLoopReadRequest>(vm, realm, *this, success_steps, failure_steps, chunk_steps);
226
227
    // 2. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).
228
0
    readable_stream_default_reader_read(*this, read_request);
229
0
}
230
231
// FIXME: This function is a promise-based wrapper around "read all bytes". The spec changed this function to not use promises
232
//        in https://github.com/whatwg/streams/commit/f894acdd417926a2121710803cef593e15127964 - however, it seems that the
233
//        FileAPI blob specification has not been updated to match, see: https://github.com/w3c/FileAPI/issues/187.
234
JS::NonnullGCPtr<WebIDL::Promise> ReadableStreamDefaultReader::read_all_bytes_deprecated()
235
0
{
236
0
    auto& realm = this->realm();
237
238
0
    auto promise = WebIDL::create_promise(realm);
239
240
0
    auto success_steps = JS::create_heap_function(realm.heap(), [promise, &realm](ByteBuffer bytes) {
241
0
        auto buffer = JS::ArrayBuffer::create(realm, move(bytes));
242
0
        WebIDL::resolve_promise(realm, promise, buffer);
243
0
    });
244
245
0
    auto failure_steps = JS::create_heap_function(realm.heap(), [promise, &realm](JS::Value error) {
246
0
        WebIDL::reject_promise(realm, promise, error);
247
0
    });
248
249
0
    read_all_bytes(success_steps, failure_steps);
250
251
0
    return promise;
252
0
}
253
254
// https://streams.spec.whatwg.org/#default-reader-release-lock
255
void ReadableStreamDefaultReader::release_lock()
256
0
{
257
    // 1. If this.[[stream]] is undefined, return.
258
0
    if (!m_stream)
259
0
        return;
260
261
    // 2. Perform ! ReadableStreamDefaultReaderRelease(this).
262
0
    readable_stream_default_reader_release(*this);
263
0
}
264
265
}