/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 | | } |