servo/components/script/body.rs
Gregory Terzian 379bbb41dd
Dom: Re-implement ReadableStream Part 1 : Default Reader and Controller (#34064)
* 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>
2024-12-17 21:14:00 +00:00

924 lines
34 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 requests 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 bodys 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>;
}