mirror of
https://github.com/servo/servo.git
synced 2025-10-02 09:39:14 +01:00
Implement fetchLater
(#39547)
Allows fetches to be deferred, only in a secure context. It does not yet implement quota computation, since we don't have a concept of document quota yet. Also update the `fetch/api/idlharness` test to run in a secure context, since this API is only available there. Positive Mozilla position: https://github.com/mozilla/standards-positions/issues/703 Positive WebKit position: https://github.com/WebKit/standards-positions/issues/85 Closes whatwg/fetch#1858 Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
parent
19c498af16
commit
680a780552
39 changed files with 403 additions and 228 deletions
|
@ -346,11 +346,15 @@ impl Callback for TransmitBodyPromiseRejectionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
/// The result of <https://fetch.spec.whatwg.org/#concept-bodyinit-extract>
|
||||
/// <https://fetch.spec.whatwg.org/#body-with-type>
|
||||
pub(crate) struct ExtractedBody {
|
||||
/// <https://fetch.spec.whatwg.org/#concept-body-stream>
|
||||
pub(crate) stream: DomRoot<ReadableStream>,
|
||||
/// <https://fetch.spec.whatwg.org/#concept-body-source>
|
||||
pub(crate) source: BodySource,
|
||||
/// <https://fetch.spec.whatwg.org/#concept-body-total-bytes>
|
||||
pub(crate) total_bytes: Option<usize>,
|
||||
/// <https://fetch.spec.whatwg.org/#body-with-type-type>
|
||||
pub(crate) content_type: Option<DOMString>,
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ use crate::dom::bindings::str::DOMString;
|
|||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::readablestream::PipeTo;
|
||||
use crate::fetch::FetchContext;
|
||||
use crate::fetch::{DeferredFetchRecord, FetchContext};
|
||||
use crate::realms::InRealm;
|
||||
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
|
||||
|
||||
|
@ -49,6 +49,12 @@ pub(crate) enum AbortAlgorithm {
|
|||
#[conditional_malloc_size_of]
|
||||
Arc<Mutex<FetchContext>>,
|
||||
),
|
||||
/// <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
|
||||
FetchLater(
|
||||
#[no_trace]
|
||||
#[conditional_malloc_size_of]
|
||||
Arc<Mutex<DeferredFetchRecord>>,
|
||||
),
|
||||
}
|
||||
|
||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
|
@ -190,6 +196,9 @@ impl AbortSignal {
|
|||
.unwrap()
|
||||
.abort_fetch(reason.handle(), cx, can_gc);
|
||||
},
|
||||
AbortAlgorithm::FetchLater(deferred_fetch_record) => {
|
||||
deferred_fetch_record.lock().unwrap().abort();
|
||||
},
|
||||
AbortAlgorithm::DomEventListener(removable_listener) => {
|
||||
removable_listener
|
||||
.event_target
|
||||
|
|
59
components/script/dom/fetchlaterresult.rs
Normal file
59
components/script/dom/fetchlaterresult.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
/* 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/. */
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::FetchLaterResultBinding::FetchLaterResultMethods;
|
||||
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::window::Window;
|
||||
use crate::fetch::DeferredFetchRecord;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#fetchlaterresult>
|
||||
#[dom_struct]
|
||||
pub(crate) struct FetchLaterResult {
|
||||
reflector_: Reflector,
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#fetchlaterresult-activated-getter-steps>
|
||||
#[conditional_malloc_size_of]
|
||||
#[no_trace]
|
||||
activated_getter_steps: Arc<Mutex<DeferredFetchRecord>>,
|
||||
}
|
||||
|
||||
impl FetchLaterResult {
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
fn new_inherited(activated_getter_steps: Arc<Mutex<DeferredFetchRecord>>) -> FetchLaterResult {
|
||||
FetchLaterResult {
|
||||
reflector_: Reflector::new(),
|
||||
activated_getter_steps,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
pub(crate) fn new(
|
||||
window: &Window,
|
||||
activated_getter_steps: Arc<Mutex<DeferredFetchRecord>>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<FetchLaterResult> {
|
||||
reflect_dom_object(
|
||||
Box::new(FetchLaterResult::new_inherited(activated_getter_steps)),
|
||||
window,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FetchLaterResultMethods<crate::DomTypeHolder> for FetchLaterResult {
|
||||
/// <https://fetch.spec.whatwg.org/#dom-fetchlaterresult-activated>
|
||||
fn Activated(&self) -> bool {
|
||||
// The activated getter steps are to return the result of running this’s activated getter steps.
|
||||
self.activated_getter_steps
|
||||
.lock()
|
||||
.expect("Activated getter not accessible")
|
||||
.activated_getter_steps()
|
||||
}
|
||||
}
|
|
@ -314,6 +314,7 @@ pub(crate) mod eventsource;
|
|||
pub(crate) mod eventtarget;
|
||||
pub(crate) mod extendableevent;
|
||||
pub(crate) mod extendablemessageevent;
|
||||
pub(crate) mod fetchlaterresult;
|
||||
pub(crate) mod file;
|
||||
pub(crate) mod filelist;
|
||||
pub(crate) mod filereader;
|
||||
|
|
|
@ -92,7 +92,7 @@ impl Request {
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-request
|
||||
fn constructor(
|
||||
pub(crate) fn constructor(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
|
|
|
@ -111,10 +111,11 @@ use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{
|
|||
};
|
||||
use crate::dom::bindings::codegen::Bindings::MediaQueryListBinding::MediaQueryList_Binding::MediaQueryListMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::Report;
|
||||
use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInit;
|
||||
use crate::dom::bindings::codegen::Bindings::RequestBinding::{RequestInfo, RequestInit};
|
||||
use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
|
||||
use crate::dom::bindings::codegen::Bindings::WindowBinding::{
|
||||
self, FrameRequestCallback, ScrollBehavior, WindowMethods, WindowPostMessageOptions,
|
||||
self, DeferredRequestInit, FrameRequestCallback, ScrollBehavior, WindowMethods,
|
||||
WindowPostMessageOptions,
|
||||
};
|
||||
use crate::dom::bindings::codegen::UnionTypes::{
|
||||
RequestOrUSVString, TrustedScriptOrString, TrustedScriptOrStringOrFunction,
|
||||
|
@ -140,6 +141,7 @@ use crate::dom::document::{AnimationFrameCallback, Document};
|
|||
use crate::dom::element::Element;
|
||||
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::fetchlaterresult::FetchLaterResult;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::hashchangeevent::HashChangeEvent;
|
||||
use crate::dom::history::History;
|
||||
|
@ -1821,6 +1823,16 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
|
|||
fetch::Fetch(self.upcast(), input, init, comp, can_gc)
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
|
||||
fn FetchLater(
|
||||
&self,
|
||||
input: RequestInfo,
|
||||
init: RootedTraceableBox<DeferredRequestInit>,
|
||||
can_gc: CanGc,
|
||||
) -> Fallible<DomRoot<FetchLaterResult>> {
|
||||
fetch::FetchLater(self, input, init, can_gc)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bluetooth")]
|
||||
fn TestRunner(&self) -> DomRoot<TestRunner> {
|
||||
self.test_runner
|
||||
|
|
|
@ -2,13 +2,17 @@
|
|||
* 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 std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use base::id::WebViewId;
|
||||
use ipc_channel::ipc;
|
||||
use js::jsapi::{ExceptionStackBehavior, JS_IsExceptionPending};
|
||||
use js::jsval::UndefinedValue;
|
||||
use js::rust::HandleValue;
|
||||
use js::rust::wrappers::JS_SetPendingException;
|
||||
use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
|
||||
use net_traits::request::{
|
||||
CorsSettings, CredentialsMode, Destination, InsecureRequestsPolicy, Referrer,
|
||||
|
@ -20,6 +24,7 @@ use net_traits::{
|
|||
ResourceTimingType, cancel_async_fetch,
|
||||
};
|
||||
use servo_url::ServoUrl;
|
||||
use timers::TimerEventRequest;
|
||||
|
||||
use crate::body::BodyMixin;
|
||||
use crate::dom::abortsignal::AbortAlgorithm;
|
||||
|
@ -29,14 +34,17 @@ use crate::dom::bindings::codegen::Bindings::RequestBinding::{
|
|||
};
|
||||
use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
|
||||
use crate::dom::bindings::error::Error;
|
||||
use crate::dom::bindings::codegen::Bindings::WindowBinding::{DeferredRequestInit, WindowMethods};
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::import::module::SafeJSContext;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
|
||||
use crate::dom::bindings::reflector::DomGlobal;
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::bindings::trace::RootedTraceableBox;
|
||||
use crate::dom::csp::{GlobalCspReporting, Violation};
|
||||
use crate::dom::fetchlaterresult::FetchLaterResult;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::headers::Guard;
|
||||
use crate::dom::performanceresourcetiming::InitiatorType;
|
||||
|
@ -44,6 +52,7 @@ use crate::dom::promise::Promise;
|
|||
use crate::dom::request::Request;
|
||||
use crate::dom::response::Response;
|
||||
use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
|
||||
use crate::dom::window::Window;
|
||||
use crate::network_listener::{self, PreInvoke, ResourceTimingListener, submit_timing_data};
|
||||
use crate::realms::{InRealm, enter_realm};
|
||||
use crate::script_runtime::CanGc;
|
||||
|
@ -266,6 +275,189 @@ pub(crate) fn Fetch(
|
|||
promise
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#queue-a-deferred-fetch>
|
||||
fn queue_deferred_fetch(
|
||||
request: NetTraitsRequest,
|
||||
activate_after: Finite<f64>,
|
||||
global: &GlobalScope,
|
||||
) -> Arc<Mutex<DeferredFetchRecord>> {
|
||||
let trusted_global = Trusted::new(global);
|
||||
// Step 1. Populate request from client given request.
|
||||
// TODO
|
||||
// Step 2. Set request’s service-workers mode to "none".
|
||||
// TODO
|
||||
// Step 3. Set request’s keepalive to true.
|
||||
// TODO
|
||||
// Step 4. Let deferredRecord be a new deferred fetch record whose request is request, and whose notify invoked is onActivatedWithoutTermination.
|
||||
let deferred_record = Arc::new(Mutex::new(DeferredFetchRecord {
|
||||
request,
|
||||
global: trusted_global.clone(),
|
||||
invoke_state: Cell::new(DeferredFetchRecordInvokeState::Pending),
|
||||
activated: Cell::new(false),
|
||||
}));
|
||||
// Step 5. Append deferredRecord to request’s client’s fetch group’s deferred fetch records.
|
||||
// TODO
|
||||
// Step 6. If activateAfter is non-null, then run the following steps in parallel:
|
||||
let deferred_record_clone = deferred_record.clone();
|
||||
global.schedule_timer(TimerEventRequest {
|
||||
callback: Box::new(move || {
|
||||
// Step 6.2. Process deferredRecord.
|
||||
deferred_record_clone.lock().unwrap().process();
|
||||
|
||||
// Last step of https://fetch.spec.whatwg.org/#process-a-deferred-fetch
|
||||
//
|
||||
// Step 4. Queue a global task on the deferred fetch task source with
|
||||
// deferredRecord’s request’s client’s global object to run deferredRecord’s notify invoked.
|
||||
let deferred_record_clone = deferred_record_clone.clone();
|
||||
trusted_global
|
||||
.root()
|
||||
.task_manager()
|
||||
.deferred_fetch_task_source()
|
||||
.queue(task!(notify_deferred_record: move || {
|
||||
deferred_record_clone.lock().unwrap().activate();
|
||||
}));
|
||||
}),
|
||||
// Step 6.1. The user agent should wait until any of the following conditions is met:
|
||||
duration: Duration::from_millis(*activate_after as u64),
|
||||
});
|
||||
// Step 7. Return deferredRecord.
|
||||
deferred_record
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
|
||||
#[allow(non_snake_case, unsafe_code)]
|
||||
pub(crate) fn FetchLater(
|
||||
window: &Window,
|
||||
input: RequestInfo,
|
||||
init: RootedTraceableBox<DeferredRequestInit>,
|
||||
can_gc: CanGc,
|
||||
) -> Fallible<DomRoot<FetchLaterResult>> {
|
||||
let global_scope = window.upcast();
|
||||
// Step 1. Let requestObject be the result of invoking the initial value
|
||||
// of Request as constructor with input and init as arguments.
|
||||
let request_object = Request::constructor(global_scope, None, can_gc, input, &init.parent)?;
|
||||
// Step 2. If requestObject’s signal is aborted, then throw signal’s abort reason.
|
||||
let signal = request_object.Signal();
|
||||
if signal.aborted() {
|
||||
let cx = GlobalScope::get_cx();
|
||||
rooted!(in(*cx) let mut abort_reason = UndefinedValue());
|
||||
signal.Reason(cx, abort_reason.handle_mut());
|
||||
unsafe {
|
||||
assert!(!JS_IsExceptionPending(*cx));
|
||||
JS_SetPendingException(*cx, abort_reason.handle(), ExceptionStackBehavior::Capture);
|
||||
}
|
||||
return Err(Error::JSFailed);
|
||||
}
|
||||
// Step 3. Let request be requestObject’s request.
|
||||
let request = request_object.get_request();
|
||||
// Step 4. Let activateAfter be null.
|
||||
let mut activate_after = Finite::wrap(0_f64);
|
||||
// Step 5. If init is given and init["activateAfter"] exists, then set
|
||||
// activateAfter to init["activateAfter"].
|
||||
if let Some(init_activate_after) = init.activateAfter.as_ref() {
|
||||
activate_after = *init_activate_after;
|
||||
}
|
||||
// Step 6. If activateAfter is less than 0, then throw a RangeError.
|
||||
if *activate_after < 0.0 {
|
||||
return Err(Error::Range("activateAfter must be at least 0".to_owned()));
|
||||
}
|
||||
// Step 7. If this’s relevant global object’s associated document is not fully active, then throw a TypeError.
|
||||
if !window.Document().is_fully_active() {
|
||||
return Err(Error::Type("Document is not fully active".to_owned()));
|
||||
}
|
||||
let url = request.url();
|
||||
// Step 8. If request’s URL’s scheme is not an HTTP(S) scheme, then throw a TypeError.
|
||||
if !matches!(url.scheme(), "http" | "https") {
|
||||
return Err(Error::Type("URL is not http(s)".to_owned()));
|
||||
}
|
||||
// Step 9. If request’s URL is not a potentially trustworthy URL, then throw a SecurityError.
|
||||
if !url.is_potentially_trustworthy() {
|
||||
return Err(Error::Type("URL is not trustworthy".to_owned()));
|
||||
}
|
||||
// Step 10. If request’s body is not null, and request’s body length is null, then throw a TypeError.
|
||||
if let Some(body) = request.body.as_ref() {
|
||||
if body.len().is_none() {
|
||||
return Err(Error::Type("Body is null".to_owned()));
|
||||
}
|
||||
}
|
||||
// Step 11. If the available deferred-fetch quota given request’s client and request’s URL’s
|
||||
// origin is less than request’s total request length, then throw a "QuotaExceededError" DOMException.
|
||||
// TODO
|
||||
// Step 12. Let activated be false.
|
||||
// Step 13. Let deferredRecord be the result of calling queue a deferred fetch given request,
|
||||
// activateAfter, and the following step: set activated to true.
|
||||
let deferred_record = queue_deferred_fetch(request, activate_after, global_scope);
|
||||
// Step 14. Add the following abort steps to requestObject’s signal: Set deferredRecord’s invoke state to "aborted".
|
||||
signal.add(&AbortAlgorithm::FetchLater(deferred_record.clone()));
|
||||
// Step 15. Return a new FetchLaterResult whose activated getter steps are to return activated.
|
||||
Ok(FetchLaterResult::new(window, deferred_record, can_gc))
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#deferred-fetch-record-invoke-state>
|
||||
#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
|
||||
enum DeferredFetchRecordInvokeState {
|
||||
Pending,
|
||||
Sent,
|
||||
Aborted,
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#deferred-fetch-record>
|
||||
#[derive(MallocSizeOf)]
|
||||
pub(crate) struct DeferredFetchRecord {
|
||||
/// <https://fetch.spec.whatwg.org/#deferred-fetch-record-request>
|
||||
request: NetTraitsRequest,
|
||||
/// <https://fetch.spec.whatwg.org/#deferred-fetch-record-invoke-state>
|
||||
invoke_state: Cell<DeferredFetchRecordInvokeState>,
|
||||
global: Trusted<GlobalScope>,
|
||||
activated: Cell<bool>,
|
||||
}
|
||||
|
||||
impl DeferredFetchRecord {
|
||||
/// Part of step 13 of <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
|
||||
fn activate(&self) {
|
||||
// and the following step: set activated to true.
|
||||
self.activated.set(true);
|
||||
}
|
||||
/// Part of step 14 of <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
|
||||
pub(crate) fn abort(&self) {
|
||||
// Set deferredRecord’s invoke state to "aborted".
|
||||
self.invoke_state
|
||||
.set(DeferredFetchRecordInvokeState::Aborted);
|
||||
}
|
||||
/// Part of step 15 of <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
|
||||
pub(crate) fn activated_getter_steps(&self) -> bool {
|
||||
// whose activated getter steps are to return activated.
|
||||
self.activated.get()
|
||||
}
|
||||
/// <https://fetch.spec.whatwg.org/#process-a-deferred-fetch>
|
||||
fn process(&self) {
|
||||
// Step 1. If deferredRecord’s invoke state is not "pending", then return.
|
||||
if self.invoke_state.get() != DeferredFetchRecordInvokeState::Pending {
|
||||
return;
|
||||
}
|
||||
// Step 2. Set deferredRecord’s invoke state to "sent".
|
||||
self.invoke_state.set(DeferredFetchRecordInvokeState::Sent);
|
||||
// Step 3. Fetch deferredRecord’s request.
|
||||
let url = self.request.url().clone();
|
||||
let fetch_later_listener = Arc::new(Mutex::new(FetchLaterListener {
|
||||
url,
|
||||
resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
|
||||
global: self.global.clone(),
|
||||
}));
|
||||
let global = self.global.root();
|
||||
let _realm = enter_realm(&*global);
|
||||
let mut request_init = request_init_from_request(self.request.clone());
|
||||
request_init.policy_container =
|
||||
RequestPolicyContainer::PolicyContainer(global.policy_container());
|
||||
global.fetch(
|
||||
request_init,
|
||||
fetch_later_listener,
|
||||
global.task_manager().networking_task_source().to_sendable(),
|
||||
);
|
||||
// Step 4 is handled by caller
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
pub(crate) struct FetchContext {
|
||||
#[ignore_malloc_size_of = "unclear ownership semantics"]
|
||||
|
@ -453,6 +645,74 @@ impl ResourceTimingListener for FetchContext {
|
|||
}
|
||||
}
|
||||
|
||||
struct FetchLaterListener {
|
||||
/// URL of this request.
|
||||
url: ServoUrl,
|
||||
/// Timing data for this resource.
|
||||
resource_timing: ResourceFetchTiming,
|
||||
/// The global object fetching the report uri violation
|
||||
global: Trusted<GlobalScope>,
|
||||
}
|
||||
|
||||
impl FetchResponseListener for FetchLaterListener {
|
||||
fn process_request_body(&mut self, _: RequestId) {}
|
||||
|
||||
fn process_request_eof(&mut self, _: RequestId) {}
|
||||
|
||||
fn process_response(
|
||||
&mut self,
|
||||
_: RequestId,
|
||||
fetch_metadata: Result<FetchMetadata, NetworkError>,
|
||||
) {
|
||||
_ = fetch_metadata;
|
||||
}
|
||||
|
||||
fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
|
||||
_ = chunk;
|
||||
}
|
||||
|
||||
fn process_response_eof(
|
||||
&mut self,
|
||||
_: RequestId,
|
||||
response: Result<ResourceFetchTiming, NetworkError>,
|
||||
) {
|
||||
_ = response;
|
||||
}
|
||||
|
||||
fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
|
||||
&mut self.resource_timing
|
||||
}
|
||||
|
||||
fn resource_timing(&self) -> &ResourceFetchTiming {
|
||||
&self.resource_timing
|
||||
}
|
||||
|
||||
fn submit_resource_timing(&mut self) {
|
||||
network_listener::submit_timing(self, CanGc::note())
|
||||
}
|
||||
|
||||
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
|
||||
let global = self.resource_timing_global();
|
||||
global.report_csp_violations(violations, None, None);
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceTimingListener for FetchLaterListener {
|
||||
fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
|
||||
(InitiatorType::Fetch, self.url.clone())
|
||||
}
|
||||
|
||||
fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
|
||||
self.global.root()
|
||||
}
|
||||
}
|
||||
|
||||
impl PreInvoke for FetchLaterListener {
|
||||
fn should_invoke(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_headers_with_metadata(r: DomRoot<Response>, m: Metadata, can_gc: CanGc) {
|
||||
r.set_headers(m.headers, can_gc);
|
||||
r.set_status(&m.status);
|
||||
|
|
|
@ -137,6 +137,7 @@ impl TaskManager {
|
|||
task_source_functions!(self, clipboard_task_source, Clipboard);
|
||||
task_source_functions!(self, crypto_task_source, Crypto);
|
||||
task_source_functions!(self, database_access_task_source, DatabaseAccess);
|
||||
task_source_functions!(self, deferred_fetch_task_source, DeferredFetch);
|
||||
task_source_functions!(self, dom_manipulation_task_source, DOMManipulation);
|
||||
task_source_functions!(self, file_reading_task_source, FileReading);
|
||||
task_source_functions!(self, font_loading_task_source, FontLoading);
|
||||
|
|
|
@ -29,6 +29,8 @@ pub(crate) enum TaskSourceName {
|
|||
/// <https://w3c.github.io/webcrypto/#dfn-crypto-task-source-0>
|
||||
Crypto,
|
||||
DatabaseAccess,
|
||||
/// <https://fetch.spec.whatwg.org/#deferred-fetch-task-source>
|
||||
DeferredFetch,
|
||||
DOMManipulation,
|
||||
FileReading,
|
||||
/// <https://drafts.csswg.org/css-font-loading/#task-source>
|
||||
|
@ -62,6 +64,7 @@ impl From<TaskSourceName> for ScriptThreadEventCategory {
|
|||
TaskSourceName::Clipboard => ScriptThreadEventCategory::ScriptEvent,
|
||||
TaskSourceName::Crypto => ScriptThreadEventCategory::ScriptEvent,
|
||||
TaskSourceName::DatabaseAccess => ScriptThreadEventCategory::ScriptEvent,
|
||||
TaskSourceName::DeferredFetch => ScriptThreadEventCategory::NetworkEvent,
|
||||
TaskSourceName::DOMManipulation => ScriptThreadEventCategory::ScriptEvent,
|
||||
TaskSourceName::FileReading => ScriptThreadEventCategory::FileRead,
|
||||
TaskSourceName::FontLoading => ScriptThreadEventCategory::FontLoading,
|
||||
|
|
|
@ -659,7 +659,7 @@ DOMInterfaces = {
|
|||
},
|
||||
|
||||
'Window': {
|
||||
'canGc': ['CreateImageBitmap', 'CreateImageBitmap_', 'CookieStore', 'Fetch', 'Open', 'SetInterval', 'SetTimeout', 'Stop', 'TrustedTypes', 'WebdriverCallback', 'WebdriverException'],
|
||||
'canGc': ['CreateImageBitmap', 'CreateImageBitmap_', 'CookieStore', 'Fetch', 'FetchLater', 'Open', 'SetInterval', 'SetTimeout', 'Stop', 'TrustedTypes', 'WebdriverCallback', 'WebdriverException'],
|
||||
'inRealms': ['Fetch', 'GetOpener', 'WebdriverCallback', 'WebdriverException'],
|
||||
'additionalTraits': ['crate::interfaces::WindowHelpers'],
|
||||
},
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/* 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/. */
|
||||
|
||||
// https://fetch.spec.whatwg.org/#fetch-method
|
||||
[Exposed=Window]
|
||||
interface FetchLaterResult {
|
||||
readonly attribute boolean activated;
|
||||
};
|
|
@ -189,3 +189,12 @@ partial interface Window {
|
|||
dictionary WindowPostMessageOptions : StructuredSerializeOptions {
|
||||
USVString targetOrigin = "/";
|
||||
};
|
||||
|
||||
// https://fetch.spec.whatwg.org/#fetch-method
|
||||
dictionary DeferredRequestInit : RequestInit {
|
||||
DOMHighResTimeStamp activateAfter;
|
||||
};
|
||||
|
||||
partial interface Window {
|
||||
[NewObject, SecureContext, Throws] FetchLaterResult fetchLater(RequestInfo input, optional DeferredRequestInit init = {});
|
||||
};
|
||||
|
|
4
tests/wpt/include.ini
vendored
4
tests/wpt/include.ini
vendored
|
@ -140,6 +140,10 @@ skip: true
|
|||
skip: false
|
||||
[fetch]
|
||||
skip: false
|
||||
[fetch-later]
|
||||
skip: false
|
||||
[quota]
|
||||
skip: true
|
||||
[FileAPI]
|
||||
skip: false
|
||||
[focus]
|
||||
|
|
12
tests/wpt/meta/MANIFEST.json
vendored
12
tests/wpt/meta/MANIFEST.json
vendored
|
@ -674692,10 +674692,10 @@
|
|||
]
|
||||
]
|
||||
},
|
||||
"idlharness.any.js": [
|
||||
"idlharness.https.any.js": [
|
||||
"7b3c694e16ac3ec2776398067cf6ddbef949c969",
|
||||
[
|
||||
"fetch/api/idlharness.any.html",
|
||||
"fetch/api/idlharness.https.any.html",
|
||||
{
|
||||
"script_metadata": [
|
||||
[
|
||||
|
@ -674719,7 +674719,7 @@
|
|||
}
|
||||
],
|
||||
[
|
||||
"fetch/api/idlharness.any.serviceworker.html",
|
||||
"fetch/api/idlharness.https.any.serviceworker.html",
|
||||
{
|
||||
"script_metadata": [
|
||||
[
|
||||
|
@ -674743,7 +674743,7 @@
|
|||
}
|
||||
],
|
||||
[
|
||||
"fetch/api/idlharness.any.sharedworker.html",
|
||||
"fetch/api/idlharness.https.any.sharedworker.html",
|
||||
{
|
||||
"script_metadata": [
|
||||
[
|
||||
|
@ -674767,7 +674767,7 @@
|
|||
}
|
||||
],
|
||||
[
|
||||
"fetch/api/idlharness.any.worker.html",
|
||||
"fetch/api/idlharness.https.any.worker.html",
|
||||
{
|
||||
"script_metadata": [
|
||||
[
|
||||
|
@ -680157,7 +680157,7 @@
|
|||
]
|
||||
],
|
||||
"basic.https.window.js": [
|
||||
"f3ed42fe35a078942980ff44f66eee26a71f38cf",
|
||||
"afedbf5d4c4fb6eb069e650911014a10e0d43b4a",
|
||||
[
|
||||
"fetch/fetch-later/basic.https.window.html",
|
||||
{}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[idlharness.any.worker.html]
|
||||
[idlharness.https.any.html]
|
||||
[Request interface: attribute keepalive]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -24,10 +24,10 @@
|
|||
expected: FAIL
|
||||
|
||||
|
||||
[idlharness.any.sharedworker.html]
|
||||
[idlharness.https.any.serviceworker.html]
|
||||
expected: ERROR
|
||||
|
||||
[idlharness.any.html]
|
||||
[idlharness.https.any.worker.html]
|
||||
[Request interface: attribute keepalive]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -52,36 +52,6 @@
|
|||
[Request interface: new Request('about:blank') must inherit property "duplex" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[FetchLaterResult interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[FetchLaterResult interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[FetchLaterResult interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[FetchLaterResult interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[FetchLaterResult interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
[FetchLaterResult interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected: FAIL
|
||||
|
||||
[FetchLaterResult interface: attribute activated]
|
||||
expected: FAIL
|
||||
|
||||
[Window interface: operation fetchLater(RequestInfo, optional DeferredRequestInit)]
|
||||
expected: FAIL
|
||||
|
||||
[Window interface: window must inherit property "fetchLater(RequestInfo, optional DeferredRequestInit)" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Window interface: calling fetchLater(RequestInfo, optional DeferredRequestInit) on window with too few arguments must throw TypeError]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[idlharness.any.serviceworker.html]
|
||||
[idlharness.https.any.sharedworker.html]
|
||||
expected: ERROR
|
3
tests/wpt/meta/fetch/fetch-later/__dir__.ini
vendored
Normal file
3
tests/wpt/meta/fetch/fetch-later/__dir__.ini
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
prefs: [
|
||||
"dom_abort_controller_enabled:true",
|
||||
]
|
|
@ -1,6 +1,4 @@
|
|||
[activate-after.https.window.html]
|
||||
[fetchLater() sends out based on activateAfter.]
|
||||
expected: FAIL
|
||||
|
||||
expected: TIMEOUT
|
||||
[fetchLater() sends out based on activateAfter, even if document is in BFCache.]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
|
|
@ -1,69 +1,3 @@
|
|||
[basic.https.window.html]
|
||||
[fetchLater() cannot be called without request.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() with same-origin (https) URL does not throw.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() with http://localhost URL does not throw.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() with https://localhost URL does not throw.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() with http://127.0.0.1 URL does not throw.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() with https://127.0.0.1 URL does not throw.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() with http://[::1\] URL does not throw.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() with https://[::1\] URL does not throw.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() with https://example.com URL does not throw.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws SecurityError on non-trustworthy http URL.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws TypeError on file:// scheme.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws TypeError on ftp:// scheme.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws TypeError on ssh:// scheme.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws TypeError on wss:// scheme.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws TypeError on about: scheme.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws TypeError on javascript: scheme.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws TypeError on data: scheme.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws TypeError on blob: scheme.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws RangeError on negative activateAfter.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater()'s return tells the deferred request is not yet sent.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws TypeError when mutating its returned state.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() throws AbortError when its initial abort signal is aborted.]
|
||||
expected: FAIL
|
||||
|
||||
[fetchLater() does not throw error when it is aborted before sending.]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[header-referrer-no-referrer-when-downgrade.https.html]
|
||||
[Test referer header https://web-platform.test:8443]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[header-referrer-no-referrer.https.html]
|
||||
[Test referer header ]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[header-referrer-origin-when-cross-origin.https.html]
|
||||
[Test referer header https://web-platform.test:8443]
|
||||
expected: FAIL
|
||||
|
||||
[Test referer header https://www1.web-platform.test:8443]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[header-referrer-origin.https.html]
|
||||
[Test referer header https://www1.web-platform.test:8443]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[header-referrer-same-origin.https.html]
|
||||
[Test referer header ]
|
||||
expected: FAIL
|
||||
|
||||
[Test referer header https://www1.web-platform.test:8443]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[header-referrer-strict-origin-when-cross-origin.https.html]
|
||||
[Test referer header https://www1.web-platform.test:8443]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[header-referrer-strict-origin.https.html]
|
||||
[Test referer header https://web-platform.test:8443]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[header-referrer-unsafe-url.https.html]
|
||||
[Test referer header https://web-platform.test:8443]
|
||||
expected: FAIL
|
|
@ -1,4 +0,0 @@
|
|||
[iframe.https.window.html]
|
||||
expected: ERROR
|
||||
[A blank iframe can trigger fetchLater.]
|
||||
expected: FAIL
|
|
@ -1,36 +0,0 @@
|
|||
[new-window.https.window.html]
|
||||
[A blank window[target=''\][features=''\] can trigger fetchLater.]
|
||||
expected: FAIL
|
||||
|
||||
[A same-origin window[target=''\][features=''\] can trigger fetchLater.]
|
||||
expected: FAIL
|
||||
|
||||
[A cross-origin window[target=''\][features=''\] can trigger fetchLater.]
|
||||
expected: FAIL
|
||||
|
||||
[A blank window[target=''\][features='popup'\] can trigger fetchLater.]
|
||||
expected: FAIL
|
||||
|
||||
[A same-origin window[target=''\][features='popup'\] can trigger fetchLater.]
|
||||
expected: FAIL
|
||||
|
||||
[A cross-origin window[target=''\][features='popup'\] can trigger fetchLater.]
|
||||
expected: FAIL
|
||||
|
||||
[A blank window[target='_blank'\][features=''\] can trigger fetchLater.]
|
||||
expected: FAIL
|
||||
|
||||
[A same-origin window[target='_blank'\][features=''\] can trigger fetchLater.]
|
||||
expected: FAIL
|
||||
|
||||
[A cross-origin window[target='_blank'\][features=''\] can trigger fetchLater.]
|
||||
expected: FAIL
|
||||
|
||||
[A blank window[target='_blank'\][features='popup'\] can trigger fetchLater.]
|
||||
expected: FAIL
|
||||
|
||||
[A same-origin window[target='_blank'\][features='popup'\] can trigger fetchLater.]
|
||||
expected: FAIL
|
||||
|
||||
[A cross-origin window[target='_blank'\][features='popup'\] can trigger fetchLater.]
|
||||
expected: FAIL
|
|
@ -1,12 +1,3 @@
|
|||
[deferred-fetch-allowed-by-permissions-policy.https.window.html]
|
||||
[Permissions policy header: "deferred-fetch=*" allows fetchLater() in the top-level document.]
|
||||
expected: FAIL
|
||||
|
||||
[Permissions policy header: "deferred-fetch=*" allows fetchLater() in the same-origin iframe.]
|
||||
expected: FAIL
|
||||
|
||||
[Permissions policy header: "deferred-fetch=*" allows fetchLater() in the cross-origin iframe.]
|
||||
expected: FAIL
|
||||
|
||||
[Permissions policy header: "deferred-fetch=*" allow="deferred-fetch" allows fetchLater() in the cross-origin iframe.]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
[deferred-fetch-default-permissions-policy.https.window.html]
|
||||
[Default "deferred-fetch" permissions policy ["self"\] allows fetchLater() in the top-level document.]
|
||||
expected: FAIL
|
||||
|
||||
[Default "deferred-fetch" permissions policy ["self"\] allows fetchLater() in the same-origin iframe.]
|
||||
expected: FAIL
|
||||
|
||||
[Default "deferred-fetch-minimal" permissions policy ["*"\] allows fetchLater() in the cross-origin iframe.]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[csp-allowed.https.window.html]
|
||||
[FetchLater allowed by CSP should succeed]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[csp-blocked.https.window.html]
|
||||
[FetchLater blocked by CSP should reject]
|
||||
expected: FAIL
|
|
@ -1,15 +1,16 @@
|
|||
[send-on-deactivate.https.window.html]
|
||||
expected: TIMEOUT
|
||||
[fetchLater() sends on page entering BFCache if BackgroundSync is off.]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
||||
[Call fetchLater() when BFCached with activateAfter=0 sends immediately.]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
||||
[fetchLater() sends on navigating away a page w/o BFCache.]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
||||
[fetchLater() does not send aborted request on navigating away a page w/o BFCache.]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
||||
[fetchLater() with activateAfter=1m sends on page entering BFCache if BackgroundSync is off.]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[not-send-after-abort.https.window.html]
|
||||
[A discarded document does not send an already aborted fetchLater request.]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[send-multiple.https.window.html]
|
||||
[A discarded document sends all its fetchLater requests.]
|
||||
expected: FAIL
|
2
tests/wpt/mozilla/meta/MANIFEST.json
vendored
2
tests/wpt/mozilla/meta/MANIFEST.json
vendored
|
@ -13775,7 +13775,7 @@
|
|||
]
|
||||
],
|
||||
"interfaces.https.html": [
|
||||
"1397b723a1cb001521ac1b2032c380f1e02cf1f0",
|
||||
"027e138f4af5e2a13df9bf8c78765dcf57f6b611",
|
||||
[
|
||||
null,
|
||||
{}
|
||||
|
|
|
@ -92,6 +92,7 @@ test_interfaces([
|
|||
"Event",
|
||||
"EventSource",
|
||||
"EventTarget",
|
||||
"FetchLaterResult",
|
||||
"File",
|
||||
"FileList",
|
||||
"FileReader",
|
||||
|
|
|
@ -52,11 +52,8 @@ test(() => {
|
|||
}, `fetchLater() with https://example.com URL does not throw.`);
|
||||
|
||||
test(() => {
|
||||
const httpUrl = 'http://example.com';
|
||||
assert_throws_dom(
|
||||
'SecurityError', () => fetchLater(httpUrl),
|
||||
`should throw SecurityError for insecure http url ${httpUrl}`);
|
||||
}, `fetchLater() throws SecurityError on non-trustworthy http URL.`);
|
||||
assert_throws_js(TypeError, () => fetchLater('http://example.com'));
|
||||
}, `fetchLater() throws TypeError on non-trustworthy http URL.`);
|
||||
|
||||
test(() => {
|
||||
assert_throws_js(TypeError, () => fetchLater('file://tmp'));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue