mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00: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]]
|
||||
name = "mozjs"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/servo/rust-mozjs#dbb9bee06e0b0168ccae0619c5077e302669d2fb"
|
||||
source = "git+https://github.com/servo/rust-mozjs#28248e1d6658e92dd5ecb0866e53a97f043b9b38"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"lazy_static",
|
||||
|
|
|
@ -32,13 +32,17 @@ use http::header::{
|
|||
use http::{HeaderMap, Request as HyperRequest};
|
||||
use hyper::{Body, Client, Method, Response as HyperResponse, StatusCode};
|
||||
use hyper_serde::Serde;
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
use ipc_channel::router::ROUTER;
|
||||
use msg::constellation_msg::{HistoryStateId, PipelineId};
|
||||
use net_traits::pub_domains::reg_suffix;
|
||||
use net_traits::quality::{quality_to_value, Quality, QualityItem};
|
||||
use net_traits::request::Origin::Origin as SpecificOrigin;
|
||||
use net_traits::request::{is_cors_safelisted_method, is_cors_safelisted_request_header};
|
||||
use net_traits::request::{
|
||||
BodyChunkRequest, RedirectMode, Referrer, Request, RequestBuilder, RequestMode,
|
||||
};
|
||||
use net_traits::request::{CacheMode, CredentialsMode, Destination, Origin};
|
||||
use net_traits::request::{RedirectMode, Referrer, Request, RequestBuilder, RequestMode};
|
||||
use net_traits::request::{ResponseTainting, ServiceWorkersMode};
|
||||
use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType};
|
||||
use net_traits::{CookieSource, FetchMetadata, NetworkError, ReferrerPolicy};
|
||||
|
@ -52,11 +56,12 @@ use std::iter::FromIterator;
|
|||
use std::mem;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Condvar, Mutex, RwLock};
|
||||
use std::sync::{Arc as StdArc, Condvar, Mutex, RwLock};
|
||||
use std::time::{Duration, SystemTime};
|
||||
use time::{self, Tm};
|
||||
use tokio::prelude::{future, Future, Stream};
|
||||
use tokio::prelude::{future, Future, Sink, Stream};
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::sync::mpsc::channel;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref HANDLE: Mutex<Option<Runtime>> = Mutex::new(Some(Runtime::new().unwrap()));
|
||||
|
@ -400,8 +405,10 @@ fn obtain_response(
|
|||
client: &Client<Connector, Body>,
|
||||
url: &ServoUrl,
|
||||
method: &Method,
|
||||
headers: &HeaderMap,
|
||||
data: &Option<Vec<u8>>,
|
||||
request_headers: &HeaderMap,
|
||||
body: Option<IpcSender<BodyChunkRequest>>,
|
||||
request_len: Option<usize>,
|
||||
load_data_method: &Method,
|
||||
pipeline_id: &Option<PipelineId>,
|
||||
request_id: Option<&str>,
|
||||
is_xhr: bool,
|
||||
|
@ -412,7 +419,71 @@ fn obtain_response(
|
|||
Error = NetworkError,
|
||||
>,
|
||||
> {
|
||||
let request_body = data.as_ref().cloned().unwrap_or(vec![]);
|
||||
let mut headers = request_headers.clone();
|
||||
|
||||
let devtools_bytes = StdArc::new(Mutex::new(vec![]));
|
||||
|
||||
let request_body = match body {
|
||||
Some(chunk_requester) => {
|
||||
// TODO: If body is a stream, append `Transfer-Encoding`/`chunked`,
|
||||
// see step 4.2 of https://fetch.spec.whatwg.org/#concept-http-network-fetch
|
||||
|
||||
// Step 5.6 of https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch
|
||||
// If source is non-null,
|
||||
// set contentLengthValue to 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
|
||||
.timing
|
||||
|
@ -440,7 +511,7 @@ fn obtain_response(
|
|||
.replace("{", "%7B")
|
||||
.replace("}", "%7D"),
|
||||
)
|
||||
.body(request_body.clone().into());
|
||||
.body(Body::wrap_stream(request_body));
|
||||
|
||||
// TODO: We currently don't know when the handhhake before the connection is done
|
||||
// so our best bet would be to set `secure_connection_start` here when we are currently
|
||||
|
@ -488,7 +559,7 @@ fn obtain_response(
|
|||
closure_url,
|
||||
method.clone(),
|
||||
headers,
|
||||
Some(request_body.clone()),
|
||||
Some(devtools_bytes.lock().unwrap().clone()),
|
||||
pipeline_id,
|
||||
time::now(),
|
||||
connect_end - connect_start,
|
||||
|
@ -804,7 +875,7 @@ pub fn http_redirect_fetch(
|
|||
.status
|
||||
.as_ref()
|
||||
.map_or(true, |s| s.0 != StatusCode::SEE_OTHER) &&
|
||||
request.body.as_ref().map_or(false, |b| b.is_empty())
|
||||
request.body.as_ref().map_or(false, |b| b.source_is_null())
|
||||
{
|
||||
return Response::network_error(NetworkError::Internal("Request body is not done".into()));
|
||||
}
|
||||
|
@ -943,7 +1014,7 @@ fn http_network_or_cache_fetch(
|
|||
_ => None,
|
||||
},
|
||||
// Step 5.6
|
||||
Some(ref http_request_body) => Some(http_request_body.len() as u64),
|
||||
Some(ref http_request_body) => http_request_body.len().map(|size| size as u64),
|
||||
};
|
||||
|
||||
// Step 5.7
|
||||
|
@ -1460,7 +1531,7 @@ impl Drop for ResponseEndTimer {
|
|||
|
||||
/// [HTTP network fetch](https://fetch.spec.whatwg.org/#http-network-fetch)
|
||||
fn http_network_fetch(
|
||||
request: &Request,
|
||||
request: &mut Request,
|
||||
credentials_flag: bool,
|
||||
done_chan: &mut DoneChannel,
|
||||
context: &FetchContext,
|
||||
|
@ -1503,7 +1574,9 @@ fn http_network_fetch(
|
|||
&url,
|
||||
&request.method,
|
||||
&request.headers,
|
||||
&request.body,
|
||||
request.body.as_mut().and_then(|body| body.take_stream()),
|
||||
request.body.as_ref().and_then(|body| body.len()),
|
||||
&request.method,
|
||||
&request.pipeline_id,
|
||||
request_id.as_ref().map(Deref::deref),
|
||||
is_xhr,
|
||||
|
|
|
@ -24,13 +24,18 @@ use http::uri::Authority;
|
|||
use http::{Method, StatusCode};
|
||||
use hyper::body::Body;
|
||||
use hyper::{Request as HyperRequest, Response as HyperResponse};
|
||||
use ipc_channel::ipc;
|
||||
use ipc_channel::router::ROUTER;
|
||||
use msg::constellation_msg::TEST_PIPELINE_ID;
|
||||
use net::cookie::Cookie;
|
||||
use net::cookie_storage::CookieStorage;
|
||||
use net::http_loader::determine_request_referrer;
|
||||
use net::resource_thread::AuthCacheEntry;
|
||||
use net::test::replace_host_table;
|
||||
use net_traits::request::{CredentialsMode, Destination, RequestBuilder, RequestMode};
|
||||
use net_traits::request::{
|
||||
BodyChunkRequest, BodySource, CredentialsMode, Destination, RequestBody, RequestBuilder,
|
||||
RequestMode,
|
||||
};
|
||||
use net_traits::response::{HttpsState, ResponseBody};
|
||||
use net_traits::{CookieSource, NetworkError, ReferrerPolicy};
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
|
@ -94,6 +99,27 @@ pub fn expect_devtools_http_response(
|
|||
}
|
||||
}
|
||||
|
||||
fn create_request_body_with_content(content: Vec<u8>) -> RequestBody {
|
||||
let content_len = content.len();
|
||||
|
||||
let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
|
||||
ROUTER.add_route(
|
||||
chunk_request_receiver.to_opaque(),
|
||||
Box::new(move |message| {
|
||||
let request = message.to().unwrap();
|
||||
if let BodyChunkRequest::Connect(sender) = request {
|
||||
let _ = sender.send(content.clone());
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
RequestBody {
|
||||
stream: Some(chunk_request_sender),
|
||||
source: BodySource::USVString,
|
||||
total_bytes: Some(content_len),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_default_headers_loaded_in_every_request() {
|
||||
let expected_headers = Arc::new(Mutex::new(None));
|
||||
|
@ -276,7 +302,7 @@ fn test_request_and_response_data_with_network_messages() {
|
|||
url: url,
|
||||
method: Method::GET,
|
||||
headers: headers,
|
||||
body: Some(b"".to_vec()),
|
||||
body: Some(vec![]),
|
||||
pipeline_id: TEST_PIPELINE_ID,
|
||||
startedDateTime: devhttprequest.startedDateTime,
|
||||
timeStamp: devhttprequest.timeStamp,
|
||||
|
@ -526,8 +552,11 @@ fn test_load_doesnt_send_request_body_on_any_redirect() {
|
|||
};
|
||||
let (pre_server, pre_url) = make_server(pre_handler);
|
||||
|
||||
let content = b"Body on POST!";
|
||||
let request_body = create_request_body_with_content(content.to_vec());
|
||||
|
||||
let mut request = RequestBuilder::new(pre_url.clone())
|
||||
.body(Some(b"Body on POST!".to_vec()))
|
||||
.body(Some(request_body))
|
||||
.method(Method::POST)
|
||||
.destination(Destination::Document)
|
||||
.origin(mock_origin())
|
||||
|
@ -819,9 +848,11 @@ fn test_load_sets_content_length_to_length_of_request_body() {
|
|||
};
|
||||
let (server, url) = make_server(handler);
|
||||
|
||||
let request_body = create_request_body_with_content(content.to_vec());
|
||||
|
||||
let mut request = RequestBuilder::new(url.clone())
|
||||
.method(Method::POST)
|
||||
.body(Some(content.to_vec()))
|
||||
.body(Some(request_body))
|
||||
.destination(Destination::Document)
|
||||
.origin(mock_origin())
|
||||
.pipeline_id(Some(TEST_PIPELINE_ID))
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::ResourceTimingType;
|
|||
use content_security_policy::{self as csp, CspList};
|
||||
use http::HeaderMap;
|
||||
use hyper::Method;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use mime::Mime;
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
|
@ -115,6 +116,57 @@ pub enum ParserMetadata {
|
|||
NotParserInserted,
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#concept-body-source>
|
||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub enum BodySource {
|
||||
Null,
|
||||
Blob,
|
||||
BufferSource,
|
||||
FormData,
|
||||
URLSearchParams,
|
||||
USVString,
|
||||
}
|
||||
|
||||
/// Messages used to implement <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum BodyChunkRequest {
|
||||
/// Connect a fetch in `net`, with a stream of bytes from `script`.
|
||||
Connect(IpcSender<Vec<u8>>),
|
||||
/// Ask for another chunk.
|
||||
Chunk,
|
||||
/// Signal the stream is done.
|
||||
Done,
|
||||
}
|
||||
|
||||
/// The net component's view into <https://fetch.spec.whatwg.org/#bodies>
|
||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub struct RequestBody {
|
||||
/// Net's view into a <https://fetch.spec.whatwg.org/#concept-body-stream>
|
||||
#[ignore_malloc_size_of = "Channels are hard"]
|
||||
pub stream: Option<IpcSender<BodyChunkRequest>>,
|
||||
/// <https://fetch.spec.whatwg.org/#concept-body-source>
|
||||
pub source: BodySource,
|
||||
/// <https://fetch.spec.whatwg.org/#concept-body-total-bytes>
|
||||
pub total_bytes: Option<usize>,
|
||||
}
|
||||
|
||||
impl RequestBody {
|
||||
pub fn take_stream(&mut self) -> Option<IpcSender<BodyChunkRequest>> {
|
||||
self.stream.take()
|
||||
}
|
||||
|
||||
pub fn source_is_null(&self) -> bool {
|
||||
if let BodySource::Null = self.source {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn len(&self) -> Option<usize> {
|
||||
self.total_bytes.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub struct RequestBuilder {
|
||||
#[serde(
|
||||
|
@ -131,7 +183,7 @@ pub struct RequestBuilder {
|
|||
#[ignore_malloc_size_of = "Defined in hyper"]
|
||||
pub headers: HeaderMap,
|
||||
pub unsafe_request: bool,
|
||||
pub body: Option<Vec<u8>>,
|
||||
pub body: Option<RequestBody>,
|
||||
pub service_workers_mode: ServiceWorkersMode,
|
||||
// TODO: client object
|
||||
pub destination: Destination,
|
||||
|
@ -210,7 +262,7 @@ impl RequestBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn body(mut self, body: Option<Vec<u8>>) -> RequestBuilder {
|
||||
pub fn body(mut self, body: Option<RequestBody>) -> RequestBuilder {
|
||||
self.body = body;
|
||||
self
|
||||
}
|
||||
|
@ -338,7 +390,7 @@ pub struct Request {
|
|||
/// <https://fetch.spec.whatwg.org/#unsafe-request-flag>
|
||||
pub unsafe_request: bool,
|
||||
/// <https://fetch.spec.whatwg.org/#concept-request-body>
|
||||
pub body: Option<Vec<u8>>,
|
||||
pub body: Option<RequestBody>,
|
||||
// TODO: client object
|
||||
pub window: Window,
|
||||
// TODO: target browsing context
|
||||
|
|
|
@ -2,19 +2,34 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::dom::bindings::cell::Ref;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobBinding::BlobMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::DomObject;
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::bindings::str::USVString;
|
||||
use crate::dom::bindings::settings_stack::AutoIncumbentScript;
|
||||
use crate::dom::bindings::str::{DOMString, USVString};
|
||||
use crate::dom::bindings::trace::RootedTraceableBox;
|
||||
use crate::dom::blob::{normalize_type_string, Blob};
|
||||
use crate::dom::formdata::FormData;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::htmlformelement::{encode_multipart_form_data, generate_boundary};
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::realms::{AlreadyInRealm, InRealm};
|
||||
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
|
||||
use crate::dom::readablestream::{get_read_promise_bytes, get_read_promise_done, ReadableStream};
|
||||
use crate::dom::urlsearchparams::URLSearchParams;
|
||||
use crate::realms::{enter_realm, AlreadyInRealm, InRealm};
|
||||
use crate::script_runtime::JSContext;
|
||||
use crate::task::TaskCanceller;
|
||||
use crate::task_source::networking::NetworkingTaskSource;
|
||||
use crate::task_source::TaskSource;
|
||||
use crate::task_source::TaskSourceName;
|
||||
use encoding_rs::UTF_8;
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
use ipc_channel::router::ROUTER;
|
||||
use js::jsapi::Heap;
|
||||
use js::jsapi::JSObject;
|
||||
use js::jsapi::JS_ClearPendingException;
|
||||
|
@ -23,14 +38,394 @@ use js::jsval::JSVal;
|
|||
use js::jsval::UndefinedValue;
|
||||
use js::rust::wrappers::JS_GetPendingException;
|
||||
use js::rust::wrappers::JS_ParseJSON;
|
||||
use js::rust::HandleValue;
|
||||
use js::typedarray::{ArrayBuffer, CreateWith};
|
||||
use mime::{self, Mime};
|
||||
use net_traits::request::{BodyChunkRequest, BodySource, RequestBody};
|
||||
use script_traits::serializable::BlobImpl;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
use std::str;
|
||||
use url::form_urlencoded;
|
||||
|
||||
/// The IPC route handler
|
||||
/// for <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
|
||||
/// This route runs in the script process,
|
||||
/// and will queue tasks to perform operations
|
||||
/// on the stream and transmit body chunks over IPC.
|
||||
struct TransmitBodyConnectHandler {
|
||||
stream: Trusted<ReadableStream>,
|
||||
task_source: NetworkingTaskSource,
|
||||
canceller: TaskCanceller,
|
||||
bytes_sender: Option<IpcSender<Vec<u8>>>,
|
||||
control_sender: IpcSender<BodyChunkRequest>,
|
||||
}
|
||||
|
||||
impl TransmitBodyConnectHandler {
|
||||
pub fn new(
|
||||
stream: Trusted<ReadableStream>,
|
||||
task_source: NetworkingTaskSource,
|
||||
canceller: TaskCanceller,
|
||||
control_sender: IpcSender<BodyChunkRequest>,
|
||||
) -> TransmitBodyConnectHandler {
|
||||
TransmitBodyConnectHandler {
|
||||
stream: stream,
|
||||
task_source,
|
||||
canceller,
|
||||
bytes_sender: None,
|
||||
control_sender,
|
||||
}
|
||||
}
|
||||
|
||||
/// Take the IPC sender sent by `net`, so we can send body chunks with it.
|
||||
pub fn start_reading(&mut self, sender: IpcSender<Vec<u8>>) {
|
||||
self.bytes_sender = Some(sender);
|
||||
}
|
||||
|
||||
/// Drop the IPC sender sent by `net`
|
||||
pub fn stop_reading(&mut self) {
|
||||
// Note: this should close the corresponding receiver,
|
||||
// and terminate the request stream in `net`.
|
||||
self.bytes_sender = None;
|
||||
}
|
||||
|
||||
/// The entry point to <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
|
||||
pub fn transmit_body_chunk(&mut self) {
|
||||
let stream = self.stream.clone();
|
||||
let control_sender = self.control_sender.clone();
|
||||
let bytes_sender = self
|
||||
.bytes_sender
|
||||
.clone()
|
||||
.expect("No bytes sender to transmit chunk.");
|
||||
|
||||
let _ = self.task_source.queue_with_canceller(
|
||||
task!(setup_native_body_promise_handler: move || {
|
||||
// Step 1, Let body be 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)]
|
||||
pub enum BodyType {
|
||||
Blob,
|
||||
|
@ -49,73 +444,212 @@ pub enum FetchedData {
|
|||
JSException(RootedTraceableBox<Heap<JSVal>>),
|
||||
}
|
||||
|
||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
struct ConsumeBodyPromiseRejectionHandler {
|
||||
#[ignore_malloc_size_of = "Rc are hard"]
|
||||
result_promise: Rc<Promise>,
|
||||
}
|
||||
|
||||
impl Callback for ConsumeBodyPromiseRejectionHandler {
|
||||
/// Continuing Step 4 of <https://fetch.spec.whatwg.org/#concept-body-consume-body>
|
||||
/// Step 3 of <https://fetch.spec.whatwg.org/#concept-read-all-bytes-from-readablestream>,
|
||||
// the rejection steps.
|
||||
fn callback(&self, cx: JSContext, v: HandleValue, _realm: InRealm) {
|
||||
self.result_promise.reject(cx, v);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
/// The promise handler used to consume the body,
|
||||
/// <https://fetch.spec.whatwg.org/#concept-body-consume-body>
|
||||
struct ConsumeBodyPromiseHandler {
|
||||
#[ignore_malloc_size_of = "Rc are hard"]
|
||||
result_promise: Rc<Promise>,
|
||||
stream: Option<DomRoot<ReadableStream>>,
|
||||
body_type: DomRefCell<Option<BodyType>>,
|
||||
mime_type: DomRefCell<Option<Vec<u8>>>,
|
||||
bytes: DomRefCell<Option<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl ConsumeBodyPromiseHandler {
|
||||
/// Step 5 of <https://fetch.spec.whatwg.org/#concept-body-consume-body>
|
||||
fn resolve_result_promise(&self, cx: JSContext) {
|
||||
let body_type = self.body_type.borrow_mut().take().unwrap();
|
||||
let mime_type = self.mime_type.borrow_mut().take().unwrap();
|
||||
let body = self.bytes.borrow_mut().take().unwrap();
|
||||
|
||||
let pkg_data_results = run_package_data_algorithm(cx, body, body_type, mime_type);
|
||||
|
||||
match pkg_data_results {
|
||||
Ok(results) => {
|
||||
match results {
|
||||
FetchedData::Text(s) => self.result_promise.resolve_native(&USVString(s)),
|
||||
FetchedData::Json(j) => self.result_promise.resolve_native(&j),
|
||||
FetchedData::BlobData(b) => self.result_promise.resolve_native(&b),
|
||||
FetchedData::FormData(f) => self.result_promise.resolve_native(&f),
|
||||
FetchedData::ArrayBuffer(a) => self.result_promise.resolve_native(&a),
|
||||
FetchedData::JSException(e) => self.result_promise.reject_native(&e.handle()),
|
||||
};
|
||||
},
|
||||
Err(err) => self.result_promise.reject_error(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Callback for ConsumeBodyPromiseHandler {
|
||||
/// Continuing Step 4 of <https://fetch.spec.whatwg.org/#concept-body-consume-body>
|
||||
/// Step 3 of <https://fetch.spec.whatwg.org/#concept-read-all-bytes-from-readablestream>.
|
||||
fn callback(&self, cx: JSContext, v: HandleValue, _realm: InRealm) {
|
||||
let stream = self
|
||||
.stream
|
||||
.as_ref()
|
||||
.expect("ConsumeBodyPromiseHandler has no stream in callback.");
|
||||
|
||||
let is_done = match get_read_promise_done(cx.clone(), &v) {
|
||||
Ok(is_done) => is_done,
|
||||
Err(err) => {
|
||||
stream.stop_reading();
|
||||
// When read is fulfilled with a value that doesn't matches with neither of the above patterns.
|
||||
return self.result_promise.reject_error(err);
|
||||
},
|
||||
};
|
||||
|
||||
if is_done {
|
||||
// When read is fulfilled with an object whose done property is true.
|
||||
self.resolve_result_promise(cx.clone());
|
||||
} else {
|
||||
let chunk = match get_read_promise_bytes(cx.clone(), &v) {
|
||||
Ok(chunk) => chunk,
|
||||
Err(err) => {
|
||||
stream.stop_reading();
|
||||
// When read is fulfilled with a value that matches with neither of the above patterns
|
||||
return self.result_promise.reject_error(err);
|
||||
},
|
||||
};
|
||||
|
||||
let mut bytes = self
|
||||
.bytes
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.expect("No bytes for ConsumeBodyPromiseHandler.");
|
||||
|
||||
// Append the value property to bytes.
|
||||
bytes.extend_from_slice(&*chunk);
|
||||
|
||||
let global = stream.global();
|
||||
|
||||
// Run the above step again.
|
||||
let read_promise = stream.read_a_chunk();
|
||||
|
||||
let promise_handler = Box::new(ConsumeBodyPromiseHandler {
|
||||
result_promise: self.result_promise.clone(),
|
||||
stream: self.stream.clone(),
|
||||
body_type: DomRefCell::new(self.body_type.borrow_mut().take()),
|
||||
mime_type: DomRefCell::new(self.mime_type.borrow_mut().take()),
|
||||
bytes: DomRefCell::new(Some(bytes)),
|
||||
});
|
||||
|
||||
let rejection_handler = Box::new(ConsumeBodyPromiseRejectionHandler {
|
||||
result_promise: self.result_promise.clone(),
|
||||
});
|
||||
|
||||
let handler =
|
||||
PromiseNativeHandler::new(&global, Some(promise_handler), Some(rejection_handler));
|
||||
|
||||
// Enter a realm, and a script,
|
||||
// before appending the native handler.
|
||||
let _realm = enter_realm(&*global);
|
||||
let _ais = AutoIncumbentScript::new(&*global);
|
||||
read_promise.append_native_handler(&handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-body-consume-body
|
||||
#[allow(unrooted_must_root)]
|
||||
pub fn consume_body<T: BodyOperations + DomObject>(object: &T, body_type: BodyType) -> Rc<Promise> {
|
||||
let in_realm_proof = AlreadyInRealm::assert(&object.global());
|
||||
pub fn consume_body<T: BodyMixin + DomObject>(object: &T, body_type: BodyType) -> Rc<Promise> {
|
||||
let global = object.global();
|
||||
let in_realm_proof = AlreadyInRealm::assert(&global);
|
||||
let promise =
|
||||
Promise::new_in_current_realm(&object.global(), InRealm::Already(&in_realm_proof));
|
||||
|
||||
// Step 1
|
||||
if object.get_body_used() || object.is_locked() {
|
||||
if object.is_disturbed() || object.is_locked() {
|
||||
promise.reject_error(Error::Type(
|
||||
"The response's stream is disturbed or locked".to_string(),
|
||||
"The body's stream is disturbed or locked".to_string(),
|
||||
));
|
||||
return promise;
|
||||
}
|
||||
|
||||
object.set_body_promise(&promise, body_type);
|
||||
|
||||
// Steps 2-4
|
||||
// TODO: Body does not yet have a stream.
|
||||
|
||||
consume_body_with_promise(object, body_type, &promise);
|
||||
consume_body_with_promise(object, body_type, promise.clone());
|
||||
|
||||
promise
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-body-consume-body
|
||||
#[allow(unrooted_must_root)]
|
||||
pub fn consume_body_with_promise<T: BodyOperations + DomObject>(
|
||||
fn consume_body_with_promise<T: BodyMixin + DomObject>(
|
||||
object: &T,
|
||||
body_type: BodyType,
|
||||
promise: &Promise,
|
||||
promise: Rc<Promise>,
|
||||
) {
|
||||
// Step 5
|
||||
let body = match object.take_body() {
|
||||
Some(body) => body,
|
||||
None => return,
|
||||
let global = object.global();
|
||||
|
||||
// Step 2.
|
||||
let stream = match object.body() {
|
||||
Some(stream) => stream,
|
||||
None => {
|
||||
let stream = ReadableStream::new_from_bytes(&global, Vec::with_capacity(0));
|
||||
stream
|
||||
},
|
||||
};
|
||||
|
||||
let pkg_data_results =
|
||||
run_package_data_algorithm(object, body, body_type, object.get_mime_type());
|
||||
|
||||
match pkg_data_results {
|
||||
Ok(results) => {
|
||||
match results {
|
||||
FetchedData::Text(s) => promise.resolve_native(&USVString(s)),
|
||||
FetchedData::Json(j) => promise.resolve_native(&j),
|
||||
FetchedData::BlobData(b) => promise.resolve_native(&b),
|
||||
FetchedData::FormData(f) => promise.resolve_native(&f),
|
||||
FetchedData::ArrayBuffer(a) => promise.resolve_native(&a),
|
||||
FetchedData::JSException(e) => promise.reject_native(&e.handle()),
|
||||
};
|
||||
},
|
||||
Err(err) => promise.reject_error(err),
|
||||
// Step 3.
|
||||
if stream.start_reading().is_err() {
|
||||
return promise.reject_error(Error::Type(
|
||||
"The response's stream is disturbed or locked".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Step 4, read all the bytes.
|
||||
// Starts here, continues in the promise handler.
|
||||
|
||||
// Step 1 of
|
||||
// https://fetch.spec.whatwg.org/#concept-read-all-bytes-from-readablestream
|
||||
let read_promise = stream.read_a_chunk();
|
||||
|
||||
let promise_handler = Box::new(ConsumeBodyPromiseHandler {
|
||||
result_promise: promise.clone(),
|
||||
stream: Some(stream),
|
||||
body_type: DomRefCell::new(Some(body_type)),
|
||||
mime_type: DomRefCell::new(Some(object.get_mime_type())),
|
||||
// Step 2.
|
||||
bytes: DomRefCell::new(Some(vec![])),
|
||||
});
|
||||
|
||||
let rejection_handler = Box::new(ConsumeBodyPromiseRejectionHandler {
|
||||
result_promise: promise,
|
||||
});
|
||||
|
||||
let handler = PromiseNativeHandler::new(
|
||||
&object.global(),
|
||||
Some(promise_handler),
|
||||
Some(rejection_handler),
|
||||
);
|
||||
// We are already in a realm and a script.
|
||||
read_promise.append_native_handler(&handler);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-body-package-data
|
||||
#[allow(unsafe_code)]
|
||||
fn run_package_data_algorithm<T: BodyOperations + DomObject>(
|
||||
object: &T,
|
||||
fn run_package_data_algorithm(
|
||||
cx: JSContext,
|
||||
bytes: Vec<u8>,
|
||||
body_type: BodyType,
|
||||
mime_type: Ref<Vec<u8>>,
|
||||
mime_type: Vec<u8>,
|
||||
) -> Fallible<FetchedData> {
|
||||
let global = object.global();
|
||||
let cx = global.get_cx();
|
||||
let mime = &*mime_type;
|
||||
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
||||
let global = GlobalScope::from_safe_context(cx, InRealm::Already(&in_realm_proof));
|
||||
match body_type {
|
||||
BodyType::Text => run_text_data_algorithm(bytes),
|
||||
BodyType::Json => run_json_data_algorithm(cx, bytes),
|
||||
|
@ -218,12 +752,14 @@ pub fn run_array_buffer_data_algorithm(cx: JSContext, bytes: Vec<u8>) -> Fallibl
|
|||
Ok(FetchedData::ArrayBuffer(rooted_heap))
|
||||
}
|
||||
|
||||
pub trait BodyOperations {
|
||||
fn get_body_used(&self) -> bool;
|
||||
fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType);
|
||||
/// Returns `Some(_)` if the body is complete, `None` if there is more to
|
||||
/// come.
|
||||
fn take_body(&self) -> Option<Vec<u8>>;
|
||||
/// <https://fetch.spec.whatwg.org/#body>
|
||||
pub trait BodyMixin {
|
||||
/// <https://fetch.spec.whatwg.org/#concept-body-disturbed>
|
||||
fn is_disturbed(&self) -> bool;
|
||||
/// <https://fetch.spec.whatwg.org/#dom-body-body>
|
||||
fn body(&self) -> Option<DomRoot<ReadableStream>>;
|
||||
/// <https://fetch.spec.whatwg.org/#concept-body-locked>
|
||||
fn is_locked(&self) -> bool;
|
||||
fn get_mime_type(&self) -> Ref<Vec<u8>>;
|
||||
/// <https://fetch.spec.whatwg.org/#concept-body-mime-type>
|
||||
fn get_mime_type(&self) -> Vec<u8>;
|
||||
}
|
||||
|
|
|
@ -906,6 +906,40 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
|
|||
|
||||
return handleOptional(templateBody, declType, handleDefault("None"))
|
||||
|
||||
if type.isReadableStream():
|
||||
assert not isEnforceRange and not isClamp
|
||||
|
||||
if failureCode is None:
|
||||
unwrapFailureCode = '''throw_type_error(*cx, "This object is not \
|
||||
an instance of ReadableStream.");\n'''
|
||||
else:
|
||||
unwrapFailureCode = failureCode
|
||||
|
||||
templateBody = fill(
|
||||
"""
|
||||
{
|
||||
use crate::realms::{AlreadyInRealm, InRealm};
|
||||
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
||||
match ReadableStream::from_js(cx, $${val}.get().to_object(), InRealm::Already(&in_realm_proof)) {
|
||||
Ok(val) => val,
|
||||
Err(()) => {
|
||||
$*{failureCode}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
""",
|
||||
failureCode=unwrapFailureCode + "\n",
|
||||
)
|
||||
|
||||
templateBody = wrapObjectTemplate(templateBody, "None",
|
||||
isDefinitelyObject, type, failureCode)
|
||||
|
||||
declType = CGGeneric("DomRoot<ReadableStream>")
|
||||
|
||||
return handleOptional(templateBody, declType,
|
||||
handleDefault("None"))
|
||||
|
||||
elif type.isSpiderMonkeyInterface():
|
||||
raise TypeError("Can't handle SpiderMonkey interface arguments other than typed arrays yet")
|
||||
|
||||
|
@ -4481,6 +4515,9 @@ def getUnionTypeTemplateVars(type, descriptorProvider):
|
|||
elif type.isObject():
|
||||
name = type.name
|
||||
typeName = "Heap<*mut JSObject>"
|
||||
elif type.isReadableStream():
|
||||
name = type.name
|
||||
typeName = "DomRoot<ReadableStream>"
|
||||
elif is_typed_array(type):
|
||||
name = type.name
|
||||
typeName = "typedarray::Heap" + name
|
||||
|
|
|
@ -138,6 +138,7 @@ pub unsafe fn create_global_object(
|
|||
let mut options = RealmOptions::default();
|
||||
options.creationOptions_.traceGlobal_ = Some(trace);
|
||||
options.creationOptions_.sharedMemoryAndAtomics_ = true;
|
||||
options.creationOptions_.streams_ = true;
|
||||
|
||||
rval.set(JS_NewGlobalObject(
|
||||
*cx,
|
||||
|
|
|
@ -14,14 +14,18 @@ use crate::dom::bindings::str::DOMString;
|
|||
use crate::dom::bindings::structuredclone::StructuredDataHolder;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::dom::readablestream::ReadableStream;
|
||||
use crate::realms::{AlreadyInRealm, InRealm};
|
||||
use crate::script_runtime::JSContext;
|
||||
use dom_struct::dom_struct;
|
||||
use encoding_rs::UTF_8;
|
||||
use js::jsapi::JSObject;
|
||||
use msg::constellation_msg::{BlobId, BlobIndex, PipelineNamespaceId};
|
||||
use net_traits::filemanager_thread::RelativePos;
|
||||
use script_traits::serializable::BlobImpl;
|
||||
use std::collections::HashMap;
|
||||
use std::num::NonZeroU32;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -34,13 +38,7 @@ pub struct Blob {
|
|||
|
||||
impl Blob {
|
||||
pub fn new(global: &GlobalScope, blob_impl: BlobImpl) -> DomRoot<Blob> {
|
||||
let dom_blob = reflect_dom_object(
|
||||
Box::new(Blob {
|
||||
reflector_: Reflector::new(),
|
||||
blob_id: blob_impl.blob_id(),
|
||||
}),
|
||||
global,
|
||||
);
|
||||
let dom_blob = reflect_dom_object(Box::new(Blob::new_inherited(&blob_impl)), global);
|
||||
global.track_blob(&dom_blob, blob_impl);
|
||||
dom_blob
|
||||
}
|
||||
|
@ -89,6 +87,11 @@ impl Blob {
|
|||
pub fn get_blob_url_id(&self) -> Uuid {
|
||||
self.global().get_blob_url_id(&self.blob_id)
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/FileAPI/#blob-get-stream>
|
||||
pub fn get_stream(&self) -> DomRoot<ReadableStream> {
|
||||
self.global().get_blob_stream(&self.blob_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serializable for Blob {
|
||||
|
@ -213,6 +216,11 @@ impl BlobMethods for Blob {
|
|||
DOMString::from(self.type_string())
|
||||
}
|
||||
|
||||
// <https://w3c.github.io/FileAPI/#blob-get-stream>
|
||||
fn Stream(&self, _cx: JSContext) -> NonNull<JSObject> {
|
||||
self.get_stream().get_js_stream()
|
||||
}
|
||||
|
||||
// https://w3c.github.io/FileAPI/#slice-method-algo
|
||||
fn Slice(
|
||||
&self,
|
||||
|
|
|
@ -43,6 +43,7 @@ use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
|
|||
use crate::dom::performance::Performance;
|
||||
use crate::dom::performanceobserver::VALID_ENTRY_TYPES;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::dom::readablestream::{ExternalUnderlyingSource, ReadableStream};
|
||||
use crate::dom::serviceworker::ServiceWorker;
|
||||
use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
|
||||
use crate::dom::window::Window;
|
||||
|
@ -319,11 +320,19 @@ struct FileListener {
|
|||
task_canceller: TaskCanceller,
|
||||
}
|
||||
|
||||
struct FileListenerCallback(Box<dyn Fn(Rc<Promise>, Result<Vec<u8>, Error>) + Send>);
|
||||
enum FileListenerCallback {
|
||||
Promise(Box<dyn Fn(Rc<Promise>, Result<Vec<u8>, Error>) + Send>),
|
||||
Stream,
|
||||
}
|
||||
|
||||
enum FileListenerTarget {
|
||||
Promise(TrustedPromise),
|
||||
Stream(Trusted<ReadableStream>),
|
||||
}
|
||||
|
||||
enum FileListenerState {
|
||||
Empty(FileListenerCallback, TrustedPromise),
|
||||
Receiving(Vec<u8>, FileListenerCallback, TrustedPromise),
|
||||
Empty(FileListenerCallback, FileListenerTarget),
|
||||
Receiving(Vec<u8>, FileListenerCallback, FileListenerTarget),
|
||||
}
|
||||
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
|
@ -356,6 +365,14 @@ pub enum BlobState {
|
|||
UnManaged,
|
||||
}
|
||||
|
||||
/// The result of looking-up the data for a Blob,
|
||||
/// containing either the in-memory bytes,
|
||||
/// or the file-id.
|
||||
enum BlobResult {
|
||||
Bytes(Vec<u8>),
|
||||
File(Uuid, usize),
|
||||
}
|
||||
|
||||
/// Data representing a message-port managed by this global.
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
#[unrooted_must_root_lint::must_root]
|
||||
|
@ -532,57 +549,137 @@ impl MessageListener {
|
|||
}
|
||||
}
|
||||
|
||||
/// Callback used to enqueue file chunks to streams as part of FileListener.
|
||||
fn stream_handle_incoming(stream: &ReadableStream, bytes: Result<Vec<u8>, Error>) {
|
||||
match bytes {
|
||||
Ok(b) => {
|
||||
stream.enqueue_native(b);
|
||||
},
|
||||
Err(e) => {
|
||||
stream.error_native(e);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback used to close streams as part of FileListener.
|
||||
fn stream_handle_eof(stream: &ReadableStream) {
|
||||
stream.close_native();
|
||||
}
|
||||
|
||||
impl FileListener {
|
||||
fn handle(&mut self, msg: FileManagerResult<ReadFileProgress>) {
|
||||
match msg {
|
||||
Ok(ReadFileProgress::Meta(blob_buf)) => match self.state.take() {
|
||||
Some(FileListenerState::Empty(callback, promise)) => {
|
||||
self.state = Some(FileListenerState::Receiving(
|
||||
blob_buf.bytes,
|
||||
callback,
|
||||
promise,
|
||||
));
|
||||
Some(FileListenerState::Empty(callback, target)) => {
|
||||
let bytes = if let FileListenerTarget::Stream(ref trusted_stream) = target {
|
||||
let trusted = trusted_stream.clone();
|
||||
|
||||
let task = task!(enqueue_stream_chunk: move || {
|
||||
let stream = trusted.root();
|
||||
stream_handle_incoming(&*stream, Ok(blob_buf.bytes));
|
||||
});
|
||||
|
||||
let _ = self
|
||||
.task_source
|
||||
.queue_with_canceller(task, &self.task_canceller);
|
||||
Vec::with_capacity(0)
|
||||
} else {
|
||||
blob_buf.bytes
|
||||
};
|
||||
|
||||
self.state = Some(FileListenerState::Receiving(bytes, callback, target));
|
||||
},
|
||||
_ => panic!(
|
||||
"Unexpected FileListenerState when receiving ReadFileProgress::Meta msg."
|
||||
),
|
||||
},
|
||||
Ok(ReadFileProgress::Partial(mut bytes_in)) => match self.state.take() {
|
||||
Some(FileListenerState::Receiving(mut bytes, callback, promise)) => {
|
||||
bytes.append(&mut bytes_in);
|
||||
self.state = Some(FileListenerState::Receiving(bytes, callback, promise));
|
||||
Some(FileListenerState::Receiving(mut bytes, callback, target)) => {
|
||||
if let FileListenerTarget::Stream(ref trusted_stream) = target {
|
||||
let trusted = trusted_stream.clone();
|
||||
|
||||
let task = task!(enqueue_stream_chunk: move || {
|
||||
let stream = trusted.root();
|
||||
stream_handle_incoming(&*stream, Ok(bytes_in));
|
||||
});
|
||||
|
||||
let _ = self
|
||||
.task_source
|
||||
.queue_with_canceller(task, &self.task_canceller);
|
||||
} else {
|
||||
bytes.append(&mut bytes_in);
|
||||
};
|
||||
|
||||
self.state = Some(FileListenerState::Receiving(bytes, callback, target));
|
||||
},
|
||||
_ => panic!(
|
||||
"Unexpected FileListenerState when receiving ReadFileProgress::Partial msg."
|
||||
),
|
||||
},
|
||||
Ok(ReadFileProgress::EOF) => match self.state.take() {
|
||||
Some(FileListenerState::Receiving(bytes, callback, trusted_promise)) => {
|
||||
let _ = self.task_source.queue_with_canceller(
|
||||
task!(resolve_promise: move || {
|
||||
Some(FileListenerState::Receiving(bytes, callback, target)) => match target {
|
||||
FileListenerTarget::Promise(trusted_promise) => {
|
||||
let callback = match callback {
|
||||
FileListenerCallback::Promise(callback) => callback,
|
||||
_ => panic!("Expected promise callback."),
|
||||
};
|
||||
let task = task!(resolve_promise: move || {
|
||||
let promise = trusted_promise.root();
|
||||
let _ac = enter_realm(&*promise.global());
|
||||
callback.0(promise, Ok(bytes));
|
||||
}),
|
||||
&self.task_canceller,
|
||||
);
|
||||
callback(promise, Ok(bytes));
|
||||
});
|
||||
|
||||
let _ = self
|
||||
.task_source
|
||||
.queue_with_canceller(task, &self.task_canceller);
|
||||
},
|
||||
FileListenerTarget::Stream(trusted_stream) => {
|
||||
let trusted = trusted_stream.clone();
|
||||
|
||||
let task = task!(enqueue_stream_chunk: move || {
|
||||
let stream = trusted.root();
|
||||
stream_handle_eof(&*stream);
|
||||
});
|
||||
|
||||
let _ = self
|
||||
.task_source
|
||||
.queue_with_canceller(task, &self.task_canceller);
|
||||
},
|
||||
},
|
||||
_ => {
|
||||
panic!("Unexpected FileListenerState when receiving ReadFileProgress::EOF msg.")
|
||||
},
|
||||
},
|
||||
Err(_) => match self.state.take() {
|
||||
Some(FileListenerState::Receiving(_, callback, trusted_promise)) |
|
||||
Some(FileListenerState::Empty(callback, trusted_promise)) => {
|
||||
let bytes = Err(Error::Network);
|
||||
let _ = self.task_source.queue_with_canceller(
|
||||
task!(reject_promise: move || {
|
||||
let promise = trusted_promise.root();
|
||||
let _ac = enter_realm(&*promise.global());
|
||||
callback.0(promise, bytes);
|
||||
}),
|
||||
&self.task_canceller,
|
||||
);
|
||||
Some(FileListenerState::Receiving(_, callback, target)) |
|
||||
Some(FileListenerState::Empty(callback, target)) => {
|
||||
let error = Err(Error::Network);
|
||||
|
||||
match target {
|
||||
FileListenerTarget::Promise(trusted_promise) => {
|
||||
let callback = match callback {
|
||||
FileListenerCallback::Promise(callback) => callback,
|
||||
_ => panic!("Expected promise callback."),
|
||||
};
|
||||
let _ = self.task_source.queue_with_canceller(
|
||||
task!(reject_promise: move || {
|
||||
let promise = trusted_promise.root();
|
||||
let _ac = enter_realm(&*promise.global());
|
||||
callback(promise, error);
|
||||
}),
|
||||
&self.task_canceller,
|
||||
);
|
||||
},
|
||||
FileListenerTarget::Stream(trusted_stream) => {
|
||||
let _ = self.task_source.queue_with_canceller(
|
||||
task!(error_stream: move || {
|
||||
let stream = trusted_stream.root();
|
||||
stream_handle_incoming(&*stream, error);
|
||||
}),
|
||||
&self.task_canceller,
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
_ => panic!("Unexpected FileListenerState when receiving Err msg."),
|
||||
},
|
||||
|
@ -1565,6 +1662,70 @@ impl GlobalScope {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get a slice to the inner data of a Blob,
|
||||
/// if it's a memory blob, or it's file-id and file-size otherwise.
|
||||
///
|
||||
/// Note: this is almost a duplicate of `get_blob_bytes`,
|
||||
/// tweaked for integration with streams.
|
||||
/// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams.
|
||||
fn get_blob_bytes_or_file_id(&self, blob_id: &BlobId) -> BlobResult {
|
||||
let parent = {
|
||||
let blob_state = self.blob_state.borrow();
|
||||
if let BlobState::Managed(blobs_map) = &*blob_state {
|
||||
let blob_info = blobs_map
|
||||
.get(blob_id)
|
||||
.expect("get_blob_bytes_or_file_id for an unknown blob.");
|
||||
match blob_info.blob_impl.blob_data() {
|
||||
BlobData::Sliced(ref parent, ref rel_pos) => {
|
||||
Some((parent.clone(), rel_pos.clone()))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
panic!("get_blob_bytes_or_file_id called on a global not managing any blobs.");
|
||||
}
|
||||
};
|
||||
|
||||
match parent {
|
||||
Some((parent_id, rel_pos)) => {
|
||||
match self.get_blob_bytes_non_sliced_or_file_id(&parent_id) {
|
||||
BlobResult::Bytes(bytes) => {
|
||||
let range = rel_pos.to_abs_range(bytes.len());
|
||||
BlobResult::Bytes(bytes.index(range).to_vec())
|
||||
},
|
||||
res => res,
|
||||
}
|
||||
},
|
||||
None => self.get_blob_bytes_non_sliced_or_file_id(blob_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get bytes from a non-sliced blob if in memory, or it's file-id and file-size.
|
||||
///
|
||||
/// Note: this is almost a duplicate of `get_blob_bytes_non_sliced`,
|
||||
/// tweaked for integration with streams.
|
||||
/// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams.
|
||||
fn get_blob_bytes_non_sliced_or_file_id(&self, blob_id: &BlobId) -> BlobResult {
|
||||
let blob_state = self.blob_state.borrow();
|
||||
if let BlobState::Managed(blobs_map) = &*blob_state {
|
||||
let blob_info = blobs_map
|
||||
.get(blob_id)
|
||||
.expect("get_blob_bytes_non_sliced_or_file_id called for a unknown blob.");
|
||||
match blob_info.blob_impl.blob_data() {
|
||||
BlobData::File(ref f) => match f.get_cache() {
|
||||
Some(bytes) => BlobResult::Bytes(bytes.clone()),
|
||||
None => BlobResult::File(f.get_id(), f.get_size() as usize),
|
||||
},
|
||||
BlobData::Memory(ref s) => BlobResult::Bytes(s.clone()),
|
||||
BlobData::Sliced(_, _) => panic!("This blob doesn't have a parent."),
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"get_blob_bytes_non_sliced_or_file_id called on a global not managing any blobs."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a copy of the type_string of a blob.
|
||||
pub fn get_blob_type_string(&self, blob_id: &BlobId) -> String {
|
||||
let blob_state = self.blob_state.borrow();
|
||||
|
@ -1769,6 +1930,50 @@ impl GlobalScope {
|
|||
GlobalScope::read_msg(recv)
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/FileAPI/#blob-get-stream>
|
||||
pub fn get_blob_stream(&self, blob_id: &BlobId) -> DomRoot<ReadableStream> {
|
||||
let (file_id, size) = match self.get_blob_bytes_or_file_id(blob_id) {
|
||||
BlobResult::Bytes(bytes) => {
|
||||
// If we have all the bytes in memory, queue them and close the stream.
|
||||
let stream = ReadableStream::new_from_bytes(self, bytes);
|
||||
return stream;
|
||||
},
|
||||
BlobResult::File(id, size) => (id, size),
|
||||
};
|
||||
|
||||
let stream = ReadableStream::new_with_external_underlying_source(
|
||||
self,
|
||||
ExternalUnderlyingSource::Blob(size as usize),
|
||||
);
|
||||
|
||||
let recv = self.send_msg(file_id);
|
||||
|
||||
let trusted_stream = Trusted::new(&*stream.clone());
|
||||
let task_canceller = self.task_canceller(TaskSourceName::FileReading);
|
||||
let task_source = self.file_reading_task_source();
|
||||
|
||||
let mut file_listener = FileListener {
|
||||
state: Some(FileListenerState::Empty(
|
||||
FileListenerCallback::Stream,
|
||||
FileListenerTarget::Stream(trusted_stream),
|
||||
)),
|
||||
task_source,
|
||||
task_canceller,
|
||||
};
|
||||
|
||||
ROUTER.add_route(
|
||||
recv.to_opaque(),
|
||||
Box::new(move |msg| {
|
||||
file_listener.handle(
|
||||
msg.to()
|
||||
.expect("Deserialization of file listener msg failed."),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
stream
|
||||
}
|
||||
|
||||
pub fn read_file_async(
|
||||
&self,
|
||||
id: Uuid,
|
||||
|
@ -1783,8 +1988,8 @@ impl GlobalScope {
|
|||
|
||||
let mut file_listener = FileListener {
|
||||
state: Some(FileListenerState::Empty(
|
||||
FileListenerCallback(callback),
|
||||
trusted_promise,
|
||||
FileListenerCallback::Promise(callback),
|
||||
FileListenerTarget::Promise(trusted_promise),
|
||||
)),
|
||||
task_source,
|
||||
task_canceller,
|
||||
|
@ -1894,6 +2099,12 @@ impl GlobalScope {
|
|||
global_scope_from_global(global, cx)
|
||||
}
|
||||
|
||||
/// Returns the global scope for the given SafeJSContext
|
||||
#[allow(unsafe_code)]
|
||||
pub fn from_safe_context(cx: SafeJSContext, realm: InRealm) -> DomRoot<Self> {
|
||||
unsafe { Self::from_context(*cx, realm) }
|
||||
}
|
||||
|
||||
/// Returns the global object of the realm that the given JS object
|
||||
/// was created in, after unwrapping any wrappers.
|
||||
#[allow(unsafe_code)]
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::body::Extractable;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrBinding::AttrMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
|
||||
|
@ -799,7 +800,15 @@ impl HTMLFormElement {
|
|||
},
|
||||
};
|
||||
|
||||
load_data.data = Some(bytes);
|
||||
let global = self.global();
|
||||
|
||||
let request_body = bytes
|
||||
.extract(&global)
|
||||
.expect("Couldn't extract body.")
|
||||
.into_net_request_body()
|
||||
.0;
|
||||
load_data.data = Some(request_body);
|
||||
|
||||
self.plan_to_navigate(load_data, target);
|
||||
}
|
||||
|
||||
|
|
|
@ -481,6 +481,7 @@ pub mod promiserejectionevent;
|
|||
pub mod radionodelist;
|
||||
pub mod range;
|
||||
pub mod raredata;
|
||||
pub mod readablestream;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod rtcicecandidate;
|
||||
|
|
|
@ -7,13 +7,14 @@ use crate::dom::bindings::root::DomRoot;
|
|||
use crate::dom::bindings::trace::JSTraceable;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::realms::InRealm;
|
||||
use crate::script_runtime::JSContext as SafeJSContext;
|
||||
use dom_struct::dom_struct;
|
||||
use js::jsapi::JSContext;
|
||||
use js::rust::HandleValue;
|
||||
use malloc_size_of::MallocSizeOf;
|
||||
|
||||
pub trait Callback: JSTraceable + MallocSizeOf {
|
||||
fn callback(&self, cx: *mut JSContext, v: HandleValue, realm: InRealm);
|
||||
fn callback(&self, cx: SafeJSContext, v: HandleValue, realm: InRealm);
|
||||
}
|
||||
|
||||
#[dom_struct]
|
||||
|
@ -39,12 +40,14 @@ impl PromiseNativeHandler {
|
|||
)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn callback(
|
||||
callback: &Option<Box<dyn Callback>>,
|
||||
cx: *mut JSContext,
|
||||
v: HandleValue,
|
||||
realm: InRealm,
|
||||
) {
|
||||
let cx = unsafe { SafeJSContext::from_ptr(cx) };
|
||||
if let Some(ref callback) = *callback {
|
||||
callback.callback(cx, v, realm)
|
||||
}
|
||||
|
|
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
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::body::{consume_body, BodyOperations, BodyType};
|
||||
use crate::dom::bindings::cell::{DomRefCell, Ref};
|
||||
use crate::body::Extractable;
|
||||
use crate::body::{consume_body, BodyMixin, BodyType};
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
|
||||
use crate::dom::bindings::codegen::Bindings::RequestBinding::ReferrerPolicy;
|
||||
use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestCache;
|
||||
|
@ -22,11 +23,13 @@ use crate::dom::bindings::trace::RootedTraceableBox;
|
|||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::headers::{Guard, Headers};
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::dom::xmlhttprequest::Extractable;
|
||||
use crate::dom::readablestream::ReadableStream;
|
||||
use crate::script_runtime::JSContext as SafeJSContext;
|
||||
use dom_struct::dom_struct;
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use http::method::InvalidMethod;
|
||||
use http::Method as HttpMethod;
|
||||
use js::jsapi::JSObject;
|
||||
use net_traits::request::CacheMode as NetTraitsRequestCache;
|
||||
use net_traits::request::CredentialsMode as NetTraitsRequestCredentials;
|
||||
use net_traits::request::Destination as NetTraitsRequestDestination;
|
||||
|
@ -37,7 +40,7 @@ use net_traits::request::RequestMode as NetTraitsRequestMode;
|
|||
use net_traits::request::{Origin, Window};
|
||||
use net_traits::ReferrerPolicy as MsgReferrerPolicy;
|
||||
use servo_url::ServoUrl;
|
||||
use std::cell::Cell;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -45,11 +48,9 @@ use std::str::FromStr;
|
|||
pub struct Request {
|
||||
reflector_: Reflector,
|
||||
request: DomRefCell<NetTraitsRequest>,
|
||||
body_used: Cell<bool>,
|
||||
body_stream: MutNullableDom<ReadableStream>,
|
||||
headers: MutNullableDom<Headers>,
|
||||
mime_type: DomRefCell<Vec<u8>>,
|
||||
#[ignore_malloc_size_of = "Rc"]
|
||||
body_promise: DomRefCell<Option<(Rc<Promise>, BodyType)>>,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
|
@ -57,10 +58,9 @@ impl Request {
|
|||
Request {
|
||||
reflector_: Reflector::new(),
|
||||
request: DomRefCell::new(net_request_from_global(global, url)),
|
||||
body_used: Cell::new(false),
|
||||
body_stream: MutNullableDom::new(None),
|
||||
headers: Default::default(),
|
||||
mime_type: DomRefCell::new("".to_string().into_bytes()),
|
||||
body_promise: DomRefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ impl Request {
|
|||
#[allow(non_snake_case)]
|
||||
pub fn Constructor(
|
||||
global: &GlobalScope,
|
||||
input: RequestInfo,
|
||||
mut input: RequestInfo,
|
||||
init: RootedTraceableBox<RequestInit>,
|
||||
) -> Fallible<DomRoot<Request>> {
|
||||
// Step 1
|
||||
|
@ -365,9 +365,9 @@ impl Request {
|
|||
r.request.borrow_mut().headers = r.Headers().get_headers_list();
|
||||
|
||||
// Step 33
|
||||
let mut input_body = if let RequestInfo::Request(ref input_request) = input {
|
||||
let input_request_request = input_request.request.borrow();
|
||||
input_request_request.body.clone()
|
||||
let mut input_body = if let RequestInfo::Request(ref mut input_request) = input {
|
||||
let mut input_request_request = input_request.request.borrow_mut();
|
||||
input_request_request.body.take()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -398,12 +398,10 @@ impl Request {
|
|||
// Step 36.2 TODO "If init["keepalive"] exists and is true..."
|
||||
|
||||
// Step 36.3
|
||||
let extracted_body_tmp = init_body.extract();
|
||||
input_body = Some(extracted_body_tmp.0);
|
||||
let content_type = extracted_body_tmp.1;
|
||||
let mut extracted_body = init_body.extract(global)?;
|
||||
|
||||
// Step 36.4
|
||||
if let Some(contents) = content_type {
|
||||
if let Some(contents) = extracted_body.content_type.take() {
|
||||
let ct_header_name = b"Content-Type";
|
||||
if !r
|
||||
.Headers()
|
||||
|
@ -427,6 +425,10 @@ impl Request {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (net_body, stream) = extracted_body.into_net_request_body();
|
||||
r.body_stream.set(Some(&*stream));
|
||||
input_body = Some(net_body);
|
||||
}
|
||||
|
||||
// Step 37 "TODO if body is non-null and body's source is null..."
|
||||
|
@ -448,13 +450,6 @@ impl Request {
|
|||
// Step 42
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-body-locked
|
||||
fn locked(&self) -> bool {
|
||||
// TODO: ReadableStream is unimplemented. Just return false
|
||||
// for now.
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Request {
|
||||
|
@ -467,7 +462,6 @@ impl Request {
|
|||
fn clone_from(r: &Request) -> Fallible<DomRoot<Request>> {
|
||||
let req = r.request.borrow();
|
||||
let url = req.url();
|
||||
let body_used = r.body_used.get();
|
||||
let mime_type = r.mime_type.borrow().clone();
|
||||
let headers_guard = r.Headers().get_guard();
|
||||
let r_clone = Request::new(&r.global(), url);
|
||||
|
@ -477,7 +471,6 @@ impl Request {
|
|||
borrowed_r_request.origin = req.origin.clone();
|
||||
}
|
||||
*r_clone.request.borrow_mut() = req.clone();
|
||||
r_clone.body_used.set(body_used);
|
||||
*r_clone.mime_type.borrow_mut() = mime_type;
|
||||
r_clone.Headers().copy_from_headers(r.Headers())?;
|
||||
r_clone.Headers().set_guard(headers_guard);
|
||||
|
@ -536,16 +529,14 @@ fn includes_credentials(input: &ServoUrl) -> bool {
|
|||
!input.username().is_empty() || input.password().is_some()
|
||||
}
|
||||
|
||||
// TODO: `Readable Stream` object is not implemented in Servo yet.
|
||||
// https://fetch.spec.whatwg.org/#concept-body-disturbed
|
||||
fn request_is_disturbed(_input: &Request) -> bool {
|
||||
false
|
||||
fn request_is_disturbed(input: &Request) -> bool {
|
||||
input.is_disturbed()
|
||||
}
|
||||
|
||||
// TODO: `Readable Stream` object is not implemented in Servo yet.
|
||||
// https://fetch.spec.whatwg.org/#concept-body-locked
|
||||
fn request_is_locked(_input: &Request) -> bool {
|
||||
false
|
||||
fn request_is_locked(input: &Request) -> bool {
|
||||
input.is_locked()
|
||||
}
|
||||
|
||||
impl RequestMethods for Request {
|
||||
|
@ -622,9 +613,14 @@ impl RequestMethods for Request {
|
|||
DOMString::from_string(r.integrity_metadata.clone())
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#dom-body-body>
|
||||
fn GetBody(&self, _cx: SafeJSContext) -> Option<NonNull<JSObject>> {
|
||||
self.body().map(|stream| stream.get_js_stream())
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-body-bodyused
|
||||
fn BodyUsed(&self) -> bool {
|
||||
self.body_used.get()
|
||||
self.is_disturbed()
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-request-clone
|
||||
|
@ -667,29 +663,25 @@ impl RequestMethods for Request {
|
|||
}
|
||||
}
|
||||
|
||||
impl BodyOperations for Request {
|
||||
fn get_body_used(&self) -> bool {
|
||||
self.BodyUsed()
|
||||
}
|
||||
|
||||
fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType) {
|
||||
assert!(self.body_promise.borrow().is_none());
|
||||
self.body_used.set(true);
|
||||
*self.body_promise.borrow_mut() = Some((p.clone(), body_type));
|
||||
impl BodyMixin for Request {
|
||||
fn is_disturbed(&self) -> bool {
|
||||
let body_stream = self.body_stream.get();
|
||||
body_stream
|
||||
.as_ref()
|
||||
.map_or(false, |stream| stream.is_disturbed())
|
||||
}
|
||||
|
||||
fn is_locked(&self) -> bool {
|
||||
self.locked()
|
||||
let body_stream = self.body_stream.get();
|
||||
body_stream.map_or(false, |stream| stream.is_locked())
|
||||
}
|
||||
|
||||
fn take_body(&self) -> Option<Vec<u8>> {
|
||||
let mut request = self.request.borrow_mut();
|
||||
let body = request.body.take();
|
||||
Some(body.unwrap_or(vec![]))
|
||||
fn body(&self) -> Option<DomRoot<ReadableStream>> {
|
||||
self.body_stream.get()
|
||||
}
|
||||
|
||||
fn get_mime_type(&self) -> Ref<Vec<u8>> {
|
||||
self.mime_type.borrow()
|
||||
fn get_mime_type(&self) -> Vec<u8> {
|
||||
self.mime_type.borrow().clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::body::{consume_body, consume_body_with_promise, BodyOperations, BodyType};
|
||||
use crate::dom::bindings::cell::{DomRefCell, Ref};
|
||||
use crate::body::{consume_body, BodyMixin, BodyType};
|
||||
use crate::body::{Extractable, ExtractedBody};
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::ResponseBinding;
|
||||
use crate::dom::bindings::codegen::Bindings::ResponseBinding::{
|
||||
|
@ -18,16 +19,16 @@ use crate::dom::globalscope::GlobalScope;
|
|||
use crate::dom::headers::{is_obs_text, is_vchar};
|
||||
use crate::dom::headers::{Guard, Headers};
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::dom::xmlhttprequest::Extractable;
|
||||
use crate::dom::readablestream::{ExternalUnderlyingSource, ReadableStream};
|
||||
use crate::script_runtime::JSContext as SafeJSContext;
|
||||
use crate::script_runtime::StreamConsumer;
|
||||
use dom_struct::dom_struct;
|
||||
use http::header::HeaderMap as HyperHeaders;
|
||||
use hyper::StatusCode;
|
||||
use hyper_serde::Serde;
|
||||
use net_traits::response::ResponseBody as NetTraitsResponseBody;
|
||||
use js::jsapi::JSObject;
|
||||
use servo_url::ServoUrl;
|
||||
use std::cell::Cell;
|
||||
use std::mem;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use url::Position;
|
||||
|
@ -37,7 +38,6 @@ pub struct Response {
|
|||
reflector_: Reflector,
|
||||
headers_reflector: MutNullableDom<Headers>,
|
||||
mime_type: DomRefCell<Vec<u8>>,
|
||||
body_used: Cell<bool>,
|
||||
/// `None` can be considered a StatusCode of `0`.
|
||||
#[ignore_malloc_size_of = "Defined in hyper"]
|
||||
status: DomRefCell<Option<StatusCode>>,
|
||||
|
@ -45,10 +45,8 @@ pub struct Response {
|
|||
response_type: DomRefCell<DOMResponseType>,
|
||||
url: DomRefCell<Option<ServoUrl>>,
|
||||
url_list: DomRefCell<Vec<ServoUrl>>,
|
||||
// For now use the existing NetTraitsResponseBody enum
|
||||
body: DomRefCell<NetTraitsResponseBody>,
|
||||
#[ignore_malloc_size_of = "Rc"]
|
||||
body_promise: DomRefCell<Option<(Rc<Promise>, BodyType)>>,
|
||||
/// The stream of https://fetch.spec.whatwg.org/#body.
|
||||
body_stream: MutNullableDom<ReadableStream>,
|
||||
#[ignore_malloc_size_of = "StreamConsumer"]
|
||||
stream_consumer: DomRefCell<Option<StreamConsumer>>,
|
||||
redirected: DomRefCell<bool>,
|
||||
|
@ -56,19 +54,21 @@ pub struct Response {
|
|||
|
||||
#[allow(non_snake_case)]
|
||||
impl Response {
|
||||
pub fn new_inherited() -> Response {
|
||||
pub fn new_inherited(global: &GlobalScope) -> Response {
|
||||
let stream = ReadableStream::new_with_external_underlying_source(
|
||||
global,
|
||||
ExternalUnderlyingSource::FetchResponse,
|
||||
);
|
||||
Response {
|
||||
reflector_: Reflector::new(),
|
||||
headers_reflector: Default::default(),
|
||||
mime_type: DomRefCell::new("".to_string().into_bytes()),
|
||||
body_used: Cell::new(false),
|
||||
status: DomRefCell::new(Some(StatusCode::OK)),
|
||||
raw_status: DomRefCell::new(Some((200, b"".to_vec()))),
|
||||
response_type: DomRefCell::new(DOMResponseType::Default),
|
||||
url: DomRefCell::new(None),
|
||||
url_list: DomRefCell::new(vec![]),
|
||||
body: DomRefCell::new(NetTraitsResponseBody::Empty),
|
||||
body_promise: DomRefCell::new(None),
|
||||
body_stream: MutNullableDom::new(Some(&*stream)),
|
||||
stream_consumer: DomRefCell::new(None),
|
||||
redirected: DomRefCell::new(false),
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ impl Response {
|
|||
|
||||
// https://fetch.spec.whatwg.org/#dom-response
|
||||
pub fn new(global: &GlobalScope) -> DomRoot<Response> {
|
||||
reflect_dom_object(Box::new(Response::new_inherited()), global)
|
||||
reflect_dom_object(Box::new(Response::new_inherited(global)), global)
|
||||
}
|
||||
|
||||
pub fn Constructor(
|
||||
|
@ -128,8 +128,14 @@ impl Response {
|
|||
};
|
||||
|
||||
// Step 7.3
|
||||
let (extracted_body, content_type) = body.extract();
|
||||
*r.body.borrow_mut() = NetTraitsResponseBody::Done(extracted_body);
|
||||
let ExtractedBody {
|
||||
stream,
|
||||
total_bytes: _,
|
||||
content_type,
|
||||
source: _,
|
||||
} = body.extract(global)?;
|
||||
|
||||
r.body_stream.set(Some(&*stream));
|
||||
|
||||
// Step 7.4
|
||||
if let Some(content_type_contents) = content_type {
|
||||
|
@ -211,42 +217,32 @@ impl Response {
|
|||
Ok(r)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-body-locked
|
||||
fn locked(&self) -> bool {
|
||||
// TODO: ReadableStream is unimplemented. Just return false
|
||||
// for now.
|
||||
false
|
||||
pub fn error_stream(&self, error: Error) {
|
||||
if let Some(body) = self.body_stream.get() {
|
||||
body.error_native(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BodyOperations for Response {
|
||||
fn get_body_used(&self) -> bool {
|
||||
self.BodyUsed()
|
||||
}
|
||||
|
||||
fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType) {
|
||||
assert!(self.body_promise.borrow().is_none());
|
||||
self.body_used.set(true);
|
||||
*self.body_promise.borrow_mut() = Some((p.clone(), body_type));
|
||||
impl BodyMixin for Response {
|
||||
fn is_disturbed(&self) -> bool {
|
||||
self.body_stream
|
||||
.get()
|
||||
.map_or(false, |stream| stream.is_disturbed())
|
||||
}
|
||||
|
||||
fn is_locked(&self) -> bool {
|
||||
self.locked()
|
||||
self.body_stream
|
||||
.get()
|
||||
.map_or(false, |stream| stream.is_locked())
|
||||
}
|
||||
|
||||
fn take_body(&self) -> Option<Vec<u8>> {
|
||||
let body = mem::replace(&mut *self.body.borrow_mut(), NetTraitsResponseBody::Empty);
|
||||
match body {
|
||||
NetTraitsResponseBody::Done(bytes) => Some(bytes),
|
||||
body => {
|
||||
let _ = mem::replace(&mut *self.body.borrow_mut(), body);
|
||||
None
|
||||
},
|
||||
}
|
||||
fn body(&self) -> Option<DomRoot<ReadableStream>> {
|
||||
self.body_stream.get()
|
||||
}
|
||||
|
||||
fn get_mime_type(&self) -> Ref<Vec<u8>> {
|
||||
self.mime_type.borrow()
|
||||
fn get_mime_type(&self) -> Vec<u8> {
|
||||
self.mime_type.borrow().clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,7 +324,7 @@ impl ResponseMethods for Response {
|
|||
// https://fetch.spec.whatwg.org/#dom-response-clone
|
||||
fn Clone(&self) -> Fallible<DomRoot<Response>> {
|
||||
// Step 1
|
||||
if self.is_locked() || self.body_used.get() {
|
||||
if self.is_locked() || self.is_disturbed() {
|
||||
return Err(Error::Type("cannot clone a disturbed response".to_string()));
|
||||
}
|
||||
|
||||
|
@ -346,8 +342,8 @@ impl ResponseMethods for Response {
|
|||
*new_response.url.borrow_mut() = self.url.borrow().clone();
|
||||
*new_response.url_list.borrow_mut() = self.url_list.borrow().clone();
|
||||
|
||||
if *self.body.borrow() != NetTraitsResponseBody::Empty {
|
||||
*new_response.body.borrow_mut() = self.body.borrow().clone();
|
||||
if let Some(stream) = self.body_stream.get().clone() {
|
||||
new_response.body_stream.set(Some(&*stream));
|
||||
}
|
||||
|
||||
// Step 3
|
||||
|
@ -359,7 +355,12 @@ impl ResponseMethods for Response {
|
|||
|
||||
// https://fetch.spec.whatwg.org/#dom-body-bodyused
|
||||
fn BodyUsed(&self) -> bool {
|
||||
self.body_used.get()
|
||||
self.is_disturbed()
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#dom-body-body>
|
||||
fn GetBody(&self, _cx: SafeJSContext) -> Option<NonNull<JSObject>> {
|
||||
self.body().map(|stream| stream.get_js_stream())
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-body-text
|
||||
|
@ -424,20 +425,19 @@ impl Response {
|
|||
*self.status.borrow_mut() = None;
|
||||
self.set_raw_status(None);
|
||||
self.set_headers(None);
|
||||
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
|
||||
},
|
||||
DOMResponseType::Opaque => {
|
||||
*self.url_list.borrow_mut() = vec![];
|
||||
*self.status.borrow_mut() = None;
|
||||
self.set_raw_status(None);
|
||||
self.set_headers(None);
|
||||
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
|
||||
self.body_stream.set(None);
|
||||
},
|
||||
DOMResponseType::Opaqueredirect => {
|
||||
*self.status.borrow_mut() = None;
|
||||
self.set_raw_status(None);
|
||||
self.set_headers(None);
|
||||
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
|
||||
self.body_stream.set(None);
|
||||
},
|
||||
DOMResponseType::Default => {},
|
||||
DOMResponseType::Basic => {},
|
||||
|
@ -449,17 +449,19 @@ impl Response {
|
|||
*self.stream_consumer.borrow_mut() = sc;
|
||||
}
|
||||
|
||||
pub fn stream_chunk(&self, stream: &[u8]) {
|
||||
pub fn stream_chunk(&self, chunk: Vec<u8>) {
|
||||
// Note, are these two actually mutually exclusive?
|
||||
if let Some(stream_consumer) = self.stream_consumer.borrow_mut().as_ref() {
|
||||
stream_consumer.consume_chunk(stream);
|
||||
stream_consumer.consume_chunk(chunk.as_slice());
|
||||
} else if let Some(body) = self.body_stream.get() {
|
||||
body.enqueue_native(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unrooted_must_root)]
|
||||
pub fn finish(&self, body: Vec<u8>) {
|
||||
*self.body.borrow_mut() = NetTraitsResponseBody::Done(body);
|
||||
if let Some((p, body_type)) = self.body_promise.borrow_mut().take() {
|
||||
consume_body_with_promise(self, body_type, &p);
|
||||
pub fn finish(&self) {
|
||||
if let Some(body) = self.body_stream.get() {
|
||||
body.close_native();
|
||||
}
|
||||
if let Some(stream_consumer) = self.stream_consumer.borrow_mut().take() {
|
||||
stream_consumer.stream_end();
|
||||
|
|
|
@ -53,7 +53,7 @@ use crate::realms::InRealm;
|
|||
use crate::script_runtime::JSContext as SafeJSContext;
|
||||
use crate::timers::OneshotTimerCallback;
|
||||
use dom_struct::dom_struct;
|
||||
use js::jsapi::{Heap, JSContext, JSObject};
|
||||
use js::jsapi::{Heap, JSObject};
|
||||
use js::jsapi::{JS_NewPlainObject, JS_NewUint8ClampedArray};
|
||||
use js::jsval::{JSVal, NullValue};
|
||||
use js::rust::CustomAutoRooterGuard;
|
||||
|
@ -1018,9 +1018,8 @@ impl TestBindingMethods for TestBinding {
|
|||
}
|
||||
}
|
||||
impl Callback for SimpleHandler {
|
||||
#[allow(unsafe_code)]
|
||||
fn callback(&self, cx: *mut JSContext, v: HandleValue, realm: InRealm) {
|
||||
let global = unsafe { GlobalScope::from_context(cx, realm) };
|
||||
fn callback(&self, cx: SafeJSContext, v: HandleValue, realm: InRealm) {
|
||||
let global = GlobalScope::from_safe_context(cx, realm);
|
||||
let _ = self.handler.Call_(&*global, v, ExceptionHandling::Report);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ interface Blob {
|
|||
optional [Clamp] long long end,
|
||||
optional DOMString contentType);
|
||||
|
||||
[NewObject] object stream();
|
||||
[NewObject] Promise<DOMString> text();
|
||||
[NewObject] Promise<ArrayBuffer> arrayBuffer();
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
[Exposed=(Window,Worker)]
|
||||
interface mixin Body {
|
||||
readonly attribute boolean bodyUsed;
|
||||
readonly attribute object? body;
|
||||
|
||||
[NewObject] Promise<ArrayBuffer> arrayBuffer();
|
||||
[NewObject] Promise<Blob> blob();
|
||||
|
|
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
|
||||
typedef (Blob or BufferSource or FormData or DOMString or URLSearchParams) BodyInit;
|
||||
typedef (Blob or BufferSource or FormData or DOMString or URLSearchParams or ReadableStream) BodyInit;
|
||||
|
||||
enum XMLHttpRequestResponseType {
|
||||
"",
|
||||
|
@ -21,7 +21,7 @@ enum XMLHttpRequestResponseType {
|
|||
"blob",
|
||||
"document",
|
||||
"json",
|
||||
"text"
|
||||
"text",
|
||||
};
|
||||
|
||||
[Exposed=(Window,Worker)]
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::body::{Extractable, ExtractedBody};
|
||||
use crate::document_loader::DocumentLoader;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobBinding::BlobMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
|
||||
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseType;
|
||||
use crate::dom::bindings::codegen::UnionTypes::DocumentOrBodyInit;
|
||||
|
@ -22,15 +21,13 @@ use crate::dom::document::DocumentSource;
|
|||
use crate::dom::document::{Document, HasBrowsingContext, IsHTMLDocument};
|
||||
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::formdata::FormData;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::headers::is_forbidden_header_name;
|
||||
use crate::dom::htmlformelement::{encode_multipart_form_data, generate_boundary};
|
||||
use crate::dom::node::Node;
|
||||
use crate::dom::performanceresourcetiming::InitiatorType;
|
||||
use crate::dom::progressevent::ProgressEvent;
|
||||
use crate::dom::readablestream::ReadableStream;
|
||||
use crate::dom::servoparser::ServoParser;
|
||||
use crate::dom::urlsearchparams::URLSearchParams;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::workerglobalscope::WorkerGlobalScope;
|
||||
use crate::dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
|
||||
|
@ -58,7 +55,9 @@ use js::jsval::{JSVal, NullValue, UndefinedValue};
|
|||
use js::rust::wrappers::JS_ParseJSON;
|
||||
use js::typedarray::{ArrayBuffer, CreateWith};
|
||||
use mime::{self, Mime, Name};
|
||||
use net_traits::request::{CredentialsMode, Destination, Referrer, RequestBuilder, RequestMode};
|
||||
use net_traits::request::{
|
||||
BodySource, CredentialsMode, Destination, Referrer, RequestBuilder, RequestMode,
|
||||
};
|
||||
use net_traits::trim_http_whitespace;
|
||||
use net_traits::CoreResourceMsg::Fetch;
|
||||
use net_traits::{FetchChannels, FetchMetadata, FilteredMetadata};
|
||||
|
@ -562,42 +561,96 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
|
|||
_ => data,
|
||||
};
|
||||
// Step 4 (first half)
|
||||
let extracted_or_serialized = match data {
|
||||
let mut extracted_or_serialized = match data {
|
||||
Some(DocumentOrBodyInit::Document(ref doc)) => {
|
||||
let data = Vec::from(serialize_document(&doc)?.as_ref());
|
||||
let bytes = Vec::from(serialize_document(&doc)?.as_ref());
|
||||
let content_type = if doc.is_html_document() {
|
||||
"text/html;charset=UTF-8"
|
||||
} else {
|
||||
"application/xml;charset=UTF-8"
|
||||
};
|
||||
Some((data, Some(DOMString::from(content_type))))
|
||||
let total_bytes = bytes.len();
|
||||
let global = self.global();
|
||||
let stream = ReadableStream::new_from_bytes(&global, bytes);
|
||||
Some(ExtractedBody {
|
||||
stream,
|
||||
total_bytes: Some(total_bytes),
|
||||
content_type: Some(DOMString::from(content_type)),
|
||||
source: BodySource::Null,
|
||||
})
|
||||
},
|
||||
Some(DocumentOrBodyInit::Blob(ref b)) => Some(b.extract()),
|
||||
Some(DocumentOrBodyInit::FormData(ref formdata)) => Some(formdata.extract()),
|
||||
Some(DocumentOrBodyInit::String(ref str)) => Some(str.extract()),
|
||||
Some(DocumentOrBodyInit::URLSearchParams(ref urlsp)) => Some(urlsp.extract()),
|
||||
Some(DocumentOrBodyInit::Blob(ref b)) => {
|
||||
Some(b.extract(&self.global()).expect("Couldn't extract body."))
|
||||
},
|
||||
Some(DocumentOrBodyInit::FormData(ref formdata)) => Some(
|
||||
formdata
|
||||
.extract(&self.global())
|
||||
.expect("Couldn't extract body."),
|
||||
),
|
||||
Some(DocumentOrBodyInit::String(ref str)) => {
|
||||
Some(str.extract(&self.global()).expect("Couldn't extract body."))
|
||||
},
|
||||
Some(DocumentOrBodyInit::URLSearchParams(ref urlsp)) => Some(
|
||||
urlsp
|
||||
.extract(&self.global())
|
||||
.expect("Couldn't extract body."),
|
||||
),
|
||||
Some(DocumentOrBodyInit::ArrayBuffer(ref typedarray)) => {
|
||||
Some((typedarray.to_vec(), None))
|
||||
let bytes = typedarray.to_vec();
|
||||
let total_bytes = bytes.len();
|
||||
let global = self.global();
|
||||
let stream = ReadableStream::new_from_bytes(&global, bytes);
|
||||
Some(ExtractedBody {
|
||||
stream,
|
||||
total_bytes: Some(total_bytes),
|
||||
content_type: None,
|
||||
source: BodySource::BufferSource,
|
||||
})
|
||||
},
|
||||
Some(DocumentOrBodyInit::ArrayBufferView(ref typedarray)) => {
|
||||
Some((typedarray.to_vec(), None))
|
||||
let bytes = typedarray.to_vec();
|
||||
let total_bytes = bytes.len();
|
||||
let global = self.global();
|
||||
let stream = ReadableStream::new_from_bytes(&global, bytes);
|
||||
Some(ExtractedBody {
|
||||
stream,
|
||||
total_bytes: Some(total_bytes),
|
||||
content_type: None,
|
||||
source: BodySource::BufferSource,
|
||||
})
|
||||
},
|
||||
Some(DocumentOrBodyInit::ReadableStream(ref stream)) => {
|
||||
// TODO:
|
||||
// 1. If the keepalive flag is set, then throw a TypeError.
|
||||
|
||||
if stream.is_locked() || stream.is_disturbed() {
|
||||
return Err(Error::Type(
|
||||
"The body's stream is disturbed or locked".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Some(ExtractedBody {
|
||||
stream: stream.clone(),
|
||||
total_bytes: None,
|
||||
content_type: None,
|
||||
source: BodySource::Null,
|
||||
})
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
self.request_body_len
|
||||
.set(extracted_or_serialized.as_ref().map_or(0, |e| e.0.len()));
|
||||
self.request_body_len.set(
|
||||
extracted_or_serialized
|
||||
.as_ref()
|
||||
.map_or(0, |e| e.total_bytes.unwrap_or(0)),
|
||||
);
|
||||
|
||||
// todo preserved headers?
|
||||
|
||||
// Step 6
|
||||
self.upload_complete.set(false);
|
||||
// Step 7
|
||||
self.upload_complete.set(match extracted_or_serialized {
|
||||
None => true,
|
||||
Some(ref e) if e.0.is_empty() => true,
|
||||
_ => false,
|
||||
});
|
||||
self.upload_complete.set(extracted_or_serialized.is_none());
|
||||
// Step 8
|
||||
self.send_flag.set(true);
|
||||
|
||||
|
@ -634,12 +687,17 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
|
|||
unreachable!()
|
||||
};
|
||||
|
||||
let content_type = match extracted_or_serialized.as_mut() {
|
||||
Some(body) => body.content_type.take(),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let mut request = RequestBuilder::new(self.request_url.borrow().clone().unwrap())
|
||||
.method(self.request_method.borrow().clone())
|
||||
.headers((*self.request_headers.borrow()).clone())
|
||||
.unsafe_request(true)
|
||||
// XXXManishearth figure out how to avoid this clone
|
||||
.body(extracted_or_serialized.as_ref().map(|e| e.0.clone()))
|
||||
.body(extracted_or_serialized.map(|e| e.into_net_request_body().0))
|
||||
// XXXManishearth actually "subresource", but it doesn't exist
|
||||
// https://github.com/whatwg/xhr/issues/71
|
||||
.destination(Destination::None)
|
||||
|
@ -658,8 +716,8 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
|
|||
.pipeline_id(Some(self.global().pipeline_id()));
|
||||
|
||||
// step 4 (second half)
|
||||
match extracted_or_serialized {
|
||||
Some((_, ref content_type)) => {
|
||||
match content_type {
|
||||
Some(content_type) => {
|
||||
let encoding = match data {
|
||||
Some(DocumentOrBodyInit::String(_)) | Some(DocumentOrBodyInit::Document(_)) =>
|
||||
// XHR spec differs from http, and says UTF-8 should be in capitals,
|
||||
|
@ -672,13 +730,12 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
|
|||
};
|
||||
|
||||
let mut content_type_set = false;
|
||||
if let Some(ref ct) = *content_type {
|
||||
if !request.headers.contains_key(header::CONTENT_TYPE) {
|
||||
request
|
||||
.headers
|
||||
.insert(header::CONTENT_TYPE, HeaderValue::from_str(ct).unwrap());
|
||||
content_type_set = true;
|
||||
}
|
||||
if !request.headers.contains_key(header::CONTENT_TYPE) {
|
||||
request.headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_str(&content_type).unwrap(),
|
||||
);
|
||||
content_type_set = true;
|
||||
}
|
||||
|
||||
if !content_type_set {
|
||||
|
@ -1555,56 +1612,6 @@ impl XHRTimeoutCallback {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait Extractable {
|
||||
fn extract(&self) -> (Vec<u8>, Option<DOMString>);
|
||||
}
|
||||
|
||||
impl Extractable for Blob {
|
||||
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
|
||||
let content_type = if self.Type().as_ref().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.Type())
|
||||
};
|
||||
let bytes = self.get_bytes().unwrap_or(vec![]);
|
||||
(bytes, content_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl Extractable for DOMString {
|
||||
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
|
||||
(
|
||||
self.as_bytes().to_owned(),
|
||||
Some(DOMString::from("text/plain;charset=UTF-8")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Extractable for FormData {
|
||||
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
|
||||
let boundary = generate_boundary();
|
||||
let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
|
||||
(
|
||||
bytes,
|
||||
Some(DOMString::from(format!(
|
||||
"multipart/form-data;boundary={}",
|
||||
boundary
|
||||
))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Extractable for URLSearchParams {
|
||||
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
|
||||
(
|
||||
self.serialize_utf8().into_bytes(),
|
||||
Some(DOMString::from(
|
||||
"application/x-www-form-urlencoded;charset=UTF-8",
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_document(doc: &Document) -> Fallible<DOMString> {
|
||||
let mut writer = vec![];
|
||||
match serialize(&mut writer, &doc.upcast::<Node>(), SerializeOpts::default()) {
|
||||
|
@ -1613,20 +1620,6 @@ fn serialize_document(doc: &Document) -> Fallible<DOMString> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Extractable for BodyInit {
|
||||
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
||||
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
|
||||
match *self {
|
||||
BodyInit::String(ref s) => s.extract(),
|
||||
BodyInit::URLSearchParams(ref usp) => usp.extract(),
|
||||
BodyInit::Blob(ref b) => b.extract(),
|
||||
BodyInit::FormData(ref formdata) => formdata.extract(),
|
||||
BodyInit::ArrayBuffer(ref typedarray) => ((typedarray.to_vec(), None)),
|
||||
BodyInit::ArrayBufferView(ref typedarray) => ((typedarray.to_vec(), None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether `bs` is a `field-value`, as defined by
|
||||
/// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-32).
|
||||
pub fn is_field_value(slice: &[u8]) -> bool {
|
||||
|
|
|
@ -36,14 +36,12 @@ use net_traits::{FetchChannels, FetchResponseListener, NetworkError};
|
|||
use net_traits::{FetchMetadata, FilteredMetadata, Metadata};
|
||||
use net_traits::{ResourceFetchTiming, ResourceTimingType};
|
||||
use servo_url::ServoUrl;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
struct FetchContext {
|
||||
fetch_promise: Option<TrustedPromise>,
|
||||
response_object: Trusted<Response>,
|
||||
body: Vec<u8>,
|
||||
resource_timing: ResourceFetchTiming,
|
||||
}
|
||||
|
||||
|
@ -149,6 +147,7 @@ pub fn Fetch(
|
|||
// Step 2
|
||||
let request = match Request::Constructor(global, input, init) {
|
||||
Err(e) => {
|
||||
response.error_stream(e.clone());
|
||||
promise.reject_error(e);
|
||||
return promise;
|
||||
},
|
||||
|
@ -172,7 +171,6 @@ pub fn Fetch(
|
|||
let fetch_context = Arc::new(Mutex::new(FetchContext {
|
||||
fetch_promise: Some(TrustedPromise::new(promise.clone())),
|
||||
response_object: Trusted::new(&*response),
|
||||
body: vec![],
|
||||
resource_timing: ResourceFetchTiming::new(timing_type),
|
||||
}));
|
||||
let listener = NetworkListener {
|
||||
|
@ -222,7 +220,9 @@ impl FetchResponseListener for FetchContext {
|
|||
Err(_) => {
|
||||
promise.reject_error(Error::Type("Network error occurred".to_string()));
|
||||
self.fetch_promise = Some(TrustedPromise::new(promise));
|
||||
self.response_object.root().set_type(DOMResponseType::Error);
|
||||
let response = self.response_object.root();
|
||||
response.set_type(DOMResponseType::Error);
|
||||
response.error_stream(Error::Type("Network error occurred".to_string()));
|
||||
return;
|
||||
},
|
||||
// Step 4.2
|
||||
|
@ -260,15 +260,15 @@ impl FetchResponseListener for FetchContext {
|
|||
self.fetch_promise = Some(TrustedPromise::new(promise));
|
||||
}
|
||||
|
||||
fn process_response_chunk(&mut self, mut chunk: Vec<u8>) {
|
||||
self.response_object.root().stream_chunk(chunk.as_slice());
|
||||
self.body.append(&mut chunk);
|
||||
fn process_response_chunk(&mut self, chunk: Vec<u8>) {
|
||||
let response = self.response_object.root();
|
||||
response.stream_chunk(chunk);
|
||||
}
|
||||
|
||||
fn process_response_eof(&mut self, _response: Result<ResourceFetchTiming, NetworkError>) {
|
||||
let response = self.response_object.root();
|
||||
let _ac = enter_realm(&*response);
|
||||
response.finish(mem::replace(&mut self.body, vec![]));
|
||||
response.finish();
|
||||
// TODO
|
||||
// ... trailerObject is not supported in Servo yet.
|
||||
}
|
||||
|
|
|
@ -675,7 +675,7 @@ impl ModuleHandler {
|
|||
}
|
||||
|
||||
impl Callback for ModuleHandler {
|
||||
fn callback(&self, _cx: *mut JSContext, _v: HandleValue, _realm: InRealm) {
|
||||
fn callback(&self, _cx: SafeJSContext, _v: HandleValue, _realm: InRealm) {
|
||||
let task = self.task.borrow_mut().take().unwrap();
|
||||
task.run_box();
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::body::BodyOperations;
|
||||
use crate::body::BodyMixin;
|
||||
use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
|
||||
use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseBinding::ResponseMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
|
||||
|
@ -979,7 +979,7 @@ unsafe extern "C" fn consume_stream(
|
|||
}
|
||||
|
||||
// Step 2.6.2 If response body is alreaady consumed, return with a TypeError and abort these substeps.
|
||||
if unwrapped_source.get_body_used() {
|
||||
if unwrapped_source.is_disturbed() {
|
||||
throw_dom_exception(
|
||||
cx,
|
||||
&global,
|
||||
|
|
|
@ -49,7 +49,7 @@ use msg::constellation_msg::{
|
|||
use msg::constellation_msg::{PipelineNamespaceId, TopLevelBrowsingContextId};
|
||||
use net_traits::image::base::Image;
|
||||
use net_traits::image_cache::ImageCache;
|
||||
use net_traits::request::Referrer;
|
||||
use net_traits::request::{Referrer, RequestBody};
|
||||
use net_traits::storage_thread::StorageType;
|
||||
use net_traits::{FetchResponseMsg, ReferrerPolicy, ResourceThreads};
|
||||
use pixels::PixelFormat;
|
||||
|
@ -171,8 +171,8 @@ pub struct LoadData {
|
|||
serialize_with = "::hyper_serde::serialize"
|
||||
)]
|
||||
pub headers: HeaderMap,
|
||||
/// The data.
|
||||
pub data: Option<Vec<u8>>,
|
||||
/// The data that will be used as the body of the request.
|
||||
pub data: Option<RequestBody>,
|
||||
/// The result of evaluating a javascript scheme url.
|
||||
pub js_eval_result: Option<JsEvalResult>,
|
||||
/// The referrer.
|
||||
|
|
|
@ -153,6 +153,10 @@ skip: true
|
|||
skip: false
|
||||
[selection]
|
||||
skip: false
|
||||
[streams]
|
||||
skip: true
|
||||
[readable-streams]
|
||||
skip: false
|
||||
[subresource-integrity]
|
||||
skip: false
|
||||
[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]
|
||||
expected: FAIL
|
||||
|
||||
[Blob interface: operation stream()]
|
||||
expected: FAIL
|
||||
|
||||
[Blob interface: operation text()]
|
||||
expected: FAIL
|
||||
|
||||
[Blob interface: new Blob(["TEST"\]) must inherit property "stream()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Blob interface: operation arrayBuffer()]
|
||||
expected: FAIL
|
||||
|
||||
[Blob interface: new File(["myFileBits"\], "myFileName") must inherit property "stream()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -30,18 +30,9 @@
|
|||
[idlharness]
|
||||
expected: FAIL
|
||||
|
||||
[Blob interface: operation stream()]
|
||||
expected: FAIL
|
||||
|
||||
[Blob interface: operation text()]
|
||||
expected: FAIL
|
||||
|
||||
[Blob interface: new Blob(["TEST"\]) must inherit property "stream()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Blob interface: operation arrayBuffer()]
|
||||
expected: FAIL
|
||||
|
||||
[Blob interface: new File(["myFileBits"\], "myFileName") must inherit property "stream()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -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]
|
||||
[Response interface: new Response() must inherit property "body" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Response interface: attribute body]
|
||||
expected: FAIL
|
||||
|
||||
[Response interface: operation blob()]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -68,9 +62,6 @@
|
|||
[Request interface: new Request('about:blank') must inherit property "signal" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Request interface: new Request('about:blank') must inherit property "body" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Request interface: operation arrayBuffer()]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -80,9 +71,6 @@
|
|||
[Response interface: attribute trailer]
|
||||
expected: FAIL
|
||||
|
||||
[Request interface: attribute body]
|
||||
expected: FAIL
|
||||
|
||||
[Window interface: calling fetch(RequestInfo, optional RequestInit) on window with too few arguments must throw TypeError]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -91,12 +79,6 @@
|
|||
|
||||
|
||||
[idlharness.any.worker.html]
|
||||
[Response interface: new Response() must inherit property "body" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Response interface: attribute body]
|
||||
expected: FAIL
|
||||
|
||||
[Response interface: operation blob()]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -154,9 +136,6 @@
|
|||
[Request interface: new Request('about:blank') must inherit property "signal" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Request interface: new Request('about:blank') must inherit property "body" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Request interface: operation arrayBuffer()]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -166,9 +145,6 @@
|
|||
[Response interface: attribute trailer]
|
||||
expected: FAIL
|
||||
|
||||
[Request interface: attribute body]
|
||||
expected: FAIL
|
||||
|
||||
[WorkerGlobalScope interface: calling fetch(RequestInfo, optional RequestInit) on self with too few arguments must throw TypeError]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -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]
|
||||
type: testharness
|
||||
[Consume request's body as arrayBuffer]
|
||||
expected: FAIL
|
||||
|
||||
[Consume request's body as formData]
|
||||
expected: FAIL
|
||||
|
||||
[Consume request's body as text]
|
||||
expected: FAIL
|
||||
|
||||
[Consume request's body as blob]
|
||||
expected: FAIL
|
||||
|
||||
[Consume request's body as json]
|
||||
expected: FAIL
|
||||
|
||||
[Consume empty FormData request body as text]
|
||||
expected: FAIL
|
||||
|
||||
[Consume request's body as json (error case)]
|
||||
expected: FAIL
|
||||
|
||||
[Consume request's body as formData with correct multipart type (error case)]
|
||||
expected: FAIL
|
||||
|
||||
[Consume request's body as formData with correct urlencoded type]
|
||||
expected: FAIL
|
||||
|
||||
[Consume request's body as formData without correct type (error case)]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,23 +1,11 @@
|
|||
[request-disturbed.html]
|
||||
type: testharness
|
||||
[Check cloning a disturbed request]
|
||||
expected: FAIL
|
||||
|
||||
[Check creating a new request from a disturbed request]
|
||||
expected: FAIL
|
||||
|
||||
[Input request used for creating new request became disturbed]
|
||||
expected: FAIL
|
||||
|
||||
[Request construction failure should not set "bodyUsed"]
|
||||
expected: FAIL
|
||||
|
||||
[Request without body cannot be disturbed]
|
||||
expected: FAIL
|
||||
|
||||
[Request's body: initial state]
|
||||
expected: FAIL
|
||||
|
||||
[Input request used for creating new request became disturbed even if body is not used]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -2,18 +2,9 @@
|
|||
[Constructing a Request with a Request on which read() and releaseLock() are called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Request with a stream on which read() and releaseLock() are called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Request with a stream on which getReader() is called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Request with a Request on which body.getReader() is called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Request with a stream on which read() is called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Request with a Request on which body.getReader().read() is called]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -28,18 +19,9 @@
|
|||
[Constructing a Request with a Request on which read() and releaseLock() are called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Request with a stream on which read() and releaseLock() are called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Request with a stream on which getReader() is called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Request with a Request on which body.getReader() is called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Request with a stream on which read() is called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Request with a Request on which body.getReader().read() is called]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,23 +1,5 @@
|
|||
[response-cancel-stream.html]
|
||||
type: testharness
|
||||
[Cancelling a starting blob Response stream]
|
||||
expected: FAIL
|
||||
|
||||
[Cancelling a loading blob Response stream]
|
||||
expected: FAIL
|
||||
|
||||
[Cancelling a closed blob Response stream]
|
||||
expected: FAIL
|
||||
|
||||
[Cancelling a starting Response stream]
|
||||
expected: FAIL
|
||||
|
||||
[Cancelling a loading Response stream]
|
||||
expected: FAIL
|
||||
|
||||
[Cancelling a closed Response stream]
|
||||
expected: FAIL
|
||||
|
||||
[Response consume blob and http bodies]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
[response-clone.html]
|
||||
type: testharness
|
||||
[Check orginal response's body after cloning]
|
||||
expected: FAIL
|
||||
|
||||
[Check cloned response's body]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -44,3 +41,4 @@
|
|||
|
||||
[Check response clone use structureClone for teed ReadableStreams (DataViewchunk)]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,29 +1,8 @@
|
|||
[response-consume-stream.html]
|
||||
type: testharness
|
||||
[Read empty text response's body as readableStream]
|
||||
expected: FAIL
|
||||
|
||||
[Read empty blob response's body as readableStream]
|
||||
expected: FAIL
|
||||
|
||||
[Read blob response's body as readableStream]
|
||||
expected: FAIL
|
||||
|
||||
[Read text response's body as readableStream]
|
||||
expected: FAIL
|
||||
|
||||
[Read array buffer response's body as readableStream]
|
||||
expected: FAIL
|
||||
|
||||
[Read form data response's body as readableStream]
|
||||
expected: FAIL
|
||||
|
||||
[Getting an error Response stream]
|
||||
expected: FAIL
|
||||
|
||||
[Getting a redirect Response stream]
|
||||
expected: FAIL
|
||||
|
||||
[Read URLSearchParams response's body as readableStream]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
[response-consume.html]
|
||||
type: testharness
|
||||
expected: ERROR
|
||||
[Consume response's body as arrayBuffer]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -34,30 +33,9 @@
|
|||
[Consume response's body: from FormData to formData]
|
||||
expected: FAIL
|
||||
|
||||
[Consume response's body: from stream to blob]
|
||||
expected: FAIL
|
||||
|
||||
[Consume response's body: from stream to text]
|
||||
expected: FAIL
|
||||
|
||||
[Consume response's body: from stream to arrayBuffer]
|
||||
expected: FAIL
|
||||
|
||||
[Consume response's body: from stream to json]
|
||||
expected: FAIL
|
||||
|
||||
[Consume response's body: from stream with correct multipart type to formData]
|
||||
expected: FAIL
|
||||
|
||||
[Consume response's body: from stream without correct multipart type to formData (error case)]
|
||||
expected: FAIL
|
||||
|
||||
[Consume response's body: from stream with correct urlencoded type to formData]
|
||||
expected: FAIL
|
||||
|
||||
[Consume response's body: from stream without correct urlencoded type to formData (error case)]
|
||||
expected: FAIL
|
||||
|
||||
[Consume response's body: from multipart form data blob to formData]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,8 +1,2 @@
|
|||
[response-error-from-stream.html]
|
||||
expected: ERROR
|
||||
[ReadableStreamDefaultReader Promise receives ReadableStream start() Error]
|
||||
expected: FAIL
|
||||
|
||||
[ReadableStreamDefaultReader Promise receives ReadableStream pull() Error]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -5,29 +5,11 @@
|
|||
|
||||
|
||||
[response-from-stream.any.html]
|
||||
[Constructing a Response with a stream on which getReader() is called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Response with a stream on which read() and releaseLock() are called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Response with a stream on which read() is called]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[response-from-stream.any.serviceworker.html]
|
||||
expected: TIMEOUT
|
||||
|
||||
[response-from-stream.any.worker.html]
|
||||
[Constructing a Response with a stream on which getReader() is called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Response with a stream on which read() and releaseLock() are called]
|
||||
expected: FAIL
|
||||
|
||||
[Constructing a Response with a stream on which read() is called]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[response-from-stream.any.serviceworker.html]
|
||||
expected: ERROR
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
[response-init-002.html]
|
||||
type: testharness
|
||||
[Read Response's body as readableStream]
|
||||
expected: FAIL
|
||||
|
||||
[Testing null Response body]
|
||||
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]
|
||||
[Untitled]
|
||||
expected: FAIL
|
||||
|
||||
[Inject {done: false, value: bye} via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
|
||||
[Inject {done: false, value: undefined} via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
|
||||
[Inject undefined via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
|
||||
[Inject 8.2 via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
|
||||
[response-stream-with-broken-then]
|
||||
expected: FAIL
|
||||
[Attempt to inject {done: false, value: bye} via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
[Attempt to inject value: undefined via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
[Attempt to inject undefined via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
[Attempt to inject 8.2 via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
|
||||
[intercepting arraybuffer to body readable stream conversion via Object.prototype.then should not be possible]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -27,26 +24,22 @@
|
|||
[response-stream-with-broken-then.any.worker.html]
|
||||
[Untitled]
|
||||
expected: FAIL
|
||||
|
||||
[Inject {done: false, value: bye} via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
|
||||
[Inject {done: false, value: undefined} via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
|
||||
[Inject undefined via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
|
||||
[Inject 8.2 via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
[Attempt to inject {done: false, value: bye} via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
[Attempt to inject value: undefined via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
[Attempt to inject undefined via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
[Attempt to inject 8.2 via Object.prototype.then.]
|
||||
expected: FAIL
|
||||
|
||||
[intercepting arraybuffer to body readable stream conversion via Object.prototype.then should not be possible]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[response-stream-with-broken-then]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -311,28 +311,3 @@
|
|||
|
||||
[fetch(): separate response Content-Type: text/plain ]
|
||||
expected: NOTRUN
|
||||
|
||||
[<iframe>: separate response Content-Type: text/html */*;charset=gbk]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: combined response Content-Type: text/html;x=" text/plain]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: combined response Content-Type: */* text/html]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: combined response Content-Type: text/html;" \\" text/plain]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: combined response Content-Type: text/html;charset=gbk text/plain text/html]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: separate response Content-Type: text/plain */*]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: combined response Content-Type: text/html */*;charset=gbk]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: separate response Content-Type: text/html;" \\" text/plain]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -53,9 +53,15 @@
|
|||
[combined text/javascript ]
|
||||
expected: FAIL
|
||||
|
||||
[separate text/javascript x/x]
|
||||
[separate text/javascript;charset=windows-1252 error text/javascript]
|
||||
expected: FAIL
|
||||
|
||||
[separate text/javascript ]
|
||||
expected: FAIL
|
||||
|
||||
[separate text/javascript error]
|
||||
expected: FAIL
|
||||
|
||||
[separate text/javascript x/x]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -10,4 +10,3 @@
|
|||
|
||||
[X-Content-Type-Options%3A%20nosniff%2C%2C%40%23%24%23%25%25%26%5E%26%5E*()()11!]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,128 +1,37 @@
|
|||
[assorted.window.html]
|
||||
expected: TIMEOUT
|
||||
[Origin header and POST same-origin fetch cors mode with Referrer-Policy no-referrer-when-downgrade]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST cross-origin fetch cors mode with Referrer-Policy no-referrer-when-downgrade]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST same-origin fetch cors mode with Referrer-Policy no-referrer]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST same-origin navigation with Referrer-Policy origin-when-cross-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy unsafe-url]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and 308 redirect]
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy same-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy no-referrer]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST same-origin navigation with Referrer-Policy no-referrer-when-downgrade]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy unsafe-url]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy no-referrer]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin navigation with Referrer-Policy origin-when-cross-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy origin-when-cross-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST cross-origin fetch cors mode with Referrer-Policy origin-when-cross-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy no-referrer-when-downgrade]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin navigation with Referrer-Policy unsafe-url]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST navigation]
|
||||
expected: TIMEOUT
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin navigation with Referrer-Policy no-referrer]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin navigation with Referrer-Policy same-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST cross-origin fetch cors mode with Referrer-Policy same-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy origin-when-cross-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST cross-origin fetch cors mode with Referrer-Policy unsafe-url]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST same-origin navigation with Referrer-Policy unsafe-url]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy no-referrer-when-downgrade]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy same-origin]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST same-origin navigation with Referrer-Policy no-referrer]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST same-origin fetch cors mode with Referrer-Policy unsafe-url]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST same-origin navigation with Referrer-Policy same-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST same-origin fetch cors mode with Referrer-Policy same-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST same-origin fetch cors mode with Referrer-Policy origin-when-cross-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and POST cross-origin fetch cors mode with Referrer-Policy no-referrer]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin navigation with Referrer-Policy no-referrer-when-downgrade]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and GET same-origin fetch cors mode with Referrer-Policy no-referrer]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and GET cross-origin fetch cors mode with Referrer-Policy no-referrer-when-downgrade]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and GET same-origin fetch cors mode with Referrer-Policy origin-when-cross-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and GET same-origin fetch cors mode with Referrer-Policy no-referrer-when-downgrade]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and GET same-origin fetch cors mode with Referrer-Policy same-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and GET same-origin fetch cors mode with Referrer-Policy unsafe-url]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and GET cross-origin fetch cors mode with Referrer-Policy unsafe-url]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and GET cross-origin fetch cors mode with Referrer-Policy same-origin]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and GET cross-origin fetch cors mode with Referrer-Policy no-referrer]
|
||||
expected: NOTRUN
|
||||
|
||||
[Origin header and GET cross-origin fetch cors mode with Referrer-Policy origin-when-cross-origin]
|
||||
expected: NOTRUN
|
||||
[Origin header and POST same-origin fetch cors mode with Referrer-Policy no-referrer]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -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.]
|
||||
expected: FAIL
|
||||
|
||||
[The ReadableStream interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
[The WritableStream interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
[The ByteLengthQueuingStrategy interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
[The CountQueuingStrategy interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
[The IDBRequest interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -53,3 +44,4 @@
|
|||
|
||||
[The IDBTransaction interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,24 +1,6 @@
|
|||
[send-data-readablestream.any.html]
|
||||
[XMLHttpRequest: send() with a stream on which read() is called]
|
||||
expected: FAIL
|
||||
|
||||
[XMLHttpRequest: send() with a stream on which read() and releaseLock() are called]
|
||||
expected: FAIL
|
||||
|
||||
[XMLHttpRequest: send() with a stream on which getReader() is called]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[send-data-readablestream.any.worker.html]
|
||||
[XMLHttpRequest: send() with a stream on which read() is called]
|
||||
expected: FAIL
|
||||
|
||||
[XMLHttpRequest: send() with a stream on which read() and releaseLock() are called]
|
||||
expected: FAIL
|
||||
|
||||
[XMLHttpRequest: send() with a stream on which getReader() is called]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[send-data-readablestream.any.sharedworker.html]
|
||||
expected: ERROR
|
||||
|
|
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]
|
||||
expected: FAIL
|
||||
|
||||
[ReadableStream request respects setRequestHeader("")]
|
||||
expected: FAIL
|
||||
|
||||
[ReadableStream request with under type sends no Content-Type without setRequestHeader() call]
|
||||
expected: FAIL
|
||||
|
||||
[ReadableStream request keeps setRequestHeader() Content-Type and charset]
|
||||
expected: FAIL
|
||||
|
||||
[String request keeps setRequestHeader() Content-Type, with charset adjusted to UTF-8]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -10988,7 +10988,7 @@
|
|||
[]
|
||||
],
|
||||
"interfaces.js": [
|
||||
"f62df6b9c75332a2918b18ec70acfc5d7a795ac0",
|
||||
"253dd124cebe9b7d3aa4fc5ff8c9137e7aeb2f66",
|
||||
[]
|
||||
],
|
||||
"nested_asap_script.js": [
|
||||
|
@ -13924,7 +13924,7 @@
|
|||
]
|
||||
],
|
||||
"interfaces.worker.js": [
|
||||
"c1223084790b2980c8184e3cd9ab5ae17bc8b303",
|
||||
"59d4aa1855fbf281736b28581fbc519a847c8a0d",
|
||||
[
|
||||
"mozilla/interfaces.worker.html",
|
||||
{}
|
||||
|
|
|
@ -11,6 +11,8 @@ function test_interfaces(interfaceNamesInGlobalScope) {
|
|||
"BigUint64Array",
|
||||
"Boolean",
|
||||
"BroadcastChannel",
|
||||
"ByteLengthQueuingStrategy",
|
||||
"CountQueuingStrategy",
|
||||
"Crypto",
|
||||
"DataView",
|
||||
"Date",
|
||||
|
@ -36,6 +38,7 @@ function test_interfaces(interfaceNamesInGlobalScope) {
|
|||
"Promise",
|
||||
"Proxy",
|
||||
"RangeError",
|
||||
"ReadableStream",
|
||||
"ReferenceError",
|
||||
"Reflect",
|
||||
"RegExp",
|
||||
|
|
|
@ -9,9 +9,11 @@ importScripts("interfaces.js");
|
|||
test_interfaces([
|
||||
"Blob",
|
||||
"BroadcastChannel",
|
||||
"ByteLengthQueuingStrategy",
|
||||
"CanvasGradient",
|
||||
"CanvasPattern",
|
||||
"CloseEvent",
|
||||
"CountQueuingStrategy",
|
||||
"DOMMatrix",
|
||||
"DOMMatrixReadOnly",
|
||||
"DOMPoint",
|
||||
|
@ -48,6 +50,7 @@ test_interfaces([
|
|||
"PerformanceResourceTiming",
|
||||
"ProgressEvent",
|
||||
"PromiseRejectionEvent",
|
||||
"ReadableStream",
|
||||
"Request",
|
||||
"Response",
|
||||
"TextDecoder",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue