mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
integrate readablestream with fetch and blob
This commit is contained in:
parent
0281acea95
commit
bd5796c90b
74 changed files with 2219 additions and 899 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -3516,7 +3516,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mozjs"
|
name = "mozjs"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://github.com/servo/rust-mozjs#dbb9bee06e0b0168ccae0619c5077e302669d2fb"
|
source = "git+https://github.com/servo/rust-mozjs#28248e1d6658e92dd5ecb0866e53a97f043b9b38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
|
|
@ -32,13 +32,17 @@ use http::header::{
|
||||||
use http::{HeaderMap, Request as HyperRequest};
|
use http::{HeaderMap, Request as HyperRequest};
|
||||||
use hyper::{Body, Client, Method, Response as HyperResponse, StatusCode};
|
use hyper::{Body, Client, Method, Response as HyperResponse, StatusCode};
|
||||||
use hyper_serde::Serde;
|
use hyper_serde::Serde;
|
||||||
|
use ipc_channel::ipc::{self, IpcSender};
|
||||||
|
use ipc_channel::router::ROUTER;
|
||||||
use msg::constellation_msg::{HistoryStateId, PipelineId};
|
use msg::constellation_msg::{HistoryStateId, PipelineId};
|
||||||
use net_traits::pub_domains::reg_suffix;
|
use net_traits::pub_domains::reg_suffix;
|
||||||
use net_traits::quality::{quality_to_value, Quality, QualityItem};
|
use net_traits::quality::{quality_to_value, Quality, QualityItem};
|
||||||
use net_traits::request::Origin::Origin as SpecificOrigin;
|
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::{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::{CacheMode, CredentialsMode, Destination, Origin};
|
||||||
use net_traits::request::{RedirectMode, Referrer, Request, RequestBuilder, RequestMode};
|
|
||||||
use net_traits::request::{ResponseTainting, ServiceWorkersMode};
|
use net_traits::request::{ResponseTainting, ServiceWorkersMode};
|
||||||
use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType};
|
use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType};
|
||||||
use net_traits::{CookieSource, FetchMetadata, NetworkError, ReferrerPolicy};
|
use net_traits::{CookieSource, FetchMetadata, NetworkError, ReferrerPolicy};
|
||||||
|
@ -52,11 +56,12 @@ use std::iter::FromIterator;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
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 std::time::{Duration, SystemTime};
|
||||||
use time::{self, Tm};
|
use time::{self, Tm};
|
||||||
use tokio::prelude::{future, Future, Stream};
|
use tokio::prelude::{future, Future, Sink, Stream};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
use tokio::sync::mpsc::channel;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref HANDLE: Mutex<Option<Runtime>> = Mutex::new(Some(Runtime::new().unwrap()));
|
pub static ref HANDLE: Mutex<Option<Runtime>> = Mutex::new(Some(Runtime::new().unwrap()));
|
||||||
|
@ -400,8 +405,10 @@ fn obtain_response(
|
||||||
client: &Client<Connector, Body>,
|
client: &Client<Connector, Body>,
|
||||||
url: &ServoUrl,
|
url: &ServoUrl,
|
||||||
method: &Method,
|
method: &Method,
|
||||||
headers: &HeaderMap,
|
request_headers: &HeaderMap,
|
||||||
data: &Option<Vec<u8>>,
|
body: Option<IpcSender<BodyChunkRequest>>,
|
||||||
|
request_len: Option<usize>,
|
||||||
|
load_data_method: &Method,
|
||||||
pipeline_id: &Option<PipelineId>,
|
pipeline_id: &Option<PipelineId>,
|
||||||
request_id: Option<&str>,
|
request_id: Option<&str>,
|
||||||
is_xhr: bool,
|
is_xhr: bool,
|
||||||
|
@ -412,7 +419,71 @@ fn obtain_response(
|
||||||
Error = NetworkError,
|
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 httpRequest’s body’s 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
|
context
|
||||||
.timing
|
.timing
|
||||||
|
@ -440,7 +511,7 @@ fn obtain_response(
|
||||||
.replace("{", "%7B")
|
.replace("{", "%7B")
|
||||||
.replace("}", "%7D"),
|
.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
|
// 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
|
// 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,
|
closure_url,
|
||||||
method.clone(),
|
method.clone(),
|
||||||
headers,
|
headers,
|
||||||
Some(request_body.clone()),
|
Some(devtools_bytes.lock().unwrap().clone()),
|
||||||
pipeline_id,
|
pipeline_id,
|
||||||
time::now(),
|
time::now(),
|
||||||
connect_end - connect_start,
|
connect_end - connect_start,
|
||||||
|
@ -804,7 +875,7 @@ pub fn http_redirect_fetch(
|
||||||
.status
|
.status
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |s| s.0 != StatusCode::SEE_OTHER) &&
|
.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()));
|
return Response::network_error(NetworkError::Internal("Request body is not done".into()));
|
||||||
}
|
}
|
||||||
|
@ -943,7 +1014,7 @@ fn http_network_or_cache_fetch(
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
// Step 5.6
|
// 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
|
// Step 5.7
|
||||||
|
@ -1460,7 +1531,7 @@ impl Drop for ResponseEndTimer {
|
||||||
|
|
||||||
/// [HTTP network fetch](https://fetch.spec.whatwg.org/#http-network-fetch)
|
/// [HTTP network fetch](https://fetch.spec.whatwg.org/#http-network-fetch)
|
||||||
fn http_network_fetch(
|
fn http_network_fetch(
|
||||||
request: &Request,
|
request: &mut Request,
|
||||||
credentials_flag: bool,
|
credentials_flag: bool,
|
||||||
done_chan: &mut DoneChannel,
|
done_chan: &mut DoneChannel,
|
||||||
context: &FetchContext,
|
context: &FetchContext,
|
||||||
|
@ -1503,7 +1574,9 @@ fn http_network_fetch(
|
||||||
&url,
|
&url,
|
||||||
&request.method,
|
&request.method,
|
||||||
&request.headers,
|
&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.pipeline_id,
|
||||||
request_id.as_ref().map(Deref::deref),
|
request_id.as_ref().map(Deref::deref),
|
||||||
is_xhr,
|
is_xhr,
|
||||||
|
|
|
@ -24,13 +24,18 @@ use http::uri::Authority;
|
||||||
use http::{Method, StatusCode};
|
use http::{Method, StatusCode};
|
||||||
use hyper::body::Body;
|
use hyper::body::Body;
|
||||||
use hyper::{Request as HyperRequest, Response as HyperResponse};
|
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 msg::constellation_msg::TEST_PIPELINE_ID;
|
||||||
use net::cookie::Cookie;
|
use net::cookie::Cookie;
|
||||||
use net::cookie_storage::CookieStorage;
|
use net::cookie_storage::CookieStorage;
|
||||||
use net::http_loader::determine_request_referrer;
|
use net::http_loader::determine_request_referrer;
|
||||||
use net::resource_thread::AuthCacheEntry;
|
use net::resource_thread::AuthCacheEntry;
|
||||||
use net::test::replace_host_table;
|
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::response::{HttpsState, ResponseBody};
|
||||||
use net_traits::{CookieSource, NetworkError, ReferrerPolicy};
|
use net_traits::{CookieSource, NetworkError, ReferrerPolicy};
|
||||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
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]
|
#[test]
|
||||||
fn test_check_default_headers_loaded_in_every_request() {
|
fn test_check_default_headers_loaded_in_every_request() {
|
||||||
let expected_headers = Arc::new(Mutex::new(None));
|
let expected_headers = Arc::new(Mutex::new(None));
|
||||||
|
@ -276,7 +302,7 @@ fn test_request_and_response_data_with_network_messages() {
|
||||||
url: url,
|
url: url,
|
||||||
method: Method::GET,
|
method: Method::GET,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: Some(b"".to_vec()),
|
body: Some(vec![]),
|
||||||
pipeline_id: TEST_PIPELINE_ID,
|
pipeline_id: TEST_PIPELINE_ID,
|
||||||
startedDateTime: devhttprequest.startedDateTime,
|
startedDateTime: devhttprequest.startedDateTime,
|
||||||
timeStamp: devhttprequest.timeStamp,
|
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 (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())
|
let mut request = RequestBuilder::new(pre_url.clone())
|
||||||
.body(Some(b"Body on POST!".to_vec()))
|
.body(Some(request_body))
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
.destination(Destination::Document)
|
.destination(Destination::Document)
|
||||||
.origin(mock_origin())
|
.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 (server, url) = make_server(handler);
|
||||||
|
|
||||||
|
let request_body = create_request_body_with_content(content.to_vec());
|
||||||
|
|
||||||
let mut request = RequestBuilder::new(url.clone())
|
let mut request = RequestBuilder::new(url.clone())
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
.body(Some(content.to_vec()))
|
.body(Some(request_body))
|
||||||
.destination(Destination::Document)
|
.destination(Destination::Document)
|
||||||
.origin(mock_origin())
|
.origin(mock_origin())
|
||||||
.pipeline_id(Some(TEST_PIPELINE_ID))
|
.pipeline_id(Some(TEST_PIPELINE_ID))
|
||||||
|
|
|
@ -8,6 +8,7 @@ use crate::ResourceTimingType;
|
||||||
use content_security_policy::{self as csp, CspList};
|
use content_security_policy::{self as csp, CspList};
|
||||||
use http::HeaderMap;
|
use http::HeaderMap;
|
||||||
use hyper::Method;
|
use hyper::Method;
|
||||||
|
use ipc_channel::ipc::IpcSender;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use msg::constellation_msg::PipelineId;
|
use msg::constellation_msg::PipelineId;
|
||||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||||
|
@ -115,6 +116,57 @@ pub enum ParserMetadata {
|
||||||
NotParserInserted,
|
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)]
|
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||||
pub struct RequestBuilder {
|
pub struct RequestBuilder {
|
||||||
#[serde(
|
#[serde(
|
||||||
|
@ -131,7 +183,7 @@ pub struct RequestBuilder {
|
||||||
#[ignore_malloc_size_of = "Defined in hyper"]
|
#[ignore_malloc_size_of = "Defined in hyper"]
|
||||||
pub headers: HeaderMap,
|
pub headers: HeaderMap,
|
||||||
pub unsafe_request: bool,
|
pub unsafe_request: bool,
|
||||||
pub body: Option<Vec<u8>>,
|
pub body: Option<RequestBody>,
|
||||||
pub service_workers_mode: ServiceWorkersMode,
|
pub service_workers_mode: ServiceWorkersMode,
|
||||||
// TODO: client object
|
// TODO: client object
|
||||||
pub destination: Destination,
|
pub destination: Destination,
|
||||||
|
@ -210,7 +262,7 @@ impl RequestBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn body(mut self, body: Option<Vec<u8>>) -> RequestBuilder {
|
pub fn body(mut self, body: Option<RequestBody>) -> RequestBuilder {
|
||||||
self.body = body;
|
self.body = body;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -338,7 +390,7 @@ pub struct Request {
|
||||||
/// <https://fetch.spec.whatwg.org/#unsafe-request-flag>
|
/// <https://fetch.spec.whatwg.org/#unsafe-request-flag>
|
||||||
pub unsafe_request: bool,
|
pub unsafe_request: bool,
|
||||||
/// <https://fetch.spec.whatwg.org/#concept-request-body>
|
/// <https://fetch.spec.whatwg.org/#concept-request-body>
|
||||||
pub body: Option<Vec<u8>>,
|
pub body: Option<RequestBody>,
|
||||||
// TODO: client object
|
// TODO: client object
|
||||||
pub window: Window,
|
pub window: Window,
|
||||||
// TODO: target browsing context
|
// TODO: target browsing context
|
||||||
|
|
|
@ -2,19 +2,34 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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::FormDataBinding::FormDataMethods;
|
||||||
|
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
|
||||||
use crate::dom::bindings::error::{Error, Fallible};
|
use crate::dom::bindings::error::{Error, Fallible};
|
||||||
|
use crate::dom::bindings::refcounted::Trusted;
|
||||||
use crate::dom::bindings::reflector::DomObject;
|
use crate::dom::bindings::reflector::DomObject;
|
||||||
use crate::dom::bindings::root::DomRoot;
|
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::bindings::trace::RootedTraceableBox;
|
||||||
use crate::dom::blob::{normalize_type_string, Blob};
|
use crate::dom::blob::{normalize_type_string, Blob};
|
||||||
use crate::dom::formdata::FormData;
|
use crate::dom::formdata::FormData;
|
||||||
use crate::dom::globalscope::GlobalScope;
|
use crate::dom::globalscope::GlobalScope;
|
||||||
|
use crate::dom::htmlformelement::{encode_multipart_form_data, generate_boundary};
|
||||||
use crate::dom::promise::Promise;
|
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::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::Heap;
|
||||||
use js::jsapi::JSObject;
|
use js::jsapi::JSObject;
|
||||||
use js::jsapi::JS_ClearPendingException;
|
use js::jsapi::JS_ClearPendingException;
|
||||||
|
@ -23,14 +38,394 @@ use js::jsval::JSVal;
|
||||||
use js::jsval::UndefinedValue;
|
use js::jsval::UndefinedValue;
|
||||||
use js::rust::wrappers::JS_GetPendingException;
|
use js::rust::wrappers::JS_GetPendingException;
|
||||||
use js::rust::wrappers::JS_ParseJSON;
|
use js::rust::wrappers::JS_ParseJSON;
|
||||||
|
use js::rust::HandleValue;
|
||||||
use js::typedarray::{ArrayBuffer, CreateWith};
|
use js::typedarray::{ArrayBuffer, CreateWith};
|
||||||
use mime::{self, Mime};
|
use mime::{self, Mime};
|
||||||
|
use net_traits::request::{BodyChunkRequest, BodySource, RequestBody};
|
||||||
use script_traits::serializable::BlobImpl;
|
use script_traits::serializable::BlobImpl;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str;
|
use std::str;
|
||||||
use url::form_urlencoded;
|
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 request’s 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 body’s stream with reader.
|
||||||
|
let promise = rooted_stream.read_a_chunk();
|
||||||
|
|
||||||
|
// Step 5, the parallel steps waiting for and handling the result of the read promise,
|
||||||
|
// are a combination of the promise native handler here,
|
||||||
|
// and the corresponding IPC route in `component::net::http_loader`.
|
||||||
|
let promise_handler = Box::new(TransmitBodyPromiseHandler {
|
||||||
|
bytes_sender,
|
||||||
|
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)]
|
#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
|
||||||
pub enum BodyType {
|
pub enum BodyType {
|
||||||
Blob,
|
Blob,
|
||||||
|
@ -49,73 +444,212 @@ pub enum FetchedData {
|
||||||
JSException(RootedTraceableBox<Heap<JSVal>>),
|
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
|
// https://fetch.spec.whatwg.org/#concept-body-consume-body
|
||||||
#[allow(unrooted_must_root)]
|
#[allow(unrooted_must_root)]
|
||||||
pub fn consume_body<T: BodyOperations + DomObject>(object: &T, body_type: BodyType) -> Rc<Promise> {
|
pub fn consume_body<T: BodyMixin + DomObject>(object: &T, body_type: BodyType) -> Rc<Promise> {
|
||||||
let in_realm_proof = AlreadyInRealm::assert(&object.global());
|
let global = object.global();
|
||||||
|
let in_realm_proof = AlreadyInRealm::assert(&global);
|
||||||
let promise =
|
let promise =
|
||||||
Promise::new_in_current_realm(&object.global(), InRealm::Already(&in_realm_proof));
|
Promise::new_in_current_realm(&object.global(), InRealm::Already(&in_realm_proof));
|
||||||
|
|
||||||
// Step 1
|
// Step 1
|
||||||
if object.get_body_used() || object.is_locked() {
|
if object.is_disturbed() || object.is_locked() {
|
||||||
promise.reject_error(Error::Type(
|
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;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
object.set_body_promise(&promise, body_type);
|
consume_body_with_promise(object, body_type, promise.clone());
|
||||||
|
|
||||||
// Steps 2-4
|
|
||||||
// TODO: Body does not yet have a stream.
|
|
||||||
|
|
||||||
consume_body_with_promise(object, body_type, &promise);
|
|
||||||
|
|
||||||
promise
|
promise
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#concept-body-consume-body
|
// https://fetch.spec.whatwg.org/#concept-body-consume-body
|
||||||
#[allow(unrooted_must_root)]
|
#[allow(unrooted_must_root)]
|
||||||
pub fn consume_body_with_promise<T: BodyOperations + DomObject>(
|
fn consume_body_with_promise<T: BodyMixin + DomObject>(
|
||||||
object: &T,
|
object: &T,
|
||||||
body_type: BodyType,
|
body_type: BodyType,
|
||||||
promise: &Promise,
|
promise: Rc<Promise>,
|
||||||
) {
|
) {
|
||||||
// Step 5
|
let global = object.global();
|
||||||
let body = match object.take_body() {
|
|
||||||
Some(body) => body,
|
// Step 2.
|
||||||
None => return,
|
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 =
|
// Step 3.
|
||||||
run_package_data_algorithm(object, body, body_type, object.get_mime_type());
|
if stream.start_reading().is_err() {
|
||||||
|
return promise.reject_error(Error::Type(
|
||||||
match pkg_data_results {
|
"The response's stream is disturbed or locked".to_string(),
|
||||||
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 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
|
// https://fetch.spec.whatwg.org/#concept-body-package-data
|
||||||
#[allow(unsafe_code)]
|
fn run_package_data_algorithm(
|
||||||
fn run_package_data_algorithm<T: BodyOperations + DomObject>(
|
cx: JSContext,
|
||||||
object: &T,
|
|
||||||
bytes: Vec<u8>,
|
bytes: Vec<u8>,
|
||||||
body_type: BodyType,
|
body_type: BodyType,
|
||||||
mime_type: Ref<Vec<u8>>,
|
mime_type: Vec<u8>,
|
||||||
) -> Fallible<FetchedData> {
|
) -> Fallible<FetchedData> {
|
||||||
let global = object.global();
|
|
||||||
let cx = global.get_cx();
|
|
||||||
let mime = &*mime_type;
|
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 {
|
match body_type {
|
||||||
BodyType::Text => run_text_data_algorithm(bytes),
|
BodyType::Text => run_text_data_algorithm(bytes),
|
||||||
BodyType::Json => run_json_data_algorithm(cx, 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))
|
Ok(FetchedData::ArrayBuffer(rooted_heap))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BodyOperations {
|
/// <https://fetch.spec.whatwg.org/#body>
|
||||||
fn get_body_used(&self) -> bool;
|
pub trait BodyMixin {
|
||||||
fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType);
|
/// <https://fetch.spec.whatwg.org/#concept-body-disturbed>
|
||||||
/// Returns `Some(_)` if the body is complete, `None` if there is more to
|
fn is_disturbed(&self) -> bool;
|
||||||
/// come.
|
/// <https://fetch.spec.whatwg.org/#dom-body-body>
|
||||||
fn take_body(&self) -> Option<Vec<u8>>;
|
fn body(&self) -> Option<DomRoot<ReadableStream>>;
|
||||||
|
/// <https://fetch.spec.whatwg.org/#concept-body-locked>
|
||||||
fn is_locked(&self) -> bool;
|
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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -906,6 +906,40 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
|
||||||
|
|
||||||
return handleOptional(templateBody, declType, handleDefault("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():
|
elif type.isSpiderMonkeyInterface():
|
||||||
raise TypeError("Can't handle SpiderMonkey interface arguments other than typed arrays yet")
|
raise TypeError("Can't handle SpiderMonkey interface arguments other than typed arrays yet")
|
||||||
|
|
||||||
|
@ -4481,6 +4515,9 @@ def getUnionTypeTemplateVars(type, descriptorProvider):
|
||||||
elif type.isObject():
|
elif type.isObject():
|
||||||
name = type.name
|
name = type.name
|
||||||
typeName = "Heap<*mut JSObject>"
|
typeName = "Heap<*mut JSObject>"
|
||||||
|
elif type.isReadableStream():
|
||||||
|
name = type.name
|
||||||
|
typeName = "DomRoot<ReadableStream>"
|
||||||
elif is_typed_array(type):
|
elif is_typed_array(type):
|
||||||
name = type.name
|
name = type.name
|
||||||
typeName = "typedarray::Heap" + name
|
typeName = "typedarray::Heap" + name
|
||||||
|
|
|
@ -138,6 +138,7 @@ pub unsafe fn create_global_object(
|
||||||
let mut options = RealmOptions::default();
|
let mut options = RealmOptions::default();
|
||||||
options.creationOptions_.traceGlobal_ = Some(trace);
|
options.creationOptions_.traceGlobal_ = Some(trace);
|
||||||
options.creationOptions_.sharedMemoryAndAtomics_ = true;
|
options.creationOptions_.sharedMemoryAndAtomics_ = true;
|
||||||
|
options.creationOptions_.streams_ = true;
|
||||||
|
|
||||||
rval.set(JS_NewGlobalObject(
|
rval.set(JS_NewGlobalObject(
|
||||||
*cx,
|
*cx,
|
||||||
|
|
|
@ -14,14 +14,18 @@ use crate::dom::bindings::str::DOMString;
|
||||||
use crate::dom::bindings::structuredclone::StructuredDataHolder;
|
use crate::dom::bindings::structuredclone::StructuredDataHolder;
|
||||||
use crate::dom::globalscope::GlobalScope;
|
use crate::dom::globalscope::GlobalScope;
|
||||||
use crate::dom::promise::Promise;
|
use crate::dom::promise::Promise;
|
||||||
|
use crate::dom::readablestream::ReadableStream;
|
||||||
use crate::realms::{AlreadyInRealm, InRealm};
|
use crate::realms::{AlreadyInRealm, InRealm};
|
||||||
|
use crate::script_runtime::JSContext;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use encoding_rs::UTF_8;
|
use encoding_rs::UTF_8;
|
||||||
|
use js::jsapi::JSObject;
|
||||||
use msg::constellation_msg::{BlobId, BlobIndex, PipelineNamespaceId};
|
use msg::constellation_msg::{BlobId, BlobIndex, PipelineNamespaceId};
|
||||||
use net_traits::filemanager_thread::RelativePos;
|
use net_traits::filemanager_thread::RelativePos;
|
||||||
use script_traits::serializable::BlobImpl;
|
use script_traits::serializable::BlobImpl;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
use std::ptr::NonNull;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -34,13 +38,7 @@ pub struct Blob {
|
||||||
|
|
||||||
impl Blob {
|
impl Blob {
|
||||||
pub fn new(global: &GlobalScope, blob_impl: BlobImpl) -> DomRoot<Blob> {
|
pub fn new(global: &GlobalScope, blob_impl: BlobImpl) -> DomRoot<Blob> {
|
||||||
let dom_blob = reflect_dom_object(
|
let dom_blob = reflect_dom_object(Box::new(Blob::new_inherited(&blob_impl)), global);
|
||||||
Box::new(Blob {
|
|
||||||
reflector_: Reflector::new(),
|
|
||||||
blob_id: blob_impl.blob_id(),
|
|
||||||
}),
|
|
||||||
global,
|
|
||||||
);
|
|
||||||
global.track_blob(&dom_blob, blob_impl);
|
global.track_blob(&dom_blob, blob_impl);
|
||||||
dom_blob
|
dom_blob
|
||||||
}
|
}
|
||||||
|
@ -89,6 +87,11 @@ impl Blob {
|
||||||
pub fn get_blob_url_id(&self) -> Uuid {
|
pub fn get_blob_url_id(&self) -> Uuid {
|
||||||
self.global().get_blob_url_id(&self.blob_id)
|
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 {
|
impl Serializable for Blob {
|
||||||
|
@ -213,6 +216,11 @@ impl BlobMethods for Blob {
|
||||||
DOMString::from(self.type_string())
|
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
|
// https://w3c.github.io/FileAPI/#slice-method-algo
|
||||||
fn Slice(
|
fn Slice(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -43,6 +43,7 @@ use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
|
||||||
use crate::dom::performance::Performance;
|
use crate::dom::performance::Performance;
|
||||||
use crate::dom::performanceobserver::VALID_ENTRY_TYPES;
|
use crate::dom::performanceobserver::VALID_ENTRY_TYPES;
|
||||||
use crate::dom::promise::Promise;
|
use crate::dom::promise::Promise;
|
||||||
|
use crate::dom::readablestream::{ExternalUnderlyingSource, ReadableStream};
|
||||||
use crate::dom::serviceworker::ServiceWorker;
|
use crate::dom::serviceworker::ServiceWorker;
|
||||||
use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
|
use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
|
||||||
use crate::dom::window::Window;
|
use crate::dom::window::Window;
|
||||||
|
@ -319,11 +320,19 @@ struct FileListener {
|
||||||
task_canceller: TaskCanceller,
|
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 {
|
enum FileListenerState {
|
||||||
Empty(FileListenerCallback, TrustedPromise),
|
Empty(FileListenerCallback, FileListenerTarget),
|
||||||
Receiving(Vec<u8>, FileListenerCallback, TrustedPromise),
|
Receiving(Vec<u8>, FileListenerCallback, FileListenerTarget),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(JSTraceable, MallocSizeOf)]
|
#[derive(JSTraceable, MallocSizeOf)]
|
||||||
|
@ -356,6 +365,14 @@ pub enum BlobState {
|
||||||
UnManaged,
|
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.
|
/// Data representing a message-port managed by this global.
|
||||||
#[derive(JSTraceable, MallocSizeOf)]
|
#[derive(JSTraceable, MallocSizeOf)]
|
||||||
#[unrooted_must_root_lint::must_root]
|
#[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 {
|
impl FileListener {
|
||||||
fn handle(&mut self, msg: FileManagerResult<ReadFileProgress>) {
|
fn handle(&mut self, msg: FileManagerResult<ReadFileProgress>) {
|
||||||
match msg {
|
match msg {
|
||||||
Ok(ReadFileProgress::Meta(blob_buf)) => match self.state.take() {
|
Ok(ReadFileProgress::Meta(blob_buf)) => match self.state.take() {
|
||||||
Some(FileListenerState::Empty(callback, promise)) => {
|
Some(FileListenerState::Empty(callback, target)) => {
|
||||||
self.state = Some(FileListenerState::Receiving(
|
let bytes = if let FileListenerTarget::Stream(ref trusted_stream) = target {
|
||||||
blob_buf.bytes,
|
let trusted = trusted_stream.clone();
|
||||||
callback,
|
|
||||||
promise,
|
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!(
|
_ => panic!(
|
||||||
"Unexpected FileListenerState when receiving ReadFileProgress::Meta msg."
|
"Unexpected FileListenerState when receiving ReadFileProgress::Meta msg."
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Ok(ReadFileProgress::Partial(mut bytes_in)) => match self.state.take() {
|
Ok(ReadFileProgress::Partial(mut bytes_in)) => match self.state.take() {
|
||||||
Some(FileListenerState::Receiving(mut bytes, callback, promise)) => {
|
Some(FileListenerState::Receiving(mut bytes, callback, target)) => {
|
||||||
bytes.append(&mut bytes_in);
|
if let FileListenerTarget::Stream(ref trusted_stream) = target {
|
||||||
self.state = Some(FileListenerState::Receiving(bytes, callback, promise));
|
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!(
|
_ => panic!(
|
||||||
"Unexpected FileListenerState when receiving ReadFileProgress::Partial msg."
|
"Unexpected FileListenerState when receiving ReadFileProgress::Partial msg."
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Ok(ReadFileProgress::EOF) => match self.state.take() {
|
Ok(ReadFileProgress::EOF) => match self.state.take() {
|
||||||
Some(FileListenerState::Receiving(bytes, callback, trusted_promise)) => {
|
Some(FileListenerState::Receiving(bytes, callback, target)) => match target {
|
||||||
let _ = self.task_source.queue_with_canceller(
|
FileListenerTarget::Promise(trusted_promise) => {
|
||||||
task!(resolve_promise: move || {
|
let callback = match callback {
|
||||||
|
FileListenerCallback::Promise(callback) => callback,
|
||||||
|
_ => panic!("Expected promise callback."),
|
||||||
|
};
|
||||||
|
let task = task!(resolve_promise: move || {
|
||||||
let promise = trusted_promise.root();
|
let promise = trusted_promise.root();
|
||||||
let _ac = enter_realm(&*promise.global());
|
let _ac = enter_realm(&*promise.global());
|
||||||
callback.0(promise, Ok(bytes));
|
callback(promise, Ok(bytes));
|
||||||
}),
|
});
|
||||||
&self.task_canceller,
|
|
||||||
);
|
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.")
|
panic!("Unexpected FileListenerState when receiving ReadFileProgress::EOF msg.")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Err(_) => match self.state.take() {
|
Err(_) => match self.state.take() {
|
||||||
Some(FileListenerState::Receiving(_, callback, trusted_promise)) |
|
Some(FileListenerState::Receiving(_, callback, target)) |
|
||||||
Some(FileListenerState::Empty(callback, trusted_promise)) => {
|
Some(FileListenerState::Empty(callback, target)) => {
|
||||||
let bytes = Err(Error::Network);
|
let error = Err(Error::Network);
|
||||||
let _ = self.task_source.queue_with_canceller(
|
|
||||||
task!(reject_promise: move || {
|
match target {
|
||||||
let promise = trusted_promise.root();
|
FileListenerTarget::Promise(trusted_promise) => {
|
||||||
let _ac = enter_realm(&*promise.global());
|
let callback = match callback {
|
||||||
callback.0(promise, bytes);
|
FileListenerCallback::Promise(callback) => callback,
|
||||||
}),
|
_ => panic!("Expected promise callback."),
|
||||||
&self.task_canceller,
|
};
|
||||||
);
|
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."),
|
_ => 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.
|
/// Get a copy of the type_string of a blob.
|
||||||
pub fn get_blob_type_string(&self, blob_id: &BlobId) -> String {
|
pub fn get_blob_type_string(&self, blob_id: &BlobId) -> String {
|
||||||
let blob_state = self.blob_state.borrow();
|
let blob_state = self.blob_state.borrow();
|
||||||
|
@ -1769,6 +1930,50 @@ impl GlobalScope {
|
||||||
GlobalScope::read_msg(recv)
|
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(
|
pub fn read_file_async(
|
||||||
&self,
|
&self,
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
|
@ -1783,8 +1988,8 @@ impl GlobalScope {
|
||||||
|
|
||||||
let mut file_listener = FileListener {
|
let mut file_listener = FileListener {
|
||||||
state: Some(FileListenerState::Empty(
|
state: Some(FileListenerState::Empty(
|
||||||
FileListenerCallback(callback),
|
FileListenerCallback::Promise(callback),
|
||||||
trusted_promise,
|
FileListenerTarget::Promise(trusted_promise),
|
||||||
)),
|
)),
|
||||||
task_source,
|
task_source,
|
||||||
task_canceller,
|
task_canceller,
|
||||||
|
@ -1894,6 +2099,12 @@ impl GlobalScope {
|
||||||
global_scope_from_global(global, cx)
|
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
|
/// Returns the global object of the realm that the given JS object
|
||||||
/// was created in, after unwrapping any wrappers.
|
/// was created in, after unwrapping any wrappers.
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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::cell::DomRefCell;
|
||||||
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrBinding::AttrMethods;
|
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrBinding::AttrMethods;
|
||||||
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
|
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);
|
self.plan_to_navigate(load_data, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -481,6 +481,7 @@ pub mod promiserejectionevent;
|
||||||
pub mod radionodelist;
|
pub mod radionodelist;
|
||||||
pub mod range;
|
pub mod range;
|
||||||
pub mod raredata;
|
pub mod raredata;
|
||||||
|
pub mod readablestream;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
pub mod rtcicecandidate;
|
pub mod rtcicecandidate;
|
||||||
|
|
|
@ -7,13 +7,14 @@ use crate::dom::bindings::root::DomRoot;
|
||||||
use crate::dom::bindings::trace::JSTraceable;
|
use crate::dom::bindings::trace::JSTraceable;
|
||||||
use crate::dom::globalscope::GlobalScope;
|
use crate::dom::globalscope::GlobalScope;
|
||||||
use crate::realms::InRealm;
|
use crate::realms::InRealm;
|
||||||
|
use crate::script_runtime::JSContext as SafeJSContext;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use js::jsapi::JSContext;
|
use js::jsapi::JSContext;
|
||||||
use js::rust::HandleValue;
|
use js::rust::HandleValue;
|
||||||
use malloc_size_of::MallocSizeOf;
|
use malloc_size_of::MallocSizeOf;
|
||||||
|
|
||||||
pub trait Callback: JSTraceable + 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]
|
#[dom_struct]
|
||||||
|
@ -39,12 +40,14 @@ impl PromiseNativeHandler {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unsafe_code)]
|
||||||
fn callback(
|
fn callback(
|
||||||
callback: &Option<Box<dyn Callback>>,
|
callback: &Option<Box<dyn Callback>>,
|
||||||
cx: *mut JSContext,
|
cx: *mut JSContext,
|
||||||
v: HandleValue,
|
v: HandleValue,
|
||||||
realm: InRealm,
|
realm: InRealm,
|
||||||
) {
|
) {
|
||||||
|
let cx = unsafe { SafeJSContext::from_ptr(cx) };
|
||||||
if let Some(ref callback) = *callback {
|
if let Some(ref callback) = *callback {
|
||||||
callback.callback(cx, v, realm)
|
callback.callback(cx, v, realm)
|
||||||
}
|
}
|
||||||
|
|
509
components/script/dom/readablestream.rs
Normal file
509
components/script/dom/readablestream.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,9 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use crate::body::{consume_body, BodyOperations, BodyType};
|
use crate::body::Extractable;
|
||||||
use crate::dom::bindings::cell::{DomRefCell, Ref};
|
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::HeadersBinding::{HeadersInit, HeadersMethods};
|
||||||
use crate::dom::bindings::codegen::Bindings::RequestBinding::ReferrerPolicy;
|
use crate::dom::bindings::codegen::Bindings::RequestBinding::ReferrerPolicy;
|
||||||
use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestCache;
|
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::globalscope::GlobalScope;
|
||||||
use crate::dom::headers::{Guard, Headers};
|
use crate::dom::headers::{Guard, Headers};
|
||||||
use crate::dom::promise::Promise;
|
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 dom_struct::dom_struct;
|
||||||
use http::header::{HeaderName, HeaderValue};
|
use http::header::{HeaderName, HeaderValue};
|
||||||
use http::method::InvalidMethod;
|
use http::method::InvalidMethod;
|
||||||
use http::Method as HttpMethod;
|
use http::Method as HttpMethod;
|
||||||
|
use js::jsapi::JSObject;
|
||||||
use net_traits::request::CacheMode as NetTraitsRequestCache;
|
use net_traits::request::CacheMode as NetTraitsRequestCache;
|
||||||
use net_traits::request::CredentialsMode as NetTraitsRequestCredentials;
|
use net_traits::request::CredentialsMode as NetTraitsRequestCredentials;
|
||||||
use net_traits::request::Destination as NetTraitsRequestDestination;
|
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::request::{Origin, Window};
|
||||||
use net_traits::ReferrerPolicy as MsgReferrerPolicy;
|
use net_traits::ReferrerPolicy as MsgReferrerPolicy;
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
use std::cell::Cell;
|
use std::ptr::NonNull;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
@ -45,11 +48,9 @@ use std::str::FromStr;
|
||||||
pub struct Request {
|
pub struct Request {
|
||||||
reflector_: Reflector,
|
reflector_: Reflector,
|
||||||
request: DomRefCell<NetTraitsRequest>,
|
request: DomRefCell<NetTraitsRequest>,
|
||||||
body_used: Cell<bool>,
|
body_stream: MutNullableDom<ReadableStream>,
|
||||||
headers: MutNullableDom<Headers>,
|
headers: MutNullableDom<Headers>,
|
||||||
mime_type: DomRefCell<Vec<u8>>,
|
mime_type: DomRefCell<Vec<u8>>,
|
||||||
#[ignore_malloc_size_of = "Rc"]
|
|
||||||
body_promise: DomRefCell<Option<(Rc<Promise>, BodyType)>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
|
@ -57,10 +58,9 @@ impl Request {
|
||||||
Request {
|
Request {
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
request: DomRefCell::new(net_request_from_global(global, url)),
|
request: DomRefCell::new(net_request_from_global(global, url)),
|
||||||
body_used: Cell::new(false),
|
body_stream: MutNullableDom::new(None),
|
||||||
headers: Default::default(),
|
headers: Default::default(),
|
||||||
mime_type: DomRefCell::new("".to_string().into_bytes()),
|
mime_type: DomRefCell::new("".to_string().into_bytes()),
|
||||||
body_promise: DomRefCell::new(None),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ impl Request {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn Constructor(
|
pub fn Constructor(
|
||||||
global: &GlobalScope,
|
global: &GlobalScope,
|
||||||
input: RequestInfo,
|
mut input: RequestInfo,
|
||||||
init: RootedTraceableBox<RequestInit>,
|
init: RootedTraceableBox<RequestInit>,
|
||||||
) -> Fallible<DomRoot<Request>> {
|
) -> Fallible<DomRoot<Request>> {
|
||||||
// Step 1
|
// Step 1
|
||||||
|
@ -365,9 +365,9 @@ impl Request {
|
||||||
r.request.borrow_mut().headers = r.Headers().get_headers_list();
|
r.request.borrow_mut().headers = r.Headers().get_headers_list();
|
||||||
|
|
||||||
// Step 33
|
// Step 33
|
||||||
let mut input_body = if let RequestInfo::Request(ref input_request) = input {
|
let mut input_body = if let RequestInfo::Request(ref mut input_request) = input {
|
||||||
let input_request_request = input_request.request.borrow();
|
let mut input_request_request = input_request.request.borrow_mut();
|
||||||
input_request_request.body.clone()
|
input_request_request.body.take()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -398,12 +398,10 @@ impl Request {
|
||||||
// Step 36.2 TODO "If init["keepalive"] exists and is true..."
|
// Step 36.2 TODO "If init["keepalive"] exists and is true..."
|
||||||
|
|
||||||
// Step 36.3
|
// Step 36.3
|
||||||
let extracted_body_tmp = init_body.extract();
|
let mut extracted_body = init_body.extract(global)?;
|
||||||
input_body = Some(extracted_body_tmp.0);
|
|
||||||
let content_type = extracted_body_tmp.1;
|
|
||||||
|
|
||||||
// Step 36.4
|
// 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";
|
let ct_header_name = b"Content-Type";
|
||||||
if !r
|
if !r
|
||||||
.Headers()
|
.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..."
|
// Step 37 "TODO if body is non-null and body's source is null..."
|
||||||
|
@ -448,13 +450,6 @@ impl Request {
|
||||||
// Step 42
|
// Step 42
|
||||||
Ok(r)
|
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 {
|
impl Request {
|
||||||
|
@ -467,7 +462,6 @@ impl Request {
|
||||||
fn clone_from(r: &Request) -> Fallible<DomRoot<Request>> {
|
fn clone_from(r: &Request) -> Fallible<DomRoot<Request>> {
|
||||||
let req = r.request.borrow();
|
let req = r.request.borrow();
|
||||||
let url = req.url();
|
let url = req.url();
|
||||||
let body_used = r.body_used.get();
|
|
||||||
let mime_type = r.mime_type.borrow().clone();
|
let mime_type = r.mime_type.borrow().clone();
|
||||||
let headers_guard = r.Headers().get_guard();
|
let headers_guard = r.Headers().get_guard();
|
||||||
let r_clone = Request::new(&r.global(), url);
|
let r_clone = Request::new(&r.global(), url);
|
||||||
|
@ -477,7 +471,6 @@ impl Request {
|
||||||
borrowed_r_request.origin = req.origin.clone();
|
borrowed_r_request.origin = req.origin.clone();
|
||||||
}
|
}
|
||||||
*r_clone.request.borrow_mut() = req.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.mime_type.borrow_mut() = mime_type;
|
||||||
r_clone.Headers().copy_from_headers(r.Headers())?;
|
r_clone.Headers().copy_from_headers(r.Headers())?;
|
||||||
r_clone.Headers().set_guard(headers_guard);
|
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()
|
!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
|
// https://fetch.spec.whatwg.org/#concept-body-disturbed
|
||||||
fn request_is_disturbed(_input: &Request) -> bool {
|
fn request_is_disturbed(input: &Request) -> bool {
|
||||||
false
|
input.is_disturbed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: `Readable Stream` object is not implemented in Servo yet.
|
|
||||||
// https://fetch.spec.whatwg.org/#concept-body-locked
|
// https://fetch.spec.whatwg.org/#concept-body-locked
|
||||||
fn request_is_locked(_input: &Request) -> bool {
|
fn request_is_locked(input: &Request) -> bool {
|
||||||
false
|
input.is_locked()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestMethods for Request {
|
impl RequestMethods for Request {
|
||||||
|
@ -622,9 +613,14 @@ impl RequestMethods for Request {
|
||||||
DOMString::from_string(r.integrity_metadata.clone())
|
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
|
// https://fetch.spec.whatwg.org/#dom-body-bodyused
|
||||||
fn BodyUsed(&self) -> bool {
|
fn BodyUsed(&self) -> bool {
|
||||||
self.body_used.get()
|
self.is_disturbed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-request-clone
|
// https://fetch.spec.whatwg.org/#dom-request-clone
|
||||||
|
@ -667,29 +663,25 @@ impl RequestMethods for Request {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BodyOperations for Request {
|
impl BodyMixin for Request {
|
||||||
fn get_body_used(&self) -> bool {
|
fn is_disturbed(&self) -> bool {
|
||||||
self.BodyUsed()
|
let body_stream = self.body_stream.get();
|
||||||
}
|
body_stream
|
||||||
|
.as_ref()
|
||||||
fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType) {
|
.map_or(false, |stream| stream.is_disturbed())
|
||||||
assert!(self.body_promise.borrow().is_none());
|
|
||||||
self.body_used.set(true);
|
|
||||||
*self.body_promise.borrow_mut() = Some((p.clone(), body_type));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_locked(&self) -> bool {
|
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>> {
|
fn body(&self) -> Option<DomRoot<ReadableStream>> {
|
||||||
let mut request = self.request.borrow_mut();
|
self.body_stream.get()
|
||||||
let body = request.body.take();
|
|
||||||
Some(body.unwrap_or(vec![]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_mime_type(&self) -> Ref<Vec<u8>> {
|
fn get_mime_type(&self) -> Vec<u8> {
|
||||||
self.mime_type.borrow()
|
self.mime_type.borrow().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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::body::{consume_body, BodyMixin, BodyType};
|
||||||
use crate::dom::bindings::cell::{DomRefCell, Ref};
|
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::HeadersBinding::HeadersMethods;
|
||||||
use crate::dom::bindings::codegen::Bindings::ResponseBinding;
|
use crate::dom::bindings::codegen::Bindings::ResponseBinding;
|
||||||
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::{is_obs_text, is_vchar};
|
||||||
use crate::dom::headers::{Guard, Headers};
|
use crate::dom::headers::{Guard, Headers};
|
||||||
use crate::dom::promise::Promise;
|
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 crate::script_runtime::StreamConsumer;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use http::header::HeaderMap as HyperHeaders;
|
use http::header::HeaderMap as HyperHeaders;
|
||||||
use hyper::StatusCode;
|
use hyper::StatusCode;
|
||||||
use hyper_serde::Serde;
|
use hyper_serde::Serde;
|
||||||
use net_traits::response::ResponseBody as NetTraitsResponseBody;
|
use js::jsapi::JSObject;
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
use std::cell::Cell;
|
use std::ptr::NonNull;
|
||||||
use std::mem;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use url::Position;
|
use url::Position;
|
||||||
|
@ -37,7 +38,6 @@ pub struct Response {
|
||||||
reflector_: Reflector,
|
reflector_: Reflector,
|
||||||
headers_reflector: MutNullableDom<Headers>,
|
headers_reflector: MutNullableDom<Headers>,
|
||||||
mime_type: DomRefCell<Vec<u8>>,
|
mime_type: DomRefCell<Vec<u8>>,
|
||||||
body_used: Cell<bool>,
|
|
||||||
/// `None` can be considered a StatusCode of `0`.
|
/// `None` can be considered a StatusCode of `0`.
|
||||||
#[ignore_malloc_size_of = "Defined in hyper"]
|
#[ignore_malloc_size_of = "Defined in hyper"]
|
||||||
status: DomRefCell<Option<StatusCode>>,
|
status: DomRefCell<Option<StatusCode>>,
|
||||||
|
@ -45,10 +45,8 @@ pub struct Response {
|
||||||
response_type: DomRefCell<DOMResponseType>,
|
response_type: DomRefCell<DOMResponseType>,
|
||||||
url: DomRefCell<Option<ServoUrl>>,
|
url: DomRefCell<Option<ServoUrl>>,
|
||||||
url_list: DomRefCell<Vec<ServoUrl>>,
|
url_list: DomRefCell<Vec<ServoUrl>>,
|
||||||
// For now use the existing NetTraitsResponseBody enum
|
/// The stream of https://fetch.spec.whatwg.org/#body.
|
||||||
body: DomRefCell<NetTraitsResponseBody>,
|
body_stream: MutNullableDom<ReadableStream>,
|
||||||
#[ignore_malloc_size_of = "Rc"]
|
|
||||||
body_promise: DomRefCell<Option<(Rc<Promise>, BodyType)>>,
|
|
||||||
#[ignore_malloc_size_of = "StreamConsumer"]
|
#[ignore_malloc_size_of = "StreamConsumer"]
|
||||||
stream_consumer: DomRefCell<Option<StreamConsumer>>,
|
stream_consumer: DomRefCell<Option<StreamConsumer>>,
|
||||||
redirected: DomRefCell<bool>,
|
redirected: DomRefCell<bool>,
|
||||||
|
@ -56,19 +54,21 @@ pub struct Response {
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
impl Response {
|
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 {
|
Response {
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
headers_reflector: Default::default(),
|
headers_reflector: Default::default(),
|
||||||
mime_type: DomRefCell::new("".to_string().into_bytes()),
|
mime_type: DomRefCell::new("".to_string().into_bytes()),
|
||||||
body_used: Cell::new(false),
|
|
||||||
status: DomRefCell::new(Some(StatusCode::OK)),
|
status: DomRefCell::new(Some(StatusCode::OK)),
|
||||||
raw_status: DomRefCell::new(Some((200, b"".to_vec()))),
|
raw_status: DomRefCell::new(Some((200, b"".to_vec()))),
|
||||||
response_type: DomRefCell::new(DOMResponseType::Default),
|
response_type: DomRefCell::new(DOMResponseType::Default),
|
||||||
url: DomRefCell::new(None),
|
url: DomRefCell::new(None),
|
||||||
url_list: DomRefCell::new(vec![]),
|
url_list: DomRefCell::new(vec![]),
|
||||||
body: DomRefCell::new(NetTraitsResponseBody::Empty),
|
body_stream: MutNullableDom::new(Some(&*stream)),
|
||||||
body_promise: DomRefCell::new(None),
|
|
||||||
stream_consumer: DomRefCell::new(None),
|
stream_consumer: DomRefCell::new(None),
|
||||||
redirected: DomRefCell::new(false),
|
redirected: DomRefCell::new(false),
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ impl Response {
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response
|
// https://fetch.spec.whatwg.org/#dom-response
|
||||||
pub fn new(global: &GlobalScope) -> DomRoot<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(
|
pub fn Constructor(
|
||||||
|
@ -128,8 +128,14 @@ impl Response {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 7.3
|
// Step 7.3
|
||||||
let (extracted_body, content_type) = body.extract();
|
let ExtractedBody {
|
||||||
*r.body.borrow_mut() = NetTraitsResponseBody::Done(extracted_body);
|
stream,
|
||||||
|
total_bytes: _,
|
||||||
|
content_type,
|
||||||
|
source: _,
|
||||||
|
} = body.extract(global)?;
|
||||||
|
|
||||||
|
r.body_stream.set(Some(&*stream));
|
||||||
|
|
||||||
// Step 7.4
|
// Step 7.4
|
||||||
if let Some(content_type_contents) = content_type {
|
if let Some(content_type_contents) = content_type {
|
||||||
|
@ -211,42 +217,32 @@ impl Response {
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#concept-body-locked
|
pub fn error_stream(&self, error: Error) {
|
||||||
fn locked(&self) -> bool {
|
if let Some(body) = self.body_stream.get() {
|
||||||
// TODO: ReadableStream is unimplemented. Just return false
|
body.error_native(error);
|
||||||
// for now.
|
}
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BodyOperations for Response {
|
impl BodyMixin for Response {
|
||||||
fn get_body_used(&self) -> bool {
|
fn is_disturbed(&self) -> bool {
|
||||||
self.BodyUsed()
|
self.body_stream
|
||||||
}
|
.get()
|
||||||
|
.map_or(false, |stream| stream.is_disturbed())
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_locked(&self) -> bool {
|
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>> {
|
fn body(&self) -> Option<DomRoot<ReadableStream>> {
|
||||||
let body = mem::replace(&mut *self.body.borrow_mut(), NetTraitsResponseBody::Empty);
|
self.body_stream.get()
|
||||||
match body {
|
|
||||||
NetTraitsResponseBody::Done(bytes) => Some(bytes),
|
|
||||||
body => {
|
|
||||||
let _ = mem::replace(&mut *self.body.borrow_mut(), body);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_mime_type(&self) -> Ref<Vec<u8>> {
|
fn get_mime_type(&self) -> Vec<u8> {
|
||||||
self.mime_type.borrow()
|
self.mime_type.borrow().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,7 +324,7 @@ impl ResponseMethods for Response {
|
||||||
// https://fetch.spec.whatwg.org/#dom-response-clone
|
// https://fetch.spec.whatwg.org/#dom-response-clone
|
||||||
fn Clone(&self) -> Fallible<DomRoot<Response>> {
|
fn Clone(&self) -> Fallible<DomRoot<Response>> {
|
||||||
// Step 1
|
// 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()));
|
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.borrow_mut() = self.url.borrow().clone();
|
||||||
*new_response.url_list.borrow_mut() = self.url_list.borrow().clone();
|
*new_response.url_list.borrow_mut() = self.url_list.borrow().clone();
|
||||||
|
|
||||||
if *self.body.borrow() != NetTraitsResponseBody::Empty {
|
if let Some(stream) = self.body_stream.get().clone() {
|
||||||
*new_response.body.borrow_mut() = self.body.borrow().clone();
|
new_response.body_stream.set(Some(&*stream));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3
|
// Step 3
|
||||||
|
@ -359,7 +355,12 @@ impl ResponseMethods for Response {
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-body-bodyused
|
// https://fetch.spec.whatwg.org/#dom-body-bodyused
|
||||||
fn BodyUsed(&self) -> bool {
|
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
|
// https://fetch.spec.whatwg.org/#dom-body-text
|
||||||
|
@ -424,20 +425,19 @@ impl Response {
|
||||||
*self.status.borrow_mut() = None;
|
*self.status.borrow_mut() = None;
|
||||||
self.set_raw_status(None);
|
self.set_raw_status(None);
|
||||||
self.set_headers(None);
|
self.set_headers(None);
|
||||||
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
|
|
||||||
},
|
},
|
||||||
DOMResponseType::Opaque => {
|
DOMResponseType::Opaque => {
|
||||||
*self.url_list.borrow_mut() = vec![];
|
*self.url_list.borrow_mut() = vec![];
|
||||||
*self.status.borrow_mut() = None;
|
*self.status.borrow_mut() = None;
|
||||||
self.set_raw_status(None);
|
self.set_raw_status(None);
|
||||||
self.set_headers(None);
|
self.set_headers(None);
|
||||||
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
|
self.body_stream.set(None);
|
||||||
},
|
},
|
||||||
DOMResponseType::Opaqueredirect => {
|
DOMResponseType::Opaqueredirect => {
|
||||||
*self.status.borrow_mut() = None;
|
*self.status.borrow_mut() = None;
|
||||||
self.set_raw_status(None);
|
self.set_raw_status(None);
|
||||||
self.set_headers(None);
|
self.set_headers(None);
|
||||||
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
|
self.body_stream.set(None);
|
||||||
},
|
},
|
||||||
DOMResponseType::Default => {},
|
DOMResponseType::Default => {},
|
||||||
DOMResponseType::Basic => {},
|
DOMResponseType::Basic => {},
|
||||||
|
@ -449,17 +449,19 @@ impl Response {
|
||||||
*self.stream_consumer.borrow_mut() = sc;
|
*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() {
|
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)]
|
#[allow(unrooted_must_root)]
|
||||||
pub fn finish(&self, body: Vec<u8>) {
|
pub fn finish(&self) {
|
||||||
*self.body.borrow_mut() = NetTraitsResponseBody::Done(body);
|
if let Some(body) = self.body_stream.get() {
|
||||||
if let Some((p, body_type)) = self.body_promise.borrow_mut().take() {
|
body.close_native();
|
||||||
consume_body_with_promise(self, body_type, &p);
|
|
||||||
}
|
}
|
||||||
if let Some(stream_consumer) = self.stream_consumer.borrow_mut().take() {
|
if let Some(stream_consumer) = self.stream_consumer.borrow_mut().take() {
|
||||||
stream_consumer.stream_end();
|
stream_consumer.stream_end();
|
||||||
|
|
|
@ -53,7 +53,7 @@ use crate::realms::InRealm;
|
||||||
use crate::script_runtime::JSContext as SafeJSContext;
|
use crate::script_runtime::JSContext as SafeJSContext;
|
||||||
use crate::timers::OneshotTimerCallback;
|
use crate::timers::OneshotTimerCallback;
|
||||||
use dom_struct::dom_struct;
|
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::jsapi::{JS_NewPlainObject, JS_NewUint8ClampedArray};
|
||||||
use js::jsval::{JSVal, NullValue};
|
use js::jsval::{JSVal, NullValue};
|
||||||
use js::rust::CustomAutoRooterGuard;
|
use js::rust::CustomAutoRooterGuard;
|
||||||
|
@ -1018,9 +1018,8 @@ impl TestBindingMethods for TestBinding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Callback for SimpleHandler {
|
impl Callback for SimpleHandler {
|
||||||
#[allow(unsafe_code)]
|
fn callback(&self, cx: SafeJSContext, v: HandleValue, realm: InRealm) {
|
||||||
fn callback(&self, cx: *mut JSContext, v: HandleValue, realm: InRealm) {
|
let global = GlobalScope::from_safe_context(cx, realm);
|
||||||
let global = unsafe { GlobalScope::from_context(cx, realm) };
|
|
||||||
let _ = self.handler.Call_(&*global, v, ExceptionHandling::Report);
|
let _ = self.handler.Call_(&*global, v, ExceptionHandling::Report);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ interface Blob {
|
||||||
optional [Clamp] long long end,
|
optional [Clamp] long long end,
|
||||||
optional DOMString contentType);
|
optional DOMString contentType);
|
||||||
|
|
||||||
|
[NewObject] object stream();
|
||||||
[NewObject] Promise<DOMString> text();
|
[NewObject] Promise<DOMString> text();
|
||||||
[NewObject] Promise<ArrayBuffer> arrayBuffer();
|
[NewObject] Promise<ArrayBuffer> arrayBuffer();
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
[Exposed=(Window,Worker)]
|
[Exposed=(Window,Worker)]
|
||||||
interface mixin Body {
|
interface mixin Body {
|
||||||
readonly attribute boolean bodyUsed;
|
readonly attribute boolean bodyUsed;
|
||||||
|
readonly attribute object? body;
|
||||||
|
|
||||||
[NewObject] Promise<ArrayBuffer> arrayBuffer();
|
[NewObject] Promise<ArrayBuffer> arrayBuffer();
|
||||||
[NewObject] Promise<Blob> blob();
|
[NewObject] Promise<Blob> blob();
|
||||||
|
|
11
components/script/dom/webidls/ReadableStream.webidl
Normal file
11
components/script/dom/webidls/ReadableStream.webidl
Normal 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 {
|
||||||
|
};
|
|
@ -13,7 +13,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#bodyinit
|
// 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 {
|
enum XMLHttpRequestResponseType {
|
||||||
"",
|
"",
|
||||||
|
@ -21,7 +21,7 @@ enum XMLHttpRequestResponseType {
|
||||||
"blob",
|
"blob",
|
||||||
"document",
|
"document",
|
||||||
"json",
|
"json",
|
||||||
"text"
|
"text",
|
||||||
};
|
};
|
||||||
|
|
||||||
[Exposed=(Window,Worker)]
|
[Exposed=(Window,Worker)]
|
||||||
|
|
|
@ -2,11 +2,10 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use crate::body::{Extractable, ExtractedBody};
|
||||||
use crate::document_loader::DocumentLoader;
|
use crate::document_loader::DocumentLoader;
|
||||||
use crate::dom::bindings::cell::DomRefCell;
|
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::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::XMLHttpRequestMethods;
|
||||||
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseType;
|
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseType;
|
||||||
use crate::dom::bindings::codegen::UnionTypes::DocumentOrBodyInit;
|
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::document::{Document, HasBrowsingContext, IsHTMLDocument};
|
||||||
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
||||||
use crate::dom::eventtarget::EventTarget;
|
use crate::dom::eventtarget::EventTarget;
|
||||||
use crate::dom::formdata::FormData;
|
|
||||||
use crate::dom::globalscope::GlobalScope;
|
use crate::dom::globalscope::GlobalScope;
|
||||||
use crate::dom::headers::is_forbidden_header_name;
|
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::node::Node;
|
||||||
use crate::dom::performanceresourcetiming::InitiatorType;
|
use crate::dom::performanceresourcetiming::InitiatorType;
|
||||||
use crate::dom::progressevent::ProgressEvent;
|
use crate::dom::progressevent::ProgressEvent;
|
||||||
|
use crate::dom::readablestream::ReadableStream;
|
||||||
use crate::dom::servoparser::ServoParser;
|
use crate::dom::servoparser::ServoParser;
|
||||||
use crate::dom::urlsearchparams::URLSearchParams;
|
|
||||||
use crate::dom::window::Window;
|
use crate::dom::window::Window;
|
||||||
use crate::dom::workerglobalscope::WorkerGlobalScope;
|
use crate::dom::workerglobalscope::WorkerGlobalScope;
|
||||||
use crate::dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
|
use crate::dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
|
||||||
|
@ -58,7 +55,9 @@ use js::jsval::{JSVal, NullValue, UndefinedValue};
|
||||||
use js::rust::wrappers::JS_ParseJSON;
|
use js::rust::wrappers::JS_ParseJSON;
|
||||||
use js::typedarray::{ArrayBuffer, CreateWith};
|
use js::typedarray::{ArrayBuffer, CreateWith};
|
||||||
use mime::{self, Mime, Name};
|
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::trim_http_whitespace;
|
||||||
use net_traits::CoreResourceMsg::Fetch;
|
use net_traits::CoreResourceMsg::Fetch;
|
||||||
use net_traits::{FetchChannels, FetchMetadata, FilteredMetadata};
|
use net_traits::{FetchChannels, FetchMetadata, FilteredMetadata};
|
||||||
|
@ -562,42 +561,96 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
|
||||||
_ => data,
|
_ => data,
|
||||||
};
|
};
|
||||||
// Step 4 (first half)
|
// Step 4 (first half)
|
||||||
let extracted_or_serialized = match data {
|
let mut extracted_or_serialized = match data {
|
||||||
Some(DocumentOrBodyInit::Document(ref doc)) => {
|
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() {
|
let content_type = if doc.is_html_document() {
|
||||||
"text/html;charset=UTF-8"
|
"text/html;charset=UTF-8"
|
||||||
} else {
|
} else {
|
||||||
"application/xml;charset=UTF-8"
|
"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::Blob(ref b)) => {
|
||||||
Some(DocumentOrBodyInit::FormData(ref formdata)) => Some(formdata.extract()),
|
Some(b.extract(&self.global()).expect("Couldn't extract body."))
|
||||||
Some(DocumentOrBodyInit::String(ref str)) => Some(str.extract()),
|
},
|
||||||
Some(DocumentOrBodyInit::URLSearchParams(ref urlsp)) => Some(urlsp.extract()),
|
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(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(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,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.request_body_len
|
self.request_body_len.set(
|
||||||
.set(extracted_or_serialized.as_ref().map_or(0, |e| e.0.len()));
|
extracted_or_serialized
|
||||||
|
.as_ref()
|
||||||
|
.map_or(0, |e| e.total_bytes.unwrap_or(0)),
|
||||||
|
);
|
||||||
|
|
||||||
// todo preserved headers?
|
// todo preserved headers?
|
||||||
|
|
||||||
// Step 6
|
// Step 6
|
||||||
self.upload_complete.set(false);
|
self.upload_complete.set(false);
|
||||||
// Step 7
|
// Step 7
|
||||||
self.upload_complete.set(match extracted_or_serialized {
|
self.upload_complete.set(extracted_or_serialized.is_none());
|
||||||
None => true,
|
|
||||||
Some(ref e) if e.0.is_empty() => true,
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
// Step 8
|
// Step 8
|
||||||
self.send_flag.set(true);
|
self.send_flag.set(true);
|
||||||
|
|
||||||
|
@ -634,12 +687,17 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
|
||||||
unreachable!()
|
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())
|
let mut request = RequestBuilder::new(self.request_url.borrow().clone().unwrap())
|
||||||
.method(self.request_method.borrow().clone())
|
.method(self.request_method.borrow().clone())
|
||||||
.headers((*self.request_headers.borrow()).clone())
|
.headers((*self.request_headers.borrow()).clone())
|
||||||
.unsafe_request(true)
|
.unsafe_request(true)
|
||||||
// XXXManishearth figure out how to avoid this clone
|
// 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
|
// XXXManishearth actually "subresource", but it doesn't exist
|
||||||
// https://github.com/whatwg/xhr/issues/71
|
// https://github.com/whatwg/xhr/issues/71
|
||||||
.destination(Destination::None)
|
.destination(Destination::None)
|
||||||
|
@ -658,8 +716,8 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
|
||||||
.pipeline_id(Some(self.global().pipeline_id()));
|
.pipeline_id(Some(self.global().pipeline_id()));
|
||||||
|
|
||||||
// step 4 (second half)
|
// step 4 (second half)
|
||||||
match extracted_or_serialized {
|
match content_type {
|
||||||
Some((_, ref content_type)) => {
|
Some(content_type) => {
|
||||||
let encoding = match data {
|
let encoding = match data {
|
||||||
Some(DocumentOrBodyInit::String(_)) | Some(DocumentOrBodyInit::Document(_)) =>
|
Some(DocumentOrBodyInit::String(_)) | Some(DocumentOrBodyInit::Document(_)) =>
|
||||||
// XHR spec differs from http, and says UTF-8 should be in capitals,
|
// 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;
|
let mut content_type_set = false;
|
||||||
if let Some(ref ct) = *content_type {
|
if !request.headers.contains_key(header::CONTENT_TYPE) {
|
||||||
if !request.headers.contains_key(header::CONTENT_TYPE) {
|
request.headers.insert(
|
||||||
request
|
header::CONTENT_TYPE,
|
||||||
.headers
|
HeaderValue::from_str(&content_type).unwrap(),
|
||||||
.insert(header::CONTENT_TYPE, HeaderValue::from_str(ct).unwrap());
|
);
|
||||||
content_type_set = true;
|
content_type_set = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !content_type_set {
|
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> {
|
fn serialize_document(doc: &Document) -> Fallible<DOMString> {
|
||||||
let mut writer = vec![];
|
let mut writer = vec![];
|
||||||
match serialize(&mut writer, &doc.upcast::<Node>(), SerializeOpts::default()) {
|
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
|
/// Returns whether `bs` is a `field-value`, as defined by
|
||||||
/// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-32).
|
/// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-32).
|
||||||
pub fn is_field_value(slice: &[u8]) -> bool {
|
pub fn is_field_value(slice: &[u8]) -> bool {
|
||||||
|
|
|
@ -36,14 +36,12 @@ use net_traits::{FetchChannels, FetchResponseListener, NetworkError};
|
||||||
use net_traits::{FetchMetadata, FilteredMetadata, Metadata};
|
use net_traits::{FetchMetadata, FilteredMetadata, Metadata};
|
||||||
use net_traits::{ResourceFetchTiming, ResourceTimingType};
|
use net_traits::{ResourceFetchTiming, ResourceTimingType};
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
use std::mem;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
struct FetchContext {
|
struct FetchContext {
|
||||||
fetch_promise: Option<TrustedPromise>,
|
fetch_promise: Option<TrustedPromise>,
|
||||||
response_object: Trusted<Response>,
|
response_object: Trusted<Response>,
|
||||||
body: Vec<u8>,
|
|
||||||
resource_timing: ResourceFetchTiming,
|
resource_timing: ResourceFetchTiming,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +147,7 @@ pub fn Fetch(
|
||||||
// Step 2
|
// Step 2
|
||||||
let request = match Request::Constructor(global, input, init) {
|
let request = match Request::Constructor(global, input, init) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
response.error_stream(e.clone());
|
||||||
promise.reject_error(e);
|
promise.reject_error(e);
|
||||||
return promise;
|
return promise;
|
||||||
},
|
},
|
||||||
|
@ -172,7 +171,6 @@ pub fn Fetch(
|
||||||
let fetch_context = Arc::new(Mutex::new(FetchContext {
|
let fetch_context = Arc::new(Mutex::new(FetchContext {
|
||||||
fetch_promise: Some(TrustedPromise::new(promise.clone())),
|
fetch_promise: Some(TrustedPromise::new(promise.clone())),
|
||||||
response_object: Trusted::new(&*response),
|
response_object: Trusted::new(&*response),
|
||||||
body: vec![],
|
|
||||||
resource_timing: ResourceFetchTiming::new(timing_type),
|
resource_timing: ResourceFetchTiming::new(timing_type),
|
||||||
}));
|
}));
|
||||||
let listener = NetworkListener {
|
let listener = NetworkListener {
|
||||||
|
@ -222,7 +220,9 @@ impl FetchResponseListener for FetchContext {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
promise.reject_error(Error::Type("Network error occurred".to_string()));
|
promise.reject_error(Error::Type("Network error occurred".to_string()));
|
||||||
self.fetch_promise = Some(TrustedPromise::new(promise));
|
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;
|
return;
|
||||||
},
|
},
|
||||||
// Step 4.2
|
// Step 4.2
|
||||||
|
@ -260,15 +260,15 @@ impl FetchResponseListener for FetchContext {
|
||||||
self.fetch_promise = Some(TrustedPromise::new(promise));
|
self.fetch_promise = Some(TrustedPromise::new(promise));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_response_chunk(&mut self, mut chunk: Vec<u8>) {
|
fn process_response_chunk(&mut self, chunk: Vec<u8>) {
|
||||||
self.response_object.root().stream_chunk(chunk.as_slice());
|
let response = self.response_object.root();
|
||||||
self.body.append(&mut chunk);
|
response.stream_chunk(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_response_eof(&mut self, _response: Result<ResourceFetchTiming, NetworkError>) {
|
fn process_response_eof(&mut self, _response: Result<ResourceFetchTiming, NetworkError>) {
|
||||||
let response = self.response_object.root();
|
let response = self.response_object.root();
|
||||||
let _ac = enter_realm(&*response);
|
let _ac = enter_realm(&*response);
|
||||||
response.finish(mem::replace(&mut self.body, vec![]));
|
response.finish();
|
||||||
// TODO
|
// TODO
|
||||||
// ... trailerObject is not supported in Servo yet.
|
// ... trailerObject is not supported in Servo yet.
|
||||||
}
|
}
|
||||||
|
|
|
@ -675,7 +675,7 @@ impl ModuleHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Callback for 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();
|
let task = self.task.borrow_mut().take().unwrap();
|
||||||
task.run_box();
|
task.run_box();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![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::PromiseBinding::PromiseJobCallback;
|
||||||
use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseBinding::ResponseMethods;
|
use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseBinding::ResponseMethods;
|
||||||
use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
|
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.
|
// 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(
|
throw_dom_exception(
|
||||||
cx,
|
cx,
|
||||||
&global,
|
&global,
|
||||||
|
|
|
@ -49,7 +49,7 @@ use msg::constellation_msg::{
|
||||||
use msg::constellation_msg::{PipelineNamespaceId, TopLevelBrowsingContextId};
|
use msg::constellation_msg::{PipelineNamespaceId, TopLevelBrowsingContextId};
|
||||||
use net_traits::image::base::Image;
|
use net_traits::image::base::Image;
|
||||||
use net_traits::image_cache::ImageCache;
|
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::storage_thread::StorageType;
|
||||||
use net_traits::{FetchResponseMsg, ReferrerPolicy, ResourceThreads};
|
use net_traits::{FetchResponseMsg, ReferrerPolicy, ResourceThreads};
|
||||||
use pixels::PixelFormat;
|
use pixels::PixelFormat;
|
||||||
|
@ -171,8 +171,8 @@ pub struct LoadData {
|
||||||
serialize_with = "::hyper_serde::serialize"
|
serialize_with = "::hyper_serde::serialize"
|
||||||
)]
|
)]
|
||||||
pub headers: HeaderMap,
|
pub headers: HeaderMap,
|
||||||
/// The data.
|
/// The data that will be used as the body of the request.
|
||||||
pub data: Option<Vec<u8>>,
|
pub data: Option<RequestBody>,
|
||||||
/// The result of evaluating a javascript scheme url.
|
/// The result of evaluating a javascript scheme url.
|
||||||
pub js_eval_result: Option<JsEvalResult>,
|
pub js_eval_result: Option<JsEvalResult>,
|
||||||
/// The referrer.
|
/// The referrer.
|
||||||
|
|
|
@ -153,6 +153,10 @@ skip: true
|
||||||
skip: false
|
skip: false
|
||||||
[selection]
|
[selection]
|
||||||
skip: false
|
skip: false
|
||||||
|
[streams]
|
||||||
|
skip: true
|
||||||
|
[readable-streams]
|
||||||
|
skip: false
|
||||||
[subresource-integrity]
|
[subresource-integrity]
|
||||||
skip: false
|
skip: false
|
||||||
[touch-events]
|
[touch-events]
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -18,18 +18,9 @@
|
||||||
[File API automated IDL tests]
|
[File API automated IDL tests]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Blob interface: operation stream()]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Blob interface: operation text()]
|
[Blob interface: operation text()]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Blob interface: new Blob(["TEST"\]) must inherit property "stream()" with the proper type]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Blob interface: operation arrayBuffer()]
|
[Blob interface: operation arrayBuffer()]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Blob interface: new File(["myFileBits"\], "myFileName") must inherit property "stream()" with the proper type]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
|
@ -30,18 +30,9 @@
|
||||||
[idlharness]
|
[idlharness]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Blob interface: operation stream()]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Blob interface: operation text()]
|
[Blob interface: operation text()]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Blob interface: new Blob(["TEST"\]) must inherit property "stream()" with the proper type]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Blob interface: operation arrayBuffer()]
|
[Blob interface: operation arrayBuffer()]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Blob interface: new File(["myFileBits"\], "myFileName") must inherit property "stream()" with the proper type]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -5,12 +5,6 @@
|
||||||
|
|
||||||
|
|
||||||
[idlharness.any.html]
|
[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()]
|
[Response interface: operation blob()]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -68,9 +62,6 @@
|
||||||
[Request interface: new Request('about:blank') must inherit property "signal" with the proper type]
|
[Request interface: new Request('about:blank') must inherit property "signal" with the proper type]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Request interface: new Request('about:blank') must inherit property "body" with the proper type]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Request interface: operation arrayBuffer()]
|
[Request interface: operation arrayBuffer()]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -80,9 +71,6 @@
|
||||||
[Response interface: attribute trailer]
|
[Response interface: attribute trailer]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Request interface: attribute body]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Window interface: calling fetch(RequestInfo, optional RequestInit) on window with too few arguments must throw TypeError]
|
[Window interface: calling fetch(RequestInfo, optional RequestInit) on window with too few arguments must throw TypeError]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -91,12 +79,6 @@
|
||||||
|
|
||||||
|
|
||||||
[idlharness.any.worker.html]
|
[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()]
|
[Response interface: operation blob()]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -154,9 +136,6 @@
|
||||||
[Request interface: new Request('about:blank') must inherit property "signal" with the proper type]
|
[Request interface: new Request('about:blank') must inherit property "signal" with the proper type]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Request interface: new Request('about:blank') must inherit property "body" with the proper type]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Request interface: operation arrayBuffer()]
|
[Request interface: operation arrayBuffer()]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -166,9 +145,6 @@
|
||||||
[Response interface: attribute trailer]
|
[Response interface: attribute trailer]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Request interface: attribute body]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[WorkerGlobalScope interface: calling fetch(RequestInfo, optional RequestInit) on self with too few arguments must throw TypeError]
|
[WorkerGlobalScope interface: calling fetch(RequestInfo, optional RequestInit) on self with too few arguments must throw TypeError]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -1,32 +1,11 @@
|
||||||
[request-consume-empty.html]
|
[request-consume-empty.html]
|
||||||
type: testharness
|
type: testharness
|
||||||
[Consume request's body as arrayBuffer]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Consume request's body as formData]
|
[Consume request's body as formData]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Consume request's body as text]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Consume request's body as blob]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Consume request's body as json]
|
[Consume request's body as json]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Consume empty FormData request body as text]
|
[Consume empty FormData request body as text]
|
||||||
expected: FAIL
|
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
|
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,11 @@
|
||||||
[request-disturbed.html]
|
[request-disturbed.html]
|
||||||
type: testharness
|
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]
|
[Input request used for creating new request became disturbed]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Request construction failure should not set "bodyUsed"]
|
[Request construction failure should not set "bodyUsed"]
|
||||||
expected: FAIL
|
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]
|
[Input request used for creating new request became disturbed even if body is not used]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,9 @@
|
||||||
[Constructing a Request with a Request on which read() and releaseLock() are called]
|
[Constructing a Request with a Request on which read() and releaseLock() are called]
|
||||||
expected: FAIL
|
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]
|
[Constructing a Request with a Request on which body.getReader() is called]
|
||||||
expected: FAIL
|
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]
|
[Constructing a Request with a Request on which body.getReader().read() is called]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -28,18 +19,9 @@
|
||||||
[Constructing a Request with a Request on which read() and releaseLock() are called]
|
[Constructing a Request with a Request on which read() and releaseLock() are called]
|
||||||
expected: FAIL
|
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]
|
[Constructing a Request with a Request on which body.getReader() is called]
|
||||||
expected: FAIL
|
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]
|
[Constructing a Request with a Request on which body.getReader().read() is called]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,5 @@
|
||||||
[response-cancel-stream.html]
|
[response-cancel-stream.html]
|
||||||
type: testharness
|
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]
|
[Response consume blob and http bodies]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
[response-clone.html]
|
[response-clone.html]
|
||||||
type: testharness
|
type: testharness
|
||||||
[Check orginal response's body after cloning]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check cloned response's body]
|
[Check cloned response's body]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -44,3 +41,4 @@
|
||||||
|
|
||||||
[Check response clone use structureClone for teed ReadableStreams (DataViewchunk)]
|
[Check response clone use structureClone for teed ReadableStreams (DataViewchunk)]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,8 @@
|
||||||
[response-consume-stream.html]
|
[response-consume-stream.html]
|
||||||
type: testharness
|
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]
|
[Getting an error Response stream]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Getting a redirect Response stream]
|
[Getting a redirect Response stream]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Read URLSearchParams response's body as readableStream]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
[response-consume.html]
|
[response-consume.html]
|
||||||
type: testharness
|
type: testharness
|
||||||
expected: ERROR
|
|
||||||
[Consume response's body as arrayBuffer]
|
[Consume response's body as arrayBuffer]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -34,30 +33,9 @@
|
||||||
[Consume response's body: from FormData to formData]
|
[Consume response's body: from FormData to formData]
|
||||||
expected: FAIL
|
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]
|
[Consume response's body: from stream with correct multipart type to formData]
|
||||||
expected: FAIL
|
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]
|
[Consume response's body: from multipart form data blob to formData]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,2 @@
|
||||||
[response-error-from-stream.html]
|
[response-error-from-stream.html]
|
||||||
expected: ERROR
|
expected: ERROR
|
||||||
[ReadableStreamDefaultReader Promise receives ReadableStream start() Error]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[ReadableStreamDefaultReader Promise receives ReadableStream pull() Error]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
|
@ -5,29 +5,11 @@
|
||||||
|
|
||||||
|
|
||||||
[response-from-stream.any.html]
|
[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]
|
[response-from-stream.any.serviceworker.html]
|
||||||
expected: TIMEOUT
|
expected: TIMEOUT
|
||||||
|
|
||||||
[response-from-stream.any.worker.html]
|
[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]
|
[response-from-stream.any.serviceworker.html]
|
||||||
expected: ERROR
|
expected: ERROR
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
[response-init-002.html]
|
[response-init-002.html]
|
||||||
type: testharness
|
type: testharness
|
||||||
[Read Response's body as readableStream]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Testing null Response body]
|
[Testing null Response body]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -1,25 +1,22 @@
|
||||||
[response-stream-with-broken-then.any.html]
|
[response-stream-with-broken-then.any.html]
|
||||||
[Untitled]
|
[Untitled]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Inject {done: false, value: bye} via Object.prototype.then.]
|
[Inject {done: false, value: bye} via Object.prototype.then.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Inject {done: false, value: undefined} via Object.prototype.then.]
|
[Inject {done: false, value: undefined} via Object.prototype.then.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Inject undefined via Object.prototype.then.]
|
[Inject undefined via Object.prototype.then.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Inject 8.2 via Object.prototype.then.]
|
[Inject 8.2 via Object.prototype.then.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[response-stream-with-broken-then]
|
[response-stream-with-broken-then]
|
||||||
expected: FAIL
|
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]
|
[intercepting arraybuffer to body readable stream conversion via Object.prototype.then should not be possible]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -27,26 +24,22 @@
|
||||||
[response-stream-with-broken-then.any.worker.html]
|
[response-stream-with-broken-then.any.worker.html]
|
||||||
[Untitled]
|
[Untitled]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Inject {done: false, value: bye} via Object.prototype.then.]
|
[Inject {done: false, value: bye} via Object.prototype.then.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Inject {done: false, value: undefined} via Object.prototype.then.]
|
[Inject {done: false, value: undefined} via Object.prototype.then.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Inject undefined via Object.prototype.then.]
|
[Inject undefined via Object.prototype.then.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Inject 8.2 via Object.prototype.then.]
|
[Inject 8.2 via Object.prototype.then.]
|
||||||
expected: FAIL
|
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]
|
[intercepting arraybuffer to body readable stream conversion via Object.prototype.then should not be possible]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
||||||
[response-stream-with-broken-then]
|
[response-stream-with-broken-then]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -311,28 +311,3 @@
|
||||||
|
|
||||||
[fetch(): separate response Content-Type: text/plain ]
|
[fetch(): separate response Content-Type: text/plain ]
|
||||||
expected: NOTRUN
|
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
|
|
||||||
|
|
||||||
|
|
|
@ -53,9 +53,15 @@
|
||||||
[combined text/javascript ]
|
[combined text/javascript ]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[separate text/javascript x/x]
|
[separate text/javascript;charset=windows-1252 error text/javascript]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[separate text/javascript ]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[separate text/javascript error]
|
[separate text/javascript error]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
[separate text/javascript x/x]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -10,4 +10,3 @@
|
||||||
|
|
||||||
[X-Content-Type-Options%3A%20nosniff%2C%2C%40%23%24%23%25%25%26%5E%26%5E*()()11!]
|
[X-Content-Type-Options%3A%20nosniff%2C%2C%40%23%24%23%25%25%26%5E%26%5E*()()11!]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,128 +1,37 @@
|
||||||
[assorted.window.html]
|
[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]
|
[Origin header and 308 redirect]
|
||||||
expected: FAIL
|
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]
|
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy no-referrer]
|
||||||
expected: NOTRUN
|
expected: FAIL
|
||||||
|
|
||||||
[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
|
|
||||||
|
|
||||||
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy no-referrer]
|
[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]
|
[Origin header and POST cross-origin navigation with Referrer-Policy origin-when-cross-origin]
|
||||||
expected: NOTRUN
|
expected: FAIL
|
||||||
|
|
||||||
[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
|
|
||||||
|
|
||||||
[Origin header and POST cross-origin navigation with Referrer-Policy unsafe-url]
|
[Origin header and POST cross-origin navigation with Referrer-Policy unsafe-url]
|
||||||
expected: NOTRUN
|
expected: FAIL
|
||||||
|
|
||||||
[Origin header and POST navigation]
|
[Origin header and POST navigation]
|
||||||
expected: TIMEOUT
|
expected: FAIL
|
||||||
|
|
||||||
[Origin header and POST cross-origin navigation with Referrer-Policy no-referrer]
|
[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]
|
[Origin header and POST cross-origin navigation with Referrer-Policy same-origin]
|
||||||
expected: NOTRUN
|
expected: FAIL
|
||||||
|
|
||||||
[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
|
|
||||||
|
|
||||||
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy same-origin]
|
[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]
|
[Origin header and POST same-origin navigation with Referrer-Policy no-referrer]
|
||||||
expected: NOTRUN
|
expected: FAIL
|
||||||
|
|
||||||
[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
|
|
||||||
|
|
||||||
[Origin header and POST cross-origin navigation with Referrer-Policy no-referrer-when-downgrade]
|
[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]
|
[Origin header and POST same-origin fetch cors mode with Referrer-Policy no-referrer]
|
||||||
expected: NOTRUN
|
expected: FAIL
|
||||||
|
|
||||||
[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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,9 @@
|
||||||
|
[cancel.any.worker.html]
|
||||||
|
|
||||||
|
[cancel.any.sharedworker.html]
|
||||||
|
expected: ERROR
|
||||||
|
|
||||||
|
[cancel.any.html]
|
||||||
|
|
||||||
|
[cancel.any.serviceworker.html]
|
||||||
|
expected: ERROR
|
|
@ -0,0 +1,9 @@
|
||||||
|
[constructor.any.sharedworker.html]
|
||||||
|
expected: ERROR
|
||||||
|
|
||||||
|
[constructor.any.serviceworker.html]
|
||||||
|
expected: ERROR
|
||||||
|
|
||||||
|
[constructor.any.html]
|
||||||
|
|
||||||
|
[constructor.any.worker.html]
|
|
@ -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]
|
|
@ -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
|
|
@ -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
|
|
@ -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]
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
35
tests/wpt/metadata/streams/readable-streams/tee.any.js.ini
Normal file
35
tests/wpt/metadata/streams/readable-streams/tee.any.js.ini
Normal 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -9,18 +9,9 @@
|
||||||
[The Path2D interface object should be exposed.]
|
[The Path2D interface object should be exposed.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[The ReadableStream interface object should be exposed.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[The WritableStream interface object should be exposed.]
|
[The WritableStream interface object should be exposed.]
|
||||||
expected: FAIL
|
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.]
|
[The IDBRequest interface object should be exposed.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -53,3 +44,4 @@
|
||||||
|
|
||||||
[The IDBTransaction interface object should be exposed.]
|
[The IDBTransaction interface object should be exposed.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,6 @@
|
||||||
[send-data-readablestream.any.html]
|
[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]
|
[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]
|
[send-data-readablestream.any.sharedworker.html]
|
||||||
expected: ERROR
|
expected: ERROR
|
||||||
|
|
4
tests/wpt/metadata/xhr/send-timeout-events.htm.ini
Normal file
4
tests/wpt/metadata/xhr/send-timeout-events.htm.ini
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[send-timeout-events.htm]
|
||||||
|
expected:
|
||||||
|
if os == "linux": TIMEOUT
|
||||||
|
|
|
@ -3,15 +3,6 @@
|
||||||
[URLSearchParams request keeps setRequestHeader() Content-Type, with charset adjusted to UTF-8]
|
[URLSearchParams request keeps setRequestHeader() Content-Type, with charset adjusted to UTF-8]
|
||||||
expected: FAIL
|
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]
|
[String request keeps setRequestHeader() Content-Type, with charset adjusted to UTF-8]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -10988,7 +10988,7 @@
|
||||||
[]
|
[]
|
||||||
],
|
],
|
||||||
"interfaces.js": [
|
"interfaces.js": [
|
||||||
"f62df6b9c75332a2918b18ec70acfc5d7a795ac0",
|
"253dd124cebe9b7d3aa4fc5ff8c9137e7aeb2f66",
|
||||||
[]
|
[]
|
||||||
],
|
],
|
||||||
"nested_asap_script.js": [
|
"nested_asap_script.js": [
|
||||||
|
@ -13924,7 +13924,7 @@
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"interfaces.worker.js": [
|
"interfaces.worker.js": [
|
||||||
"c1223084790b2980c8184e3cd9ab5ae17bc8b303",
|
"59d4aa1855fbf281736b28581fbc519a847c8a0d",
|
||||||
[
|
[
|
||||||
"mozilla/interfaces.worker.html",
|
"mozilla/interfaces.worker.html",
|
||||||
{}
|
{}
|
||||||
|
|
|
@ -11,6 +11,8 @@ function test_interfaces(interfaceNamesInGlobalScope) {
|
||||||
"BigUint64Array",
|
"BigUint64Array",
|
||||||
"Boolean",
|
"Boolean",
|
||||||
"BroadcastChannel",
|
"BroadcastChannel",
|
||||||
|
"ByteLengthQueuingStrategy",
|
||||||
|
"CountQueuingStrategy",
|
||||||
"Crypto",
|
"Crypto",
|
||||||
"DataView",
|
"DataView",
|
||||||
"Date",
|
"Date",
|
||||||
|
@ -36,6 +38,7 @@ function test_interfaces(interfaceNamesInGlobalScope) {
|
||||||
"Promise",
|
"Promise",
|
||||||
"Proxy",
|
"Proxy",
|
||||||
"RangeError",
|
"RangeError",
|
||||||
|
"ReadableStream",
|
||||||
"ReferenceError",
|
"ReferenceError",
|
||||||
"Reflect",
|
"Reflect",
|
||||||
"RegExp",
|
"RegExp",
|
||||||
|
|
|
@ -9,9 +9,11 @@ importScripts("interfaces.js");
|
||||||
test_interfaces([
|
test_interfaces([
|
||||||
"Blob",
|
"Blob",
|
||||||
"BroadcastChannel",
|
"BroadcastChannel",
|
||||||
|
"ByteLengthQueuingStrategy",
|
||||||
"CanvasGradient",
|
"CanvasGradient",
|
||||||
"CanvasPattern",
|
"CanvasPattern",
|
||||||
"CloseEvent",
|
"CloseEvent",
|
||||||
|
"CountQueuingStrategy",
|
||||||
"DOMMatrix",
|
"DOMMatrix",
|
||||||
"DOMMatrixReadOnly",
|
"DOMMatrixReadOnly",
|
||||||
"DOMPoint",
|
"DOMPoint",
|
||||||
|
@ -48,6 +50,7 @@ test_interfaces([
|
||||||
"PerformanceResourceTiming",
|
"PerformanceResourceTiming",
|
||||||
"ProgressEvent",
|
"ProgressEvent",
|
||||||
"PromiseRejectionEvent",
|
"PromiseRejectionEvent",
|
||||||
|
"ReadableStream",
|
||||||
"Request",
|
"Request",
|
||||||
"Response",
|
"Response",
|
||||||
"TextDecoder",
|
"TextDecoder",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue