diff --git a/components/script/body.rs b/components/script/body.rs index fabff7397c0..b8d3532964b 100644 --- a/components/script/body.rs +++ b/components/script/body.rs @@ -58,6 +58,7 @@ struct TransmitBodyConnectHandler { canceller: TaskCanceller, bytes_sender: Option>>, control_sender: IpcSender, + in_memory: Option>, } impl TransmitBodyConnectHandler { @@ -66,6 +67,7 @@ impl TransmitBodyConnectHandler { task_source: NetworkingTaskSource, canceller: TaskCanceller, control_sender: IpcSender, + in_memory: Option>, ) -> TransmitBodyConnectHandler { TransmitBodyConnectHandler { stream: stream, @@ -73,6 +75,7 @@ impl TransmitBodyConnectHandler { canceller, bytes_sender: None, control_sender, + in_memory, } } @@ -97,6 +100,12 @@ impl TransmitBodyConnectHandler { .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.take() { + let _ = bytes_sender.send(bytes); + return; + } + let _ = self.task_source.queue_with_canceller( task!(setup_native_body_promise_handler: move || { // Step 1, Let body be request’s body. @@ -247,11 +256,15 @@ impl ExtractedBody { 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 mut body_handler = TransmitBodyConnectHandler::new( trusted_stream, task_source, canceller, chunk_request_sender.clone(), + in_memory, ); ROUTER.add_route( @@ -281,6 +294,11 @@ impl ExtractedBody { // 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() + } } /// diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs index d2677e80547..1c6ac242e9b 100644 --- a/components/script/dom/readablestream.rs +++ b/components/script/dom/readablestream.rs @@ -196,6 +196,21 @@ impl ReadableStream { .close(cx, handle); } + /// Does the stream have all data in memory? + pub fn in_memory(&self) -> bool { + self.external_underlying_source + .as_ref() + .map(|source| source.in_memory()) + .unwrap_or(false) + } + + /// Return bytes for synchronous use, if the stream has all data in memory. + pub fn get_in_memory_bytes(&self) -> Option> { + self.external_underlying_source + .as_ref() + .and_then(|source| source.get_in_memory_bytes()) + } + /// Acquires a reader and locks the stream, /// must be done before `read_a_chunk`. #[allow(unsafe_code)] @@ -372,22 +387,37 @@ struct ExternalUnderlyingSourceController { buffer: RefCell>, /// Has the stream been closed by native code? closed: Cell, + /// Does this stream contains all it's data in memory? + in_memory: Cell, } impl ExternalUnderlyingSourceController { fn new(source: ExternalUnderlyingSource) -> ExternalUnderlyingSourceController { - let buffer = match source { - ExternalUnderlyingSource::Blob(size) | ExternalUnderlyingSource::Memory(size) => { - Vec::with_capacity(size) - }, - ExternalUnderlyingSource::FetchResponse => vec![], + let (buffer, in_mem) = match source { + ExternalUnderlyingSource::Blob(size) => (Vec::with_capacity(size), false), + ExternalUnderlyingSource::Memory(size) => (Vec::with_capacity(size), true), + ExternalUnderlyingSource::FetchResponse => (vec![], false), }; ExternalUnderlyingSourceController { buffer: RefCell::new(buffer), closed: Cell::new(false), + in_memory: Cell::new(in_mem), } } + /// Does the stream have all data in memory? + pub fn in_memory(&self) -> bool { + self.in_memory.get() + } + + /// Return bytes synchronously if the stream has all data in memory. + pub fn get_in_memory_bytes(&self) -> Option> { + if self.in_memory.get() { + return Some(self.buffer.borrow().clone()); + } + None + } + /// Signal available bytes if the stream is currently readable. #[allow(unsafe_code)] fn maybe_signal_available_bytes( diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 1bb0ebd60dd..4114abc029f 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -580,7 +580,13 @@ impl XMLHttpRequestMethods for XMLHttpRequest { }) }, Some(DocumentOrBodyInit::Blob(ref b)) => { - Some(b.extract(&self.global()).expect("Couldn't extract body.")) + let extracted_body = b.extract(&self.global()).expect("Couldn't extract body."); + if !extracted_body.in_memory() && self.sync.get() { + warn!("Sync XHR with not in-memory Blob as body not supported"); + None + } else { + Some(extracted_body) + } }, Some(DocumentOrBodyInit::FormData(ref formdata)) => Some( formdata @@ -620,21 +626,23 @@ impl XMLHttpRequestMethods for XMLHttpRequest { }) }, Some(DocumentOrBodyInit::ReadableStream(ref stream)) => { - // TODO: - // 1. If the keepalive flag is set, then throw a TypeError. + if self.sync.get() { + warn!("Sync XHR with ReadableStream as body not supported"); + None + } else { + if stream.is_locked() || stream.is_disturbed() { + return Err(Error::Type( + "The body's stream is disturbed or locked".to_string(), + )); + } - if stream.is_locked() || stream.is_disturbed() { - return Err(Error::Type( - "The body's stream is disturbed or locked".to_string(), - )); + Some(ExtractedBody { + stream: stream.clone(), + total_bytes: None, + content_type: None, + source: BodySource::Null, + }) } - - Some(ExtractedBody { - stream: stream.clone(), - total_bytes: None, - content_type: None, - source: BodySource::Null, - }) }, None => None, };