/src/serenity/Userland/Libraries/LibWeb/Streams/ReadableByteStreamController.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org> |
3 | | * Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <LibJS/Runtime/TypedArray.h> |
9 | | #include <LibWeb/Bindings/Intrinsics.h> |
10 | | #include <LibWeb/Bindings/ReadableByteStreamControllerPrototype.h> |
11 | | #include <LibWeb/Streams/AbstractOperations.h> |
12 | | #include <LibWeb/Streams/ReadableByteStreamController.h> |
13 | | #include <LibWeb/Streams/ReadableStream.h> |
14 | | #include <LibWeb/Streams/ReadableStreamBYOBRequest.h> |
15 | | #include <LibWeb/Streams/ReadableStreamDefaultReader.h> |
16 | | #include <LibWeb/WebIDL/Buffers.h> |
17 | | |
18 | | namespace Web::Streams { |
19 | | |
20 | | JS_DEFINE_ALLOCATOR(ReadableByteStreamController); |
21 | | |
22 | | // https://streams.spec.whatwg.org/#rbs-controller-desired-size |
23 | | Optional<double> ReadableByteStreamController::desired_size() const |
24 | 0 | { |
25 | | // 1. Return ! ReadableByteStreamControllerGetDesiredSize(this). |
26 | 0 | return readable_byte_stream_controller_get_desired_size(*this); |
27 | 0 | } |
28 | | |
29 | | // https://streams.spec.whatwg.org/#rbs-controller-byob-request |
30 | | JS::GCPtr<ReadableStreamBYOBRequest> ReadableByteStreamController::byob_request() |
31 | 0 | { |
32 | | // 1. Return ! ReadableByteStreamControllerGetBYOBRequest(this). |
33 | 0 | return readable_byte_stream_controller_get_byob_request(*this); |
34 | 0 | } |
35 | | |
36 | | // https://streams.spec.whatwg.org/#rbs-controller-close |
37 | | WebIDL::ExceptionOr<void> ReadableByteStreamController::close() |
38 | 0 | { |
39 | | // 1. If this.[[closeRequested]] is true, throw a TypeError exception. |
40 | 0 | if (m_close_requested) |
41 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Controller is already closed"sv }; |
42 | | |
43 | | // 2. If this.[[stream]].[[state]] is not "readable", throw a TypeError exception. |
44 | 0 | if (m_stream->state() != ReadableStream::State::Readable) { |
45 | 0 | auto message = m_stream->state() == ReadableStream::State::Closed ? "Cannot close a closed stream"sv : "Cannot close an errored stream"sv; |
46 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, message }; |
47 | 0 | } |
48 | | |
49 | | // 3. Perform ? ReadableByteStreamControllerClose(this). |
50 | 0 | TRY(readable_byte_stream_controller_close(*this)); |
51 | |
|
52 | 0 | return {}; |
53 | 0 | } |
54 | | |
55 | | // https://streams.spec.whatwg.org/#rbs-controller-error |
56 | | void ReadableByteStreamController::error(JS::Value error) |
57 | 0 | { |
58 | | // 1. Perform ! ReadableByteStreamControllerError(this, e). |
59 | 0 | readable_byte_stream_controller_error(*this, error); |
60 | 0 | } |
61 | | |
62 | | ReadableByteStreamController::ReadableByteStreamController(JS::Realm& realm) |
63 | 0 | : Bindings::PlatformObject(realm) |
64 | 0 | { |
65 | 0 | } |
66 | | |
67 | | void ReadableByteStreamController::initialize(JS::Realm& realm) |
68 | 0 | { |
69 | 0 | Base::initialize(realm); |
70 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(ReadableByteStreamController); |
71 | 0 | } |
72 | | |
73 | | // https://streams.spec.whatwg.org/#rbs-controller-enqueue |
74 | | WebIDL::ExceptionOr<void> ReadableByteStreamController::enqueue(JS::Handle<WebIDL::ArrayBufferView>& chunk) |
75 | 0 | { |
76 | | // 1. If chunk.[[ByteLength]] is 0, throw a TypeError exception. |
77 | | // 2. If chunk.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is 0, throw a TypeError exception. |
78 | 0 | if (chunk->byte_length() == 0 || chunk->viewed_array_buffer()->byte_length() == 0) |
79 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Cannot enqueue chunk with byte length of zero"sv }; |
80 | | |
81 | | // 3. If this.[[closeRequested]] is true, throw a TypeError exception. |
82 | 0 | if (m_close_requested) |
83 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Close is requested for controller"sv }; |
84 | | |
85 | | // 4. If this.[[stream]].[[state]] is not "readable", throw a TypeError exception. |
86 | 0 | if (!m_stream->is_readable()) |
87 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Stream is not readable"sv }; |
88 | | |
89 | | // 5. Return ? ReadableByteStreamControllerEnqueue(this, chunk). |
90 | 0 | return readable_byte_stream_controller_enqueue(*this, chunk->raw_object()); |
91 | 0 | } |
92 | | |
93 | | // https://streams.spec.whatwg.org/#rbs-controller-private-cancel |
94 | | JS::NonnullGCPtr<WebIDL::Promise> ReadableByteStreamController::cancel_steps(JS::Value reason) |
95 | 0 | { |
96 | | // 1. Perform ! ReadableByteStreamControllerClearPendingPullIntos(this). |
97 | 0 | readable_byte_stream_controller_clear_pending_pull_intos(*this); |
98 | | |
99 | | // 2. Perform ! ResetQueue(this). |
100 | 0 | reset_queue(*this); |
101 | | |
102 | | // 3. Let result be the result of performing this.[[cancelAlgorithm]], passing in reason. |
103 | 0 | auto result = m_cancel_algorithm->function()(reason); |
104 | | |
105 | | // 4. Perform ! ReadableByteStreamControllerClearAlgorithms(this). |
106 | 0 | readable_byte_stream_controller_clear_algorithms(*this); |
107 | | |
108 | | // 5. Return result. |
109 | 0 | return result; |
110 | 0 | } |
111 | | |
112 | | // https://streams.spec.whatwg.org/#rbs-controller-private-pull |
113 | | void ReadableByteStreamController::pull_steps(JS::NonnullGCPtr<ReadRequest> read_request) |
114 | 0 | { |
115 | 0 | auto& realm = this->realm(); |
116 | | |
117 | | // 1. Let stream be this.[[stream]]. |
118 | | |
119 | | // 2. Assert: ! ReadableStreamHasDefaultReader(stream) is true. |
120 | 0 | VERIFY(readable_stream_has_default_reader(*m_stream)); |
121 | | |
122 | | // 3. If this.[[queueTotalSize]] > 0, |
123 | 0 | if (m_queue_total_size > 0) { |
124 | | // 1. Assert: ! ReadableStreamGetNumReadRequests(stream) is 0. |
125 | 0 | VERIFY(readable_stream_get_num_read_requests(*m_stream) == 0); |
126 | | |
127 | | // 2. Perform ! ReadableByteStreamControllerFillReadRequestFromQueue(this, readRequest). |
128 | 0 | readable_byte_stream_controller_fill_read_request_from_queue(*this, read_request); |
129 | | |
130 | | // 3. Return. |
131 | 0 | return; |
132 | 0 | } |
133 | | |
134 | | // 4. Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]]. |
135 | | |
136 | | // 5. If autoAllocateChunkSize is not undefined, |
137 | 0 | if (m_auto_allocate_chunk_size.has_value()) { |
138 | | // 1. Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize »). |
139 | 0 | auto buffer = JS::ArrayBuffer::create(realm, *m_auto_allocate_chunk_size); |
140 | | |
141 | | // 2. If buffer is an abrupt completion, |
142 | 0 | if (buffer.is_throw_completion()) { |
143 | | // 1. Perform readRequest’s error steps, given buffer.[[Value]]. |
144 | 0 | read_request->on_error(*buffer.throw_completion().value()); |
145 | | |
146 | | // 2. Return. |
147 | 0 | return; |
148 | 0 | } |
149 | | |
150 | | // 3. Let pullIntoDescriptor be a new pull-into descriptor with buffer buffer.[[Value]], buffer byte length autoAllocateChunkSize, byte offset 0, |
151 | | // byte length autoAllocateChunkSize, bytes filled 0, element size 1, view constructor %Uint8Array%, and reader type "default". |
152 | 0 | PullIntoDescriptor pull_into_descriptor { |
153 | 0 | .buffer = buffer.release_value(), |
154 | 0 | .buffer_byte_length = *m_auto_allocate_chunk_size, |
155 | 0 | .byte_offset = 0, |
156 | 0 | .byte_length = *m_auto_allocate_chunk_size, |
157 | 0 | .bytes_filled = 0, |
158 | 0 | .minimum_fill = 1, |
159 | 0 | .element_size = 1, |
160 | 0 | .view_constructor = *realm.intrinsics().uint8_array_constructor(), |
161 | 0 | .reader_type = ReaderType::Default, |
162 | 0 | }; |
163 | | |
164 | | // 4. Append pullIntoDescriptor to this.[[pendingPullIntos]]. |
165 | 0 | m_pending_pull_intos.append(move(pull_into_descriptor)); |
166 | 0 | } |
167 | | |
168 | | // 6. Perform ! ReadableStreamAddReadRequest(stream, readRequest). |
169 | 0 | readable_stream_add_read_request(*m_stream, read_request); |
170 | | |
171 | | // 7. Perform ! ReadableByteStreamControllerCallPullIfNeeded(this). |
172 | 0 | readable_byte_stream_controller_call_pull_if_needed(*this); |
173 | 0 | } |
174 | | |
175 | | // https://streams.spec.whatwg.org/#rbs-controller-private-pull |
176 | | void ReadableByteStreamController::release_steps() |
177 | 0 | { |
178 | | // 1. If this.[[pendingPullIntos]] is not empty, |
179 | 0 | if (!m_pending_pull_intos.is_empty()) { |
180 | | // 1. Let firstPendingPullInto be this.[[pendingPullIntos]][0]. |
181 | 0 | auto first_pending_pull_into = m_pending_pull_intos.first(); |
182 | | |
183 | | // 2. Set firstPendingPullInto’s reader type to "none". |
184 | 0 | first_pending_pull_into.reader_type = ReaderType::None; |
185 | | |
186 | | // 3. Set this.[[pendingPullIntos]] to the list « firstPendingPullInto ». |
187 | 0 | m_pending_pull_intos.clear(); |
188 | 0 | m_pending_pull_intos.append(first_pending_pull_into); |
189 | 0 | } |
190 | 0 | } |
191 | | |
192 | | void ReadableByteStreamController::visit_edges(Cell::Visitor& visitor) |
193 | 0 | { |
194 | 0 | Base::visit_edges(visitor); |
195 | 0 | visitor.visit(m_byob_request); |
196 | 0 | for (auto const& pending_pull_into : m_pending_pull_intos) { |
197 | 0 | visitor.visit(pending_pull_into.buffer); |
198 | 0 | visitor.visit(pending_pull_into.view_constructor); |
199 | 0 | } |
200 | 0 | for (auto const& item : m_queue) |
201 | 0 | visitor.visit(item.buffer); |
202 | 0 | visitor.visit(m_stream); |
203 | 0 | visitor.visit(m_cancel_algorithm); |
204 | 0 | visitor.visit(m_pull_algorithm); |
205 | 0 | } |
206 | | |
207 | | } |