aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/body.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/body.rs')
-rw-r--r--components/script/body.rs951
1 files changed, 846 insertions, 105 deletions
diff --git a/components/script/body.rs b/components/script/body.rs
index 1967d69cf50..b7436c3e5e0 100644
--- a/components/script/body.rs
+++ b/components/script/body.rs
@@ -1,180 +1,921 @@
/* 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 http://mozilla.org/MPL/2.0/. */
-
-use dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
-use dom::bindings::error::{Error, Fallible};
-use dom::bindings::js::Root;
-use dom::bindings::reflector::DomObject;
-use dom::bindings::str::USVString;
-use dom::blob::{Blob, BlobImpl};
-use dom::formdata::FormData;
-use dom::globalscope::GlobalScope;
-use dom::promise::Promise;
-use encoding::all::UTF_8;
-use encoding::types::{DecoderTrap, Encoding};
-use js::jsapi::JSContext;
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobBinding::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::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::JSContext;
+use crate::task::TaskCanceller;
+use crate::task_source::networking::NetworkingTaskSource;
+use crate::task_source::TaskSource;
+use crate::task_source::TaskSourceName;
+use encoding_rs::UTF_8;
+use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
+use ipc_channel::router::ROUTER;
+use js::jsapi::Heap;
+use js::jsapi::JSObject;
use js::jsapi::JS_ClearPendingException;
-use js::jsapi::JS_ParseJSON;
use js::jsapi::Value as JSValue;
+use js::jsval::JSVal;
use js::jsval::UndefinedValue;
-use mime::{Mime, TopLevel, SubLevel};
-use std::cell::Ref;
+use js::rust::wrappers::JS_GetPendingException;
+use js::rust::wrappers::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 std::ptr;
use std::rc::Rc;
use std::str;
use url::form_urlencoded;
-#[derive(Copy, Clone, JSTraceable, HeapSizeOf)]
+/// 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: 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_route(
+ chunk_request_receiver.to_opaque(),
+ Box::new(move |message| {
+ let request = message.to().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.start_reading().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();
+
+ // 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: 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);
+ }),
+ &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)]
+struct TransmitBodyPromiseHandler {
+ #[ignore_malloc_size_of = "Channels are hard"]
+ bytes_sender: IpcSender<BodyChunkResponse>,
+ stream: DomRoot<ReadableStream>,
+ #[ignore_malloc_size_of = "Channels are hard"]
+ 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) {
+ let is_done = match get_read_promise_done(cx.clone(), &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.clone(), &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"]
+ bytes_sender: IpcSender<BodyChunkResponse>,
+ stream: DomRoot<ReadableStream>,
+ #[ignore_malloc_size_of = "Channels are hard"]
+ 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) {
+ // Step 5.4, the "rejection" steps.
+ let _ = self.control_sender.send(BodyChunkRequest::Error);
+ return 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_route(
+ chunk_request_receiver.to_opaque(),
+ Box::new(move |message| {
+ let request = message.to().unwrap();
+ match request {
+ 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) -> Fallible<ExtractedBody>;
+}
+
+impl Extractable for BodyInit {
+ // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
+ fn extract(&self, global: &GlobalScope) -> Fallible<ExtractedBody> {
+ match self {
+ BodyInit::String(ref s) => s.extract(global),
+ BodyInit::URLSearchParams(ref usp) => usp.extract(global),
+ BodyInit::Blob(ref b) => b.extract(global),
+ BodyInit::FormData(ref formdata) => formdata.extract(global),
+ BodyInit::ArrayBuffer(ref typedarray) => {
+ let bytes = typedarray.to_vec();
+ let total_bytes = bytes.len();
+ let stream = ReadableStream::new_from_bytes(&global, bytes);
+ 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);
+ 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) -> Fallible<ExtractedBody> {
+ let bytes = self.clone();
+ let total_bytes = self.len();
+ let stream = ReadableStream::new_from_bytes(&global, bytes);
+ 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) -> 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(),
+ total_bytes: Some(total_bytes),
+ content_type,
+ source: BodySource::Object,
+ })
+ }
+}
+
+impl Extractable for DOMString {
+ fn extract(&self, global: &GlobalScope) -> 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);
+ Ok(ExtractedBody {
+ stream,
+ total_bytes: Some(total_bytes),
+ content_type,
+ source: BodySource::Object,
+ })
+ }
+}
+
+impl Extractable for FormData {
+ fn extract(&self, global: &GlobalScope) -> 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);
+ Ok(ExtractedBody {
+ stream,
+ total_bytes: Some(total_bytes),
+ content_type,
+ source: BodySource::Object,
+ })
+ }
+}
+
+impl Extractable for URLSearchParams {
+ fn extract(&self, global: &GlobalScope) -> 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);
+ 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
+ Text,
+ ArrayBuffer,
}
pub enum FetchedData {
Text(String),
- Json(JSValue),
- BlobData(Root<Blob>),
- FormData(Root<FormData>),
+ 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) {
+ self.result_promise.reject(cx, v);
+ }
+}
+
+#[derive(Clone, JSTraceable, MallocSizeOf)]
+/// 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<DomRoot<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) {
+ 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);
+
+ 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>.
+ fn callback(&self, cx: JSContext, v: HandleValue, _realm: InRealm) {
+ let stream = self
+ .stream
+ .as_ref()
+ .expect("ConsumeBodyPromiseHandler has no stream in callback.");
+
+ let is_done = match get_read_promise_done(cx.clone(), &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.clone());
+ } else {
+ let chunk = match get_read_promise_bytes(cx.clone(), &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();
+
+ 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);
+ }
+ }
}
// https://fetch.spec.whatwg.org/#concept-body-consume-body
#[allow(unrooted_must_root)]
-pub fn consume_body<T: BodyOperations + DomObject>(object: &T, body_type: BodyType) -> Rc<Promise> {
- let promise = Promise::new(&object.global());
+pub fn consume_body<T: BodyMixin + DomObject>(object: &T, body_type: BodyType) -> Rc<Promise> {
+ let global = object.global();
+ let in_realm_proof = AlreadyInRealm::assert(&global);
+ let promise =
+ Promise::new_in_current_realm(&object.global(), InRealm::Already(&in_realm_proof));
// Step 1
- if object.get_body_used() || object.is_locked() {
- promise.reject_error(promise.global().get_cx(), Error::Type(
- "The response's stream is disturbed or locked".to_string()));
+ if object.is_disturbed() || object.is_locked() {
+ promise.reject_error(Error::Type(
+ "The body's stream is disturbed or locked".to_string(),
+ ));
return promise;
}
- object.set_body_promise(&promise, body_type);
-
- // Steps 2-4
- // TODO: Body does not yet have a stream.
-
- consume_body_with_promise(object, body_type, &promise);
+ consume_body_with_promise(
+ object,
+ body_type,
+ promise.clone(),
+ InRealm::Already(&in_realm_proof),
+ );
promise
}
// https://fetch.spec.whatwg.org/#concept-body-consume-body
#[allow(unrooted_must_root)]
-pub fn consume_body_with_promise<T: BodyOperations + DomObject>(object: &T,
- body_type: BodyType,
- promise: &Promise) {
- // Step 5
- let body = match object.take_body() {
- Some(body) => body,
- None => return,
- };
+fn consume_body_with_promise<T: BodyMixin + DomObject>(
+ object: &T,
+ body_type: BodyType,
+ promise: Rc<Promise>,
+ comp: InRealm,
+) {
+ let global = object.global();
- let pkg_data_results = run_package_data_algorithm(object,
- body,
- body_type,
- object.get_mime_type());
-
- let cx = promise.global().get_cx();
- match pkg_data_results {
- Ok(results) => {
- match results {
- FetchedData::Text(s) => promise.resolve_native(cx, &USVString(s)),
- FetchedData::Json(j) => promise.resolve_native(cx, &j),
- FetchedData::BlobData(b) => promise.resolve_native(cx, &b),
- FetchedData::FormData(f) => promise.resolve_native(cx, &f),
- };
+ // Step 2.
+ let stream = match object.body() {
+ Some(stream) => stream,
+ None => {
+ let stream = ReadableStream::new_from_bytes(&global, Vec::with_capacity(0));
+ stream
},
- Err(err) => promise.reject_error(cx, err),
+ };
+
+ // Step 3.
+ if stream.start_reading().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();
+
+ let promise_handler = Box::new(ConsumeBodyPromiseHandler {
+ result_promise: promise.clone(),
+ stream: Some(stream),
+ body_type: DomRefCell::new(Some(body_type)),
+ mime_type: DomRefCell::new(Some(object.get_mime_type())),
+ // 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);
}
// https://fetch.spec.whatwg.org/#concept-body-package-data
-#[allow(unsafe_code)]
-fn run_package_data_algorithm<T: BodyOperations + DomObject>(object: &T,
- bytes: Vec<u8>,
- body_type: BodyType,
- mime_type: Ref<Vec<u8>>)
- -> Fallible<FetchedData> {
- let global = object.global();
- let cx = global.get_cx();
+fn run_package_data_algorithm(
+ cx: JSContext,
+ bytes: Vec<u8>,
+ body_type: BodyType,
+ mime_type: Vec<u8>,
+) -> 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),
BodyType::FormData => run_form_data_algorithm(&global, bytes, mime),
+ BodyType::ArrayBuffer => run_array_buffer_data_algorithm(cx, bytes),
}
}
fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
- let text = UTF_8.decode(&bytes, DecoderTrap::Replace).unwrap();
- Ok(FetchedData::Text(text))
+ Ok(FetchedData::Text(
+ String::from_utf8_lossy(&bytes).into_owned(),
+ ))
}
#[allow(unsafe_code)]
-fn run_json_data_algorithm(cx: *mut JSContext,
- bytes: Vec<u8>) -> Fallible<FetchedData> {
- let json_text = UTF_8.decode(&bytes, DecoderTrap::Replace).unwrap();
+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());
+ rooted!(in(*cx) let mut rval = UndefinedValue());
unsafe {
- if !JS_ParseJSON(cx,
- json_text.as_ptr(),
- json_text.len() as u32,
- rval.handle_mut()) {
- JS_ClearPendingException(cx);
- // TODO: See issue #13464. Exception should be thrown instead of cleared.
- return Err(Error::Type("Failed to parse JSON".to_string()));
+ 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()),
+ )));
}
- Ok(FetchedData::Json(rval.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]) -> Fallible<FetchedData> {
+fn run_blob_data_algorithm(
+ root: &GlobalScope,
+ bytes: Vec<u8>,
+ mime: &[u8],
+) -> 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), mime_string);
+ let blob = Blob::new(
+ root,
+ BlobImpl::new_from_bytes(bytes, normalize_type_string(&mime_string)),
+ );
Ok(FetchedData::BlobData(blob))
}
-fn run_form_data_algorithm(root: &GlobalScope, bytes: Vec<u8>, mime: &[u8]) -> Fallible<FetchedData> {
+fn run_form_data_algorithm(
+ root: &GlobalScope,
+ bytes: Vec<u8>,
+ mime: &[u8],
+) -> Fallible<FetchedData> {
let mime_str = if let Ok(s) = str::from_utf8(mime) {
s
} else {
""
};
- let mime: Mime = try!(mime_str.parse().map_err(
- |_| Error::Type("Inappropriate MIME-type for Body".to_string())));
- match mime {
- // TODO
- // ... Parser for Mime(TopLevel::Multipart, SubLevel::FormData, _)
- // ... is not fully determined yet.
- Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, _) => {
- let entries = form_urlencoded::parse(&bytes);
- let formdata = FormData::new(None, root);
- for (k, e) in entries {
- formdata.Append(USVString(k.into_owned()), USVString(e.into_owned()));
- }
- return Ok(FetchedData::FormData(formdata));
- },
- _ => return Err(Error::Type("Inappropriate MIME-type for Body".to_string())),
+ 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);
+ 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))
}
-pub trait BodyOperations {
- fn get_body_used(&self) -> bool;
- fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType);
- /// Returns `Some(_)` if the body is complete, `None` if there is more to
- /// come.
- fn take_body(&self) -> Option<Vec<u8>>;
+/// <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;
- fn get_mime_type(&self) -> Ref<Vec<u8>>;
+ /// <https://fetch.spec.whatwg.org/#concept-body-mime-type>
+ fn get_mime_type(&self) -> Vec<u8>;
}