Skip to main content

script/dom/stream/
readablestreambyobreader.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::collections::VecDeque;
7use std::mem;
8use std::rc::Rc;
9
10use dom_struct::dom_struct;
11use js::context::JSContext;
12use js::gc::CustomAutoRooterGuard;
13use js::jsapi::Heap;
14use js::jsval::{JSVal, UndefinedValue};
15use js::realm::CurrentRealm;
16use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
17use js::typedarray::{ArrayBufferView, ArrayBufferViewU8};
18use script_bindings::cell::DomRefCell;
19use script_bindings::reflector::{Reflector, reflect_dom_object, reflect_dom_object_with_proto};
20use script_bindings::root::Dom;
21
22use super::byteteereadintorequest::ByteTeeReadIntoRequest;
23use super::readablebytestreamcontroller::ReadableByteStreamController;
24use super::readablestreamgenericreader::ReadableStreamGenericReader;
25use crate::dom::bindings::buffer_source::HeapBufferSource;
26use crate::dom::bindings::codegen::Bindings::ReadableStreamBYOBReaderBinding::{
27    ReadableStreamBYOBReaderMethods, ReadableStreamBYOBReaderReadOptions,
28};
29use crate::dom::bindings::codegen::Bindings::ReadableStreamDefaultReaderBinding::ReadableStreamReadResult;
30use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible};
31use crate::dom::bindings::reflector::DomGlobal;
32use crate::dom::bindings::root::{DomRoot, MutNullableDom};
33use crate::dom::bindings::trace::RootedTraceableBox;
34use crate::dom::globalscope::GlobalScope;
35use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
36use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
37use crate::dom::stream::readablestream::ReadableStream;
38use crate::realms::enter_auto_realm;
39
40/// <https://streams.spec.whatwg.org/#read-into-request>
41#[derive(Clone, JSTraceable, MallocSizeOf)]
42#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
43pub enum ReadIntoRequest {
44    /// <https://streams.spec.whatwg.org/#byob-reader-read>
45    Read(TracedPromise),
46    ByteTee {
47        byte_tee_read_into_request: Dom<ByteTeeReadIntoRequest>,
48    },
49}
50
51impl js::gc::Rootable for ReadIntoRequest {}
52
53impl ReadIntoRequest {
54    /// <https://streams.spec.whatwg.org/#ref-for-read-into-request-chunk-steps%E2%91%A0>
55    pub fn chunk_steps(&self, cx: &mut JSContext, chunk: RootedTraceableBox<Heap<JSVal>>) {
56        match self {
57            ReadIntoRequest::Read(promise) => {
58                // chunk steps, given chunk
59                // Resolve promise with «[ "value" → chunk, "done" → false ]».
60                promise.resolve_native(
61                    cx,
62                    &ReadableStreamReadResult {
63                        done: Some(false),
64                        value: chunk.into_box(),
65                    },
66                );
67            },
68            ReadIntoRequest::ByteTee {
69                byte_tee_read_into_request,
70            } => {
71                rooted!(&in(cx) let chunk_object = chunk.get().to_object());
72                byte_tee_read_into_request.enqueue_chunk_steps(
73                    cx,
74                    RootedTraceableBox::new(HeapBufferSource::<ArrayBufferViewU8>::new(
75                        chunk_object.handle(),
76                    )),
77                )
78            },
79        }
80    }
81
82    /// <https://streams.spec.whatwg.org/#ref-for-read-into-request-close-steps%E2%91%A0>
83    pub fn close_steps(&self, cx: &mut JSContext, chunk: Option<RootedTraceableBox<Heap<JSVal>>>) {
84        match self {
85            ReadIntoRequest::Read(promise) => match chunk {
86                // close steps, given chunk
87                // Resolve promise with «[ "value" → chunk, "done" → true ]».
88                Some(chunk) => promise.resolve_native(
89                    cx,
90                    &ReadableStreamReadResult {
91                        done: Some(true),
92                        value: chunk.into_box(),
93                    },
94                ),
95                None => {
96                    promise.resolve_native(
97                        cx,
98                        &ReadableStreamReadResult {
99                            done: Some(true),
100                            value: Heap::boxed(UndefinedValue()),
101                        },
102                    );
103                },
104            },
105            ReadIntoRequest::ByteTee {
106                byte_tee_read_into_request,
107            } => match chunk {
108                Some(chunk) => {
109                    rooted!(&in(cx) let chunk_object = chunk.get().to_object());
110                    byte_tee_read_into_request
111                        .close_steps(
112                            cx,
113                            Some(RootedTraceableBox::new(
114                                HeapBufferSource::<ArrayBufferViewU8>::new(chunk_object.handle()),
115                            )),
116                        )
117                        .expect("close steps should not fail")
118                },
119                None => byte_tee_read_into_request
120                    .close_steps(cx, None)
121                    .expect("close steps should not fail"),
122            },
123        }
124    }
125
126    /// <https://streams.spec.whatwg.org/#ref-for-read-into-request-error-steps%E2%91%A0>
127    pub(crate) fn error_steps(&self, cx: &mut JSContext, e: SafeHandleValue) {
128        match self {
129            ReadIntoRequest::Read(promise) => {
130                // error steps, given e
131                // Reject promise with e.
132                promise.reject_native(cx, &e)
133            },
134            ReadIntoRequest::ByteTee {
135                byte_tee_read_into_request,
136            } => {
137                byte_tee_read_into_request.error_steps();
138            },
139        }
140    }
141}
142
143/// The rejection handler for
144/// <https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee>
145#[derive(Clone, JSTraceable, MallocSizeOf)]
146#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
147struct ByteTeeClosedPromiseRejectionHandler {
148    branch_1_controller: Dom<ReadableByteStreamController>,
149    branch_2_controller: Dom<ReadableByteStreamController>,
150    #[conditional_malloc_size_of]
151    canceled_1: Rc<Cell<bool>>,
152    #[conditional_malloc_size_of]
153    canceled_2: Rc<Cell<bool>>,
154    cancel_promise: TracedPromise,
155    #[conditional_malloc_size_of]
156    reader_version: Rc<Cell<u64>>,
157    expected_version: u64,
158}
159
160impl Callback for ByteTeeClosedPromiseRejectionHandler {
161    /// Continuation of <https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee>
162    /// Upon rejection of `reader.closedPromise` with reason `r``,
163    fn callback(&self, cx: &mut CurrentRealm, v: SafeHandleValue) {
164        // If thisReader is not reader, return.
165        if self.reader_version.get() != self.expected_version {
166            return;
167        }
168
169        // Perform ! ReadableByteStreamControllerError(branch1.[[controller]], r).
170        self.branch_1_controller.error(cx, v);
171
172        // Perform ! ReadableByteStreamControllerError(branch2.[[controller]], r).
173        self.branch_2_controller.error(cx, v);
174
175        // If canceled1 is false or canceled2 is false, resolve cancelPromise with undefined.
176        if !self.canceled_1.get() || !self.canceled_2.get() {
177            self.cancel_promise.resolve_native(cx, &());
178        }
179    }
180}
181
182/// <https://streams.spec.whatwg.org/#readablestreambyobreader>
183#[dom_struct]
184pub(crate) struct ReadableStreamBYOBReader {
185    reflector_: Reflector,
186
187    /// <https://streams.spec.whatwg.org/#readablestreamgenericreader-stream>
188    stream: MutNullableDom<ReadableStream>,
189
190    read_into_requests: DomRefCell<VecDeque<ReadIntoRequest>>,
191
192    /// <https://streams.spec.whatwg.org/#readablestreamgenericreader-closedpromise>
193    closed_promise: DomRefCell<TracedPromise>,
194}
195
196impl ReadableStreamBYOBReader {
197    fn new_with_proto(
198        cx: &mut JSContext,
199        global: &GlobalScope,
200        proto: Option<SafeHandleObject>,
201    ) -> DomRoot<ReadableStreamBYOBReader> {
202        let closed_promise = Promise::new(cx, global);
203        reflect_dom_object_with_proto(
204            cx,
205            Box::new(ReadableStreamBYOBReader::new_inherited(&closed_promise)),
206            global,
207            proto,
208        )
209    }
210
211    fn new_inherited(promise: &RootedPromise) -> ReadableStreamBYOBReader {
212        ReadableStreamBYOBReader {
213            reflector_: Reflector::new(),
214            stream: MutNullableDom::new(None),
215            read_into_requests: DomRefCell::new(Default::default()),
216            closed_promise: DomRefCell::new(promise.to_traced()),
217        }
218    }
219
220    pub(crate) fn new(
221        cx: &mut JSContext,
222        global: &GlobalScope,
223    ) -> DomRoot<ReadableStreamBYOBReader> {
224        let closed_promise = Promise::new(cx, global);
225        reflect_dom_object(cx, Box::new(Self::new_inherited(&closed_promise)), global)
226    }
227
228    /// <https://streams.spec.whatwg.org/#set-up-readable-stream-byob-reader>
229    pub(crate) fn set_up(
230        &self,
231        cx: &mut JSContext,
232        stream: &ReadableStream,
233        global: &GlobalScope,
234    ) -> Fallible<()> {
235        // If ! IsReadableStreamLocked(stream) is true, throw a TypeError exception.
236        if stream.is_locked() {
237            return Err(Error::Type(c"stream is locked".to_owned()));
238        }
239
240        // If stream.[[controller]] does not implement ReadableByteStreamController, throw a TypeError exception.
241        if !stream.has_byte_controller() {
242            return Err(Error::Type(
243                c"stream controller is not a byte stream controller".to_owned(),
244            ));
245        }
246
247        // Perform ! ReadableStreamReaderGenericInitialize(reader, stream).
248        self.generic_initialize(cx, global, stream);
249
250        // Set reader.[[readIntoRequests]] to a new empty list.
251        self.read_into_requests.borrow_mut().clear();
252
253        Ok(())
254    }
255
256    /// <https://streams.spec.whatwg.org/#abstract-opdef-readablestreambyobreaderrelease>
257    pub(crate) fn release(&self, cx: &mut JSContext) -> Fallible<()> {
258        // Perform ! ReadableStreamReaderGenericRelease(reader).
259        self.generic_release(cx).expect("Generic release failed");
260        // Let e be a new TypeError exception.
261        rooted!(&in(cx) let mut error = UndefinedValue());
262        Error::Type(c"Reader is released".to_owned()).to_jsval(
263            cx,
264            &self.global(),
265            error.handle_mut(),
266        );
267
268        // Perform ! ReadableStreamBYOBReaderErrorReadIntoRequests(reader, e).
269        self.error_read_into_requests(cx, error.handle());
270        Ok(())
271    }
272
273    /// <https://streams.spec.whatwg.org/#abstract-opdef-readablestreambyobreadererrorreadintorequests>
274    pub(crate) fn error_read_into_requests(&self, cx: &mut JSContext, e: SafeHandleValue) {
275        // Reject reader.[[closedPromise]] with e.
276        self.closed_promise.borrow().reject_native(cx, &e);
277
278        // Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
279        self.closed_promise.borrow().set_promise_is_handled(cx);
280
281        // Let readRequests be reader.[[readRequests]].
282        rooted!(&in(cx) let read_into_requests = self.take_read_into_requests());
283
284        // Set reader.[[readIntoRequests]] to a new empty list.
285        for request in read_into_requests.iter() {
286            // Perform readIntoRequest’s error steps, given e.
287            request.error_steps(cx, e);
288        }
289    }
290
291    fn take_read_into_requests(&self) -> VecDeque<ReadIntoRequest> {
292        mem::take(&mut *self.read_into_requests.borrow_mut())
293    }
294
295    /// <https://streams.spec.whatwg.org/#readable-stream-add-read-into-request>
296    pub(crate) fn add_read_into_request(&self, read_request: &ReadIntoRequest) {
297        self.read_into_requests
298            .borrow_mut()
299            .push_back(read_request.clone());
300    }
301
302    /// <https://streams.spec.whatwg.org/#readable-stream-cancel>
303    pub(crate) fn cancel(&self, cx: &mut JSContext) {
304        // If reader is not undefined and reader implements ReadableStreamBYOBReader,
305        // Let readIntoRequests be reader.[[readIntoRequests]].
306        rooted!(&in(cx) let read_into_requests = self.take_read_into_requests());
307        // Set reader.[[readIntoRequests]] to an empty list.
308        // Perform readIntoRequest’s close steps, given undefined.
309        for request in read_into_requests.iter() {
310            // Perform readIntoRequest’s close steps, given undefined.
311            request.close_steps(cx, None);
312        }
313    }
314
315    pub(crate) fn close(&self, cx: &mut JSContext) {
316        // Resolve reader.[[closedPromise]] with undefined.
317        self.closed_promise.borrow().resolve_native(cx, &());
318    }
319
320    /// <https://streams.spec.whatwg.org/#readable-stream-byob-reader-read>
321    pub(crate) fn read(
322        &self,
323        cx: &mut JSContext,
324        view: &HeapBufferSource<ArrayBufferViewU8>,
325        min: u64,
326        read_into_request: &ReadIntoRequest,
327    ) {
328        // Let stream be reader.[[stream]].
329
330        // Assert: stream is not undefined.
331        assert!(self.stream.get().is_some());
332
333        let stream = self.stream.get().unwrap();
334
335        // Set stream.[[disturbed]] to true.
336        stream.set_is_disturbed(true);
337        // If stream.[[state]] is "errored", perform readIntoRequest’s error steps given stream.[[storedError]].
338        if stream.is_errored() {
339            rooted!(&in(cx) let mut error = UndefinedValue());
340            stream.get_stored_error(error.handle_mut());
341
342            read_into_request.error_steps(cx, error.handle());
343        } else {
344            // Otherwise,
345            // perform ! ReadableByteStreamControllerPullInto(stream.[[controller]], view, min, readIntoRequest).
346            stream.perform_pull_into(cx, read_into_request, view, min);
347        }
348    }
349
350    pub(crate) fn get_num_read_into_requests(&self) -> usize {
351        self.read_into_requests.borrow().len()
352    }
353
354    pub(crate) fn remove_read_into_request(&self) -> ReadIntoRequest {
355        self.read_into_requests
356            .borrow_mut()
357            .pop_front()
358            .expect("read into requests is empty")
359    }
360
361    #[allow(clippy::too_many_arguments)]
362    pub(crate) fn byte_tee_append_native_handler_to_closed_promise(
363        &self,
364        cx: &mut JSContext,
365        branch_1: &ReadableStream,
366        branch_2: &ReadableStream,
367        canceled_1: Rc<Cell<bool>>,
368        canceled_2: Rc<Cell<bool>>,
369        cancel_promise: &RootedPromise,
370        reader_version: Rc<Cell<u64>>,
371        expected_version: u64,
372    ) {
373        let branch_1_controller = branch_1.get_byte_controller();
374
375        let branch_2_controller = branch_2.get_byte_controller();
376
377        let global = self.global();
378        let handler = PromiseNativeHandler::new(
379            cx,
380            &global,
381            None,
382            Some(Box::new(ByteTeeClosedPromiseRejectionHandler {
383                branch_1_controller: Dom::from_ref(&branch_1_controller),
384                branch_2_controller: Dom::from_ref(&branch_2_controller),
385                canceled_1,
386                canceled_2,
387                cancel_promise: cancel_promise.to_traced(),
388                reader_version,
389                expected_version,
390            })),
391        );
392
393        let mut realm = enter_auto_realm(cx, &*global);
394        let cx = &mut realm.current_realm();
395
396        self.closed_promise
397            .borrow()
398            .append_native_handler(cx, &handler);
399    }
400}
401
402impl ReadableStreamBYOBReaderMethods<crate::DomTypeHolder> for ReadableStreamBYOBReader {
403    /// <https://streams.spec.whatwg.org/#byob-reader-constructor>
404    fn Constructor(
405        cx: &mut JSContext,
406        global: &GlobalScope,
407        proto: Option<SafeHandleObject>,
408        stream: &ReadableStream,
409    ) -> Fallible<DomRoot<Self>> {
410        let reader = Self::new_with_proto(cx, global, proto);
411
412        // Perform ? SetUpReadableStreamBYOBReader(this, stream).
413        reader.set_up(cx, stream, global)?;
414
415        Ok(reader)
416    }
417
418    /// <https://streams.spec.whatwg.org/#byob-reader-read>
419    fn Read(
420        &self,
421        cx: &mut JSContext,
422        view: CustomAutoRooterGuard<ArrayBufferView>,
423        options: &ReadableStreamBYOBReaderReadOptions,
424    ) -> RootedPromise {
425        let view = HeapBufferSource::<ArrayBufferViewU8>::from_view(cx, view);
426        let min = options.min;
427        // Let promise be a new promise.
428        let promise = Promise::new(cx, &self.global());
429
430        // If view.[[ByteLength]] is 0, return a promise rejected with a TypeError exception.
431        if view.byte_length() == 0 {
432            promise.reject_error(cx, Error::Type(c"view byte length is 0".to_owned()));
433            return promise;
434        }
435        // If view.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is 0,
436        // return a promise rejected with a TypeError exception.
437        if view.viewed_buffer_array_byte_length(cx) == 0 {
438            promise.reject_error(
439                cx,
440                Error::Type(c"viewed buffer byte length is 0".to_owned()),
441            );
442            return promise;
443        }
444
445        // If ! IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true,
446        // return a promise rejected with a TypeError exception.
447        if view.is_detached_buffer(cx) {
448            promise.reject_error(cx, Error::Type(c"view is detached".to_owned()));
449            return promise;
450        }
451
452        // If options["min"] is 0, return a promise rejected with a TypeError exception.
453        if min == 0 {
454            promise.reject_error(cx, Error::Type(c"min is 0".to_owned()));
455            return promise;
456        }
457
458        // If view has a [[TypedArrayName]] internal slot,
459        if view.has_typed_array_name() {
460            // If options["min"] > view.[[ArrayLength]], return a promise rejected with a RangeError exception.
461            if min > (view.get_typed_array_length() as u64) {
462                promise.reject_error(
463                    cx,
464                    Error::Range(c"min is greater than array length".to_owned()),
465                );
466                return promise;
467            }
468        } else {
469            // Otherwise (i.e., it is a DataView),
470            // If options["min"] > view.[[ByteLength]], return a promise rejected with a RangeError exception.
471            if min > (view.byte_length() as u64) {
472                promise.reject_error(
473                    cx,
474                    Error::Range(c"min is greater than byte length".to_owned()),
475                );
476                return promise;
477            }
478        }
479
480        // If this.[[stream]] is undefined, return a promise rejected with a TypeError exception.
481        if self.stream.get().is_none() {
482            promise.reject_error(
483                cx,
484                Error::Type(c"min is greater than byte length".to_owned()),
485            );
486            return promise;
487        }
488
489        // Let readIntoRequest be a new read-into request with the following items:
490        //
491        // chunk steps, given chunk
492        // Resolve promise with «[ "value" → chunk, "done" → false ]».
493        //
494        // close steps, given chunk
495        // Resolve promise with «[ "value" → chunk, "done" → true ]».
496        //
497        // error steps, given e
498        // Reject promise with e
499        rooted!(&in(cx) let read_into_request = ReadIntoRequest::Read(promise.to_traced()));
500
501        // Perform ! ReadableStreamBYOBReaderRead(this, view, options["min"], readIntoRequest).
502        self.read(cx, &view, min, &read_into_request);
503
504        // Return promise.
505        promise
506    }
507
508    /// <https://streams.spec.whatwg.org/#byob-reader-release-lock>
509    fn ReleaseLock(&self, cx: &mut JSContext) -> Fallible<()> {
510        if self.stream.get().is_none() {
511            // If this.[[stream]] is undefined, return.
512            return Ok(());
513        }
514
515        // Perform !ReadableStreamBYOBReaderRelease(this).
516        self.release(cx)
517    }
518
519    /// <https://streams.spec.whatwg.org/#generic-reader-closed>
520    fn Closed(&self, cx: &JSContext) -> RootedPromise {
521        self.closed(cx)
522    }
523
524    /// <https://streams.spec.whatwg.org/#generic-reader-cancel>
525    fn Cancel(&self, cx: &mut JSContext, reason: SafeHandleValue) -> RootedPromise {
526        self.generic_cancel(cx, &self.global(), reason)
527    }
528}
529
530impl ReadableStreamGenericReader for ReadableStreamBYOBReader {
531    fn get_closed_promise(&self, cx: &JSContext) -> RootedPromise {
532        self.closed_promise.borrow().root(cx)
533    }
534
535    fn set_closed_promise(&self, promise: &RootedPromise) {
536        *self.closed_promise.borrow_mut() = promise.to_traced();
537    }
538
539    fn set_stream(&self, stream: Option<&ReadableStream>) {
540        self.stream.set(stream);
541    }
542
543    fn get_stream(&self) -> Option<DomRoot<ReadableStream>> {
544        self.stream.get()
545    }
546
547    fn as_byob_reader(&self) -> Option<&ReadableStreamBYOBReader> {
548        Some(self)
549    }
550}