mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
* Re-implement readablestream: basics and default reader and controller --------- Co-authored-by: Jason Tsai <jason@pews.dev> Signed-off-by: Wu Wayne <yuweiwu@pm.me> Add remaining WebIDLs of ReadableStream (#32605) * Add Reader's WebIDL files * Add necessary methods in ReadableStream.webidl Signed-off-by: Wu Wayne <yuweiwu@pm.me> Create safe wrapper for JSFunctions (#32620) * Create safe wrapper for JSFunctions Signed-off-by: Bentaimia Haddadi <haddadi.taym@gmail.com> * Add assert to check if the name ends in a null character Signed-off-by: Bentaimia Haddadi <haddadi.taym@gmail.com> * Create macro to wrap unsafe extern "C" function calls Signed-off-by: Bentaimia Haddadi <haddadi.taym@gmail.com> * Remove WRAPPER_FN Signed-off-by: Bentaimia Haddadi <haddadi.taym@gmail.com> * Add macro example documentation Signed-off-by: Bentaimia Haddadi <haddadi.taym@gmail.com> * Use C-string literals Signed-off-by: Bentaimia Haddadi <haddadi.taym@gmail.com> * Ensure name is Cstr type Signed-off-by: Bentaimia Haddadi <haddadi.taym@gmail.com> * Scope #[allow(unsafe_code)] Signed-off-by: Bentaimia Haddadi <haddadi.taym@gmail.com> --------- Signed-off-by: Bentaimia Haddadi <haddadi.taym@gmail.com> Signed-off-by: Wu Wayne <yuweiwu@pm.me> Start implementation of default controller and reader Start implementation of default controller and reader * implement basic internal slots, with todos Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * enum for controller Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * re-implement native controller methods Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * add calling into pull algo Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * more details on chunk enqueuing Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * add fulfill read request, clean-up warnings Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * read request and reader typing Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * allow for more than one non-native underlying source type Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * add todo for should pull Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * add underlying source dom struct container Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * remove rc around source type * add default controller init in stream constructor * setup source container with prototype of source dict * clean-up docs, dispatch of controller in pull algo call * turn off SM streams * remove prototype setting on underlying source container * fix read request promise resolving * tidy * clean-up js conversions in read req handlers * add queue with sizes concept * use dom in pull promise handlers * Demonstrate using dictionary as callback this object. * move value with size to a struct * fmt * put readable stream state in a cell * nits in expectations * remove allow unroot by passing read result directly to promise resolving * tidy * root default controller inside call_pull_if_needed --------- Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Co-authored-by: Josh Matthews <josh@joshmatthews.net> Signed-off-by: Wu Wayne <yuweiwu@pm.me> ReadableStream: implement Cancel and Locked (#33136) * implement Locked * implement Cancel and close Signed-off-by: Wu Wayne <yuweiwu@pm.me> Add GetPromiseIsHandled and SetAnyPromiseIsHandled to Promise Signed-off-by: Taym <haddadi.taym@gmail.com> mach fmt Signed-off-by: Taym <haddadi.taym@gmail.com> Readablestream default controller: get desired size (#33497) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> stream: implement controller close (#33498) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> implement stream default controller error (#33503) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Readablestream default controller: enqueue (#33528) * Implement ReadableStreamDefaultControllerMethods::Enqueue Signed-off-by: Wu Wayne <yuweiwu@pm.me> * Add spec comments Signed-off-by: Wu Wayne <yuweiwu@pm.me> --------- Signed-off-by: Wu Wayne <yuweiwu@pm.me> readablestream default controller: fulfill read requests (#33542) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Fix extract_size_algorithm (#33561) Signed-off-by: Wu Wayne <yuweiwu@pm.me> Readablestream default controller: use strategy size (#33551) * readablestream default controller: use strategy size, fallible enqueue Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> docs * readablestream default controller: clear strategy size Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * prevent potential re-borrow panics when calling into the strategy size Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * document readablestream constructor Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> --------- Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Readablestream: impl default controller should pull, start algo (#33586) * implement should-pull algo for default controller Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * add start algorithm setup for default controller Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> --------- Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> implement promise native handling for start and pull algorithms (#33603) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Implement ReadableStreamDefaultReader (#33160) * Implement ReadableStreamDefaultReader Make the stream mutable readable-stream-reader-generic-release Proper error types when releasing Closed Cancel Signed-off-by: Taym <haddadi.taym@gmail.com> * follow the spec more closely Signed-off-by: Taym <haddadi.taym@gmail.com> --------- Signed-off-by: Taym <haddadi.taym@gmail.com> Implement ReadableStreamDefaultReader read (#34007) * Implement ReadableStreamDefaultReader read Signed-off-by: Taym <haddadi.taym@gmail.com> * Perform readRequest’s error steps with stream.stored_error Signed-off-by: Taym <haddadi.taym@gmail.com> --------- Signed-off-by: Taym <haddadi.taym@gmail.com> Improve ReadableStreamDefaultReader close (#34014) * improve ReadableStreamDefaultReader close Signed-off-by: Taym <haddadi.taym@gmail.com> * remove resolve_closed_promise Signed-off-by: Taym <haddadi.taym@gmail.com> --------- Signed-off-by: Taym <haddadi.taym@gmail.com> Use Rc<Box<[u8]>> for queue to optimize get_in_memory_bytes Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> * Improve read_a_chunk and stop_reading implemntation (#34077) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Implement ReadableStreamDefaultReader::Constructor (#34056) * Implement ReadableStreamDefaultReader::Constructor Signed-off-by: Taym <haddadi.taym@gmail.com> * make start_reading returns ReadableStreamDefaultReader Signed-off-by: Taym <haddadi.taym@gmail.com> * Fix can_gc Signed-off-by: Taym <haddadi.taym@gmail.com> * Add canGc to ReadableStream::GetReader Signed-off-by: Taym <haddadi.taym@gmail.com> --------- Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Readablestream fix CanGc (#34080) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * correct ReadableStream::error_native implementation and fix clippy warnings (#34088) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * turn assertion of stream present on controller on a early return with false (#34097) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Fix already mutably borrowed crash (#34105) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Refactor `get_in_memory_bytes` to return `Option<Vec<u8>> and avoid `unreachable!` panic (#34123) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Set ReadableStream ReadableStreamDefaultReader in start_reading (#34125) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Fix Unhandled rejection with value: object `TypeError: stream is not locked` (#34204) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Fix assert!(self.is_readable()) crash in ReadableStream::close (#34207) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * fix call to to_js_object in underlying source algos (#34098) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * do not assume presence of a stream when performing pull steps (#34244) * do not assume presence of a stream when performing pull steps Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * add doc comments Co-authored-by: Taym Haddadi <haddadi.taym@gmail.com> Signed-off-by: Gregory Terzian <2792687+gterzian@users.noreply.github.com> --------- Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Gregory Terzian <2792687+gterzian@users.noreply.github.com> Co-authored-by: Taym Haddadi <haddadi.taym@gmail.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * gracefully handle failure of underlying source algorithms (#34243) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * ensure result of calling start algo is an object (#34245) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * return js failed error if underlying source constructor threw (#34246) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Use JSVal for ValueWithSize::value (#34259) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * fix release reader lock, (#34255) fix setting stream on controller in new, fix matching fallthrough, reduce visibility of controller error method Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * in stream cancel, reject promist if locked (#34271) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Fix UnderlyingSourceContainer::call_start_algorithm (#34277) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * implement controller cancel steps, fix stream cancel method (#34301) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * fix conditional in perform pull steps (#34324) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * set reader closed promise to one resolved with undefined if stream closed on init (#34321) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * fix init of stream and controller (#34323) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Stream: Fix reborrow in controller enqueue, and fix error and exception handling. (#34338) * fix re-borrow in controller enqueue Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * do not call to_jsval on JSFailed error in enqueue Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * fix error and exception handling in controller enqueue Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * remove TODO about correctness of stored error, since this was done as part of the switch to a js val. Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> --------- Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Stream: Fix incorrect "this" object in underlying source callbacks (#34368) * in controller close, throw type error if stream cannot be closed Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * store original js object for underlying source, for use as this object in callbacks Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> --------- Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * fix conditional logic in enqueue to ensure pull is called into (#34375) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Stream: Fix bytelength queueing strategy (#34376) * fix handling of value that is not an object in bytelength queuing strategy Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * return type error if strategy size call fails, to prevent panic because no exception is pending Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> --------- Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * set correct default count queuing size strategy (#34389) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * use proto in stream constructor (#34441) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * fix edge cases in get_desired_size (#34440) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Stream: fix algo and strategy calls error handling. (#34424) * fix error handling in cancel steps Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * in pull steps, reject promise if pull algo throws Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * if start algorithm fails, rethrow the error Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * when the strategy size fails, directly get the pending exception and use it to error the stream Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * add error handling to enqueue value with size Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * when enqueueing a value errors, ensure we error and stream with the same error used to throw an exception Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> --------- Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * fix native use of streams (#34468) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Implement readablestreamdefaulttee (#34405) * Implement readablestreamdefaulttee Signed-off-by: Taym <haddadi.taym@gmail.com> * Create UnderlyingSourceType::Tee each stream Signed-off-by: Taym <haddadi.taym@gmail.com> * Use Dom instead of DomRoot Signed-off-by: Taym <haddadi.taym@gmail.com> * Queue a microtask for readRequest chunk steps Signed-off-by: Taym <haddadi.taym@gmail.com> * fix create_readable_stream Signed-off-by: Taym <haddadi.taym@gmail.com> * Remove unnecessary Rc Signed-off-by: Taym <haddadi.taym@gmail.com> * Use correct doc link Signed-off-by: Taym <haddadi.taym@gmail.com> * Add #[allow(crown::unrooted_must_root)] Signed-off-by: Taym <haddadi.taym@gmail.com> * Fix crash in ClosedPromiseRejectionHandler Signed-off-by: Taym <haddadi.taym@gmail.com> * reflect TeeReadRequest and TeeUnderlyingSource Signed-off-by: Taym <haddadi.taym@gmail.com> * fix can_gc Signed-off-by: Taym <haddadi.taym@gmail.com> * reflect tee source, and fix use of mutable dom for tee source and request Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * Fix typo that resolves multiple test failures in 'Tee' tests Signed-off-by: Taym <haddadi.taym@gmail.com> * Fix readable-streams/tee.any.js test Signed-off-by: Taym <haddadi.taym@gmail.com> --------- Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Co-authored-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Align ReadableStreamDefaultReader with spec and fix additional tests in default-reader.any.js (#34531) And fix crate::DomTypeHolder usage * Align ReadableStreamDefaultReader with spec and fix additional tests in default-reader.any.js Signed-off-by: Taym <haddadi.taym@gmail.com> * make reader rooted in Constructor and acquire_default_reader Signed-off-by: Taym <haddadi.taym@gmail.com> * Remove spaces Signed-off-by: Taym <haddadi.taym@gmail.com> --------- Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Streams: fetch stream chunks should be uint8 arrays (#34553) * fetch stream chunks should be uint8 arrays Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * fix clippy Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> --------- Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> Co-authored-by: Taym Haddadi <haddadi.taym@gmail.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Update wpt test for ReadableStream reimplementation (#34579) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Fix ignore_malloc_size_of in readablestream tee (#34578) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Remove incorrect use of handle array, fail test safely by giving only one reason (#34560) Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Update more wpt test for ReadableStream reimplementation (#34598) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Fix doc and rename Tee to DefaultTee (#34612) Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * fix: Address review comments Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Update response-stream-with-broken-then.any.js.ini test expectation Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * fix reflect_dom_object can_gc Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Fix compositeReason for DefaultTeeUnderlyingSource (#34627) * Fix compositeReason for DefaultTeeUnderlyingSource Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Update test Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> --------- Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * Last fixes stream (#34636) * remove now unsused from_js method of readable stream * fix documenation of error steps * return type error instread of panicking on a todo, when trying to construct a stream of type bytes Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> --------- Signed-off-by: Gregory Terzian <2792687+gterzian@users.noreply.github.com> * fix crown rooting related errors (#34662) Signed-off-by: Gregory Terzian <2792687+gterzian@users.noreply.github.com> --------- Signed-off-by: Taym <haddadi.taym@gmail.com> Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> Signed-off-by: Gregory Terzian <2792687+gterzian@users.noreply.github.com> Co-authored-by: Wu Wayne <yuweiwu@pm.me> Co-authored-by: Taym Haddadi <haddadi.taym@gmail.com>
924 lines
34 KiB
Rust
924 lines
34 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||
|
||
use std::rc::Rc;
|
||
use std::{ptr, str};
|
||
|
||
use encoding_rs::UTF_8;
|
||
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
|
||
use ipc_channel::router::ROUTER;
|
||
use js::jsapi::{Heap, JSObject, JS_ClearPendingException, Value as JSValue};
|
||
use js::jsval::{JSVal, UndefinedValue};
|
||
use js::rust::wrappers::{JS_GetPendingException, JS_ParseJSON};
|
||
use js::rust::HandleValue;
|
||
use js::typedarray::{ArrayBuffer, CreateWith};
|
||
use mime::{self, Mime};
|
||
use net_traits::request::{
|
||
BodyChunkRequest, BodyChunkResponse, BodySource as NetBodySource, RequestBody,
|
||
};
|
||
use script_traits::serializable::BlobImpl;
|
||
use url::form_urlencoded;
|
||
|
||
use crate::dom::bindings::cell::DomRefCell;
|
||
use crate::dom::bindings::codegen::Bindings::BlobBinding::Blob_Binding::BlobMethods;
|
||
use crate::dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
|
||
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
|
||
use crate::dom::bindings::error::{Error, Fallible};
|
||
use crate::dom::bindings::refcounted::Trusted;
|
||
use crate::dom::bindings::reflector::DomObject;
|
||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||
use crate::dom::bindings::str::{DOMString, USVString};
|
||
use crate::dom::bindings::trace::RootedTraceableBox;
|
||
use crate::dom::blob::{normalize_type_string, Blob};
|
||
use crate::dom::formdata::FormData;
|
||
use crate::dom::globalscope::GlobalScope;
|
||
use crate::dom::htmlformelement::{encode_multipart_form_data, generate_boundary};
|
||
use crate::dom::promise::Promise;
|
||
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
|
||
use crate::dom::readablestream::{get_read_promise_bytes, get_read_promise_done, ReadableStream};
|
||
use crate::dom::urlsearchparams::URLSearchParams;
|
||
use crate::realms::{enter_realm, AlreadyInRealm, InRealm};
|
||
use crate::script_runtime::{CanGc, JSContext};
|
||
use crate::task::TaskCanceller;
|
||
use crate::task_source::networking::NetworkingTaskSource;
|
||
use crate::task_source::{TaskSource, TaskSourceName};
|
||
|
||
/// The Dom object, or ReadableStream, that is the source of a body.
|
||
/// <https://fetch.spec.whatwg.org/#concept-body-source>
|
||
#[derive(Clone, PartialEq)]
|
||
pub enum BodySource {
|
||
/// A ReadableStream comes with a null-source.
|
||
Null,
|
||
/// Another Dom object as source,
|
||
/// TODO: store the actual object
|
||
/// and re-extract a stream on re-direct.
|
||
Object,
|
||
}
|
||
|
||
/// The reason to stop reading from the body.
|
||
enum StopReading {
|
||
/// The stream has errored.
|
||
Error,
|
||
/// The stream is done.
|
||
Done,
|
||
}
|
||
|
||
/// The IPC route handler
|
||
/// for <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
|
||
/// This route runs in the script process,
|
||
/// and will queue tasks to perform operations
|
||
/// on the stream and transmit body chunks over IPC.
|
||
#[derive(Clone)]
|
||
struct TransmitBodyConnectHandler {
|
||
stream: Trusted<ReadableStream>,
|
||
task_source: NetworkingTaskSource,
|
||
canceller: TaskCanceller,
|
||
bytes_sender: Option<IpcSender<BodyChunkResponse>>,
|
||
control_sender: IpcSender<BodyChunkRequest>,
|
||
in_memory: Option<Vec<u8>>,
|
||
in_memory_done: bool,
|
||
source: BodySource,
|
||
}
|
||
|
||
impl TransmitBodyConnectHandler {
|
||
pub fn new(
|
||
stream: Trusted<ReadableStream>,
|
||
task_source: NetworkingTaskSource,
|
||
canceller: TaskCanceller,
|
||
control_sender: IpcSender<BodyChunkRequest>,
|
||
in_memory: Option<Vec<u8>>,
|
||
source: BodySource,
|
||
) -> TransmitBodyConnectHandler {
|
||
TransmitBodyConnectHandler {
|
||
stream,
|
||
task_source,
|
||
canceller,
|
||
bytes_sender: None,
|
||
control_sender,
|
||
in_memory,
|
||
in_memory_done: false,
|
||
source,
|
||
}
|
||
}
|
||
|
||
/// Reset `in_memory_done`, called when a stream is
|
||
/// re-extracted from the source to support a re-direct.
|
||
pub fn reset_in_memory_done(&mut self) {
|
||
self.in_memory_done = false;
|
||
}
|
||
|
||
/// Re-extract the source to support streaming it again for a re-direct.
|
||
/// TODO: actually re-extract the source, instead of just cloning data, to support Blob.
|
||
fn re_extract(&mut self, chunk_request_receiver: IpcReceiver<BodyChunkRequest>) {
|
||
let mut body_handler = self.clone();
|
||
body_handler.reset_in_memory_done();
|
||
|
||
ROUTER.add_typed_route(
|
||
chunk_request_receiver,
|
||
Box::new(move |message| {
|
||
let request = message.unwrap();
|
||
match request {
|
||
BodyChunkRequest::Connect(sender) => {
|
||
body_handler.start_reading(sender);
|
||
},
|
||
BodyChunkRequest::Extract(receiver) => {
|
||
body_handler.re_extract(receiver);
|
||
},
|
||
BodyChunkRequest::Chunk => body_handler.transmit_source(),
|
||
// Note: this is actually sent from this process
|
||
// by the TransmitBodyPromiseHandler when reading stops.
|
||
BodyChunkRequest::Done => {
|
||
body_handler.stop_reading(StopReading::Done);
|
||
},
|
||
// Note: this is actually sent from this process
|
||
// by the TransmitBodyPromiseHandler when the stream errors.
|
||
BodyChunkRequest::Error => {
|
||
body_handler.stop_reading(StopReading::Error);
|
||
},
|
||
}
|
||
}),
|
||
);
|
||
}
|
||
|
||
/// In case of re-direct, and of a source available in memory,
|
||
/// send it all in one chunk.
|
||
///
|
||
/// TODO: this method should be deprecated
|
||
/// in favor of making `re_extract` actually re-extract a stream from the source.
|
||
/// See #26686
|
||
fn transmit_source(&mut self) {
|
||
if self.in_memory_done {
|
||
// Step 5.1.3
|
||
self.stop_reading(StopReading::Done);
|
||
return;
|
||
}
|
||
|
||
if let BodySource::Null = self.source {
|
||
panic!("ReadableStream(Null) sources should not re-direct.");
|
||
}
|
||
|
||
if let Some(bytes) = self.in_memory.clone() {
|
||
// The memoized bytes are sent so we mark it as done again
|
||
self.in_memory_done = true;
|
||
let _ = self
|
||
.bytes_sender
|
||
.as_ref()
|
||
.expect("No bytes sender to transmit source.")
|
||
.send(BodyChunkResponse::Chunk(bytes.clone()));
|
||
return;
|
||
}
|
||
warn!("Re-directs for file-based Blobs not supported yet.");
|
||
}
|
||
|
||
/// Take the IPC sender sent by `net`, so we can send body chunks with it.
|
||
/// Also the entry point to <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
|
||
fn start_reading(&mut self, sender: IpcSender<BodyChunkResponse>) {
|
||
self.bytes_sender = Some(sender);
|
||
|
||
// If we're using an actual ReadableStream, acquire a reader for it.
|
||
if self.source == BodySource::Null {
|
||
let stream = self.stream.clone();
|
||
let _ = self.task_source.queue_with_canceller(
|
||
task!(start_reading_request_body_stream: move || {
|
||
// Step 1, Let body be request’s body.
|
||
let rooted_stream = stream.root();
|
||
|
||
// TODO: Step 2, If body is null.
|
||
|
||
// Step 3, get a reader for stream.
|
||
rooted_stream.acquire_default_reader(CanGc::note())
|
||
.expect("Couldn't acquire a reader for the body stream.");
|
||
|
||
// Note: this algorithm continues when the first chunk is requested by `net`.
|
||
}),
|
||
&self.canceller,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// Drop the IPC sender sent by `net`
|
||
fn stop_reading(&mut self, reason: StopReading) {
|
||
let bytes_sender = self
|
||
.bytes_sender
|
||
.take()
|
||
.expect("Stop reading called multiple times on TransmitBodyConnectHandler.");
|
||
match reason {
|
||
StopReading::Error => {
|
||
let _ = bytes_sender.send(BodyChunkResponse::Error);
|
||
},
|
||
StopReading::Done => {
|
||
let _ = bytes_sender.send(BodyChunkResponse::Done);
|
||
},
|
||
}
|
||
}
|
||
|
||
/// Step 4 and following of <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
|
||
fn transmit_body_chunk(&mut self) {
|
||
if self.in_memory_done {
|
||
// Step 5.1.3
|
||
self.stop_reading(StopReading::Done);
|
||
return;
|
||
}
|
||
|
||
let stream = self.stream.clone();
|
||
let control_sender = self.control_sender.clone();
|
||
let bytes_sender = self
|
||
.bytes_sender
|
||
.clone()
|
||
.expect("No bytes sender to transmit chunk.");
|
||
|
||
// In case of the data being in-memory, send everything in one chunk, by-passing SpiderMonkey.
|
||
if let Some(bytes) = self.in_memory.clone() {
|
||
let _ = bytes_sender.send(BodyChunkResponse::Chunk(bytes));
|
||
// Mark this body as `done` so that we can stop reading in the next tick,
|
||
// matching the behavior of the promise-based flow
|
||
self.in_memory_done = true;
|
||
return;
|
||
}
|
||
|
||
let _ = self.task_source.queue_with_canceller(
|
||
task!(setup_native_body_promise_handler: move || {
|
||
let rooted_stream = stream.root();
|
||
let global = rooted_stream.global();
|
||
|
||
// Step 4, the result of reading a chunk from body’s stream with reader.
|
||
let promise = rooted_stream.read_a_chunk(CanGc::note());
|
||
|
||
// Step 5, the parallel steps waiting for and handling the result of the read promise,
|
||
// are a combination of the promise native handler here,
|
||
// and the corresponding IPC route in `component::net::http_loader`.
|
||
let promise_handler = Box::new(TransmitBodyPromiseHandler {
|
||
bytes_sender: bytes_sender.clone(),
|
||
stream: Dom::from_ref(&rooted_stream.clone()),
|
||
control_sender: control_sender.clone(),
|
||
});
|
||
|
||
let rejection_handler = Box::new(TransmitBodyPromiseRejectionHandler {
|
||
bytes_sender,
|
||
stream: rooted_stream,
|
||
control_sender,
|
||
});
|
||
|
||
let handler =
|
||
PromiseNativeHandler::new(&global, Some(promise_handler), Some(rejection_handler));
|
||
|
||
let realm = enter_realm(&*global);
|
||
let comp = InRealm::Entered(&realm);
|
||
promise.append_native_handler(&handler, comp, CanGc::note());
|
||
}),
|
||
&self.canceller,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// The handler of read promises of body streams used in
|
||
/// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
|
||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||
#[crown::unrooted_must_root_lint::must_root]
|
||
struct TransmitBodyPromiseHandler {
|
||
#[ignore_malloc_size_of = "Channels are hard"]
|
||
#[no_trace]
|
||
bytes_sender: IpcSender<BodyChunkResponse>,
|
||
stream: Dom<ReadableStream>,
|
||
#[ignore_malloc_size_of = "Channels are hard"]
|
||
#[no_trace]
|
||
control_sender: IpcSender<BodyChunkRequest>,
|
||
}
|
||
|
||
impl Callback for TransmitBodyPromiseHandler {
|
||
/// Step 5 of <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
|
||
fn callback(&self, cx: JSContext, v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
|
||
let is_done = match get_read_promise_done(cx, &v) {
|
||
Ok(is_done) => is_done,
|
||
Err(_) => {
|
||
// Step 5.5, the "otherwise" steps.
|
||
// TODO: terminate fetch.
|
||
let _ = self.control_sender.send(BodyChunkRequest::Done);
|
||
return self.stream.stop_reading();
|
||
},
|
||
};
|
||
|
||
if is_done {
|
||
// Step 5.3, the "done" steps.
|
||
// TODO: queue a fetch task on request to process request end-of-body.
|
||
let _ = self.control_sender.send(BodyChunkRequest::Done);
|
||
return self.stream.stop_reading();
|
||
}
|
||
|
||
let chunk = match get_read_promise_bytes(cx, &v) {
|
||
Ok(chunk) => chunk,
|
||
Err(_) => {
|
||
// Step 5.5, the "otherwise" steps.
|
||
let _ = self.control_sender.send(BodyChunkRequest::Error);
|
||
return self.stream.stop_reading();
|
||
},
|
||
};
|
||
|
||
// Step 5.1 and 5.2, transmit chunk.
|
||
// Send the chunk to the body transmitter in net::http_loader::obtain_response.
|
||
// TODO: queue a fetch task on request to process request body for request.
|
||
let _ = self.bytes_sender.send(BodyChunkResponse::Chunk(chunk));
|
||
}
|
||
}
|
||
|
||
/// The handler of read promises rejection of body streams used in
|
||
/// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
|
||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||
struct TransmitBodyPromiseRejectionHandler {
|
||
#[ignore_malloc_size_of = "Channels are hard"]
|
||
#[no_trace]
|
||
bytes_sender: IpcSender<BodyChunkResponse>,
|
||
stream: DomRoot<ReadableStream>,
|
||
#[ignore_malloc_size_of = "Channels are hard"]
|
||
#[no_trace]
|
||
control_sender: IpcSender<BodyChunkRequest>,
|
||
}
|
||
|
||
impl Callback for TransmitBodyPromiseRejectionHandler {
|
||
/// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
|
||
fn callback(&self, _cx: JSContext, _v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
|
||
// Step 5.4, the "rejection" steps.
|
||
let _ = self.control_sender.send(BodyChunkRequest::Error);
|
||
self.stream.stop_reading();
|
||
}
|
||
}
|
||
|
||
/// The result of <https://fetch.spec.whatwg.org/#concept-bodyinit-extract>
|
||
pub struct ExtractedBody {
|
||
pub stream: DomRoot<ReadableStream>,
|
||
pub source: BodySource,
|
||
pub total_bytes: Option<usize>,
|
||
pub content_type: Option<DOMString>,
|
||
}
|
||
|
||
impl ExtractedBody {
|
||
/// Build a request body from the extracted body,
|
||
/// to be sent over IPC to net to use with `concept-request-transmit-body`,
|
||
/// see <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
|
||
///
|
||
/// Also returning the corresponding readable stream,
|
||
/// to be stored on the request in script,
|
||
/// and potentially used as part of `consume_body`,
|
||
/// see <https://fetch.spec.whatwg.org/#concept-body-consume-body>
|
||
///
|
||
/// Transmitting a body over fetch, and consuming it in script,
|
||
/// are mutually exclusive operations, since each will lock the stream to a reader.
|
||
pub fn into_net_request_body(self) -> (RequestBody, DomRoot<ReadableStream>) {
|
||
let ExtractedBody {
|
||
stream,
|
||
total_bytes,
|
||
content_type: _,
|
||
source,
|
||
} = self;
|
||
|
||
// First, setup some infra to be used to transmit body
|
||
// from `components::script` to `components::net`.
|
||
let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
|
||
|
||
let trusted_stream = Trusted::new(&*stream);
|
||
|
||
let global = stream.global();
|
||
let task_source = global.networking_task_source();
|
||
let canceller = global.task_canceller(TaskSourceName::Networking);
|
||
|
||
// In case of the data being in-memory, send everything in one chunk, by-passing SM.
|
||
let in_memory = stream.get_in_memory_bytes();
|
||
|
||
let net_source = match source {
|
||
BodySource::Null => NetBodySource::Null,
|
||
_ => NetBodySource::Object,
|
||
};
|
||
|
||
let mut body_handler = TransmitBodyConnectHandler::new(
|
||
trusted_stream,
|
||
task_source,
|
||
canceller,
|
||
chunk_request_sender.clone(),
|
||
in_memory,
|
||
source,
|
||
);
|
||
|
||
ROUTER.add_typed_route(
|
||
chunk_request_receiver,
|
||
Box::new(move |message| {
|
||
match message.unwrap() {
|
||
BodyChunkRequest::Connect(sender) => {
|
||
body_handler.start_reading(sender);
|
||
},
|
||
BodyChunkRequest::Extract(receiver) => {
|
||
body_handler.re_extract(receiver);
|
||
},
|
||
BodyChunkRequest::Chunk => body_handler.transmit_body_chunk(),
|
||
// Note: this is actually sent from this process
|
||
// by the TransmitBodyPromiseHandler when reading stops.
|
||
BodyChunkRequest::Done => {
|
||
body_handler.stop_reading(StopReading::Done);
|
||
},
|
||
// Note: this is actually sent from this process
|
||
// by the TransmitBodyPromiseHandler when the stream errors.
|
||
BodyChunkRequest::Error => {
|
||
body_handler.stop_reading(StopReading::Error);
|
||
},
|
||
}
|
||
}),
|
||
);
|
||
|
||
// Return `components::net` view into this request body,
|
||
// which can be used by `net` to transmit it over the network.
|
||
let request_body = RequestBody::new(chunk_request_sender, net_source, total_bytes);
|
||
|
||
// Also return the stream for this body, which can be used by script to consume it.
|
||
(request_body, stream)
|
||
}
|
||
|
||
/// Is the data of the stream of this extracted body available in memory?
|
||
pub fn in_memory(&self) -> bool {
|
||
self.stream.in_memory()
|
||
}
|
||
}
|
||
|
||
/// <https://fetch.spec.whatwg.org/#concept-bodyinit-extract>
|
||
pub trait Extractable {
|
||
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody>;
|
||
}
|
||
|
||
impl Extractable for BodyInit {
|
||
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
||
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
|
||
match self {
|
||
BodyInit::String(ref s) => s.extract(global, can_gc),
|
||
BodyInit::URLSearchParams(ref usp) => usp.extract(global, can_gc),
|
||
BodyInit::Blob(ref b) => b.extract(global, can_gc),
|
||
BodyInit::FormData(ref formdata) => formdata.extract(global, can_gc),
|
||
BodyInit::ArrayBuffer(ref typedarray) => {
|
||
let bytes = typedarray.to_vec();
|
||
let total_bytes = bytes.len();
|
||
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
|
||
Ok(ExtractedBody {
|
||
stream,
|
||
total_bytes: Some(total_bytes),
|
||
content_type: None,
|
||
source: BodySource::Object,
|
||
})
|
||
},
|
||
BodyInit::ArrayBufferView(ref typedarray) => {
|
||
let bytes = typedarray.to_vec();
|
||
let total_bytes = bytes.len();
|
||
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
|
||
Ok(ExtractedBody {
|
||
stream,
|
||
total_bytes: Some(total_bytes),
|
||
content_type: None,
|
||
source: BodySource::Object,
|
||
})
|
||
},
|
||
BodyInit::ReadableStream(stream) => {
|
||
// TODO:
|
||
// 1. If the keepalive flag is set, then throw a TypeError.
|
||
|
||
if stream.is_locked() || stream.is_disturbed() {
|
||
return Err(Error::Type(
|
||
"The body's stream is disturbed or locked".to_string(),
|
||
));
|
||
}
|
||
|
||
Ok(ExtractedBody {
|
||
stream: stream.clone(),
|
||
total_bytes: None,
|
||
content_type: None,
|
||
source: BodySource::Null,
|
||
})
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Extractable for Vec<u8> {
|
||
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
|
||
let bytes = self.clone();
|
||
let total_bytes = self.len();
|
||
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
|
||
Ok(ExtractedBody {
|
||
stream,
|
||
total_bytes: Some(total_bytes),
|
||
content_type: None,
|
||
// A vec is used only in `submit_entity_body`.
|
||
source: BodySource::Object,
|
||
})
|
||
}
|
||
}
|
||
|
||
impl Extractable for Blob {
|
||
fn extract(&self, _global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
|
||
let blob_type = self.Type();
|
||
let content_type = if blob_type.as_ref().is_empty() {
|
||
None
|
||
} else {
|
||
Some(blob_type)
|
||
};
|
||
let total_bytes = self.Size() as usize;
|
||
Ok(ExtractedBody {
|
||
stream: self.get_stream(can_gc),
|
||
total_bytes: Some(total_bytes),
|
||
content_type,
|
||
source: BodySource::Object,
|
||
})
|
||
}
|
||
}
|
||
|
||
impl Extractable for DOMString {
|
||
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
|
||
let bytes = self.as_bytes().to_owned();
|
||
let total_bytes = bytes.len();
|
||
let content_type = Some(DOMString::from("text/plain;charset=UTF-8"));
|
||
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
|
||
Ok(ExtractedBody {
|
||
stream,
|
||
total_bytes: Some(total_bytes),
|
||
content_type,
|
||
source: BodySource::Object,
|
||
})
|
||
}
|
||
}
|
||
|
||
impl Extractable for FormData {
|
||
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
|
||
let boundary = generate_boundary();
|
||
let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
|
||
let total_bytes = bytes.len();
|
||
let content_type = Some(DOMString::from(format!(
|
||
"multipart/form-data;boundary={}",
|
||
boundary
|
||
)));
|
||
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
|
||
Ok(ExtractedBody {
|
||
stream,
|
||
total_bytes: Some(total_bytes),
|
||
content_type,
|
||
source: BodySource::Object,
|
||
})
|
||
}
|
||
}
|
||
|
||
impl Extractable for URLSearchParams {
|
||
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
|
||
let bytes = self.serialize_utf8().into_bytes();
|
||
let total_bytes = bytes.len();
|
||
let content_type = Some(DOMString::from(
|
||
"application/x-www-form-urlencoded;charset=UTF-8",
|
||
));
|
||
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
|
||
Ok(ExtractedBody {
|
||
stream,
|
||
total_bytes: Some(total_bytes),
|
||
content_type,
|
||
source: BodySource::Object,
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
|
||
pub enum BodyType {
|
||
Blob,
|
||
FormData,
|
||
Json,
|
||
Text,
|
||
ArrayBuffer,
|
||
}
|
||
|
||
pub enum FetchedData {
|
||
Text(String),
|
||
Json(RootedTraceableBox<Heap<JSValue>>),
|
||
BlobData(DomRoot<Blob>),
|
||
FormData(DomRoot<FormData>),
|
||
ArrayBuffer(RootedTraceableBox<Heap<*mut JSObject>>),
|
||
JSException(RootedTraceableBox<Heap<JSVal>>),
|
||
}
|
||
|
||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||
struct ConsumeBodyPromiseRejectionHandler {
|
||
#[ignore_malloc_size_of = "Rc are hard"]
|
||
result_promise: Rc<Promise>,
|
||
}
|
||
|
||
impl Callback for ConsumeBodyPromiseRejectionHandler {
|
||
/// Continuing Step 4 of <https://fetch.spec.whatwg.org/#concept-body-consume-body>
|
||
/// Step 3 of <https://fetch.spec.whatwg.org/#concept-read-all-bytes-from-readablestream>,
|
||
// the rejection steps.
|
||
fn callback(&self, cx: JSContext, v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
|
||
self.result_promise.reject(cx, v);
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||
#[crown::unrooted_must_root_lint::must_root]
|
||
/// The promise handler used to consume the body,
|
||
/// <https://fetch.spec.whatwg.org/#concept-body-consume-body>
|
||
struct ConsumeBodyPromiseHandler {
|
||
#[ignore_malloc_size_of = "Rc are hard"]
|
||
result_promise: Rc<Promise>,
|
||
stream: Option<Dom<ReadableStream>>,
|
||
body_type: DomRefCell<Option<BodyType>>,
|
||
mime_type: DomRefCell<Option<Vec<u8>>>,
|
||
bytes: DomRefCell<Option<Vec<u8>>>,
|
||
}
|
||
|
||
impl ConsumeBodyPromiseHandler {
|
||
/// Step 5 of <https://fetch.spec.whatwg.org/#concept-body-consume-body>
|
||
fn resolve_result_promise(&self, cx: JSContext, can_gc: CanGc) {
|
||
let body_type = self.body_type.borrow_mut().take().unwrap();
|
||
let mime_type = self.mime_type.borrow_mut().take().unwrap();
|
||
let body = self.bytes.borrow_mut().take().unwrap();
|
||
|
||
let pkg_data_results = run_package_data_algorithm(cx, body, body_type, mime_type, can_gc);
|
||
|
||
match pkg_data_results {
|
||
Ok(results) => {
|
||
match results {
|
||
FetchedData::Text(s) => self.result_promise.resolve_native(&USVString(s)),
|
||
FetchedData::Json(j) => self.result_promise.resolve_native(&j),
|
||
FetchedData::BlobData(b) => self.result_promise.resolve_native(&b),
|
||
FetchedData::FormData(f) => self.result_promise.resolve_native(&f),
|
||
FetchedData::ArrayBuffer(a) => self.result_promise.resolve_native(&a),
|
||
FetchedData::JSException(e) => self.result_promise.reject_native(&e.handle()),
|
||
};
|
||
},
|
||
Err(err) => self.result_promise.reject_error(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Callback for ConsumeBodyPromiseHandler {
|
||
/// Continuing Step 4 of <https://fetch.spec.whatwg.org/#concept-body-consume-body>
|
||
/// Step 3 of <https://fetch.spec.whatwg.org/#concept-read-all-bytes-from-readablestream>.
|
||
#[allow(crown::unrooted_must_root)]
|
||
fn callback(&self, cx: JSContext, v: HandleValue, _realm: InRealm, can_gc: CanGc) {
|
||
let stream = self
|
||
.stream
|
||
.as_ref()
|
||
.expect("ConsumeBodyPromiseHandler has no stream in callback.");
|
||
|
||
let is_done = match get_read_promise_done(cx, &v) {
|
||
Ok(is_done) => is_done,
|
||
Err(err) => {
|
||
stream.stop_reading();
|
||
// When read is fulfilled with a value that doesn't matches with neither of the above patterns.
|
||
return self.result_promise.reject_error(err);
|
||
},
|
||
};
|
||
|
||
if is_done {
|
||
// When read is fulfilled with an object whose done property is true.
|
||
self.resolve_result_promise(cx, can_gc);
|
||
} else {
|
||
let chunk = match get_read_promise_bytes(cx, &v) {
|
||
Ok(chunk) => chunk,
|
||
Err(err) => {
|
||
stream.stop_reading();
|
||
// When read is fulfilled with a value that matches with neither of the above patterns
|
||
return self.result_promise.reject_error(err);
|
||
},
|
||
};
|
||
|
||
let mut bytes = self
|
||
.bytes
|
||
.borrow_mut()
|
||
.take()
|
||
.expect("No bytes for ConsumeBodyPromiseHandler.");
|
||
|
||
// Append the value property to bytes.
|
||
bytes.extend_from_slice(&chunk);
|
||
|
||
let global = stream.global();
|
||
|
||
// Run the above step again.
|
||
let read_promise = stream.read_a_chunk(can_gc);
|
||
|
||
let promise_handler = Box::new(ConsumeBodyPromiseHandler {
|
||
result_promise: self.result_promise.clone(),
|
||
stream: self.stream.clone(),
|
||
body_type: DomRefCell::new(self.body_type.borrow_mut().take()),
|
||
mime_type: DomRefCell::new(self.mime_type.borrow_mut().take()),
|
||
bytes: DomRefCell::new(Some(bytes)),
|
||
});
|
||
|
||
let rejection_handler = Box::new(ConsumeBodyPromiseRejectionHandler {
|
||
result_promise: self.result_promise.clone(),
|
||
});
|
||
|
||
let handler =
|
||
PromiseNativeHandler::new(&global, Some(promise_handler), Some(rejection_handler));
|
||
|
||
let realm = enter_realm(&*global);
|
||
let comp = InRealm::Entered(&realm);
|
||
read_promise.append_native_handler(&handler, comp, can_gc);
|
||
}
|
||
}
|
||
}
|
||
|
||
// https://fetch.spec.whatwg.org/#concept-body-consume-body
|
||
#[allow(crown::unrooted_must_root)]
|
||
pub fn consume_body<T: BodyMixin + DomObject>(
|
||
object: &T,
|
||
body_type: BodyType,
|
||
can_gc: CanGc,
|
||
) -> Rc<Promise> {
|
||
let in_realm_proof = AlreadyInRealm::assert();
|
||
let promise = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
|
||
|
||
// Step 1
|
||
if object.is_disturbed() || object.is_locked() {
|
||
promise.reject_error(Error::Type(
|
||
"The body's stream is disturbed or locked".to_string(),
|
||
));
|
||
return promise;
|
||
}
|
||
|
||
consume_body_with_promise(
|
||
object,
|
||
body_type,
|
||
promise.clone(),
|
||
InRealm::Already(&in_realm_proof),
|
||
can_gc,
|
||
);
|
||
|
||
promise
|
||
}
|
||
|
||
// https://fetch.spec.whatwg.org/#concept-body-consume-body
|
||
#[allow(crown::unrooted_must_root)]
|
||
fn consume_body_with_promise<T: BodyMixin + DomObject>(
|
||
object: &T,
|
||
body_type: BodyType,
|
||
promise: Rc<Promise>,
|
||
comp: InRealm,
|
||
can_gc: CanGc,
|
||
) {
|
||
let global = object.global();
|
||
|
||
// Step 2.
|
||
let stream = match object.body() {
|
||
Some(stream) => stream,
|
||
None => ReadableStream::new_from_bytes(&global, Vec::with_capacity(0), can_gc),
|
||
};
|
||
|
||
// Step 3.
|
||
if stream.acquire_default_reader(can_gc).is_err() {
|
||
return promise.reject_error(Error::Type(
|
||
"The response's stream is disturbed or locked".to_string(),
|
||
));
|
||
}
|
||
|
||
// Step 4, read all the bytes.
|
||
// Starts here, continues in the promise handler.
|
||
|
||
// Step 1 of
|
||
// https://fetch.spec.whatwg.org/#concept-read-all-bytes-from-readablestream
|
||
let read_promise = stream.read_a_chunk(can_gc);
|
||
|
||
let promise_handler = Box::new(ConsumeBodyPromiseHandler {
|
||
result_promise: promise.clone(),
|
||
stream: Some(Dom::from_ref(&stream)),
|
||
body_type: DomRefCell::new(Some(body_type)),
|
||
mime_type: DomRefCell::new(Some(object.get_mime_type(can_gc))),
|
||
// Step 2.
|
||
bytes: DomRefCell::new(Some(vec![])),
|
||
});
|
||
|
||
let rejection_handler = Box::new(ConsumeBodyPromiseRejectionHandler {
|
||
result_promise: promise,
|
||
});
|
||
|
||
let handler = PromiseNativeHandler::new(
|
||
&object.global(),
|
||
Some(promise_handler),
|
||
Some(rejection_handler),
|
||
);
|
||
// We are already in a realm and a script.
|
||
read_promise.append_native_handler(&handler, comp, can_gc);
|
||
}
|
||
|
||
// https://fetch.spec.whatwg.org/#concept-body-package-data
|
||
fn run_package_data_algorithm(
|
||
cx: JSContext,
|
||
bytes: Vec<u8>,
|
||
body_type: BodyType,
|
||
mime_type: Vec<u8>,
|
||
can_gc: CanGc,
|
||
) -> Fallible<FetchedData> {
|
||
let mime = &*mime_type;
|
||
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
||
let global = GlobalScope::from_safe_context(cx, InRealm::Already(&in_realm_proof));
|
||
match body_type {
|
||
BodyType::Text => run_text_data_algorithm(bytes),
|
||
BodyType::Json => run_json_data_algorithm(cx, bytes),
|
||
BodyType::Blob => run_blob_data_algorithm(&global, bytes, mime, can_gc),
|
||
BodyType::FormData => run_form_data_algorithm(&global, bytes, mime, can_gc),
|
||
BodyType::ArrayBuffer => run_array_buffer_data_algorithm(cx, bytes),
|
||
}
|
||
}
|
||
|
||
fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
|
||
Ok(FetchedData::Text(
|
||
String::from_utf8_lossy(&bytes).into_owned(),
|
||
))
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
fn run_json_data_algorithm(cx: JSContext, bytes: Vec<u8>) -> Fallible<FetchedData> {
|
||
let json_text = String::from_utf8_lossy(&bytes);
|
||
let json_text: Vec<u16> = json_text.encode_utf16().collect();
|
||
rooted!(in(*cx) let mut rval = UndefinedValue());
|
||
unsafe {
|
||
if !JS_ParseJSON(
|
||
*cx,
|
||
json_text.as_ptr(),
|
||
json_text.len() as u32,
|
||
rval.handle_mut(),
|
||
) {
|
||
rooted!(in(*cx) let mut exception = UndefinedValue());
|
||
assert!(JS_GetPendingException(*cx, exception.handle_mut()));
|
||
JS_ClearPendingException(*cx);
|
||
return Ok(FetchedData::JSException(RootedTraceableBox::from_box(
|
||
Heap::boxed(exception.get()),
|
||
)));
|
||
}
|
||
let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(rval.get()));
|
||
Ok(FetchedData::Json(rooted_heap))
|
||
}
|
||
}
|
||
|
||
fn run_blob_data_algorithm(
|
||
root: &GlobalScope,
|
||
bytes: Vec<u8>,
|
||
mime: &[u8],
|
||
can_gc: CanGc,
|
||
) -> Fallible<FetchedData> {
|
||
let mime_string = if let Ok(s) = String::from_utf8(mime.to_vec()) {
|
||
s
|
||
} else {
|
||
"".to_string()
|
||
};
|
||
let blob = Blob::new(
|
||
root,
|
||
BlobImpl::new_from_bytes(bytes, normalize_type_string(&mime_string)),
|
||
can_gc,
|
||
);
|
||
Ok(FetchedData::BlobData(blob))
|
||
}
|
||
|
||
fn run_form_data_algorithm(
|
||
root: &GlobalScope,
|
||
bytes: Vec<u8>,
|
||
mime: &[u8],
|
||
can_gc: CanGc,
|
||
) -> Fallible<FetchedData> {
|
||
let mime_str = str::from_utf8(mime).unwrap_or_default();
|
||
let mime: Mime = mime_str
|
||
.parse()
|
||
.map_err(|_| Error::Type("Inappropriate MIME-type for Body".to_string()))?;
|
||
|
||
// TODO
|
||
// ... Parser for Mime(TopLevel::Multipart, SubLevel::FormData, _)
|
||
// ... is not fully determined yet.
|
||
if mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED {
|
||
let entries = form_urlencoded::parse(&bytes);
|
||
let formdata = FormData::new(None, root, can_gc);
|
||
for (k, e) in entries {
|
||
formdata.Append(USVString(k.into_owned()), USVString(e.into_owned()));
|
||
}
|
||
return Ok(FetchedData::FormData(formdata));
|
||
}
|
||
|
||
Err(Error::Type("Inappropriate MIME-type for Body".to_string()))
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub fn run_array_buffer_data_algorithm(cx: JSContext, bytes: Vec<u8>) -> Fallible<FetchedData> {
|
||
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
|
||
let arraybuffer = unsafe {
|
||
ArrayBuffer::create(
|
||
*cx,
|
||
CreateWith::Slice(&bytes),
|
||
array_buffer_ptr.handle_mut(),
|
||
)
|
||
};
|
||
if arraybuffer.is_err() {
|
||
return Err(Error::JSFailed);
|
||
}
|
||
let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
|
||
Ok(FetchedData::ArrayBuffer(rooted_heap))
|
||
}
|
||
|
||
/// <https://fetch.spec.whatwg.org/#body>
|
||
pub trait BodyMixin {
|
||
/// <https://fetch.spec.whatwg.org/#concept-body-disturbed>
|
||
fn is_disturbed(&self) -> bool;
|
||
/// <https://fetch.spec.whatwg.org/#dom-body-body>
|
||
fn body(&self) -> Option<DomRoot<ReadableStream>>;
|
||
/// <https://fetch.spec.whatwg.org/#concept-body-locked>
|
||
fn is_locked(&self) -> bool;
|
||
/// <https://fetch.spec.whatwg.org/#concept-body-mime-type>
|
||
fn get_mime_type(&self, can_gc: CanGc) -> Vec<u8>;
|
||
}
|