bypass SM for in-memory streams in request bodies, dis-allow other cases in sync XHR

This commit is contained in:
Gregory Terzian 2020-05-29 16:13:29 +08:00
parent 3535dd7412
commit ad4dea7d84
3 changed files with 75 additions and 19 deletions

View file

@ -58,6 +58,7 @@ struct TransmitBodyConnectHandler {
canceller: TaskCanceller, canceller: TaskCanceller,
bytes_sender: Option<IpcSender<Vec<u8>>>, bytes_sender: Option<IpcSender<Vec<u8>>>,
control_sender: IpcSender<BodyChunkRequest>, control_sender: IpcSender<BodyChunkRequest>,
in_memory: Option<Vec<u8>>,
} }
impl TransmitBodyConnectHandler { impl TransmitBodyConnectHandler {
@ -66,6 +67,7 @@ impl TransmitBodyConnectHandler {
task_source: NetworkingTaskSource, task_source: NetworkingTaskSource,
canceller: TaskCanceller, canceller: TaskCanceller,
control_sender: IpcSender<BodyChunkRequest>, control_sender: IpcSender<BodyChunkRequest>,
in_memory: Option<Vec<u8>>,
) -> TransmitBodyConnectHandler { ) -> TransmitBodyConnectHandler {
TransmitBodyConnectHandler { TransmitBodyConnectHandler {
stream: stream, stream: stream,
@ -73,6 +75,7 @@ impl TransmitBodyConnectHandler {
canceller, canceller,
bytes_sender: None, bytes_sender: None,
control_sender, control_sender,
in_memory,
} }
} }
@ -97,6 +100,12 @@ impl TransmitBodyConnectHandler {
.clone() .clone()
.expect("No bytes sender to transmit chunk."); .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( let _ = self.task_source.queue_with_canceller(
task!(setup_native_body_promise_handler: move || { task!(setup_native_body_promise_handler: move || {
// Step 1, Let body be requests body. // Step 1, Let body be requests body.
@ -247,11 +256,15 @@ impl ExtractedBody {
let task_source = global.networking_task_source(); let task_source = global.networking_task_source();
let canceller = global.task_canceller(TaskSourceName::Networking); 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( let mut body_handler = TransmitBodyConnectHandler::new(
trusted_stream, trusted_stream,
task_source, task_source,
canceller, canceller,
chunk_request_sender.clone(), chunk_request_sender.clone(),
in_memory,
); );
ROUTER.add_route( 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. // Also return the stream for this body, which can be used by script to consume it.
(request_body, stream) (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> /// <https://fetch.spec.whatwg.org/#concept-bodyinit-extract>

View file

@ -196,6 +196,21 @@ impl ReadableStream {
.close(cx, handle); .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<Vec<u8>> {
self.external_underlying_source
.as_ref()
.and_then(|source| source.get_in_memory_bytes())
}
/// Acquires a reader and locks the stream, /// Acquires a reader and locks the stream,
/// must be done before `read_a_chunk`. /// must be done before `read_a_chunk`.
#[allow(unsafe_code)] #[allow(unsafe_code)]
@ -372,22 +387,37 @@ struct ExternalUnderlyingSourceController {
buffer: RefCell<Vec<u8>>, buffer: RefCell<Vec<u8>>,
/// Has the stream been closed by native code? /// Has the stream been closed by native code?
closed: Cell<bool>, closed: Cell<bool>,
/// Does this stream contains all it's data in memory?
in_memory: Cell<bool>,
} }
impl ExternalUnderlyingSourceController { impl ExternalUnderlyingSourceController {
fn new(source: ExternalUnderlyingSource) -> ExternalUnderlyingSourceController { fn new(source: ExternalUnderlyingSource) -> ExternalUnderlyingSourceController {
let buffer = match source { let (buffer, in_mem) = match source {
ExternalUnderlyingSource::Blob(size) | ExternalUnderlyingSource::Memory(size) => { ExternalUnderlyingSource::Blob(size) => (Vec::with_capacity(size), false),
Vec::with_capacity(size) ExternalUnderlyingSource::Memory(size) => (Vec::with_capacity(size), true),
}, ExternalUnderlyingSource::FetchResponse => (vec![], false),
ExternalUnderlyingSource::FetchResponse => vec![],
}; };
ExternalUnderlyingSourceController { ExternalUnderlyingSourceController {
buffer: RefCell::new(buffer), buffer: RefCell::new(buffer),
closed: Cell::new(false), 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<Vec<u8>> {
if self.in_memory.get() {
return Some(self.buffer.borrow().clone());
}
None
}
/// Signal available bytes if the stream is currently readable. /// Signal available bytes if the stream is currently readable.
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn maybe_signal_available_bytes( fn maybe_signal_available_bytes(

View file

@ -580,7 +580,13 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
}) })
}, },
Some(DocumentOrBodyInit::Blob(ref b)) => { 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( Some(DocumentOrBodyInit::FormData(ref formdata)) => Some(
formdata formdata
@ -620,21 +626,23 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
}) })
}, },
Some(DocumentOrBodyInit::ReadableStream(ref stream)) => { Some(DocumentOrBodyInit::ReadableStream(ref stream)) => {
// TODO: if self.sync.get() {
// 1. If the keepalive flag is set, then throw a TypeError. 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() { Some(ExtractedBody {
return Err(Error::Type( stream: stream.clone(),
"The body's stream is disturbed or locked".to_string(), 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, None => None,
}; };