integrate readablestream with fetch and blob

This commit is contained in:
Gregory Terzian 2020-02-29 11:59:10 +08:00
parent 0281acea95
commit bd5796c90b
74 changed files with 2219 additions and 899 deletions

2
Cargo.lock generated
View file

@ -3516,7 +3516,7 @@ dependencies = [
[[package]]
name = "mozjs"
version = "0.13.0"
source = "git+https://github.com/servo/rust-mozjs#dbb9bee06e0b0168ccae0619c5077e302669d2fb"
source = "git+https://github.com/servo/rust-mozjs#28248e1d6658e92dd5ecb0866e53a97f043b9b38"
dependencies = [
"cc",
"lazy_static",

View file

@ -32,13 +32,17 @@ use http::header::{
use http::{HeaderMap, Request as HyperRequest};
use hyper::{Body, Client, Method, Response as HyperResponse, StatusCode};
use hyper_serde::Serde;
use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::router::ROUTER;
use msg::constellation_msg::{HistoryStateId, PipelineId};
use net_traits::pub_domains::reg_suffix;
use net_traits::quality::{quality_to_value, Quality, QualityItem};
use net_traits::request::Origin::Origin as SpecificOrigin;
use net_traits::request::{is_cors_safelisted_method, is_cors_safelisted_request_header};
use net_traits::request::{
BodyChunkRequest, RedirectMode, Referrer, Request, RequestBuilder, RequestMode,
};
use net_traits::request::{CacheMode, CredentialsMode, Destination, Origin};
use net_traits::request::{RedirectMode, Referrer, Request, RequestBuilder, RequestMode};
use net_traits::request::{ResponseTainting, ServiceWorkersMode};
use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType};
use net_traits::{CookieSource, FetchMetadata, NetworkError, ReferrerPolicy};
@ -52,11 +56,12 @@ use std::iter::FromIterator;
use std::mem;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::{Condvar, Mutex, RwLock};
use std::sync::{Arc as StdArc, Condvar, Mutex, RwLock};
use std::time::{Duration, SystemTime};
use time::{self, Tm};
use tokio::prelude::{future, Future, Stream};
use tokio::prelude::{future, Future, Sink, Stream};
use tokio::runtime::Runtime;
use tokio::sync::mpsc::channel;
lazy_static! {
pub static ref HANDLE: Mutex<Option<Runtime>> = Mutex::new(Some(Runtime::new().unwrap()));
@ -400,8 +405,10 @@ fn obtain_response(
client: &Client<Connector, Body>,
url: &ServoUrl,
method: &Method,
headers: &HeaderMap,
data: &Option<Vec<u8>>,
request_headers: &HeaderMap,
body: Option<IpcSender<BodyChunkRequest>>,
request_len: Option<usize>,
load_data_method: &Method,
pipeline_id: &Option<PipelineId>,
request_id: Option<&str>,
is_xhr: bool,
@ -412,7 +419,71 @@ fn obtain_response(
Error = NetworkError,
>,
> {
let request_body = data.as_ref().cloned().unwrap_or(vec![]);
let mut headers = request_headers.clone();
let devtools_bytes = StdArc::new(Mutex::new(vec![]));
let request_body = match body {
Some(chunk_requester) => {
// TODO: If body is a stream, append `Transfer-Encoding`/`chunked`,
// see step 4.2 of https://fetch.spec.whatwg.org/#concept-http-network-fetch
// Step 5.6 of https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch
// If source is non-null,
// set contentLengthValue to httpRequests bodys total bytes
if let Some(request_len) = request_len {
headers.typed_insert(ContentLength(request_len as u64));
}
let (body_chan, body_port) = ipc::channel().unwrap();
let (sender, receiver) = channel(1);
let _ = chunk_requester.send(BodyChunkRequest::Connect(body_chan));
// https://fetch.spec.whatwg.org/#concept-request-transmit-body
// Request the first chunk, corresponding to Step 3 and 4.
let _ = chunk_requester.send(BodyChunkRequest::Chunk);
let devtools_bytes = devtools_bytes.clone();
ROUTER.add_route(
body_port.to_opaque(),
Box::new(move |message| {
let bytes: Vec<u8> = message.to().unwrap();
let chunk_requester = chunk_requester.clone();
let sender = sender.clone();
devtools_bytes.lock().unwrap().append(&mut bytes.clone());
HANDLE.lock().unwrap().as_mut().unwrap().spawn(
// Step 5.1.2.2
// Transmit a chunk over the network(and blocking until this is done).
sender
.send(bytes)
.map(move |_| {
// Step 5.1.2.3
// Request the next chunk.
let _ = chunk_requester.send(BodyChunkRequest::Chunk);
()
})
.map_err(|_| ()),
);
}),
);
receiver
},
_ => {
if *load_data_method != Method::GET && *load_data_method != Method::HEAD {
headers.typed_insert(ContentLength(0))
}
let (_sender, mut receiver) = channel(1);
receiver.close();
receiver
},
};
context
.timing
@ -440,7 +511,7 @@ fn obtain_response(
.replace("{", "%7B")
.replace("}", "%7D"),
)
.body(request_body.clone().into());
.body(Body::wrap_stream(request_body));
// TODO: We currently don't know when the handhhake before the connection is done
// so our best bet would be to set `secure_connection_start` here when we are currently
@ -488,7 +559,7 @@ fn obtain_response(
closure_url,
method.clone(),
headers,
Some(request_body.clone()),
Some(devtools_bytes.lock().unwrap().clone()),
pipeline_id,
time::now(),
connect_end - connect_start,
@ -804,7 +875,7 @@ pub fn http_redirect_fetch(
.status
.as_ref()
.map_or(true, |s| s.0 != StatusCode::SEE_OTHER) &&
request.body.as_ref().map_or(false, |b| b.is_empty())
request.body.as_ref().map_or(false, |b| b.source_is_null())
{
return Response::network_error(NetworkError::Internal("Request body is not done".into()));
}
@ -943,7 +1014,7 @@ fn http_network_or_cache_fetch(
_ => None,
},
// Step 5.6
Some(ref http_request_body) => Some(http_request_body.len() as u64),
Some(ref http_request_body) => http_request_body.len().map(|size| size as u64),
};
// Step 5.7
@ -1460,7 +1531,7 @@ impl Drop for ResponseEndTimer {
/// [HTTP network fetch](https://fetch.spec.whatwg.org/#http-network-fetch)
fn http_network_fetch(
request: &Request,
request: &mut Request,
credentials_flag: bool,
done_chan: &mut DoneChannel,
context: &FetchContext,
@ -1503,7 +1574,9 @@ fn http_network_fetch(
&url,
&request.method,
&request.headers,
&request.body,
request.body.as_mut().and_then(|body| body.take_stream()),
request.body.as_ref().and_then(|body| body.len()),
&request.method,
&request.pipeline_id,
request_id.as_ref().map(Deref::deref),
is_xhr,

View file

@ -24,13 +24,18 @@ use http::uri::Authority;
use http::{Method, StatusCode};
use hyper::body::Body;
use hyper::{Request as HyperRequest, Response as HyperResponse};
use ipc_channel::ipc;
use ipc_channel::router::ROUTER;
use msg::constellation_msg::TEST_PIPELINE_ID;
use net::cookie::Cookie;
use net::cookie_storage::CookieStorage;
use net::http_loader::determine_request_referrer;
use net::resource_thread::AuthCacheEntry;
use net::test::replace_host_table;
use net_traits::request::{CredentialsMode, Destination, RequestBuilder, RequestMode};
use net_traits::request::{
BodyChunkRequest, BodySource, CredentialsMode, Destination, RequestBody, RequestBuilder,
RequestMode,
};
use net_traits::response::{HttpsState, ResponseBody};
use net_traits::{CookieSource, NetworkError, ReferrerPolicy};
use servo_url::{ImmutableOrigin, ServoUrl};
@ -94,6 +99,27 @@ pub fn expect_devtools_http_response(
}
}
fn create_request_body_with_content(content: Vec<u8>) -> RequestBody {
let content_len = content.len();
let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
ROUTER.add_route(
chunk_request_receiver.to_opaque(),
Box::new(move |message| {
let request = message.to().unwrap();
if let BodyChunkRequest::Connect(sender) = request {
let _ = sender.send(content.clone());
}
}),
);
RequestBody {
stream: Some(chunk_request_sender),
source: BodySource::USVString,
total_bytes: Some(content_len),
}
}
#[test]
fn test_check_default_headers_loaded_in_every_request() {
let expected_headers = Arc::new(Mutex::new(None));
@ -276,7 +302,7 @@ fn test_request_and_response_data_with_network_messages() {
url: url,
method: Method::GET,
headers: headers,
body: Some(b"".to_vec()),
body: Some(vec![]),
pipeline_id: TEST_PIPELINE_ID,
startedDateTime: devhttprequest.startedDateTime,
timeStamp: devhttprequest.timeStamp,
@ -526,8 +552,11 @@ fn test_load_doesnt_send_request_body_on_any_redirect() {
};
let (pre_server, pre_url) = make_server(pre_handler);
let content = b"Body on POST!";
let request_body = create_request_body_with_content(content.to_vec());
let mut request = RequestBuilder::new(pre_url.clone())
.body(Some(b"Body on POST!".to_vec()))
.body(Some(request_body))
.method(Method::POST)
.destination(Destination::Document)
.origin(mock_origin())
@ -819,9 +848,11 @@ fn test_load_sets_content_length_to_length_of_request_body() {
};
let (server, url) = make_server(handler);
let request_body = create_request_body_with_content(content.to_vec());
let mut request = RequestBuilder::new(url.clone())
.method(Method::POST)
.body(Some(content.to_vec()))
.body(Some(request_body))
.destination(Destination::Document)
.origin(mock_origin())
.pipeline_id(Some(TEST_PIPELINE_ID))

View file

@ -8,6 +8,7 @@ use crate::ResourceTimingType;
use content_security_policy::{self as csp, CspList};
use http::HeaderMap;
use hyper::Method;
use ipc_channel::ipc::IpcSender;
use mime::Mime;
use msg::constellation_msg::PipelineId;
use servo_url::{ImmutableOrigin, ServoUrl};
@ -115,6 +116,57 @@ pub enum ParserMetadata {
NotParserInserted,
}
/// <https://fetch.spec.whatwg.org/#concept-body-source>
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub enum BodySource {
Null,
Blob,
BufferSource,
FormData,
URLSearchParams,
USVString,
}
/// Messages used to implement <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum BodyChunkRequest {
/// Connect a fetch in `net`, with a stream of bytes from `script`.
Connect(IpcSender<Vec<u8>>),
/// Ask for another chunk.
Chunk,
/// Signal the stream is done.
Done,
}
/// The net component's view into <https://fetch.spec.whatwg.org/#bodies>
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct RequestBody {
/// Net's view into a <https://fetch.spec.whatwg.org/#concept-body-stream>
#[ignore_malloc_size_of = "Channels are hard"]
pub stream: Option<IpcSender<BodyChunkRequest>>,
/// <https://fetch.spec.whatwg.org/#concept-body-source>
pub source: BodySource,
/// <https://fetch.spec.whatwg.org/#concept-body-total-bytes>
pub total_bytes: Option<usize>,
}
impl RequestBody {
pub fn take_stream(&mut self) -> Option<IpcSender<BodyChunkRequest>> {
self.stream.take()
}
pub fn source_is_null(&self) -> bool {
if let BodySource::Null = self.source {
return true;
}
false
}
pub fn len(&self) -> Option<usize> {
self.total_bytes.clone()
}
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct RequestBuilder {
#[serde(
@ -131,7 +183,7 @@ pub struct RequestBuilder {
#[ignore_malloc_size_of = "Defined in hyper"]
pub headers: HeaderMap,
pub unsafe_request: bool,
pub body: Option<Vec<u8>>,
pub body: Option<RequestBody>,
pub service_workers_mode: ServiceWorkersMode,
// TODO: client object
pub destination: Destination,
@ -210,7 +262,7 @@ impl RequestBuilder {
self
}
pub fn body(mut self, body: Option<Vec<u8>>) -> RequestBuilder {
pub fn body(mut self, body: Option<RequestBody>) -> RequestBuilder {
self.body = body;
self
}
@ -338,7 +390,7 @@ pub struct Request {
/// <https://fetch.spec.whatwg.org/#unsafe-request-flag>
pub unsafe_request: bool,
/// <https://fetch.spec.whatwg.org/#concept-request-body>
pub body: Option<Vec<u8>>,
pub body: Option<RequestBody>,
// TODO: client object
pub window: Window,
// TODO: target browsing context

View file

@ -2,19 +2,34 @@
* 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 crate::dom::bindings::cell::Ref;
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::USVString;
use crate::dom::bindings::settings_stack::AutoIncumbentScript;
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::realms::{AlreadyInRealm, InRealm};
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, IpcSender};
use ipc_channel::router::ROUTER;
use js::jsapi::Heap;
use js::jsapi::JSObject;
use js::jsapi::JS_ClearPendingException;
@ -23,14 +38,394 @@ use js::jsval::JSVal;
use js::jsval::UndefinedValue;
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, BodySource, RequestBody};
use script_traits::serializable::BlobImpl;
use std::ptr;
use std::rc::Rc;
use std::str;
use url::form_urlencoded;
/// 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.
struct TransmitBodyConnectHandler {
stream: Trusted<ReadableStream>,
task_source: NetworkingTaskSource,
canceller: TaskCanceller,
bytes_sender: Option<IpcSender<Vec<u8>>>,
control_sender: IpcSender<BodyChunkRequest>,
}
impl TransmitBodyConnectHandler {
pub fn new(
stream: Trusted<ReadableStream>,
task_source: NetworkingTaskSource,
canceller: TaskCanceller,
control_sender: IpcSender<BodyChunkRequest>,
) -> TransmitBodyConnectHandler {
TransmitBodyConnectHandler {
stream: stream,
task_source,
canceller,
bytes_sender: None,
control_sender,
}
}
/// Take the IPC sender sent by `net`, so we can send body chunks with it.
pub fn start_reading(&mut self, sender: IpcSender<Vec<u8>>) {
self.bytes_sender = Some(sender);
}
/// Drop the IPC sender sent by `net`
pub fn stop_reading(&mut self) {
// Note: this should close the corresponding receiver,
// and terminate the request stream in `net`.
self.bytes_sender = None;
}
/// The entry point to <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
pub fn transmit_body_chunk(&mut self) {
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.");
let _ = self.task_source.queue_with_canceller(
task!(setup_native_body_promise_handler: move || {
// Step 1, Let body be requests body.
//
// TODO: We need the handle the body null case,
// here assuming body is something and we have the corresponding stream.
let rooted_stream = stream.root();
let global = rooted_stream.global();
// TODO: Step 2, If body is null,
// then queue a fetch task on request to process request end-of-body
// for request and abort these steps.
// TODO: queuing those "process request ..." tasks means we also need a handle on Request here.
// Step 3, get a reader for stream.
if rooted_stream.start_reading().is_err() {
// Note: this can happen if script starts consuming request body
// before fetch starts transmitting it.
// Not in the spec.
return;
}
// Step 4, the result of reading a chunk from bodys 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,
stream: rooted_stream.clone(),
control_sender,
});
let rejection_handler = Box::new(TransmitBodyPromiseRejectionHandler {stream: rooted_stream});
let handler = PromiseNativeHandler::new(&global, Some(promise_handler), Some(rejection_handler));
// Enter a realm, and a script,
// before appending the native handler.
let _realm = enter_realm(&*global);
let _ais = AutoIncumbentScript::new(&*global);
promise.append_native_handler(&handler);
}),
&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<Vec<u8>>,
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.
// TODO: terminate fetch.
let _ = self.control_sender.send(BodyChunkRequest::Done);
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(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 {
stream: DomRoot<ReadableStream>,
}
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.
// TODO: terminate fetch.
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);
let mut body_handler = TransmitBodyConnectHandler::new(
trusted_stream,
task_source,
canceller,
chunk_request_sender.clone(),
);
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::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(),
}
}),
);
// Return `components::net` view into this request body,
// which can be used by `net` to transmit it over the network.
let request_body = RequestBody {
stream: Some(chunk_request_sender),
source,
total_bytes,
};
// Also return the stream for this body, which can be used by script to consume it.
(request_body, stream)
}
}
/// <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::BufferSource,
})
},
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::BufferSource,
})
},
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::FormData,
})
}
}
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::Blob,
})
}
}
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::USVString,
})
}
}
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::FormData,
})
}
}
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::URLSearchParams,
})
}
}
#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
pub enum BodyType {
Blob,
@ -49,73 +444,212 @@ pub enum FetchedData {
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));
// Enter a realm, and a script,
// before appending the native handler.
let _realm = enter_realm(&*global);
let _ais = AutoIncumbentScript::new(&*global);
read_promise.append_native_handler(&handler);
}
}
}
// 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 in_realm_proof = AlreadyInRealm::assert(&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() {
if object.is_disturbed() || object.is_locked() {
promise.reject_error(Error::Type(
"The response's stream is disturbed or locked".to_string(),
"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());
promise
}
// https://fetch.spec.whatwg.org/#concept-body-consume-body
#[allow(unrooted_must_root)]
pub fn consume_body_with_promise<T: BodyOperations + DomObject>(
fn consume_body_with_promise<T: BodyMixin + DomObject>(
object: &T,
body_type: BodyType,
promise: &Promise,
promise: Rc<Promise>,
) {
// Step 5
let body = match object.take_body() {
Some(body) => body,
None => return,
let global = object.global();
// Step 2.
let stream = match object.body() {
Some(stream) => stream,
None => {
let stream = ReadableStream::new_from_bytes(&global, Vec::with_capacity(0));
stream
},
};
let pkg_data_results =
run_package_data_algorithm(object, body, body_type, object.get_mime_type());
match pkg_data_results {
Ok(results) => {
match results {
FetchedData::Text(s) => promise.resolve_native(&USVString(s)),
FetchedData::Json(j) => promise.resolve_native(&j),
FetchedData::BlobData(b) => promise.resolve_native(&b),
FetchedData::FormData(f) => promise.resolve_native(&f),
FetchedData::ArrayBuffer(a) => promise.resolve_native(&a),
FetchedData::JSException(e) => promise.reject_native(&e.handle()),
};
},
Err(err) => promise.reject_error(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);
}
// https://fetch.spec.whatwg.org/#concept-body-package-data
#[allow(unsafe_code)]
fn run_package_data_algorithm<T: BodyOperations + DomObject>(
object: &T,
fn run_package_data_algorithm(
cx: JSContext,
bytes: Vec<u8>,
body_type: BodyType,
mime_type: Ref<Vec<u8>>,
mime_type: Vec<u8>,
) -> Fallible<FetchedData> {
let global = object.global();
let cx = global.get_cx();
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),
@ -218,12 +752,14 @@ pub fn run_array_buffer_data_algorithm(cx: JSContext, bytes: Vec<u8>) -> Fallibl
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>;
}

View file

@ -906,6 +906,40 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
return handleOptional(templateBody, declType, handleDefault("None"))
if type.isReadableStream():
assert not isEnforceRange and not isClamp
if failureCode is None:
unwrapFailureCode = '''throw_type_error(*cx, "This object is not \
an instance of ReadableStream.");\n'''
else:
unwrapFailureCode = failureCode
templateBody = fill(
"""
{
use crate::realms::{AlreadyInRealm, InRealm};
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
match ReadableStream::from_js(cx, $${val}.get().to_object(), InRealm::Already(&in_realm_proof)) {
Ok(val) => val,
Err(()) => {
$*{failureCode}
}
}
}
""",
failureCode=unwrapFailureCode + "\n",
)
templateBody = wrapObjectTemplate(templateBody, "None",
isDefinitelyObject, type, failureCode)
declType = CGGeneric("DomRoot<ReadableStream>")
return handleOptional(templateBody, declType,
handleDefault("None"))
elif type.isSpiderMonkeyInterface():
raise TypeError("Can't handle SpiderMonkey interface arguments other than typed arrays yet")
@ -4481,6 +4515,9 @@ def getUnionTypeTemplateVars(type, descriptorProvider):
elif type.isObject():
name = type.name
typeName = "Heap<*mut JSObject>"
elif type.isReadableStream():
name = type.name
typeName = "DomRoot<ReadableStream>"
elif is_typed_array(type):
name = type.name
typeName = "typedarray::Heap" + name

View file

@ -138,6 +138,7 @@ pub unsafe fn create_global_object(
let mut options = RealmOptions::default();
options.creationOptions_.traceGlobal_ = Some(trace);
options.creationOptions_.sharedMemoryAndAtomics_ = true;
options.creationOptions_.streams_ = true;
rval.set(JS_NewGlobalObject(
*cx,

View file

@ -14,14 +14,18 @@ use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::structuredclone::StructuredDataHolder;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::dom::readablestream::ReadableStream;
use crate::realms::{AlreadyInRealm, InRealm};
use crate::script_runtime::JSContext;
use dom_struct::dom_struct;
use encoding_rs::UTF_8;
use js::jsapi::JSObject;
use msg::constellation_msg::{BlobId, BlobIndex, PipelineNamespaceId};
use net_traits::filemanager_thread::RelativePos;
use script_traits::serializable::BlobImpl;
use std::collections::HashMap;
use std::num::NonZeroU32;
use std::ptr::NonNull;
use std::rc::Rc;
use uuid::Uuid;
@ -34,13 +38,7 @@ pub struct Blob {
impl Blob {
pub fn new(global: &GlobalScope, blob_impl: BlobImpl) -> DomRoot<Blob> {
let dom_blob = reflect_dom_object(
Box::new(Blob {
reflector_: Reflector::new(),
blob_id: blob_impl.blob_id(),
}),
global,
);
let dom_blob = reflect_dom_object(Box::new(Blob::new_inherited(&blob_impl)), global);
global.track_blob(&dom_blob, blob_impl);
dom_blob
}
@ -89,6 +87,11 @@ impl Blob {
pub fn get_blob_url_id(&self) -> Uuid {
self.global().get_blob_url_id(&self.blob_id)
}
/// <https://w3c.github.io/FileAPI/#blob-get-stream>
pub fn get_stream(&self) -> DomRoot<ReadableStream> {
self.global().get_blob_stream(&self.blob_id)
}
}
impl Serializable for Blob {
@ -213,6 +216,11 @@ impl BlobMethods for Blob {
DOMString::from(self.type_string())
}
// <https://w3c.github.io/FileAPI/#blob-get-stream>
fn Stream(&self, _cx: JSContext) -> NonNull<JSObject> {
self.get_stream().get_js_stream()
}
// https://w3c.github.io/FileAPI/#slice-method-algo
fn Slice(
&self,

View file

@ -43,6 +43,7 @@ use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
use crate::dom::performance::Performance;
use crate::dom::performanceobserver::VALID_ENTRY_TYPES;
use crate::dom::promise::Promise;
use crate::dom::readablestream::{ExternalUnderlyingSource, ReadableStream};
use crate::dom::serviceworker::ServiceWorker;
use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
use crate::dom::window::Window;
@ -319,11 +320,19 @@ struct FileListener {
task_canceller: TaskCanceller,
}
struct FileListenerCallback(Box<dyn Fn(Rc<Promise>, Result<Vec<u8>, Error>) + Send>);
enum FileListenerCallback {
Promise(Box<dyn Fn(Rc<Promise>, Result<Vec<u8>, Error>) + Send>),
Stream,
}
enum FileListenerTarget {
Promise(TrustedPromise),
Stream(Trusted<ReadableStream>),
}
enum FileListenerState {
Empty(FileListenerCallback, TrustedPromise),
Receiving(Vec<u8>, FileListenerCallback, TrustedPromise),
Empty(FileListenerCallback, FileListenerTarget),
Receiving(Vec<u8>, FileListenerCallback, FileListenerTarget),
}
#[derive(JSTraceable, MallocSizeOf)]
@ -356,6 +365,14 @@ pub enum BlobState {
UnManaged,
}
/// The result of looking-up the data for a Blob,
/// containing either the in-memory bytes,
/// or the file-id.
enum BlobResult {
Bytes(Vec<u8>),
File(Uuid, usize),
}
/// Data representing a message-port managed by this global.
#[derive(JSTraceable, MallocSizeOf)]
#[unrooted_must_root_lint::must_root]
@ -532,57 +549,137 @@ impl MessageListener {
}
}
/// Callback used to enqueue file chunks to streams as part of FileListener.
fn stream_handle_incoming(stream: &ReadableStream, bytes: Result<Vec<u8>, Error>) {
match bytes {
Ok(b) => {
stream.enqueue_native(b);
},
Err(e) => {
stream.error_native(e);
},
}
}
/// Callback used to close streams as part of FileListener.
fn stream_handle_eof(stream: &ReadableStream) {
stream.close_native();
}
impl FileListener {
fn handle(&mut self, msg: FileManagerResult<ReadFileProgress>) {
match msg {
Ok(ReadFileProgress::Meta(blob_buf)) => match self.state.take() {
Some(FileListenerState::Empty(callback, promise)) => {
self.state = Some(FileListenerState::Receiving(
blob_buf.bytes,
callback,
promise,
));
Some(FileListenerState::Empty(callback, target)) => {
let bytes = if let FileListenerTarget::Stream(ref trusted_stream) = target {
let trusted = trusted_stream.clone();
let task = task!(enqueue_stream_chunk: move || {
let stream = trusted.root();
stream_handle_incoming(&*stream, Ok(blob_buf.bytes));
});
let _ = self
.task_source
.queue_with_canceller(task, &self.task_canceller);
Vec::with_capacity(0)
} else {
blob_buf.bytes
};
self.state = Some(FileListenerState::Receiving(bytes, callback, target));
},
_ => panic!(
"Unexpected FileListenerState when receiving ReadFileProgress::Meta msg."
),
},
Ok(ReadFileProgress::Partial(mut bytes_in)) => match self.state.take() {
Some(FileListenerState::Receiving(mut bytes, callback, promise)) => {
bytes.append(&mut bytes_in);
self.state = Some(FileListenerState::Receiving(bytes, callback, promise));
Some(FileListenerState::Receiving(mut bytes, callback, target)) => {
if let FileListenerTarget::Stream(ref trusted_stream) = target {
let trusted = trusted_stream.clone();
let task = task!(enqueue_stream_chunk: move || {
let stream = trusted.root();
stream_handle_incoming(&*stream, Ok(bytes_in));
});
let _ = self
.task_source
.queue_with_canceller(task, &self.task_canceller);
} else {
bytes.append(&mut bytes_in);
};
self.state = Some(FileListenerState::Receiving(bytes, callback, target));
},
_ => panic!(
"Unexpected FileListenerState when receiving ReadFileProgress::Partial msg."
),
},
Ok(ReadFileProgress::EOF) => match self.state.take() {
Some(FileListenerState::Receiving(bytes, callback, trusted_promise)) => {
let _ = self.task_source.queue_with_canceller(
task!(resolve_promise: move || {
Some(FileListenerState::Receiving(bytes, callback, target)) => match target {
FileListenerTarget::Promise(trusted_promise) => {
let callback = match callback {
FileListenerCallback::Promise(callback) => callback,
_ => panic!("Expected promise callback."),
};
let task = task!(resolve_promise: move || {
let promise = trusted_promise.root();
let _ac = enter_realm(&*promise.global());
callback.0(promise, Ok(bytes));
}),
&self.task_canceller,
);
callback(promise, Ok(bytes));
});
let _ = self
.task_source
.queue_with_canceller(task, &self.task_canceller);
},
FileListenerTarget::Stream(trusted_stream) => {
let trusted = trusted_stream.clone();
let task = task!(enqueue_stream_chunk: move || {
let stream = trusted.root();
stream_handle_eof(&*stream);
});
let _ = self
.task_source
.queue_with_canceller(task, &self.task_canceller);
},
},
_ => {
panic!("Unexpected FileListenerState when receiving ReadFileProgress::EOF msg.")
},
},
Err(_) => match self.state.take() {
Some(FileListenerState::Receiving(_, callback, trusted_promise)) |
Some(FileListenerState::Empty(callback, trusted_promise)) => {
let bytes = Err(Error::Network);
let _ = self.task_source.queue_with_canceller(
task!(reject_promise: move || {
let promise = trusted_promise.root();
let _ac = enter_realm(&*promise.global());
callback.0(promise, bytes);
}),
&self.task_canceller,
);
Some(FileListenerState::Receiving(_, callback, target)) |
Some(FileListenerState::Empty(callback, target)) => {
let error = Err(Error::Network);
match target {
FileListenerTarget::Promise(trusted_promise) => {
let callback = match callback {
FileListenerCallback::Promise(callback) => callback,
_ => panic!("Expected promise callback."),
};
let _ = self.task_source.queue_with_canceller(
task!(reject_promise: move || {
let promise = trusted_promise.root();
let _ac = enter_realm(&*promise.global());
callback(promise, error);
}),
&self.task_canceller,
);
},
FileListenerTarget::Stream(trusted_stream) => {
let _ = self.task_source.queue_with_canceller(
task!(error_stream: move || {
let stream = trusted_stream.root();
stream_handle_incoming(&*stream, error);
}),
&self.task_canceller,
);
},
}
},
_ => panic!("Unexpected FileListenerState when receiving Err msg."),
},
@ -1565,6 +1662,70 @@ impl GlobalScope {
}
}
/// Get a slice to the inner data of a Blob,
/// if it's a memory blob, or it's file-id and file-size otherwise.
///
/// Note: this is almost a duplicate of `get_blob_bytes`,
/// tweaked for integration with streams.
/// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams.
fn get_blob_bytes_or_file_id(&self, blob_id: &BlobId) -> BlobResult {
let parent = {
let blob_state = self.blob_state.borrow();
if let BlobState::Managed(blobs_map) = &*blob_state {
let blob_info = blobs_map
.get(blob_id)
.expect("get_blob_bytes_or_file_id for an unknown blob.");
match blob_info.blob_impl.blob_data() {
BlobData::Sliced(ref parent, ref rel_pos) => {
Some((parent.clone(), rel_pos.clone()))
},
_ => None,
}
} else {
panic!("get_blob_bytes_or_file_id called on a global not managing any blobs.");
}
};
match parent {
Some((parent_id, rel_pos)) => {
match self.get_blob_bytes_non_sliced_or_file_id(&parent_id) {
BlobResult::Bytes(bytes) => {
let range = rel_pos.to_abs_range(bytes.len());
BlobResult::Bytes(bytes.index(range).to_vec())
},
res => res,
}
},
None => self.get_blob_bytes_non_sliced_or_file_id(blob_id),
}
}
/// Get bytes from a non-sliced blob if in memory, or it's file-id and file-size.
///
/// Note: this is almost a duplicate of `get_blob_bytes_non_sliced`,
/// tweaked for integration with streams.
/// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams.
fn get_blob_bytes_non_sliced_or_file_id(&self, blob_id: &BlobId) -> BlobResult {
let blob_state = self.blob_state.borrow();
if let BlobState::Managed(blobs_map) = &*blob_state {
let blob_info = blobs_map
.get(blob_id)
.expect("get_blob_bytes_non_sliced_or_file_id called for a unknown blob.");
match blob_info.blob_impl.blob_data() {
BlobData::File(ref f) => match f.get_cache() {
Some(bytes) => BlobResult::Bytes(bytes.clone()),
None => BlobResult::File(f.get_id(), f.get_size() as usize),
},
BlobData::Memory(ref s) => BlobResult::Bytes(s.clone()),
BlobData::Sliced(_, _) => panic!("This blob doesn't have a parent."),
}
} else {
panic!(
"get_blob_bytes_non_sliced_or_file_id called on a global not managing any blobs."
);
}
}
/// Get a copy of the type_string of a blob.
pub fn get_blob_type_string(&self, blob_id: &BlobId) -> String {
let blob_state = self.blob_state.borrow();
@ -1769,6 +1930,50 @@ impl GlobalScope {
GlobalScope::read_msg(recv)
}
/// <https://w3c.github.io/FileAPI/#blob-get-stream>
pub fn get_blob_stream(&self, blob_id: &BlobId) -> DomRoot<ReadableStream> {
let (file_id, size) = match self.get_blob_bytes_or_file_id(blob_id) {
BlobResult::Bytes(bytes) => {
// If we have all the bytes in memory, queue them and close the stream.
let stream = ReadableStream::new_from_bytes(self, bytes);
return stream;
},
BlobResult::File(id, size) => (id, size),
};
let stream = ReadableStream::new_with_external_underlying_source(
self,
ExternalUnderlyingSource::Blob(size as usize),
);
let recv = self.send_msg(file_id);
let trusted_stream = Trusted::new(&*stream.clone());
let task_canceller = self.task_canceller(TaskSourceName::FileReading);
let task_source = self.file_reading_task_source();
let mut file_listener = FileListener {
state: Some(FileListenerState::Empty(
FileListenerCallback::Stream,
FileListenerTarget::Stream(trusted_stream),
)),
task_source,
task_canceller,
};
ROUTER.add_route(
recv.to_opaque(),
Box::new(move |msg| {
file_listener.handle(
msg.to()
.expect("Deserialization of file listener msg failed."),
);
}),
);
stream
}
pub fn read_file_async(
&self,
id: Uuid,
@ -1783,8 +1988,8 @@ impl GlobalScope {
let mut file_listener = FileListener {
state: Some(FileListenerState::Empty(
FileListenerCallback(callback),
trusted_promise,
FileListenerCallback::Promise(callback),
FileListenerTarget::Promise(trusted_promise),
)),
task_source,
task_canceller,
@ -1894,6 +2099,12 @@ impl GlobalScope {
global_scope_from_global(global, cx)
}
/// Returns the global scope for the given SafeJSContext
#[allow(unsafe_code)]
pub fn from_safe_context(cx: SafeJSContext, realm: InRealm) -> DomRoot<Self> {
unsafe { Self::from_context(*cx, realm) }
}
/// Returns the global object of the realm that the given JS object
/// was created in, after unwrapping any wrappers.
#[allow(unsafe_code)]

View file

@ -2,6 +2,7 @@
* 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 crate::body::Extractable;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrBinding::AttrMethods;
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
@ -799,7 +800,15 @@ impl HTMLFormElement {
},
};
load_data.data = Some(bytes);
let global = self.global();
let request_body = bytes
.extract(&global)
.expect("Couldn't extract body.")
.into_net_request_body()
.0;
load_data.data = Some(request_body);
self.plan_to_navigate(load_data, target);
}

View file

@ -481,6 +481,7 @@ pub mod promiserejectionevent;
pub mod radionodelist;
pub mod range;
pub mod raredata;
pub mod readablestream;
pub mod request;
pub mod response;
pub mod rtcicecandidate;

View file

@ -7,13 +7,14 @@ use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::trace::JSTraceable;
use crate::dom::globalscope::GlobalScope;
use crate::realms::InRealm;
use crate::script_runtime::JSContext as SafeJSContext;
use dom_struct::dom_struct;
use js::jsapi::JSContext;
use js::rust::HandleValue;
use malloc_size_of::MallocSizeOf;
pub trait Callback: JSTraceable + MallocSizeOf {
fn callback(&self, cx: *mut JSContext, v: HandleValue, realm: InRealm);
fn callback(&self, cx: SafeJSContext, v: HandleValue, realm: InRealm);
}
#[dom_struct]
@ -39,12 +40,14 @@ impl PromiseNativeHandler {
)
}
#[allow(unsafe_code)]
fn callback(
callback: &Option<Box<dyn Callback>>,
cx: *mut JSContext,
v: HandleValue,
realm: InRealm,
) {
let cx = unsafe { SafeJSContext::from_ptr(cx) };
if let Some(ref callback) = *callback {
callback.callback(cx, v, realm)
}

View file

@ -0,0 +1,509 @@
/* 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 crate::dom::bindings::conversions::{ConversionBehavior, ConversionResult};
use crate::dom::bindings::error::Error;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::settings_stack::AutoEntryScript;
use crate::dom::bindings::utils::get_dictionary_property;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::js::conversions::FromJSValConvertible;
use crate::realms::{enter_realm, InRealm};
use crate::script_runtime::JSContext as SafeJSContext;
use dom_struct::dom_struct;
use js::glue::{
CreateReadableStreamUnderlyingSource, DeleteReadableStreamUnderlyingSource,
ReadableStreamUnderlyingSourceTraps,
};
use js::jsapi::{HandleObject, HandleValue, Heap, JSContext, JSObject};
use js::jsapi::{
IsReadableStream, NewReadableExternalSourceStreamObject, ReadableStreamClose,
ReadableStreamDefaultReaderRead, ReadableStreamError, ReadableStreamGetReader,
ReadableStreamIsDisturbed, ReadableStreamIsLocked, ReadableStreamIsReadable,
ReadableStreamReaderMode, ReadableStreamReaderReleaseLock, ReadableStreamUnderlyingSource,
ReadableStreamUpdateDataAvailableFromSource, UnwrapReadableStream,
};
use js::jsval::JSVal;
use js::jsval::UndefinedValue;
use js::rust::HandleValue as SafeHandleValue;
use js::rust::IntoHandle;
use std::cell::{Cell, RefCell};
use std::os::raw::c_void;
use std::ptr::{self, NonNull};
use std::rc::Rc;
use std::slice;
static UNDERLYING_SOURCE_TRAPS: ReadableStreamUnderlyingSourceTraps =
ReadableStreamUnderlyingSourceTraps {
requestData: Some(request_data),
writeIntoReadRequestBuffer: Some(write_into_read_request_buffer),
cancel: Some(cancel),
onClosed: Some(close),
onErrored: Some(error),
finalize: Some(finalize),
};
#[dom_struct]
pub struct ReadableStream {
reflector_: Reflector,
#[ignore_malloc_size_of = "SM handles JS values"]
js_stream: Heap<*mut JSObject>,
#[ignore_malloc_size_of = "SM handles JS values"]
js_reader: Heap<*mut JSObject>,
has_reader: Cell<bool>,
#[ignore_malloc_size_of = "Rc is hard"]
external_underlying_source: Option<Rc<ExternalUnderlyingSourceController>>,
}
impl ReadableStream {
fn new_inherited(
external_underlying_source: Option<Rc<ExternalUnderlyingSourceController>>,
) -> ReadableStream {
ReadableStream {
reflector_: Reflector::new(),
js_stream: Heap::default(),
js_reader: Heap::default(),
has_reader: Default::default(),
external_underlying_source: external_underlying_source,
}
}
fn new(
global: &GlobalScope,
external_underlying_source: Option<Rc<ExternalUnderlyingSourceController>>,
) -> DomRoot<ReadableStream> {
reflect_dom_object(
Box::new(ReadableStream::new_inherited(external_underlying_source)),
global,
)
}
/// Used from RustCodegen.py
#[allow(unsafe_code)]
pub unsafe fn from_js(
cx: SafeJSContext,
obj: *mut JSObject,
realm: InRealm,
) -> Result<DomRoot<ReadableStream>, ()> {
if !IsReadableStream(obj) {
return Err(());
}
let global = GlobalScope::from_safe_context(cx, realm);
let stream = ReadableStream::new(&global, None);
stream.js_stream.set(UnwrapReadableStream(obj));
Ok(stream)
}
/// Build a stream backed by a Rust source that has already been read into memory.
pub fn new_from_bytes(global: &GlobalScope, bytes: Vec<u8>) -> DomRoot<ReadableStream> {
let stream = ReadableStream::new_with_external_underlying_source(
&global,
ExternalUnderlyingSource::Memory(bytes.len()),
);
stream.enqueue_native(bytes);
stream.close_native();
stream
}
/// Build a stream backed by a Rust underlying source.
#[allow(unsafe_code)]
pub fn new_with_external_underlying_source(
global: &GlobalScope,
source: ExternalUnderlyingSource,
) -> DomRoot<ReadableStream> {
let _ar = enter_realm(global);
let cx = global.get_cx();
let source = Rc::new(ExternalUnderlyingSourceController::new(source));
let stream = ReadableStream::new(&global, Some(source.clone()));
unsafe {
let js_wrapper = CreateReadableStreamUnderlyingSource(
&UNDERLYING_SOURCE_TRAPS,
&*source as *const _ as *const c_void,
);
rooted!(in(*cx)
let js_stream = NewReadableExternalSourceStreamObject(
*cx,
js_wrapper,
ptr::null_mut(),
HandleObject::null(),
)
);
stream.js_stream.set(UnwrapReadableStream(js_stream.get()));
}
stream
}
/// Get a pointer to the underlying JS object.
pub fn get_js_stream(&self) -> NonNull<JSObject> {
NonNull::new(self.js_stream.get())
.expect("Couldn't get a non-null pointer to JS stream object.")
}
#[allow(unsafe_code)]
pub fn enqueue_native(&self, bytes: Vec<u8>) {
let global = self.global();
let _ar = enter_realm(&*global);
let cx = global.get_cx();
let handle = unsafe { self.js_stream.handle() };
self.external_underlying_source
.as_ref()
.expect("No external source to enqueue bytes.")
.enqueue_chunk(cx, handle, bytes);
}
#[allow(unsafe_code)]
pub fn error_native(&self, error: Error) {
let global = self.global();
let _ar = enter_realm(&*global);
let cx = global.get_cx();
unsafe {
rooted!(in(*cx) let mut js_error = UndefinedValue());
error.to_jsval(*cx, &global, js_error.handle_mut());
ReadableStreamError(
*cx,
self.js_stream.handle(),
js_error.handle().into_handle(),
);
}
}
#[allow(unsafe_code)]
pub fn close_native(&self) {
let global = self.global();
let _ar = enter_realm(&*global);
let cx = global.get_cx();
let handle = unsafe { self.js_stream.handle() };
self.external_underlying_source
.as_ref()
.expect("No external source to close.")
.close(cx, handle);
}
/// Acquires a reader and locks the stream,
/// must be done before `read_a_chunk`.
#[allow(unsafe_code)]
pub fn start_reading(&self) -> Result<(), ()> {
if self.is_locked() || self.is_disturbed() {
return Err(());
}
let global = self.global();
let _ar = enter_realm(&*global);
let cx = global.get_cx();
unsafe {
rooted!(in(*cx) let reader = ReadableStreamGetReader(
*cx,
self.js_stream.handle(),
ReadableStreamReaderMode::Default,
));
// Note: the stream is locked to the reader.
self.js_reader.set(reader.get());
}
self.has_reader.set(true);
Ok(())
}
/// Read a chunk from the stream,
/// must be called after `start_reading`,
/// and before `stop_reading`.
#[allow(unsafe_code)]
pub fn read_a_chunk(&self) -> Rc<Promise> {
if !self.has_reader.get() {
panic!("Attempt to read stream chunk without having acquired a reader.");
}
let global = self.global();
let _ar = enter_realm(&*global);
let _aes = AutoEntryScript::new(&*global);
let cx = global.get_cx();
unsafe {
rooted!(in(*cx) let promise_obj = ReadableStreamDefaultReaderRead(
*cx,
self.js_reader.handle(),
));
Promise::new_with_js_promise(promise_obj.handle(), cx)
}
}
/// Releases the lock on the reader,
/// must be done after `start_reading`.
#[allow(unsafe_code)]
pub fn stop_reading(&self) {
if !self.has_reader.get() {
panic!("ReadableStream::stop_reading called on a readerless stream.");
}
self.has_reader.set(false);
let global = self.global();
let _ar = enter_realm(&*global);
let cx = global.get_cx();
unsafe {
ReadableStreamReaderReleaseLock(*cx, self.js_reader.handle());
// Note: is this the way to nullify the Heap?
self.js_reader.set(ptr::null_mut());
}
}
#[allow(unsafe_code)]
pub fn is_locked(&self) -> bool {
// If we natively took a reader, we're locked.
if self.has_reader.get() {
return true;
}
// Otherwise, still double-check that script didn't lock the stream.
let cx = self.global().get_cx();
let mut locked_or_disturbed = false;
unsafe {
ReadableStreamIsLocked(*cx, self.js_stream.handle(), &mut locked_or_disturbed);
}
locked_or_disturbed
}
#[allow(unsafe_code)]
pub fn is_disturbed(&self) -> bool {
// Check that script didn't disturb the stream.
let cx = self.global().get_cx();
let mut locked_or_disturbed = false;
unsafe {
ReadableStreamIsDisturbed(*cx, self.js_stream.handle(), &mut locked_or_disturbed);
}
locked_or_disturbed
}
}
#[allow(unsafe_code)]
unsafe extern "C" fn request_data(
source: *const c_void,
cx: *mut JSContext,
stream: HandleObject,
desired_size: usize,
) {
let source = &*(source as *const ExternalUnderlyingSourceController);
source.pull(SafeJSContext::from_ptr(cx), stream, desired_size);
}
#[allow(unsafe_code)]
unsafe extern "C" fn write_into_read_request_buffer(
source: *const c_void,
_cx: *mut JSContext,
_stream: HandleObject,
buffer: *mut c_void,
length: usize,
bytes_written: *mut usize,
) {
let source = &*(source as *const ExternalUnderlyingSourceController);
let slice = slice::from_raw_parts_mut(buffer as *mut u8, length);
source.write_into_buffer(slice);
// Currently we're always able to completely fulfill the write request.
*bytes_written = length;
}
#[allow(unsafe_code)]
unsafe extern "C" fn cancel(
_source: *const c_void,
_cx: *mut JSContext,
_stream: HandleObject,
_reason: HandleValue,
_resolve_to: *mut JSVal,
) {
}
#[allow(unsafe_code)]
unsafe extern "C" fn close(_source: *const c_void, _cx: *mut JSContext, _stream: HandleObject) {}
#[allow(unsafe_code)]
unsafe extern "C" fn error(
_source: *const c_void,
_cx: *mut JSContext,
_stream: HandleObject,
_reason: HandleValue,
) {
}
#[allow(unsafe_code)]
unsafe extern "C" fn finalize(source: *mut ReadableStreamUnderlyingSource) {
DeleteReadableStreamUnderlyingSource(source);
}
pub enum ExternalUnderlyingSource {
/// Facilitate partial integration with sources
/// that are currently read into memory.
Memory(usize),
/// A blob as underlying source, with a known total size.
Blob(usize),
/// A fetch response as underlying source.
FetchResponse,
}
#[derive(JSTraceable, MallocSizeOf)]
struct ExternalUnderlyingSourceController {
/// Loosely matches the underlying queue,
/// <https://streams.spec.whatwg.org/#internal-queues>
buffer: RefCell<Vec<u8>>,
/// Has the stream been closed by native code?
closed: Cell<bool>,
}
impl ExternalUnderlyingSourceController {
fn new(source: ExternalUnderlyingSource) -> ExternalUnderlyingSourceController {
let buffer = match source {
ExternalUnderlyingSource::Blob(size) | ExternalUnderlyingSource::Memory(size) => {
Vec::with_capacity(size)
},
ExternalUnderlyingSource::FetchResponse => vec![],
};
ExternalUnderlyingSourceController {
buffer: RefCell::new(buffer),
closed: Cell::new(false),
}
}
/// Signal available bytes if the stream is currently readable.
#[allow(unsafe_code)]
fn maybe_signal_available_bytes(
&self,
cx: SafeJSContext,
stream: HandleObject,
available: usize,
) {
if available == 0 {
return;
}
unsafe {
let mut readable = false;
if !ReadableStreamIsReadable(*cx, stream, &mut readable) {
return;
}
if readable {
ReadableStreamUpdateDataAvailableFromSource(*cx, stream, available as u32);
}
}
}
/// Close a currently readable js stream.
#[allow(unsafe_code)]
fn maybe_close_js_stream(&self, cx: SafeJSContext, stream: HandleObject) {
unsafe {
let mut readable = false;
if !ReadableStreamIsReadable(*cx, stream, &mut readable) {
return;
}
if readable {
ReadableStreamClose(*cx, stream);
}
}
}
fn close(&self, cx: SafeJSContext, stream: HandleObject) {
self.closed.set(true);
self.maybe_close_js_stream(cx, stream);
}
fn enqueue_chunk(&self, cx: SafeJSContext, stream: HandleObject, mut chunk: Vec<u8>) {
let available = {
let mut buffer = self.buffer.borrow_mut();
chunk.append(&mut buffer);
*buffer = chunk;
buffer.len()
};
self.maybe_signal_available_bytes(cx, stream, available);
}
#[allow(unsafe_code)]
fn pull(&self, cx: SafeJSContext, stream: HandleObject, _desired_size: usize) {
// Note: for pull sources,
// this would be the time to ask for a chunk.
if self.closed.get() {
return self.maybe_close_js_stream(cx, stream);
}
let available = {
let buffer = self.buffer.borrow();
buffer.len()
};
self.maybe_signal_available_bytes(cx, stream, available);
}
fn get_chunk_with_length(&self, length: usize) -> Vec<u8> {
let mut buffer = self.buffer.borrow_mut();
let buffer_len = buffer.len();
assert!(buffer_len >= length as usize);
buffer.split_off(buffer_len - length)
}
fn write_into_buffer(&self, dest: &mut [u8]) {
let length = dest.len();
let chunk = self.get_chunk_with_length(length);
dest.copy_from_slice(chunk.as_slice());
}
}
#[allow(unsafe_code)]
/// Get the `done` property of an object that a read promise resolved to.
pub fn get_read_promise_done(cx: SafeJSContext, v: &SafeHandleValue) -> Result<bool, Error> {
unsafe {
rooted!(in(*cx) let object = v.to_object());
rooted!(in(*cx) let mut done = UndefinedValue());
match get_dictionary_property(*cx, object.handle(), "done", done.handle_mut()) {
Ok(true) => match bool::from_jsval(*cx, done.handle(), ()) {
Ok(ConversionResult::Success(val)) => Ok(val),
Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.to_string())),
_ => Err(Error::Type("Unknown format for done property.".to_string())),
},
Ok(false) => Err(Error::Type("Promise has no done property.".to_string())),
Err(()) => Err(Error::JSFailed),
}
}
}
#[allow(unsafe_code)]
/// Get the `value` property of an object that a read promise resolved to.
pub fn get_read_promise_bytes(cx: SafeJSContext, v: &SafeHandleValue) -> Result<Vec<u8>, Error> {
unsafe {
rooted!(in(*cx) let object = v.to_object());
rooted!(in(*cx) let mut bytes = UndefinedValue());
match get_dictionary_property(*cx, object.handle(), "value", bytes.handle_mut()) {
Ok(true) => {
match Vec::<u8>::from_jsval(*cx, bytes.handle(), ConversionBehavior::EnforceRange) {
Ok(ConversionResult::Success(val)) => Ok(val),
Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.to_string())),
_ => Err(Error::Type("Unknown format for bytes read.".to_string())),
}
},
Ok(false) => Err(Error::Type("Promise has no value property.".to_string())),
Err(()) => Err(Error::JSFailed),
}
}
}

View file

@ -2,8 +2,9 @@
* 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 crate::body::{consume_body, BodyOperations, BodyType};
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::body::Extractable;
use crate::body::{consume_body, BodyMixin, BodyType};
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
use crate::dom::bindings::codegen::Bindings::RequestBinding::ReferrerPolicy;
use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestCache;
@ -22,11 +23,13 @@ use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::globalscope::GlobalScope;
use crate::dom::headers::{Guard, Headers};
use crate::dom::promise::Promise;
use crate::dom::xmlhttprequest::Extractable;
use crate::dom::readablestream::ReadableStream;
use crate::script_runtime::JSContext as SafeJSContext;
use dom_struct::dom_struct;
use http::header::{HeaderName, HeaderValue};
use http::method::InvalidMethod;
use http::Method as HttpMethod;
use js::jsapi::JSObject;
use net_traits::request::CacheMode as NetTraitsRequestCache;
use net_traits::request::CredentialsMode as NetTraitsRequestCredentials;
use net_traits::request::Destination as NetTraitsRequestDestination;
@ -37,7 +40,7 @@ use net_traits::request::RequestMode as NetTraitsRequestMode;
use net_traits::request::{Origin, Window};
use net_traits::ReferrerPolicy as MsgReferrerPolicy;
use servo_url::ServoUrl;
use std::cell::Cell;
use std::ptr::NonNull;
use std::rc::Rc;
use std::str::FromStr;
@ -45,11 +48,9 @@ use std::str::FromStr;
pub struct Request {
reflector_: Reflector,
request: DomRefCell<NetTraitsRequest>,
body_used: Cell<bool>,
body_stream: MutNullableDom<ReadableStream>,
headers: MutNullableDom<Headers>,
mime_type: DomRefCell<Vec<u8>>,
#[ignore_malloc_size_of = "Rc"]
body_promise: DomRefCell<Option<(Rc<Promise>, BodyType)>>,
}
impl Request {
@ -57,10 +58,9 @@ impl Request {
Request {
reflector_: Reflector::new(),
request: DomRefCell::new(net_request_from_global(global, url)),
body_used: Cell::new(false),
body_stream: MutNullableDom::new(None),
headers: Default::default(),
mime_type: DomRefCell::new("".to_string().into_bytes()),
body_promise: DomRefCell::new(None),
}
}
@ -72,7 +72,7 @@ impl Request {
#[allow(non_snake_case)]
pub fn Constructor(
global: &GlobalScope,
input: RequestInfo,
mut input: RequestInfo,
init: RootedTraceableBox<RequestInit>,
) -> Fallible<DomRoot<Request>> {
// Step 1
@ -365,9 +365,9 @@ impl Request {
r.request.borrow_mut().headers = r.Headers().get_headers_list();
// Step 33
let mut input_body = if let RequestInfo::Request(ref input_request) = input {
let input_request_request = input_request.request.borrow();
input_request_request.body.clone()
let mut input_body = if let RequestInfo::Request(ref mut input_request) = input {
let mut input_request_request = input_request.request.borrow_mut();
input_request_request.body.take()
} else {
None
};
@ -398,12 +398,10 @@ impl Request {
// Step 36.2 TODO "If init["keepalive"] exists and is true..."
// Step 36.3
let extracted_body_tmp = init_body.extract();
input_body = Some(extracted_body_tmp.0);
let content_type = extracted_body_tmp.1;
let mut extracted_body = init_body.extract(global)?;
// Step 36.4
if let Some(contents) = content_type {
if let Some(contents) = extracted_body.content_type.take() {
let ct_header_name = b"Content-Type";
if !r
.Headers()
@ -427,6 +425,10 @@ impl Request {
}
}
}
let (net_body, stream) = extracted_body.into_net_request_body();
r.body_stream.set(Some(&*stream));
input_body = Some(net_body);
}
// Step 37 "TODO if body is non-null and body's source is null..."
@ -448,13 +450,6 @@ impl Request {
// Step 42
Ok(r)
}
// https://fetch.spec.whatwg.org/#concept-body-locked
fn locked(&self) -> bool {
// TODO: ReadableStream is unimplemented. Just return false
// for now.
false
}
}
impl Request {
@ -467,7 +462,6 @@ impl Request {
fn clone_from(r: &Request) -> Fallible<DomRoot<Request>> {
let req = r.request.borrow();
let url = req.url();
let body_used = r.body_used.get();
let mime_type = r.mime_type.borrow().clone();
let headers_guard = r.Headers().get_guard();
let r_clone = Request::new(&r.global(), url);
@ -477,7 +471,6 @@ impl Request {
borrowed_r_request.origin = req.origin.clone();
}
*r_clone.request.borrow_mut() = req.clone();
r_clone.body_used.set(body_used);
*r_clone.mime_type.borrow_mut() = mime_type;
r_clone.Headers().copy_from_headers(r.Headers())?;
r_clone.Headers().set_guard(headers_guard);
@ -536,16 +529,14 @@ fn includes_credentials(input: &ServoUrl) -> bool {
!input.username().is_empty() || input.password().is_some()
}
// TODO: `Readable Stream` object is not implemented in Servo yet.
// https://fetch.spec.whatwg.org/#concept-body-disturbed
fn request_is_disturbed(_input: &Request) -> bool {
false
fn request_is_disturbed(input: &Request) -> bool {
input.is_disturbed()
}
// TODO: `Readable Stream` object is not implemented in Servo yet.
// https://fetch.spec.whatwg.org/#concept-body-locked
fn request_is_locked(_input: &Request) -> bool {
false
fn request_is_locked(input: &Request) -> bool {
input.is_locked()
}
impl RequestMethods for Request {
@ -622,9 +613,14 @@ impl RequestMethods for Request {
DOMString::from_string(r.integrity_metadata.clone())
}
/// <https://fetch.spec.whatwg.org/#dom-body-body>
fn GetBody(&self, _cx: SafeJSContext) -> Option<NonNull<JSObject>> {
self.body().map(|stream| stream.get_js_stream())
}
// https://fetch.spec.whatwg.org/#dom-body-bodyused
fn BodyUsed(&self) -> bool {
self.body_used.get()
self.is_disturbed()
}
// https://fetch.spec.whatwg.org/#dom-request-clone
@ -667,29 +663,25 @@ impl RequestMethods for Request {
}
}
impl BodyOperations for Request {
fn get_body_used(&self) -> bool {
self.BodyUsed()
}
fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType) {
assert!(self.body_promise.borrow().is_none());
self.body_used.set(true);
*self.body_promise.borrow_mut() = Some((p.clone(), body_type));
impl BodyMixin for Request {
fn is_disturbed(&self) -> bool {
let body_stream = self.body_stream.get();
body_stream
.as_ref()
.map_or(false, |stream| stream.is_disturbed())
}
fn is_locked(&self) -> bool {
self.locked()
let body_stream = self.body_stream.get();
body_stream.map_or(false, |stream| stream.is_locked())
}
fn take_body(&self) -> Option<Vec<u8>> {
let mut request = self.request.borrow_mut();
let body = request.body.take();
Some(body.unwrap_or(vec![]))
fn body(&self) -> Option<DomRoot<ReadableStream>> {
self.body_stream.get()
}
fn get_mime_type(&self) -> Ref<Vec<u8>> {
self.mime_type.borrow()
fn get_mime_type(&self) -> Vec<u8> {
self.mime_type.borrow().clone()
}
}

View file

@ -2,8 +2,9 @@
* 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 crate::body::{consume_body, consume_body_with_promise, BodyOperations, BodyType};
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::body::{consume_body, BodyMixin, BodyType};
use crate::body::{Extractable, ExtractedBody};
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods;
use crate::dom::bindings::codegen::Bindings::ResponseBinding;
use crate::dom::bindings::codegen::Bindings::ResponseBinding::{
@ -18,16 +19,16 @@ use crate::dom::globalscope::GlobalScope;
use crate::dom::headers::{is_obs_text, is_vchar};
use crate::dom::headers::{Guard, Headers};
use crate::dom::promise::Promise;
use crate::dom::xmlhttprequest::Extractable;
use crate::dom::readablestream::{ExternalUnderlyingSource, ReadableStream};
use crate::script_runtime::JSContext as SafeJSContext;
use crate::script_runtime::StreamConsumer;
use dom_struct::dom_struct;
use http::header::HeaderMap as HyperHeaders;
use hyper::StatusCode;
use hyper_serde::Serde;
use net_traits::response::ResponseBody as NetTraitsResponseBody;
use js::jsapi::JSObject;
use servo_url::ServoUrl;
use std::cell::Cell;
use std::mem;
use std::ptr::NonNull;
use std::rc::Rc;
use std::str::FromStr;
use url::Position;
@ -37,7 +38,6 @@ pub struct Response {
reflector_: Reflector,
headers_reflector: MutNullableDom<Headers>,
mime_type: DomRefCell<Vec<u8>>,
body_used: Cell<bool>,
/// `None` can be considered a StatusCode of `0`.
#[ignore_malloc_size_of = "Defined in hyper"]
status: DomRefCell<Option<StatusCode>>,
@ -45,10 +45,8 @@ pub struct Response {
response_type: DomRefCell<DOMResponseType>,
url: DomRefCell<Option<ServoUrl>>,
url_list: DomRefCell<Vec<ServoUrl>>,
// For now use the existing NetTraitsResponseBody enum
body: DomRefCell<NetTraitsResponseBody>,
#[ignore_malloc_size_of = "Rc"]
body_promise: DomRefCell<Option<(Rc<Promise>, BodyType)>>,
/// The stream of https://fetch.spec.whatwg.org/#body.
body_stream: MutNullableDom<ReadableStream>,
#[ignore_malloc_size_of = "StreamConsumer"]
stream_consumer: DomRefCell<Option<StreamConsumer>>,
redirected: DomRefCell<bool>,
@ -56,19 +54,21 @@ pub struct Response {
#[allow(non_snake_case)]
impl Response {
pub fn new_inherited() -> Response {
pub fn new_inherited(global: &GlobalScope) -> Response {
let stream = ReadableStream::new_with_external_underlying_source(
global,
ExternalUnderlyingSource::FetchResponse,
);
Response {
reflector_: Reflector::new(),
headers_reflector: Default::default(),
mime_type: DomRefCell::new("".to_string().into_bytes()),
body_used: Cell::new(false),
status: DomRefCell::new(Some(StatusCode::OK)),
raw_status: DomRefCell::new(Some((200, b"".to_vec()))),
response_type: DomRefCell::new(DOMResponseType::Default),
url: DomRefCell::new(None),
url_list: DomRefCell::new(vec![]),
body: DomRefCell::new(NetTraitsResponseBody::Empty),
body_promise: DomRefCell::new(None),
body_stream: MutNullableDom::new(Some(&*stream)),
stream_consumer: DomRefCell::new(None),
redirected: DomRefCell::new(false),
}
@ -76,7 +76,7 @@ impl Response {
// https://fetch.spec.whatwg.org/#dom-response
pub fn new(global: &GlobalScope) -> DomRoot<Response> {
reflect_dom_object(Box::new(Response::new_inherited()), global)
reflect_dom_object(Box::new(Response::new_inherited(global)), global)
}
pub fn Constructor(
@ -128,8 +128,14 @@ impl Response {
};
// Step 7.3
let (extracted_body, content_type) = body.extract();
*r.body.borrow_mut() = NetTraitsResponseBody::Done(extracted_body);
let ExtractedBody {
stream,
total_bytes: _,
content_type,
source: _,
} = body.extract(global)?;
r.body_stream.set(Some(&*stream));
// Step 7.4
if let Some(content_type_contents) = content_type {
@ -211,42 +217,32 @@ impl Response {
Ok(r)
}
// https://fetch.spec.whatwg.org/#concept-body-locked
fn locked(&self) -> bool {
// TODO: ReadableStream is unimplemented. Just return false
// for now.
false
pub fn error_stream(&self, error: Error) {
if let Some(body) = self.body_stream.get() {
body.error_native(error);
}
}
}
impl BodyOperations for Response {
fn get_body_used(&self) -> bool {
self.BodyUsed()
}
fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType) {
assert!(self.body_promise.borrow().is_none());
self.body_used.set(true);
*self.body_promise.borrow_mut() = Some((p.clone(), body_type));
impl BodyMixin for Response {
fn is_disturbed(&self) -> bool {
self.body_stream
.get()
.map_or(false, |stream| stream.is_disturbed())
}
fn is_locked(&self) -> bool {
self.locked()
self.body_stream
.get()
.map_or(false, |stream| stream.is_locked())
}
fn take_body(&self) -> Option<Vec<u8>> {
let body = mem::replace(&mut *self.body.borrow_mut(), NetTraitsResponseBody::Empty);
match body {
NetTraitsResponseBody::Done(bytes) => Some(bytes),
body => {
let _ = mem::replace(&mut *self.body.borrow_mut(), body);
None
},
}
fn body(&self) -> Option<DomRoot<ReadableStream>> {
self.body_stream.get()
}
fn get_mime_type(&self) -> Ref<Vec<u8>> {
self.mime_type.borrow()
fn get_mime_type(&self) -> Vec<u8> {
self.mime_type.borrow().clone()
}
}
@ -328,7 +324,7 @@ impl ResponseMethods for Response {
// https://fetch.spec.whatwg.org/#dom-response-clone
fn Clone(&self) -> Fallible<DomRoot<Response>> {
// Step 1
if self.is_locked() || self.body_used.get() {
if self.is_locked() || self.is_disturbed() {
return Err(Error::Type("cannot clone a disturbed response".to_string()));
}
@ -346,8 +342,8 @@ impl ResponseMethods for Response {
*new_response.url.borrow_mut() = self.url.borrow().clone();
*new_response.url_list.borrow_mut() = self.url_list.borrow().clone();
if *self.body.borrow() != NetTraitsResponseBody::Empty {
*new_response.body.borrow_mut() = self.body.borrow().clone();
if let Some(stream) = self.body_stream.get().clone() {
new_response.body_stream.set(Some(&*stream));
}
// Step 3
@ -359,7 +355,12 @@ impl ResponseMethods for Response {
// https://fetch.spec.whatwg.org/#dom-body-bodyused
fn BodyUsed(&self) -> bool {
self.body_used.get()
self.is_disturbed()
}
/// <https://fetch.spec.whatwg.org/#dom-body-body>
fn GetBody(&self, _cx: SafeJSContext) -> Option<NonNull<JSObject>> {
self.body().map(|stream| stream.get_js_stream())
}
// https://fetch.spec.whatwg.org/#dom-body-text
@ -424,20 +425,19 @@ impl Response {
*self.status.borrow_mut() = None;
self.set_raw_status(None);
self.set_headers(None);
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
},
DOMResponseType::Opaque => {
*self.url_list.borrow_mut() = vec![];
*self.status.borrow_mut() = None;
self.set_raw_status(None);
self.set_headers(None);
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
self.body_stream.set(None);
},
DOMResponseType::Opaqueredirect => {
*self.status.borrow_mut() = None;
self.set_raw_status(None);
self.set_headers(None);
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
self.body_stream.set(None);
},
DOMResponseType::Default => {},
DOMResponseType::Basic => {},
@ -449,17 +449,19 @@ impl Response {
*self.stream_consumer.borrow_mut() = sc;
}
pub fn stream_chunk(&self, stream: &[u8]) {
pub fn stream_chunk(&self, chunk: Vec<u8>) {
// Note, are these two actually mutually exclusive?
if let Some(stream_consumer) = self.stream_consumer.borrow_mut().as_ref() {
stream_consumer.consume_chunk(stream);
stream_consumer.consume_chunk(chunk.as_slice());
} else if let Some(body) = self.body_stream.get() {
body.enqueue_native(chunk);
}
}
#[allow(unrooted_must_root)]
pub fn finish(&self, body: Vec<u8>) {
*self.body.borrow_mut() = NetTraitsResponseBody::Done(body);
if let Some((p, body_type)) = self.body_promise.borrow_mut().take() {
consume_body_with_promise(self, body_type, &p);
pub fn finish(&self) {
if let Some(body) = self.body_stream.get() {
body.close_native();
}
if let Some(stream_consumer) = self.stream_consumer.borrow_mut().take() {
stream_consumer.stream_end();

View file

@ -53,7 +53,7 @@ use crate::realms::InRealm;
use crate::script_runtime::JSContext as SafeJSContext;
use crate::timers::OneshotTimerCallback;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSContext, JSObject};
use js::jsapi::{Heap, JSObject};
use js::jsapi::{JS_NewPlainObject, JS_NewUint8ClampedArray};
use js::jsval::{JSVal, NullValue};
use js::rust::CustomAutoRooterGuard;
@ -1018,9 +1018,8 @@ impl TestBindingMethods for TestBinding {
}
}
impl Callback for SimpleHandler {
#[allow(unsafe_code)]
fn callback(&self, cx: *mut JSContext, v: HandleValue, realm: InRealm) {
let global = unsafe { GlobalScope::from_context(cx, realm) };
fn callback(&self, cx: SafeJSContext, v: HandleValue, realm: InRealm) {
let global = GlobalScope::from_safe_context(cx, realm);
let _ = self.handler.Call_(&*global, v, ExceptionHandling::Report);
}
}

View file

@ -17,6 +17,7 @@ interface Blob {
optional [Clamp] long long end,
optional DOMString contentType);
[NewObject] object stream();
[NewObject] Promise<DOMString> text();
[NewObject] Promise<ArrayBuffer> arrayBuffer();
};

View file

@ -7,6 +7,7 @@
[Exposed=(Window,Worker)]
interface mixin Body {
readonly attribute boolean bodyUsed;
readonly attribute object? body;
[NewObject] Promise<ArrayBuffer> arrayBuffer();
[NewObject] Promise<Blob> blob();

View file

@ -0,0 +1,11 @@
/* 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/. */
// This interface is entirely internal to Servo, and should not be accessible to
// web pages.
[NoInterfaceObject, Exposed=(Window,Worker)]
// Need to escape "ReadableStream" so it's treated as an identifier.
interface _ReadableStream {
};

View file

@ -13,7 +13,7 @@
*/
// https://fetch.spec.whatwg.org/#bodyinit
typedef (Blob or BufferSource or FormData or DOMString or URLSearchParams) BodyInit;
typedef (Blob or BufferSource or FormData or DOMString or URLSearchParams or ReadableStream) BodyInit;
enum XMLHttpRequestResponseType {
"",
@ -21,7 +21,7 @@ enum XMLHttpRequestResponseType {
"blob",
"document",
"json",
"text"
"text",
};
[Exposed=(Window,Worker)]

View file

@ -2,11 +2,10 @@
* 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 crate::body::{Extractable, ExtractedBody};
use crate::document_loader::DocumentLoader;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobBinding::BlobMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestMethods;
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseType;
use crate::dom::bindings::codegen::UnionTypes::DocumentOrBodyInit;
@ -22,15 +21,13 @@ use crate::dom::document::DocumentSource;
use crate::dom::document::{Document, HasBrowsingContext, IsHTMLDocument};
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::formdata::FormData;
use crate::dom::globalscope::GlobalScope;
use crate::dom::headers::is_forbidden_header_name;
use crate::dom::htmlformelement::{encode_multipart_form_data, generate_boundary};
use crate::dom::node::Node;
use crate::dom::performanceresourcetiming::InitiatorType;
use crate::dom::progressevent::ProgressEvent;
use crate::dom::readablestream::ReadableStream;
use crate::dom::servoparser::ServoParser;
use crate::dom::urlsearchparams::URLSearchParams;
use crate::dom::window::Window;
use crate::dom::workerglobalscope::WorkerGlobalScope;
use crate::dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
@ -58,7 +55,9 @@ use js::jsval::{JSVal, NullValue, UndefinedValue};
use js::rust::wrappers::JS_ParseJSON;
use js::typedarray::{ArrayBuffer, CreateWith};
use mime::{self, Mime, Name};
use net_traits::request::{CredentialsMode, Destination, Referrer, RequestBuilder, RequestMode};
use net_traits::request::{
BodySource, CredentialsMode, Destination, Referrer, RequestBuilder, RequestMode,
};
use net_traits::trim_http_whitespace;
use net_traits::CoreResourceMsg::Fetch;
use net_traits::{FetchChannels, FetchMetadata, FilteredMetadata};
@ -562,42 +561,96 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
_ => data,
};
// Step 4 (first half)
let extracted_or_serialized = match data {
let mut extracted_or_serialized = match data {
Some(DocumentOrBodyInit::Document(ref doc)) => {
let data = Vec::from(serialize_document(&doc)?.as_ref());
let bytes = Vec::from(serialize_document(&doc)?.as_ref());
let content_type = if doc.is_html_document() {
"text/html;charset=UTF-8"
} else {
"application/xml;charset=UTF-8"
};
Some((data, Some(DOMString::from(content_type))))
let total_bytes = bytes.len();
let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes);
Some(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type: Some(DOMString::from(content_type)),
source: BodySource::Null,
})
},
Some(DocumentOrBodyInit::Blob(ref b)) => Some(b.extract()),
Some(DocumentOrBodyInit::FormData(ref formdata)) => Some(formdata.extract()),
Some(DocumentOrBodyInit::String(ref str)) => Some(str.extract()),
Some(DocumentOrBodyInit::URLSearchParams(ref urlsp)) => Some(urlsp.extract()),
Some(DocumentOrBodyInit::Blob(ref b)) => {
Some(b.extract(&self.global()).expect("Couldn't extract body."))
},
Some(DocumentOrBodyInit::FormData(ref formdata)) => Some(
formdata
.extract(&self.global())
.expect("Couldn't extract body."),
),
Some(DocumentOrBodyInit::String(ref str)) => {
Some(str.extract(&self.global()).expect("Couldn't extract body."))
},
Some(DocumentOrBodyInit::URLSearchParams(ref urlsp)) => Some(
urlsp
.extract(&self.global())
.expect("Couldn't extract body."),
),
Some(DocumentOrBodyInit::ArrayBuffer(ref typedarray)) => {
Some((typedarray.to_vec(), None))
let bytes = typedarray.to_vec();
let total_bytes = bytes.len();
let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes);
Some(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type: None,
source: BodySource::BufferSource,
})
},
Some(DocumentOrBodyInit::ArrayBufferView(ref typedarray)) => {
Some((typedarray.to_vec(), None))
let bytes = typedarray.to_vec();
let total_bytes = bytes.len();
let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes);
Some(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type: None,
source: BodySource::BufferSource,
})
},
Some(DocumentOrBodyInit::ReadableStream(ref 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(),
));
}
Some(ExtractedBody {
stream: stream.clone(),
total_bytes: None,
content_type: None,
source: BodySource::Null,
})
},
None => None,
};
self.request_body_len
.set(extracted_or_serialized.as_ref().map_or(0, |e| e.0.len()));
self.request_body_len.set(
extracted_or_serialized
.as_ref()
.map_or(0, |e| e.total_bytes.unwrap_or(0)),
);
// todo preserved headers?
// Step 6
self.upload_complete.set(false);
// Step 7
self.upload_complete.set(match extracted_or_serialized {
None => true,
Some(ref e) if e.0.is_empty() => true,
_ => false,
});
self.upload_complete.set(extracted_or_serialized.is_none());
// Step 8
self.send_flag.set(true);
@ -634,12 +687,17 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
unreachable!()
};
let content_type = match extracted_or_serialized.as_mut() {
Some(body) => body.content_type.take(),
None => None,
};
let mut request = RequestBuilder::new(self.request_url.borrow().clone().unwrap())
.method(self.request_method.borrow().clone())
.headers((*self.request_headers.borrow()).clone())
.unsafe_request(true)
// XXXManishearth figure out how to avoid this clone
.body(extracted_or_serialized.as_ref().map(|e| e.0.clone()))
.body(extracted_or_serialized.map(|e| e.into_net_request_body().0))
// XXXManishearth actually "subresource", but it doesn't exist
// https://github.com/whatwg/xhr/issues/71
.destination(Destination::None)
@ -658,8 +716,8 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
.pipeline_id(Some(self.global().pipeline_id()));
// step 4 (second half)
match extracted_or_serialized {
Some((_, ref content_type)) => {
match content_type {
Some(content_type) => {
let encoding = match data {
Some(DocumentOrBodyInit::String(_)) | Some(DocumentOrBodyInit::Document(_)) =>
// XHR spec differs from http, and says UTF-8 should be in capitals,
@ -672,13 +730,12 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
};
let mut content_type_set = false;
if let Some(ref ct) = *content_type {
if !request.headers.contains_key(header::CONTENT_TYPE) {
request
.headers
.insert(header::CONTENT_TYPE, HeaderValue::from_str(ct).unwrap());
content_type_set = true;
}
if !request.headers.contains_key(header::CONTENT_TYPE) {
request.headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_str(&content_type).unwrap(),
);
content_type_set = true;
}
if !content_type_set {
@ -1555,56 +1612,6 @@ impl XHRTimeoutCallback {
}
}
pub trait Extractable {
fn extract(&self) -> (Vec<u8>, Option<DOMString>);
}
impl Extractable for Blob {
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
let content_type = if self.Type().as_ref().is_empty() {
None
} else {
Some(self.Type())
};
let bytes = self.get_bytes().unwrap_or(vec![]);
(bytes, content_type)
}
}
impl Extractable for DOMString {
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
(
self.as_bytes().to_owned(),
Some(DOMString::from("text/plain;charset=UTF-8")),
)
}
}
impl Extractable for FormData {
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
let boundary = generate_boundary();
let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
(
bytes,
Some(DOMString::from(format!(
"multipart/form-data;boundary={}",
boundary
))),
)
}
}
impl Extractable for URLSearchParams {
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
(
self.serialize_utf8().into_bytes(),
Some(DOMString::from(
"application/x-www-form-urlencoded;charset=UTF-8",
)),
)
}
}
fn serialize_document(doc: &Document) -> Fallible<DOMString> {
let mut writer = vec![];
match serialize(&mut writer, &doc.upcast::<Node>(), SerializeOpts::default()) {
@ -1613,20 +1620,6 @@ fn serialize_document(doc: &Document) -> Fallible<DOMString> {
}
}
impl Extractable for BodyInit {
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
match *self {
BodyInit::String(ref s) => s.extract(),
BodyInit::URLSearchParams(ref usp) => usp.extract(),
BodyInit::Blob(ref b) => b.extract(),
BodyInit::FormData(ref formdata) => formdata.extract(),
BodyInit::ArrayBuffer(ref typedarray) => ((typedarray.to_vec(), None)),
BodyInit::ArrayBufferView(ref typedarray) => ((typedarray.to_vec(), None)),
}
}
}
/// Returns whether `bs` is a `field-value`, as defined by
/// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-32).
pub fn is_field_value(slice: &[u8]) -> bool {

View file

@ -36,14 +36,12 @@ use net_traits::{FetchChannels, FetchResponseListener, NetworkError};
use net_traits::{FetchMetadata, FilteredMetadata, Metadata};
use net_traits::{ResourceFetchTiming, ResourceTimingType};
use servo_url::ServoUrl;
use std::mem;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
struct FetchContext {
fetch_promise: Option<TrustedPromise>,
response_object: Trusted<Response>,
body: Vec<u8>,
resource_timing: ResourceFetchTiming,
}
@ -149,6 +147,7 @@ pub fn Fetch(
// Step 2
let request = match Request::Constructor(global, input, init) {
Err(e) => {
response.error_stream(e.clone());
promise.reject_error(e);
return promise;
},
@ -172,7 +171,6 @@ pub fn Fetch(
let fetch_context = Arc::new(Mutex::new(FetchContext {
fetch_promise: Some(TrustedPromise::new(promise.clone())),
response_object: Trusted::new(&*response),
body: vec![],
resource_timing: ResourceFetchTiming::new(timing_type),
}));
let listener = NetworkListener {
@ -222,7 +220,9 @@ impl FetchResponseListener for FetchContext {
Err(_) => {
promise.reject_error(Error::Type("Network error occurred".to_string()));
self.fetch_promise = Some(TrustedPromise::new(promise));
self.response_object.root().set_type(DOMResponseType::Error);
let response = self.response_object.root();
response.set_type(DOMResponseType::Error);
response.error_stream(Error::Type("Network error occurred".to_string()));
return;
},
// Step 4.2
@ -260,15 +260,15 @@ impl FetchResponseListener for FetchContext {
self.fetch_promise = Some(TrustedPromise::new(promise));
}
fn process_response_chunk(&mut self, mut chunk: Vec<u8>) {
self.response_object.root().stream_chunk(chunk.as_slice());
self.body.append(&mut chunk);
fn process_response_chunk(&mut self, chunk: Vec<u8>) {
let response = self.response_object.root();
response.stream_chunk(chunk);
}
fn process_response_eof(&mut self, _response: Result<ResourceFetchTiming, NetworkError>) {
let response = self.response_object.root();
let _ac = enter_realm(&*response);
response.finish(mem::replace(&mut self.body, vec![]));
response.finish();
// TODO
// ... trailerObject is not supported in Servo yet.
}

View file

@ -675,7 +675,7 @@ impl ModuleHandler {
}
impl Callback for ModuleHandler {
fn callback(&self, _cx: *mut JSContext, _v: HandleValue, _realm: InRealm) {
fn callback(&self, _cx: SafeJSContext, _v: HandleValue, _realm: InRealm) {
let task = self.task.borrow_mut().take().unwrap();
task.run_box();
}

View file

@ -7,7 +7,7 @@
#![allow(dead_code)]
use crate::body::BodyOperations;
use crate::body::BodyMixin;
use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseBinding::ResponseMethods;
use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
@ -979,7 +979,7 @@ unsafe extern "C" fn consume_stream(
}
// Step 2.6.2 If response body is alreaady consumed, return with a TypeError and abort these substeps.
if unwrapped_source.get_body_used() {
if unwrapped_source.is_disturbed() {
throw_dom_exception(
cx,
&global,

View file

@ -49,7 +49,7 @@ use msg::constellation_msg::{
use msg::constellation_msg::{PipelineNamespaceId, TopLevelBrowsingContextId};
use net_traits::image::base::Image;
use net_traits::image_cache::ImageCache;
use net_traits::request::Referrer;
use net_traits::request::{Referrer, RequestBody};
use net_traits::storage_thread::StorageType;
use net_traits::{FetchResponseMsg, ReferrerPolicy, ResourceThreads};
use pixels::PixelFormat;
@ -171,8 +171,8 @@ pub struct LoadData {
serialize_with = "::hyper_serde::serialize"
)]
pub headers: HeaderMap,
/// The data.
pub data: Option<Vec<u8>>,
/// The data that will be used as the body of the request.
pub data: Option<RequestBody>,
/// The result of evaluating a javascript scheme url.
pub js_eval_result: Option<JsEvalResult>,
/// The referrer.

View file

@ -153,6 +153,10 @@ skip: true
skip: false
[selection]
skip: false
[streams]
skip: true
[readable-streams]
skip: false
[subresource-integrity]
skip: false
[touch-events]

View file

@ -1,27 +0,0 @@
[Blob-stream.any.worker.html]
[Blob.stream() empty Blob]
expected: FAIL
[Blob.stream()]
expected: FAIL
[Blob.stream() non-unicode input]
expected: FAIL
[Blob.stream() garbage collection of blob shouldn't break streamconsumption]
expected: FAIL
[Blob-stream.any.html]
[Blob.stream() empty Blob]
expected: FAIL
[Blob.stream()]
expected: FAIL
[Blob.stream() non-unicode input]
expected: FAIL
[Blob.stream() garbage collection of blob shouldn't break streamconsumption]
expected: FAIL

View file

@ -18,18 +18,9 @@
[File API automated IDL tests]
expected: FAIL
[Blob interface: operation stream()]
expected: FAIL
[Blob interface: operation text()]
expected: FAIL
[Blob interface: new Blob(["TEST"\]) must inherit property "stream()" with the proper type]
expected: FAIL
[Blob interface: operation arrayBuffer()]
expected: FAIL
[Blob interface: new File(["myFileBits"\], "myFileName") must inherit property "stream()" with the proper type]
expected: FAIL

View file

@ -30,18 +30,9 @@
[idlharness]
expected: FAIL
[Blob interface: operation stream()]
expected: FAIL
[Blob interface: operation text()]
expected: FAIL
[Blob interface: new Blob(["TEST"\]) must inherit property "stream()" with the proper type]
expected: FAIL
[Blob interface: operation arrayBuffer()]
expected: FAIL
[Blob interface: new File(["myFileBits"\], "myFileName") must inherit property "stream()" with the proper type]
expected: FAIL

View file

@ -1,11 +0,0 @@
[stream-response.any.html]
type: testharness
[Stream response's body]
expected: FAIL
[stream-response.any.worker.html]
type: testharness
[Stream response's body]
expected: FAIL

View file

@ -5,12 +5,6 @@
[idlharness.any.html]
[Response interface: new Response() must inherit property "body" with the proper type]
expected: FAIL
[Response interface: attribute body]
expected: FAIL
[Response interface: operation blob()]
expected: FAIL
@ -68,9 +62,6 @@
[Request interface: new Request('about:blank') must inherit property "signal" with the proper type]
expected: FAIL
[Request interface: new Request('about:blank') must inherit property "body" with the proper type]
expected: FAIL
[Request interface: operation arrayBuffer()]
expected: FAIL
@ -80,9 +71,6 @@
[Response interface: attribute trailer]
expected: FAIL
[Request interface: attribute body]
expected: FAIL
[Window interface: calling fetch(RequestInfo, optional RequestInit) on window with too few arguments must throw TypeError]
expected: FAIL
@ -91,12 +79,6 @@
[idlharness.any.worker.html]
[Response interface: new Response() must inherit property "body" with the proper type]
expected: FAIL
[Response interface: attribute body]
expected: FAIL
[Response interface: operation blob()]
expected: FAIL
@ -154,9 +136,6 @@
[Request interface: new Request('about:blank') must inherit property "signal" with the proper type]
expected: FAIL
[Request interface: new Request('about:blank') must inherit property "body" with the proper type]
expected: FAIL
[Request interface: operation arrayBuffer()]
expected: FAIL
@ -166,9 +145,6 @@
[Response interface: attribute trailer]
expected: FAIL
[Request interface: attribute body]
expected: FAIL
[WorkerGlobalScope interface: calling fetch(RequestInfo, optional RequestInit) on self with too few arguments must throw TypeError]
expected: FAIL

View file

@ -1,89 +0,0 @@
[request-bad-port.html]
expected: TIMEOUT
[Request on bad port 427 should throw TypeError.]
expected: TIMEOUT
[Request on bad port 465 should throw TypeError.]
expected: NOTRUN
[Request on bad port 512 should throw TypeError.]
expected: NOTRUN
[Request on bad port 513 should throw TypeError.]
expected: NOTRUN
[Request on bad port 514 should throw TypeError.]
expected: NOTRUN
[Request on bad port 515 should throw TypeError.]
expected: NOTRUN
[Request on bad port 526 should throw TypeError.]
expected: NOTRUN
[Request on bad port 530 should throw TypeError.]
expected: NOTRUN
[Request on bad port 531 should throw TypeError.]
expected: NOTRUN
[Request on bad port 532 should throw TypeError.]
expected: NOTRUN
[Request on bad port 540 should throw TypeError.]
expected: NOTRUN
[Request on bad port 548 should throw TypeError.]
expected: NOTRUN
[Request on bad port 556 should throw TypeError.]
expected: NOTRUN
[Request on bad port 563 should throw TypeError.]
expected: NOTRUN
[Request on bad port 587 should throw TypeError.]
expected: NOTRUN
[Request on bad port 601 should throw TypeError.]
expected: NOTRUN
[Request on bad port 636 should throw TypeError.]
expected: NOTRUN
[Request on bad port 993 should throw TypeError.]
expected: NOTRUN
[Request on bad port 995 should throw TypeError.]
expected: NOTRUN
[Request on bad port 2049 should throw TypeError.]
expected: NOTRUN
[Request on bad port 3659 should throw TypeError.]
expected: NOTRUN
[Request on bad port 4045 should throw TypeError.]
expected: NOTRUN
[Request on bad port 6000 should throw TypeError.]
expected: NOTRUN
[Request on bad port 6665 should throw TypeError.]
expected: NOTRUN
[Request on bad port 6666 should throw TypeError.]
expected: NOTRUN
[Request on bad port 6667 should throw TypeError.]
expected: NOTRUN
[Request on bad port 6668 should throw TypeError.]
expected: NOTRUN
[Request on bad port 6669 should throw TypeError.]
expected: NOTRUN
[Request on bad port 6697 should throw TypeError.]
expected: NOTRUN

View file

@ -1,32 +1,11 @@
[request-consume-empty.html]
type: testharness
[Consume request's body as arrayBuffer]
expected: FAIL
[Consume request's body as formData]
expected: FAIL
[Consume request's body as text]
expected: FAIL
[Consume request's body as blob]
expected: FAIL
[Consume request's body as json]
expected: FAIL
[Consume empty FormData request body as text]
expected: FAIL
[Consume request's body as json (error case)]
expected: FAIL
[Consume request's body as formData with correct multipart type (error case)]
expected: FAIL
[Consume request's body as formData with correct urlencoded type]
expected: FAIL
[Consume request's body as formData without correct type (error case)]
expected: FAIL

View file

@ -1,23 +1,11 @@
[request-disturbed.html]
type: testharness
[Check cloning a disturbed request]
expected: FAIL
[Check creating a new request from a disturbed request]
expected: FAIL
[Input request used for creating new request became disturbed]
expected: FAIL
[Request construction failure should not set "bodyUsed"]
expected: FAIL
[Request without body cannot be disturbed]
expected: FAIL
[Request's body: initial state]
expected: FAIL
[Input request used for creating new request became disturbed even if body is not used]
expected: FAIL

View file

@ -2,18 +2,9 @@
[Constructing a Request with a Request on which read() and releaseLock() are called]
expected: FAIL
[Constructing a Request with a stream on which read() and releaseLock() are called]
expected: FAIL
[Constructing a Request with a stream on which getReader() is called]
expected: FAIL
[Constructing a Request with a Request on which body.getReader() is called]
expected: FAIL
[Constructing a Request with a stream on which read() is called]
expected: FAIL
[Constructing a Request with a Request on which body.getReader().read() is called]
expected: FAIL
@ -28,18 +19,9 @@
[Constructing a Request with a Request on which read() and releaseLock() are called]
expected: FAIL
[Constructing a Request with a stream on which read() and releaseLock() are called]
expected: FAIL
[Constructing a Request with a stream on which getReader() is called]
expected: FAIL
[Constructing a Request with a Request on which body.getReader() is called]
expected: FAIL
[Constructing a Request with a stream on which read() is called]
expected: FAIL
[Constructing a Request with a Request on which body.getReader().read() is called]
expected: FAIL

View file

@ -1,23 +1,5 @@
[response-cancel-stream.html]
type: testharness
[Cancelling a starting blob Response stream]
expected: FAIL
[Cancelling a loading blob Response stream]
expected: FAIL
[Cancelling a closed blob Response stream]
expected: FAIL
[Cancelling a starting Response stream]
expected: FAIL
[Cancelling a loading Response stream]
expected: FAIL
[Cancelling a closed Response stream]
expected: FAIL
[Response consume blob and http bodies]
expected: FAIL

View file

@ -1,8 +1,5 @@
[response-clone.html]
type: testharness
[Check orginal response's body after cloning]
expected: FAIL
[Check cloned response's body]
expected: FAIL
@ -44,3 +41,4 @@
[Check response clone use structureClone for teed ReadableStreams (DataViewchunk)]
expected: FAIL

View file

@ -1,29 +1,8 @@
[response-consume-stream.html]
type: testharness
[Read empty text response's body as readableStream]
expected: FAIL
[Read empty blob response's body as readableStream]
expected: FAIL
[Read blob response's body as readableStream]
expected: FAIL
[Read text response's body as readableStream]
expected: FAIL
[Read array buffer response's body as readableStream]
expected: FAIL
[Read form data response's body as readableStream]
expected: FAIL
[Getting an error Response stream]
expected: FAIL
[Getting a redirect Response stream]
expected: FAIL
[Read URLSearchParams response's body as readableStream]
expected: FAIL

View file

@ -1,6 +1,5 @@
[response-consume.html]
type: testharness
expected: ERROR
[Consume response's body as arrayBuffer]
expected: FAIL
@ -34,30 +33,9 @@
[Consume response's body: from FormData to formData]
expected: FAIL
[Consume response's body: from stream to blob]
expected: FAIL
[Consume response's body: from stream to text]
expected: FAIL
[Consume response's body: from stream to arrayBuffer]
expected: FAIL
[Consume response's body: from stream to json]
expected: FAIL
[Consume response's body: from stream with correct multipart type to formData]
expected: FAIL
[Consume response's body: from stream without correct multipart type to formData (error case)]
expected: FAIL
[Consume response's body: from stream with correct urlencoded type to formData]
expected: FAIL
[Consume response's body: from stream without correct urlencoded type to formData (error case)]
expected: FAIL
[Consume response's body: from multipart form data blob to formData]
expected: FAIL

View file

@ -1,8 +1,2 @@
[response-error-from-stream.html]
expected: ERROR
[ReadableStreamDefaultReader Promise receives ReadableStream start() Error]
expected: FAIL
[ReadableStreamDefaultReader Promise receives ReadableStream pull() Error]
expected: FAIL

View file

@ -5,29 +5,11 @@
[response-from-stream.any.html]
[Constructing a Response with a stream on which getReader() is called]
expected: FAIL
[Constructing a Response with a stream on which read() and releaseLock() are called]
expected: FAIL
[Constructing a Response with a stream on which read() is called]
expected: FAIL
[response-from-stream.any.serviceworker.html]
expected: TIMEOUT
[response-from-stream.any.worker.html]
[Constructing a Response with a stream on which getReader() is called]
expected: FAIL
[Constructing a Response with a stream on which read() and releaseLock() are called]
expected: FAIL
[Constructing a Response with a stream on which read() is called]
expected: FAIL
[response-from-stream.any.serviceworker.html]
expected: ERROR

View file

@ -1,8 +1,5 @@
[response-init-002.html]
type: testharness
[Read Response's body as readableStream]
expected: FAIL
[Testing null Response body]
expected: FAIL

View file

@ -1,14 +0,0 @@
[response-stream-disturbed-1.html]
type: testharness
[Getting blob after getting the Response body - not disturbed, not locked]
expected: FAIL
[Getting text after getting the Response body - not disturbed, not locked]
expected: FAIL
[Getting json after getting the Response body - not disturbed, not locked]
expected: FAIL
[Getting arrayBuffer after getting the Response body - not disturbed, not locked]
expected: FAIL

View file

@ -1,14 +0,0 @@
[response-stream-disturbed-2.html]
type: testharness
[Getting blob after getting a locked Response body]
expected: FAIL
[Getting text after getting a locked Response body]
expected: FAIL
[Getting json after getting a locked Response body]
expected: FAIL
[Getting arrayBuffer after getting a locked Response body]
expected: FAIL

View file

@ -1,14 +0,0 @@
[response-stream-disturbed-3.html]
type: testharness
[Getting blob after reading the Response body]
expected: FAIL
[Getting text after reading the Response body]
expected: FAIL
[Getting json after reading the Response body]
expected: FAIL
[Getting arrayBuffer after reading the Response body]
expected: FAIL

View file

@ -1,14 +0,0 @@
[response-stream-disturbed-4.html]
type: testharness
[Getting blob after cancelling the Response body]
expected: FAIL
[Getting text after cancelling the Response body]
expected: FAIL
[Getting json after cancelling the Response body]
expected: FAIL
[Getting arrayBuffer after cancelling the Response body]
expected: FAIL

View file

@ -1,17 +0,0 @@
[response-stream-disturbed-6.html]
type: testharness
[A non-closed stream on which read() has been called]
expected: FAIL
[A non-closed stream on which cancel() has been called]
expected: FAIL
[A closed stream on which read() has been called]
expected: FAIL
[An errored stream on which read() has been called]
expected: FAIL
[An errored stream on which cancel() has been called]
expected: FAIL

View file

@ -1,25 +1,22 @@
[response-stream-with-broken-then.any.html]
[Untitled]
expected: FAIL
[Inject {done: false, value: bye} via Object.prototype.then.]
expected: FAIL
[Inject {done: false, value: undefined} via Object.prototype.then.]
expected: FAIL
[Inject undefined via Object.prototype.then.]
expected: FAIL
[Inject 8.2 via Object.prototype.then.]
expected: FAIL
[response-stream-with-broken-then]
expected: FAIL
[Attempt to inject {done: false, value: bye} via Object.prototype.then.]
expected: FAIL
[Attempt to inject value: undefined via Object.prototype.then.]
expected: FAIL
[Attempt to inject undefined via Object.prototype.then.]
expected: FAIL
[Attempt to inject 8.2 via Object.prototype.then.]
expected: FAIL
[intercepting arraybuffer to body readable stream conversion via Object.prototype.then should not be possible]
expected: FAIL
@ -27,26 +24,22 @@
[response-stream-with-broken-then.any.worker.html]
[Untitled]
expected: FAIL
[Inject {done: false, value: bye} via Object.prototype.then.]
expected: FAIL
[Inject {done: false, value: undefined} via Object.prototype.then.]
expected: FAIL
[Inject undefined via Object.prototype.then.]
expected: FAIL
[Inject 8.2 via Object.prototype.then.]
expected: FAIL
[Attempt to inject {done: false, value: bye} via Object.prototype.then.]
expected: FAIL
[Attempt to inject value: undefined via Object.prototype.then.]
expected: FAIL
[Attempt to inject undefined via Object.prototype.then.]
expected: FAIL
[Attempt to inject 8.2 via Object.prototype.then.]
expected: FAIL
[intercepting arraybuffer to body readable stream conversion via Object.prototype.then should not be possible]
expected: FAIL
[response-stream-with-broken-then]
expected: FAIL

View file

@ -311,28 +311,3 @@
[fetch(): separate response Content-Type: text/plain ]
expected: NOTRUN
[<iframe>: separate response Content-Type: text/html */*;charset=gbk]
expected: FAIL
[<iframe>: combined response Content-Type: text/html;x=" text/plain]
expected: FAIL
[<iframe>: combined response Content-Type: */* text/html]
expected: FAIL
[<iframe>: combined response Content-Type: text/html;" \\" text/plain]
expected: FAIL
[<iframe>: combined response Content-Type: text/html;charset=gbk text/plain text/html]
expected: FAIL
[<iframe>: separate response Content-Type: text/plain */*]
expected: FAIL
[<iframe>: combined response Content-Type: text/html */*;charset=gbk]
expected: FAIL
[<iframe>: separate response Content-Type: text/html;" \\" text/plain]
expected: FAIL

View file

@ -53,9 +53,15 @@
[combined text/javascript ]
expected: FAIL
[separate text/javascript x/x]
[separate text/javascript;charset=windows-1252 error text/javascript]
expected: FAIL
[separate text/javascript ]
expected: FAIL
[separate text/javascript error]
expected: FAIL
[separate text/javascript x/x]
expected: FAIL

View file

@ -10,4 +10,3 @@
[X-Content-Type-Options%3A%20nosniff%2C%2C%40%23%24%23%25%25%26%5E%26%5E*()()11!]
expected: FAIL

View file

@ -1,128 +1,37 @@
[assorted.window.html]
expected: TIMEOUT
[Origin header and POST same-origin fetch cors mode with Referrer-Policy no-referrer-when-downgrade]
expected: NOTRUN
[Origin header and POST cross-origin fetch cors mode with Referrer-Policy no-referrer-when-downgrade]
expected: NOTRUN
[Origin header and POST same-origin fetch cors mode with Referrer-Policy no-referrer]
expected: NOTRUN
[Origin header and POST same-origin navigation with Referrer-Policy origin-when-cross-origin]
expected: NOTRUN
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy unsafe-url]
expected: NOTRUN
[Origin header and 308 redirect]
expected: FAIL
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy same-origin]
expected: NOTRUN
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy no-referrer]
expected: NOTRUN
[Origin header and POST same-origin navigation with Referrer-Policy no-referrer-when-downgrade]
expected: NOTRUN
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy unsafe-url]
expected: NOTRUN
expected: FAIL
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy no-referrer]
expected: NOTRUN
expected: FAIL
[Origin header and POST cross-origin navigation with Referrer-Policy origin-when-cross-origin]
expected: NOTRUN
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy origin-when-cross-origin]
expected: NOTRUN
[Origin header and POST cross-origin fetch cors mode with Referrer-Policy origin-when-cross-origin]
expected: NOTRUN
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy no-referrer-when-downgrade]
expected: NOTRUN
expected: FAIL
[Origin header and POST cross-origin navigation with Referrer-Policy unsafe-url]
expected: NOTRUN
expected: FAIL
[Origin header and POST navigation]
expected: TIMEOUT
expected: FAIL
[Origin header and POST cross-origin navigation with Referrer-Policy no-referrer]
expected: NOTRUN
expected: FAIL
[Origin header and POST cross-origin navigation with Referrer-Policy same-origin]
expected: NOTRUN
[Origin header and POST cross-origin fetch cors mode with Referrer-Policy same-origin]
expected: NOTRUN
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy origin-when-cross-origin]
expected: NOTRUN
[Origin header and POST cross-origin fetch cors mode with Referrer-Policy unsafe-url]
expected: NOTRUN
[Origin header and POST same-origin navigation with Referrer-Policy unsafe-url]
expected: NOTRUN
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy no-referrer-when-downgrade]
expected: NOTRUN
expected: FAIL
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy same-origin]
expected: NOTRUN
expected: FAIL
[Origin header and POST same-origin navigation with Referrer-Policy no-referrer]
expected: NOTRUN
[Origin header and POST same-origin fetch cors mode with Referrer-Policy unsafe-url]
expected: NOTRUN
[Origin header and POST same-origin navigation with Referrer-Policy same-origin]
expected: NOTRUN
[Origin header and POST same-origin fetch cors mode with Referrer-Policy same-origin]
expected: NOTRUN
[Origin header and POST same-origin fetch cors mode with Referrer-Policy origin-when-cross-origin]
expected: NOTRUN
[Origin header and POST cross-origin fetch cors mode with Referrer-Policy no-referrer]
expected: NOTRUN
expected: FAIL
[Origin header and POST cross-origin navigation with Referrer-Policy no-referrer-when-downgrade]
expected: NOTRUN
expected: FAIL
[Origin header and GET same-origin fetch cors mode with Referrer-Policy no-referrer]
expected: NOTRUN
[Origin header and GET cross-origin fetch cors mode with Referrer-Policy no-referrer-when-downgrade]
expected: NOTRUN
[Origin header and GET same-origin fetch cors mode with Referrer-Policy origin-when-cross-origin]
expected: NOTRUN
[Origin header and GET same-origin fetch cors mode with Referrer-Policy no-referrer-when-downgrade]
expected: NOTRUN
[Origin header and GET same-origin fetch cors mode with Referrer-Policy same-origin]
expected: NOTRUN
[Origin header and GET same-origin fetch cors mode with Referrer-Policy unsafe-url]
expected: NOTRUN
[Origin header and GET cross-origin fetch cors mode with Referrer-Policy unsafe-url]
expected: NOTRUN
[Origin header and GET cross-origin fetch cors mode with Referrer-Policy same-origin]
expected: NOTRUN
[Origin header and GET cross-origin fetch cors mode with Referrer-Policy no-referrer]
expected: NOTRUN
[Origin header and GET cross-origin fetch cors mode with Referrer-Policy origin-when-cross-origin]
expected: NOTRUN
[Origin header and POST same-origin fetch cors mode with Referrer-Policy no-referrer]
expected: FAIL

View file

@ -0,0 +1,165 @@
[async-iterator.any.serviceworker.html]
expected: ERROR
[async-iterator.any.sharedworker.html]
expected: ERROR
[async-iterator.any.html]
[Async-iterating an empty but not closed/errored stream never executes the loop body and stalls the async function]
expected: FAIL
[Calling return() twice rejects]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = true]
expected: FAIL
[calling next() after return() should reject]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = true]
expected: FAIL
[getIterator() throws if there's already a lock]
expected: FAIL
[Async-iterating a push source]
expected: FAIL
[Async-iterating a closed stream never executes the loop body, but works fine]
expected: FAIL
[Acquiring a reader after exhaustively async-iterating a stream]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = true]
expected: FAIL
[Async iterator instances should have the correct list of properties]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = false]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = false]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = false]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = false]
expected: FAIL
[Async-iterating a pull source]
expected: FAIL
[Acquiring a reader and reading the remaining chunks after partially async-iterating a stream with preventCancel = true]
expected: FAIL
[Acquiring a reader after partially async-iterating a stream]
expected: FAIL
[Async-iterating a partially consumed stream]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = false]
expected: FAIL
[calling return() while there are pending reads rejects]
expected: FAIL
[Async-iterating a pull source manually]
expected: FAIL
[Async-iterating an errored stream throws]
expected: FAIL
[next()'s fulfillment value has the right shape]
expected: FAIL
[async-iterator.any.worker.html]
[Async-iterating an empty but not closed/errored stream never executes the loop body and stalls the async function]
expected: FAIL
[Calling return() twice rejects]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = true]
expected: FAIL
[calling next() after return() should reject]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = true]
expected: FAIL
[getIterator() throws if there's already a lock]
expected: FAIL
[Async-iterating a push source]
expected: FAIL
[Async-iterating a closed stream never executes the loop body, but works fine]
expected: FAIL
[Acquiring a reader after exhaustively async-iterating a stream]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = true]
expected: FAIL
[Async iterator instances should have the correct list of properties]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = false]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = false]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = false]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = false]
expected: FAIL
[Async-iterating a pull source]
expected: FAIL
[Acquiring a reader and reading the remaining chunks after partially async-iterating a stream with preventCancel = true]
expected: FAIL
[Acquiring a reader after partially async-iterating a stream]
expected: FAIL
[Async-iterating a partially consumed stream]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = false]
expected: FAIL
[calling return() while there are pending reads rejects]
expected: FAIL
[Async-iterating a pull source manually]
expected: FAIL
[Async-iterating an errored stream throws]
expected: FAIL
[next()'s fulfillment value has the right shape]
expected: FAIL

View file

@ -0,0 +1,9 @@
[bad-strategies.any.worker.html]
[bad-strategies.any.serviceworker.html]
expected: ERROR
[bad-strategies.any.html]
[bad-strategies.any.sharedworker.html]
expected: ERROR

View file

@ -0,0 +1,9 @@
[bad-underlying-sources.any.worker.html]
[bad-underlying-sources.any.sharedworker.html]
expected: ERROR
[bad-underlying-sources.any.html]
[bad-underlying-sources.any.serviceworker.html]
expected: ERROR

View file

@ -0,0 +1,33 @@
[brand-checks.any.worker.html]
[ReadableStreamAsyncIteratorPrototype.next enforces a brand check]
expected: FAIL
[ReadableStream.prototype.getIterator enforces a brand check]
expected: FAIL
[ReadableStreamAsyncIteratorPrototype.return enforces a brand check]
expected: FAIL
[Can get ReadableStreamAsyncIteratorPrototype object indirectly]
expected: FAIL
[brand-checks.any.serviceworker.html]
expected: ERROR
[brand-checks.any.html]
[ReadableStreamAsyncIteratorPrototype.next enforces a brand check]
expected: FAIL
[ReadableStream.prototype.getIterator enforces a brand check]
expected: FAIL
[ReadableStreamAsyncIteratorPrototype.return enforces a brand check]
expected: FAIL
[Can get ReadableStreamAsyncIteratorPrototype object indirectly]
expected: FAIL
[brand-checks.any.sharedworker.html]
expected: ERROR

View file

@ -0,0 +1,9 @@
[cancel.any.worker.html]
[cancel.any.sharedworker.html]
expected: ERROR
[cancel.any.html]
[cancel.any.serviceworker.html]
expected: ERROR

View file

@ -0,0 +1,9 @@
[constructor.any.sharedworker.html]
expected: ERROR
[constructor.any.serviceworker.html]
expected: ERROR
[constructor.any.html]
[constructor.any.worker.html]

View file

@ -0,0 +1,9 @@
[count-queuing-strategy-integration.any.worker.html]
[count-queuing-strategy-integration.any.sharedworker.html]
expected: ERROR
[count-queuing-strategy-integration.any.serviceworker.html]
expected: ERROR
[count-queuing-strategy-integration.any.html]

View file

@ -0,0 +1,10 @@
[default-reader.any.worker.html]
[default-reader.any.sharedworker.html]
expected: ERROR
[default-reader.any.serviceworker.html]
expected: ERROR
[default-reader.any.html]
expected: ERROR

View file

@ -0,0 +1,9 @@
[floating-point-total-queue-size.any.worker.html]
[floating-point-total-queue-size.any.serviceworker.html]
expected: ERROR
[floating-point-total-queue-size.any.html]
[floating-point-total-queue-size.any.sharedworker.html]
expected: ERROR

View file

@ -0,0 +1,9 @@
[garbage-collection.any.sharedworker.html]
expected: ERROR
[garbage-collection.any.html]
[garbage-collection.any.serviceworker.html]
expected: ERROR
[garbage-collection.any.worker.html]

View file

@ -0,0 +1,15 @@
[general.any.sharedworker.html]
expected: ERROR
[general.any.serviceworker.html]
expected: ERROR
[general.any.html]
[ReadableStream instances should have the correct list of properties]
expected: FAIL
[general.any.worker.html]
[ReadableStream instances should have the correct list of properties]
expected: FAIL

View file

@ -0,0 +1,21 @@
[patched-global.any.serviceworker.html]
expected: ERROR
[patched-global.any.sharedworker.html]
expected: ERROR
[patched-global.any.html]
[ReadableStream getIterator() should use the original values of getReader() and ReadableStreamDefaultReader methods]
expected: FAIL
[pipeTo() should not call Promise.prototype.then()]
expected: FAIL
[patched-global.any.worker.html]
[ReadableStream getIterator() should use the original values of getReader() and ReadableStreamDefaultReader methods]
expected: FAIL
[pipeTo() should not call Promise.prototype.then()]
expected: FAIL

View file

@ -0,0 +1,15 @@
[reentrant-strategies.any.worker.html]
[pipeTo() inside size() should behave as expected]
expected: FAIL
[reentrant-strategies.any.serviceworker.html]
expected: ERROR
[reentrant-strategies.any.html]
[pipeTo() inside size() should behave as expected]
expected: FAIL
[reentrant-strategies.any.sharedworker.html]
expected: ERROR

View file

@ -0,0 +1,35 @@
[tee.any.sharedworker.html]
expected: ERROR
[tee.any.worker.html]
expected: ERROR
[ReadableStreamTee should not pull more chunks than can fit in the branch queue]
expected: FAIL
[ReadableStreamTee stops pulling when original stream errors while branch 1 is reading]
expected: FAIL
[ReadableStreamTee stops pulling when original stream errors while both branches are reading]
expected: FAIL
[ReadableStreamTee stops pulling when original stream errors while branch 2 is reading]
expected: FAIL
[tee.any.serviceworker.html]
expected: ERROR
[tee.any.html]
expected: ERROR
[ReadableStreamTee should not pull more chunks than can fit in the branch queue]
expected: FAIL
[ReadableStreamTee stops pulling when original stream errors while branch 1 is reading]
expected: FAIL
[ReadableStreamTee stops pulling when original stream errors while both branches are reading]
expected: FAIL
[ReadableStreamTee stops pulling when original stream errors while branch 2 is reading]
expected: FAIL

View file

@ -0,0 +1,15 @@
[templated.any.sharedworker.html]
expected: ERROR
[templated.any.serviceworker.html]
expected: ERROR
[templated.any.html]
[ReadableStream (empty): instances have the correct methods and properties]
expected: FAIL
[templated.any.worker.html]
[ReadableStream (empty): instances have the correct methods and properties]
expected: FAIL

View file

@ -9,18 +9,9 @@
[The Path2D interface object should be exposed.]
expected: FAIL
[The ReadableStream interface object should be exposed.]
expected: FAIL
[The WritableStream interface object should be exposed.]
expected: FAIL
[The ByteLengthQueuingStrategy interface object should be exposed.]
expected: FAIL
[The CountQueuingStrategy interface object should be exposed.]
expected: FAIL
[The IDBRequest interface object should be exposed.]
expected: FAIL
@ -53,3 +44,4 @@
[The IDBTransaction interface object should be exposed.]
expected: FAIL

View file

@ -1,24 +1,6 @@
[send-data-readablestream.any.html]
[XMLHttpRequest: send() with a stream on which read() is called]
expected: FAIL
[XMLHttpRequest: send() with a stream on which read() and releaseLock() are called]
expected: FAIL
[XMLHttpRequest: send() with a stream on which getReader() is called]
expected: FAIL
[send-data-readablestream.any.worker.html]
[XMLHttpRequest: send() with a stream on which read() is called]
expected: FAIL
[XMLHttpRequest: send() with a stream on which read() and releaseLock() are called]
expected: FAIL
[XMLHttpRequest: send() with a stream on which getReader() is called]
expected: FAIL
[send-data-readablestream.any.sharedworker.html]
expected: ERROR

View file

@ -0,0 +1,4 @@
[send-timeout-events.htm]
expected:
if os == "linux": TIMEOUT

View file

@ -3,15 +3,6 @@
[URLSearchParams request keeps setRequestHeader() Content-Type, with charset adjusted to UTF-8]
expected: FAIL
[ReadableStream request respects setRequestHeader("")]
expected: FAIL
[ReadableStream request with under type sends no Content-Type without setRequestHeader() call]
expected: FAIL
[ReadableStream request keeps setRequestHeader() Content-Type and charset]
expected: FAIL
[String request keeps setRequestHeader() Content-Type, with charset adjusted to UTF-8]
expected: FAIL

View file

@ -10988,7 +10988,7 @@
[]
],
"interfaces.js": [
"f62df6b9c75332a2918b18ec70acfc5d7a795ac0",
"253dd124cebe9b7d3aa4fc5ff8c9137e7aeb2f66",
[]
],
"nested_asap_script.js": [
@ -13924,7 +13924,7 @@
]
],
"interfaces.worker.js": [
"c1223084790b2980c8184e3cd9ab5ae17bc8b303",
"59d4aa1855fbf281736b28581fbc519a847c8a0d",
[
"mozilla/interfaces.worker.html",
{}

View file

@ -11,6 +11,8 @@ function test_interfaces(interfaceNamesInGlobalScope) {
"BigUint64Array",
"Boolean",
"BroadcastChannel",
"ByteLengthQueuingStrategy",
"CountQueuingStrategy",
"Crypto",
"DataView",
"Date",
@ -36,6 +38,7 @@ function test_interfaces(interfaceNamesInGlobalScope) {
"Promise",
"Proxy",
"RangeError",
"ReadableStream",
"ReferenceError",
"Reflect",
"RegExp",

View file

@ -9,9 +9,11 @@ importScripts("interfaces.js");
test_interfaces([
"Blob",
"BroadcastChannel",
"ByteLengthQueuingStrategy",
"CanvasGradient",
"CanvasPattern",
"CloseEvent",
"CountQueuingStrategy",
"DOMMatrix",
"DOMMatrixReadOnly",
"DOMPoint",
@ -48,6 +50,7 @@ test_interfaces([
"PerformanceResourceTiming",
"ProgressEvent",
"PromiseRejectionEvent",
"ReadableStream",
"Request",
"Response",
"TextDecoder",