From ba5f36b671d742be2390f182eb42777adb7a17ac Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Sun, 28 Sep 2025 09:22:09 +0200 Subject: [PATCH] script: Implement parsing of Link HTTP headers (#39052) The Link HTTP header can do the same as link elements, in that they can preload/prefetch/etc... This implements the basics of header parsing and hooks it up for preload. Note that we use a new nom-rfc8288 crate that implements the parsing behavior. However, that crate is too strict in that empty attributes (;; as part of the header) are discarded and resulting in a parsing failure. Therefore, we use its lenient parsing mode. Part of #35035 --------- Signed-off-by: Tim van der Lippe Signed-off-by: Tim van der Lippe Co-authored-by: Josh Matthews --- Cargo.lock | 22 + Cargo.toml | 1 + components/script/Cargo.toml | 1 + components/script/dom/html/htmllinkelement.rs | 118 +---- .../script/dom/linkprocessingoptions.rs | 164 ------ components/script/dom/mod.rs | 2 +- components/script/dom/processingoptions.rs | 482 +++++++++++++++++ components/script/dom/servoparser/mod.rs | 62 ++- components/script/dom/servoparser/prefetch.rs | 11 +- components/shared/net/mime_classifier.rs | 4 +- .../link-header-preload-delay-onload.html.ini | 3 - .../link-header-preload-non-html.html.ini | 3 - .../link-header-preload-nonce.html.ini | 3 - .../preload/preload-referrer-policy.html.ini | 483 ++++-------------- .../preload-resource-match.https.html.ini | 32 +- 15 files changed, 716 insertions(+), 675 deletions(-) delete mode 100644 components/script/dom/linkprocessingoptions.rs create mode 100644 components/script/dom/processingoptions.rs delete mode 100644 tests/wpt/meta/preload/link-header-preload-delay-onload.html.ini delete mode 100644 tests/wpt/meta/preload/link-header-preload-nonce.html.ini diff --git a/Cargo.lock b/Cargo.lock index 778d24cec46..6cd233c611c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5672,6 +5672,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "nom-language" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2de2bc5b451bfedaef92c90b8939a8fff5770bdcc1fafd6239d086aab8fa6b29" +dependencies = [ + "nom 8.0.0", +] + +[[package]] +name = "nom-rfc8288" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf26c6675a34266d3d71137fc5d876adb5e3d74963181397fd39742a8341576" +dependencies = [ + "itertools 0.14.0", + "nom 8.0.0", + "nom-language", + "thiserror 2.0.16", +] + [[package]] name = "noop_proc_macro" version = "0.3.0" @@ -7352,6 +7373,7 @@ dependencies = [ "mozjs", "net_traits", "nom 8.0.0", + "nom-rfc8288", "num-traits", "num_cpus", "parking_lot", diff --git a/Cargo.toml b/Cargo.toml index f7a049b9960..170a62c8f9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ mozangle = "0.5.3" net_traits = { path = "components/shared/net" } nix = "0.30" nom = "8.0.0" +nom-rfc8288 = "0.4.0" num-traits = "0.2" num_cpus = "1.17.0" openxr = "0.19" diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 41e8029c0a4..2af39466c81 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -93,6 +93,7 @@ mime = { workspace = true } mime_guess = { workspace = true } net_traits = { workspace = true } nom = { workspace = true } +nom-rfc8288 = { workspace = true } num-traits = { workspace = true } num_cpus = { workspace = true } parking_lot = { workspace = true } diff --git a/components/script/dom/html/htmllinkelement.rs b/components/script/dom/html/htmllinkelement.rs index b023442c876..6cfc17091c7 100644 --- a/components/script/dom/html/htmllinkelement.rs +++ b/components/script/dom/html/htmllinkelement.rs @@ -24,7 +24,6 @@ use pixels::PixelFormat; use script_bindings::root::Dom; use servo_arc::Arc; use servo_url::ServoUrl; -use strum_macros::IntoStaticStr; use style::attr::AttrValue; use style::stylesheets::Stylesheet; use stylo_atoms::Atom; @@ -50,10 +49,12 @@ use crate::dom::element::{ set_cross_origin_attribute, }; use crate::dom::html::htmlelement::HTMLElement; -use crate::dom::linkprocessingoptions::LinkProcessingOptions; use crate::dom::medialist::MediaList; use crate::dom::node::{BindContext, Node, NodeTraits, UnbindContext}; use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::processingoptions::{ + LinkFetchContext, LinkFetchContextType, LinkProcessingOptions, +}; use crate::dom::stylesheet::StyleSheet as DOMStyleSheet; use crate::dom::types::{EventTarget, GlobalScope}; use crate::dom::virtualmethods::VirtualMethods; @@ -207,6 +208,10 @@ impl HTMLLinkElement { } self.cssom_stylesheet.set(None); } + + pub(crate) fn line_number(&self) -> u32 { + self.line_number as u32 + } } fn get_attr(element: &Element, local_name: &LocalName) -> Option { @@ -532,7 +537,8 @@ impl HTMLLinkElement { let document = self.upcast::().owner_doc(); let fetch_context = LinkFetchContext { url, - link: Trusted::new(self), + link: Some(Trusted::new(self)), + global: Trusted::new(&document.global()), resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), type_: LinkFetchContextType::Prefetch, }; @@ -790,19 +796,22 @@ impl HTMLLinkElement { return; }; let url = request.url.clone(); + let document = self.upcast::().owner_doc(); let fetch_context = LinkFetchContext { url, - link: Trusted::new(self), + link: Some(Trusted::new(self)), + global: Trusted::new(&document.global()), resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), type_: LinkFetchContextType::Preload, }; - self.upcast::() - .owner_doc() - .fetch_background(request, fetch_context); + document.fetch_background(request, fetch_context); } /// - fn fire_event_after_response(&self, response: Result) { + pub(crate) fn fire_event_after_response( + &self, + response: Result, + ) { // Step 3.1 If response is a network error, fire an event named error at el. // Otherwise, fire an event named load at el. if response.is_err() { @@ -1084,96 +1093,3 @@ impl PreInvoke for FaviconFetchContext { true } } - -#[derive(Clone, IntoStaticStr)] -#[strum(serialize_all = "lowercase")] -enum LinkFetchContextType { - Prefetch, - Preload, -} - -impl From for InitiatorType { - fn from(other: LinkFetchContextType) -> Self { - let name: &'static str = other.into(); - InitiatorType::LocalName(name.to_owned()) - } -} - -struct LinkFetchContext { - /// The `` element that caused this prefetch operation - link: Trusted, - - resource_timing: ResourceFetchTiming, - - /// The url being prefetched - url: ServoUrl, - - /// The type of fetching we perform, used when report timings. - type_: LinkFetchContextType, -} - -impl FetchResponseListener for LinkFetchContext { - fn process_request_body(&mut self, _: RequestId) {} - - fn process_request_eof(&mut self, _: RequestId) {} - - fn process_response( - &mut self, - _: RequestId, - fetch_metadata: Result, - ) { - _ = fetch_metadata; - } - - fn process_response_chunk(&mut self, _: RequestId, chunk: Vec) { - _ = chunk; - } - - /// Step 7 of - /// and step 3.1 of - fn process_response_eof( - &mut self, - _: RequestId, - response: Result, - ) { - self.link.root().fire_event_after_response(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) { - submit_timing(self, CanGc::note()) - } - - fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { - let global = &self.resource_timing_global(); - let link = self.link.root(); - let source_position = link - .upcast::() - .compute_source_position(link.line_number as u32); - global.report_csp_violations(violations, None, Some(source_position)); - } -} - -impl ResourceTimingListener for LinkFetchContext { - fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { - (self.type_.clone().into(), self.url.clone()) - } - - fn resource_timing_global(&self) -> DomRoot { - self.link.root().upcast::().owner_doc().global() - } -} - -impl PreInvoke for LinkFetchContext { - fn should_invoke(&self) -> bool { - // Prefetch and preload requests are never aborted. - true - } -} diff --git a/components/script/dom/linkprocessingoptions.rs b/components/script/dom/linkprocessingoptions.rs deleted file mode 100644 index 59a409a1757..00000000000 --- a/components/script/dom/linkprocessingoptions.rs +++ /dev/null @@ -1,164 +0,0 @@ -/* 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::str::FromStr; - -use base::id::WebViewId; -use mime::Mime; -use net_traits::ReferrerPolicy; -use net_traits::mime_classifier::{MediaType, MimeClassifier}; -use net_traits::policy_container::PolicyContainer; -use net_traits::request::{ - CorsSettings, Destination, Initiator, InsecureRequestsPolicy, Referrer, RequestBuilder, -}; -use servo_url::{ImmutableOrigin, ServoUrl}; - -use crate::fetch::create_a_potential_cors_request; - -/// -pub(crate) struct LinkProcessingOptions { - pub(crate) href: String, - pub(crate) destination: Option, - pub(crate) integrity: String, - pub(crate) link_type: String, - pub(crate) cryptographic_nonce_metadata: String, - pub(crate) cross_origin: Option, - pub(crate) referrer_policy: ReferrerPolicy, - pub(crate) policy_container: PolicyContainer, - pub(crate) source_set: Option<()>, - pub(crate) base_url: ServoUrl, - pub(crate) origin: ImmutableOrigin, - pub(crate) insecure_requests_policy: InsecureRequestsPolicy, - pub(crate) has_trustworthy_ancestor_origin: bool, - // Some fields that we don't need yet are missing -} - -impl LinkProcessingOptions { - /// - pub(crate) fn create_link_request(self, webview_id: WebViewId) -> Option { - // Step 1. Assert: options's href is not the empty string. - assert!(!self.href.is_empty()); - - // Step 2. If options's destination is null, then return null. - let destination = self.destination?; - - // Step 3. Let url be the result of encoding-parsing a URL given options's href, relative to options's base URL. - let Ok(url) = ServoUrl::parse_with_base(Some(&self.base_url), &self.href) else { - // Step 4. If url is failure, then return null. - return None; - }; - - // Step 5. Let request be the result of creating a potential-CORS request given - // url, options's destination, and options's crossorigin. - // Step 6. Set request's policy container to options's policy container. - // Step 7. Set request's integrity metadata to options's integrity. - // Step 8. Set request's cryptographic nonce metadata to options's cryptographic nonce metadata. - // Step 9. Set request's referrer policy to options's referrer policy. - // FIXME: Step 10. Set request's client to options's environment. - // FIXME: Step 11. Set request's priority to options's fetch priority. - // FIXME: Use correct referrer - let builder = create_a_potential_cors_request( - Some(webview_id), - url, - destination, - self.cross_origin, - None, - Referrer::NoReferrer, - self.insecure_requests_policy, - self.has_trustworthy_ancestor_origin, - self.policy_container, - ) - .initiator(Initiator::Link) - .origin(self.origin) - .integrity_metadata(self.integrity) - .cryptographic_nonce_metadata(self.cryptographic_nonce_metadata) - .referrer_policy(self.referrer_policy); - - // Step 12. Return request. - Some(builder) - } - - /// - pub(crate) fn type_matches_destination(&self) -> bool { - // Step 1. If type is an empty string, then return true. - if self.link_type.is_empty() { - return true; - } - // Step 2. If destination is "fetch", then return true. - // - // Fetch is handled as an empty string destination in the spec: - // https://fetch.spec.whatwg.org/#concept-potential-destination-translate - let Some(destination) = self.destination else { - return false; - }; - if destination == Destination::None { - return true; - } - // Step 3. Let mimeTypeRecord be the result of parsing type. - let Ok(mime_type_record) = Mime::from_str(&self.link_type) else { - // Step 4. If mimeTypeRecord is failure, then return false. - return false; - }; - // Step 5. If mimeTypeRecord is not supported by the user agent, then return false. - // - // We currently don't check if we actually support the mime type. Only if we can classify - // it according to the spec. - let Some(mime_type) = MimeClassifier::get_media_type(&mime_type_record) else { - return false; - }; - // Step 6. If any of the following are true: - if - // destination is "audio" or "video", and mimeTypeRecord is an audio or video MIME type; - ((destination == Destination::Audio || destination == Destination::Video) && - mime_type == MediaType::AudioVideo) - // destination is a script-like destination and mimeTypeRecord is a JavaScript MIME type; - || (destination.is_script_like() && mime_type == MediaType::JavaScript) - // destination is "image" and mimeTypeRecord is an image MIME type; - || (destination == Destination::Image && mime_type == MediaType::Image) - // destination is "font" and mimeTypeRecord is a font MIME type; - || (destination == Destination::Font && mime_type == MediaType::Font) - // destination is "json" and mimeTypeRecord is a JSON MIME type; - || (destination == Destination::Json && mime_type == MediaType::Json) - // destination is "style" and mimeTypeRecord's essence is text/css; or - || (destination == Destination::Style && mime_type_record == mime::TEXT_CSS) - // destination is "track" and mimeTypeRecord's essence is text/vtt, - || (destination == Destination::Track && mime_type_record.essence_str() == "text/vtt") - { - // then return true. - return true; - } - // Step 7. Return false. - false - } - - /// - pub(crate) fn preload(self, webview_id: WebViewId) -> Option { - // Step 1. If options's type doesn't match options's destination, then return. - // - // Handled by callers, since we need to check the previous destination type - assert!(self.type_matches_destination()); - // Step 2. If options's destination is "image" and options's source set is not null, - // then set options's href to the result of selecting an image source from options's source set. - // TODO - // Step 3. Let request be the result of creating a link request given options. - let Some(request) = self.create_link_request(webview_id) else { - // Step 4. If request is null, then return. - return None; - }; - // Step 5. Let unsafeEndTime be 0. - // TODO - // Step 6. Let entry be a new preload entry whose integrity metadata is options's integrity. - // TODO - // Step 7. Let key be the result of creating a preload key given request. - // TODO - // Step 8. If options's document is "pending", then set request's initiator type to "early hint". - // TODO - // Step 9. Let controller be null. - // Step 10. Let reportTiming given a Document document be to report timing for controller - // given document's relevant global object. - // Step 11. Set controller to the result of fetching request, with processResponseConsumeBody - // set to the following steps given a response response and null, failure, or a byte sequence bodyBytes: - Some(request.clone()) - } -} diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index f80d9be4eaf..c4bd5394e9a 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -347,7 +347,6 @@ pub(crate) mod inputevent; pub(crate) mod intersectionobserver; pub(crate) mod intersectionobserverentry; pub(crate) mod keyboardevent; -pub(crate) mod linkprocessingoptions; pub(crate) mod location; pub(crate) mod mediadeviceinfo; pub(crate) mod mediadevices; @@ -403,6 +402,7 @@ pub(crate) mod pluginarray; pub(crate) mod pointerevent; pub(crate) mod popstateevent; pub(crate) mod processinginstruction; +pub(crate) mod processingoptions; pub(crate) mod progressevent; #[allow(dead_code)] pub(crate) mod promise; diff --git a/components/script/dom/processingoptions.rs b/components/script/dom/processingoptions.rs new file mode 100644 index 00000000000..d4a75305ba0 --- /dev/null +++ b/components/script/dom/processingoptions.rs @@ -0,0 +1,482 @@ +/* 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::str::FromStr; + +use base::id::WebViewId; +use cssparser::match_ignore_ascii_case; +use http::header::HeaderMap; +use hyper_serde::Serde; +use mime::Mime; +use net_traits::fetch::headers::get_decode_and_split_header_name; +use net_traits::mime_classifier::{MediaType, MimeClassifier}; +use net_traits::policy_container::PolicyContainer; +use net_traits::request::{ + CorsSettings, Destination, Initiator, InsecureRequestsPolicy, Referrer, RequestBuilder, + RequestId, +}; +use net_traits::{ + FetchMetadata, FetchResponseListener, NetworkError, ReferrerPolicy, ResourceFetchTiming, + ResourceTimingType, +}; +pub use nom_rfc8288::complete::LinkDataOwned as LinkHeader; +use nom_rfc8288::complete::link_lenient as parse_link_header; +use servo_url::{ImmutableOrigin, ServoUrl}; +use strum_macros::IntoStaticStr; + +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomGlobal; +use crate::dom::bindings::root::DomRoot; +use crate::dom::csp::{GlobalCspReporting, Violation}; +use crate::dom::document::{Document, determine_policy_for_token}; +use crate::dom::element::Element; +use crate::dom::globalscope::GlobalScope; +use crate::dom::medialist::MediaList; +use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::types::HTMLLinkElement; +use crate::fetch::create_a_potential_cors_request; +use crate::network_listener::{PreInvoke, ResourceTimingListener, submit_timing}; +use crate::script_runtime::CanGc; + +trait ValueForKeyInLinkHeader { + fn has_key_in_link_header(&self, key: &str) -> bool; + fn value_for_key_in_link_header(&self, key: &str) -> Option<&str>; +} + +impl ValueForKeyInLinkHeader for LinkHeader { + fn has_key_in_link_header(&self, key: &str) -> bool { + self.params.iter().any(|p| p.key == key) + } + fn value_for_key_in_link_header(&self, key: &str) -> Option<&str> { + let param = self.params.iter().find(|p| p.key == key)?; + param.val.as_deref() + } +} + +#[derive(PartialEq)] +pub(crate) enum LinkProcessingPhase { + Media, + PreMedia, +} + +/// +#[derive(Debug)] +pub(crate) struct LinkProcessingOptions { + pub(crate) href: String, + pub(crate) destination: Option, + pub(crate) integrity: String, + pub(crate) link_type: String, + pub(crate) cryptographic_nonce_metadata: String, + pub(crate) cross_origin: Option, + pub(crate) referrer_policy: ReferrerPolicy, + pub(crate) policy_container: PolicyContainer, + pub(crate) source_set: Option<()>, + pub(crate) base_url: ServoUrl, + pub(crate) origin: ImmutableOrigin, + pub(crate) insecure_requests_policy: InsecureRequestsPolicy, + pub(crate) has_trustworthy_ancestor_origin: bool, + // Some fields that we don't need yet are missing +} + +impl LinkProcessingOptions { + /// + fn apply_link_options_from_parsed_header(&mut self, link_object: &LinkHeader) { + // Step 1. If attribs["as"] exists, then set options's destination to the result of translating attribs["as"]. + if let Some(as_) = link_object.value_for_key_in_link_header("as") { + self.destination = Some(Self::translate_a_preload_destination(as_)); + } + // Step 2. If attribs["crossorigin"] exists and is an ASCII case-insensitive match for one of the + // CORS settings attribute keywords, then set options's crossorigin to the CORS settings attribute + // state corresponding to that keyword. + if let Some(cross_origin) = link_object.value_for_key_in_link_header("crossorigin") { + self.cross_origin = determine_cors_settings_for_token(cross_origin); + } + // Step 3. If attribs["integrity"] exists, then set options's integrity to attribs["integrity"]. + if let Some(integrity) = link_object.value_for_key_in_link_header("integrity") { + self.integrity = integrity.to_owned(); + } + // Step 4. If attribs["referrerpolicy"] exists and is an ASCII case-insensitive match for + // some referrer policy, then set options's referrer policy to that referrer policy. + if let Some(referrer_policy) = link_object.value_for_key_in_link_header("referrerpolicy") { + self.referrer_policy = determine_policy_for_token(referrer_policy); + } + // Step 5. If attribs["nonce"] exists, then set options's nonce to attribs["nonce"]. + if let Some(nonce) = link_object.value_for_key_in_link_header("nonce") { + self.cryptographic_nonce_metadata = nonce.to_owned(); + } + // Step 6. If attribs["type"] exists, then set options's type to attribs["type"]. + if let Some(link_type) = link_object.value_for_key_in_link_header("type") { + self.link_type = link_type.to_owned(); + } + // Step 7. If attribs["fetchpriority"] exists and is an ASCII case-insensitive match + // for a fetch priority attribute keyword, then set options's fetch priority to that + // fetch priority attribute keyword. + // TODO + } + + /// + fn process_link_header(self, rel: &str, document: &Document) { + if rel == "preload" { + // https://html.spec.whatwg.org/multipage/#link-type-preload:process-a-link-header + // The process a link header step for this type of link given a link processing options options + // is to preload options. + if !self.type_matches_destination() { + return; + } + let Some(request) = self.preload(document.window().webview_id()) else { + return; + }; + let url = request.url.clone(); + let fetch_context = LinkFetchContext { + url, + link: None, + global: Trusted::new(&document.global()), + resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), + type_: LinkFetchContextType::Preload, + }; + document.fetch_background(request, fetch_context); + } + } + + /// + pub(crate) fn translate_a_preload_destination(potential_destination: &str) -> Destination { + match potential_destination { + "fetch" => Destination::None, + "font" => Destination::Font, + "image" => Destination::Image, + "script" => Destination::Script, + "track" => Destination::Track, + _ => Destination::None, + } + } + + /// + pub(crate) fn create_link_request(self, webview_id: WebViewId) -> Option { + // Step 1. Assert: options's href is not the empty string. + assert!(!self.href.is_empty()); + + // Step 2. If options's destination is null, then return null. + let destination = self.destination?; + + // Step 3. Let url be the result of encoding-parsing a URL given options's href, relative to options's base URL. + let Ok(url) = ServoUrl::parse_with_base(Some(&self.base_url), &self.href) else { + // Step 4. If url is failure, then return null. + return None; + }; + + // Step 5. Let request be the result of creating a potential-CORS request given + // url, options's destination, and options's crossorigin. + // Step 6. Set request's policy container to options's policy container. + // Step 7. Set request's integrity metadata to options's integrity. + // Step 8. Set request's cryptographic nonce metadata to options's cryptographic nonce metadata. + // Step 9. Set request's referrer policy to options's referrer policy. + // FIXME: Step 10. Set request's client to options's environment. + // FIXME: Step 11. Set request's priority to options's fetch priority. + // FIXME: Use correct referrer + let builder = create_a_potential_cors_request( + Some(webview_id), + url, + destination, + self.cross_origin, + None, + Referrer::NoReferrer, + self.insecure_requests_policy, + self.has_trustworthy_ancestor_origin, + self.policy_container, + ) + .initiator(Initiator::Link) + .origin(self.origin) + .integrity_metadata(self.integrity) + .cryptographic_nonce_metadata(self.cryptographic_nonce_metadata) + .referrer_policy(self.referrer_policy); + + // Step 12. Return request. + Some(builder) + } + + /// + pub(crate) fn type_matches_destination(&self) -> bool { + // Step 1. If type is an empty string, then return true. + if self.link_type.is_empty() { + return true; + } + // Step 2. If destination is "fetch", then return true. + // + // Fetch is handled as an empty string destination in the spec: + // https://fetch.spec.whatwg.org/#concept-potential-destination-translate + let Some(destination) = self.destination else { + return false; + }; + if destination == Destination::None { + return true; + } + // Step 3. Let mimeTypeRecord be the result of parsing type. + let Ok(mime_type_record) = Mime::from_str(&self.link_type) else { + // Step 4. If mimeTypeRecord is failure, then return false. + return false; + }; + // Step 5. If mimeTypeRecord is not supported by the user agent, then return false. + // + // We currently don't check if we actually support the mime type. Only if we can classify + // it according to the spec. + let Some(mime_type) = MimeClassifier::get_media_type(&mime_type_record) else { + return false; + }; + // Step 6. If any of the following are true: + if + // destination is "audio" or "video", and mimeTypeRecord is an audio or video MIME type; + ((destination == Destination::Audio || destination == Destination::Video) && + mime_type == MediaType::AudioVideo) + // destination is a script-like destination and mimeTypeRecord is a JavaScript MIME type; + || (destination.is_script_like() && mime_type == MediaType::JavaScript) + // destination is "image" and mimeTypeRecord is an image MIME type; + || (destination == Destination::Image && mime_type == MediaType::Image) + // destination is "font" and mimeTypeRecord is a font MIME type; + || (destination == Destination::Font && mime_type == MediaType::Font) + // destination is "json" and mimeTypeRecord is a JSON MIME type; + || (destination == Destination::Json && mime_type == MediaType::Json) + // destination is "style" and mimeTypeRecord's essence is text/css; or + || (destination == Destination::Style && mime_type_record == mime::TEXT_CSS) + // destination is "track" and mimeTypeRecord's essence is text/vtt, + || (destination == Destination::Track && mime_type_record.essence_str() == "text/vtt") + { + // then return true. + return true; + } + // Step 7. Return false. + false + } + + /// + pub(crate) fn preload(self, webview_id: WebViewId) -> Option { + // Step 1. If options's type doesn't match options's destination, then return. + // + // Handled by callers, since we need to check the previous destination type + assert!(self.type_matches_destination()); + // Step 2. If options's destination is "image" and options's source set is not null, + // then set options's href to the result of selecting an image source from options's source set. + // TODO + // Step 3. Let request be the result of creating a link request given options. + let Some(request) = self.create_link_request(webview_id) else { + // Step 4. If request is null, then return. + return None; + }; + // Step 5. Let unsafeEndTime be 0. + // TODO + // Step 6. Let entry be a new preload entry whose integrity metadata is options's integrity. + // TODO + // Step 7. Let key be the result of creating a preload key given request. + // TODO + // Step 8. If options's document is "pending", then set request's initiator type to "early hint". + // TODO + // Step 9. Let controller be null. + // Step 10. Let reportTiming given a Document document be to report timing for controller + // given document's relevant global object. + // Step 11. Set controller to the result of fetching request, with processResponseConsumeBody + // set to the following steps given a response response and null, failure, or a byte sequence bodyBytes: + Some(request.clone()) + } +} + +pub(crate) fn determine_cors_settings_for_token(token: &str) -> Option { + match_ignore_ascii_case! { token, + "anonymous" => Some(CorsSettings::Anonymous), + "use-credentials" => Some(CorsSettings::UseCredentials), + _ => None, + } +} + +/// +pub(crate) fn extract_links_from_headers(headers: &Option>) -> Vec { + // Step 1. Let links be a new list. + let mut links = Vec::new(); + let Some(headers) = headers else { + return links; + }; + // Step 2. Let rawLinkHeaders be the result of getting, decoding, and splitting `Link` from headers. + let Some(raw_link_headers) = get_decode_and_split_header_name("Link", headers) else { + return links; + }; + // Step 3. For each linkHeader of rawLinkHeaders: + for link_header in raw_link_headers { + // Step 3.1. Let linkObject be the result of parsing linkHeader. [WEBLINK] + let Ok(parsed_link_header) = parse_link_header(&link_header) else { + continue; + }; + for link_object in parsed_link_header { + let Some(link_object) = link_object else { + // Step 3.2. If linkObject["target_uri"] does not exist, then continue. + continue; + }; + // Step 3.3. Append linkObject to links. + links.push(link_object.to_owned()); + } + } + // Step 4. Return links. + links +} + +/// +pub(crate) fn process_link_headers( + link_headers: &[LinkHeader], + document: &Document, + phase: LinkProcessingPhase, +) { + // Step 1. Let links be the result of extracting links from response's header list. + // + // Already performed once when parsing headers by caller + // Step 2. For each linkObject in links: + for link_object in link_headers { + // Step 2.1. Let rel be linkObject["relation_type"]. + let Some(rel) = link_object.value_for_key_in_link_header("rel") else { + continue; + }; + // Step 2.2. Let attribs be linkObject["target_attributes"]. + // + // Not applicable, that's in `link_object.params` + // Step 2.3. Let expectedPhase be "media" if either "srcset", "imagesrcset", + // or "media" exist in attribs; otherwise "pre-media". + let expected_phase = if link_object.has_key_in_link_header("srcset") || + link_object.has_key_in_link_header("imagesrcset") || + link_object.has_key_in_link_header("media") + { + LinkProcessingPhase::Media + } else { + LinkProcessingPhase::PreMedia + }; + // Step 2.4. If expectedPhase is not phase, then continue. + if expected_phase != phase { + continue; + } + // Step 2.5. If attribs["media"] exists and attribs["media"] does not match the environment, then continue. + if let Some(media) = link_object.value_for_key_in_link_header("media") { + if !MediaList::matches_environment(document, media) { + continue; + } + } + // Step 2.6. Let options be a new link processing options with + let mut options = LinkProcessingOptions { + href: link_object.url.clone(), + destination: None, + integrity: String::new(), + link_type: String::new(), + cryptographic_nonce_metadata: String::new(), + cross_origin: None, + referrer_policy: ReferrerPolicy::EmptyString, + policy_container: document.policy_container().to_owned(), + source_set: None, + origin: document.origin().immutable().to_owned(), + base_url: document.base_url(), + insecure_requests_policy: document.insecure_requests_policy(), + has_trustworthy_ancestor_origin: document.has_trustworthy_ancestor_or_current_origin(), + }; + // Step 2.7. Apply link options from parsed header attributes to options given attribs. + options.apply_link_options_from_parsed_header(link_object); + // Step 2.8. If attribs["imagesrcset"] exists and attribs["imagesizes"] exists, + // then set options's source set to the result of creating a source set given + // linkObject["target_uri"], attribs["imagesrcset"], attribs["imagesizes"], and null. + // TODO + // Step 2.9. Run the process a link header steps for rel given options. + options.process_link_header(rel, document); + } +} + +#[derive(Clone, IntoStaticStr)] +#[strum(serialize_all = "lowercase")] +pub(crate) enum LinkFetchContextType { + Prefetch, + Preload, +} + +impl From for InitiatorType { + fn from(other: LinkFetchContextType) -> Self { + let name: &'static str = other.into(); + InitiatorType::LocalName(name.to_owned()) + } +} + +pub(crate) struct LinkFetchContext { + /// The `` element (if any) that caused this fetch + pub(crate) link: Option>, + + pub(crate) global: Trusted, + + pub(crate) resource_timing: ResourceFetchTiming, + + /// The url being prefetched + pub(crate) url: ServoUrl, + + /// The type of fetching we perform, used when report timings. + pub(crate) type_: LinkFetchContextType, +} + +impl FetchResponseListener for LinkFetchContext { + fn process_request_body(&mut self, _: RequestId) {} + + fn process_request_eof(&mut self, _: RequestId) {} + + fn process_response( + &mut self, + _: RequestId, + fetch_metadata: Result, + ) { + _ = fetch_metadata; + } + + fn process_response_chunk(&mut self, _: RequestId, chunk: Vec) { + _ = chunk; + } + + /// Step 7 of + /// and step 3.1 of + fn process_response_eof( + &mut self, + _: RequestId, + response: Result, + ) { + if let Some(link) = self.link.as_ref() { + link.root().fire_event_after_response(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) { + submit_timing(self, CanGc::note()) + } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + let source_position = self.link.as_ref().map(|link| { + let link = link.root(); + link.upcast::() + .compute_source_position(link.line_number()) + }); + global.report_csp_violations(violations, None, source_position); + } +} + +impl ResourceTimingListener for LinkFetchContext { + fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { + (self.type_.clone().into(), self.url.clone()) + } + + fn resource_timing_global(&self) -> DomRoot { + self.global.root() + } +} + +impl PreInvoke for LinkFetchContext { + fn should_invoke(&self) -> bool { + // Prefetch and preload requests are never aborted. + true + } +} diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 3bb1569eb64..ef894f6047e 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -66,6 +66,7 @@ use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLD use crate::dom::documentfragment::DocumentFragment; use crate::dom::documenttype::DocumentType; use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator}; +use crate::dom::globalscope::GlobalScope; use crate::dom::html::htmlformelement::{FormControlElementHelpers, HTMLFormElement}; use crate::dom::html::htmlimageelement::HTMLImageElement; use crate::dom::html::htmlinputelement::HTMLInputElement; @@ -75,6 +76,9 @@ use crate::dom::node::{Node, ShadowIncluding}; use crate::dom::performanceentry::PerformanceEntry; use crate::dom::performancenavigationtiming::PerformanceNavigationTiming; use crate::dom::processinginstruction::ProcessingInstruction; +use crate::dom::processingoptions::{ + LinkHeader, LinkProcessingPhase, extract_links_from_headers, process_link_headers, +}; use crate::dom::reportingendpoint::ReportingEndpoint; use crate::dom::shadowroot::IsUserAgentWidget; use crate::dom::text::Text; @@ -856,6 +860,8 @@ struct NavigationParams { policy_container: PolicyContainer, /// content-type of this document, if known. Otherwise need to sniff it content_type: Option, + /// link headers from the response + link_headers: Vec, /// final_sandboxing_flag_set: SandboxingFlagSet, /// @@ -896,6 +902,7 @@ impl ParserContext { navigation_params: NavigationParams { policy_container: Default::default(), content_type: None, + link_headers: vec![], final_sandboxing_flag_set: SandboxingFlagSet::empty(), resource_header: vec![], }, @@ -928,6 +935,31 @@ impl ParserContext { // Step 9. Let document be a new Document, with document.set_policy_container(self.navigation_params.policy_container.clone()); document.set_active_sandboxing_flag_set(self.navigation_params.final_sandboxing_flag_set); + // Step 17. Process link headers given document, navigationParams's response, and "pre-media". + process_link_headers( + &self.navigation_params.link_headers, + document, + LinkProcessingPhase::PreMedia, + ); + } + + /// Part of various load document methods + fn process_link_headers_in_media_phase_with_task(&mut self, document: &Document) { + // The first task that the networking task source places on the task queue + // while fetching runs must process link headers given document, + // navigationParams's response, and "media", after the task has been processed by the HTML parser. + let link_headers = std::mem::take(&mut self.navigation_params.link_headers); + if !link_headers.is_empty() { + let window = document.window(); + let document = Trusted::new(document); + window + .upcast::() + .task_manager() + .networking_task_source() + .queue(task!(process_link_headers_task: move || { + process_link_headers(&link_headers, &document.root(), LinkProcessingPhase::Media); + })); + } } /// @@ -987,24 +1019,32 @@ impl ParserContext { } /// - fn load_html_document(&self, parser: &ServoParser) { + fn load_html_document(&mut self, parser: &ServoParser) { // Step 1. Let document be the result of creating and initializing a // Document object given "html", "text/html", and navigationParams. self.initialize_document_object(&parser.document); + // The first task that the networking task source places on the task queue while fetching + // runs must process link headers given document, navigationParams's response, and "media", + // after the task has been processed by the HTML parser. + self.process_link_headers_in_media_phase_with_task(&parser.document); } /// - fn load_xml_document(&self, parser: &ServoParser) { + fn load_xml_document(&mut self, parser: &ServoParser) { // When faced with displaying an XML file inline, provided navigation params navigationParams // and a string type, user agents must follow the requirements defined in XML and Namespaces in XML, // XML Media Types, DOM, and other relevant specifications to create and initialize a // Document object document, given "xml", type, and navigationParams, and return that Document. // They must also create a corresponding XML parser. [XML] [XMLNS] [RFC7303] [DOM] self.initialize_document_object(&parser.document); + // The first task that the networking task source places on the task queue while fetching + // runs must process link headers given document, navigationParams's response, and "media", + // after the task has been processed by the XML parser. + self.process_link_headers_in_media_phase_with_task(&parser.document); } /// - fn load_text_document(&self, parser: &ServoParser) { + fn load_text_document(&mut self, parser: &ServoParser) { // Step 4. Create an HTML parser and associate it with the document. // Act as if the tokenizer had emitted a start tag token with the tag name "pre" followed by // a single U+000A LINE FEED (LF) character, and switch the HTML parser's tokenizer to the PLAINTEXT state. @@ -1015,6 +1055,10 @@ impl ParserContext { parser.push_string_input_chunk(page); parser.parse_sync(CanGc::note()); parser.tokenizer.set_plaintext_state(); + // The first task that the networking task source places on the task queue while fetching + // runs must process link headers given document, navigationParams's response, and "media", + // after the task has been processed by the HTML parser. + self.process_link_headers_in_media_phase_with_task(&parser.document); } /// @@ -1079,6 +1123,9 @@ impl ParserContext { doc_body .AppendChild(&node, CanGc::note()) .expect("Appending failed"); + // Step 7. Process link headers given document, navigationParams's response, and "media". + let link_headers = std::mem::take(&mut self.navigation_params.link_headers); + process_link_headers(&link_headers, doc, LinkProcessingPhase::Media); } /// @@ -1125,14 +1172,15 @@ impl FetchResponseListener for ParserContext { .map(Serde::into_inner) .map(Into::into); - let (policy_container, endpoints_list) = match metadata.as_ref() { - None => (PolicyContainer::default(), None), + let (policy_container, endpoints_list, link_headers) = match metadata.as_ref() { + None => (PolicyContainer::default(), None, vec![]), Some(metadata) => ( Self::create_policy_container_from_fetch_response(metadata), ReportingEndpoint::parse_reporting_endpoints_header( &self.url.clone(), &metadata.headers, ), + extract_links_from_headers(&metadata.headers), ), }; @@ -1145,6 +1193,7 @@ impl FetchResponseListener for ParserContext { } let _realm = enter_realm(&*parser.document); + let window = parser.document.window(); // From Step 23.8.3 of https://html.spec.whatwg.org/multipage/#navigate // Let finalSandboxFlags be the union of targetSnapshotParams's sandboxing flags and @@ -1157,13 +1206,14 @@ impl FetchResponseListener for ParserContext { .unwrap_or(SandboxingFlagSet::empty()); if let Some(endpoints) = endpoints_list { - parser.document.window().set_endpoints_list(endpoints); + window.set_endpoints_list(endpoints); } self.parser = Some(Trusted::new(&*parser)); self.navigation_params = NavigationParams { policy_container, content_type, final_sandboxing_flag_set, + link_headers, resource_header: vec![], }; self.submit_resource_timing(); diff --git a/components/script/dom/servoparser/prefetch.rs b/components/script/dom/servoparser/prefetch.rs index 9a46070674f..77ea2bded9a 100644 --- a/components/script/dom/servoparser/prefetch.rs +++ b/components/script/dom/servoparser/prefetch.rs @@ -26,6 +26,7 @@ use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::trace::{CustomTraceable, JSTraceable}; use crate::dom::document::{Document, determine_policy_for_token}; use crate::dom::html::htmlscriptelement::script_fetch_request; +use crate::dom::processingoptions::determine_cors_settings_for_token; use crate::fetch::create_a_potential_cors_request; use crate::script_module::ScriptFetchOptions; @@ -267,13 +268,7 @@ impl PrefetchSink { } fn get_cors_settings(&self, tag: &Tag, name: LocalName) -> Option { - let crossorigin = self.get_attr(tag, name)?; - if crossorigin.value.eq_ignore_ascii_case("anonymous") { - Some(CorsSettings::Anonymous) - } else if crossorigin.value.eq_ignore_ascii_case("use-credentials") { - Some(CorsSettings::UseCredentials) - } else { - None - } + let attr = self.get_attr(tag, name)?; + determine_cors_settings_for_token(&attr.value) } } diff --git a/components/shared/net/mime_classifier.rs b/components/shared/net/mime_classifier.rs index 00a856d96f4..b3404c6cd1d 100644 --- a/components/shared/net/mime_classifier.rs +++ b/components/shared/net/mime_classifier.rs @@ -268,7 +268,7 @@ impl MimeClassifier { } /// - fn is_javascript(mt: &Mime) -> bool { + pub fn is_javascript(mt: &Mime) -> bool { (mt.type_() == mime::APPLICATION && (["ecmascript", "javascript", "x-ecmascript", "x-javascript"] .contains(&mt.subtype().as_str()))) || @@ -291,7 +291,7 @@ impl MimeClassifier { } /// - fn is_json(mt: &Mime) -> bool { + pub fn is_json(mt: &Mime) -> bool { mt.suffix() == Some(mime::JSON) || (mt.subtype() == mime::JSON && (mt.type_() == mime::APPLICATION || mt.type_() == mime::TEXT)) diff --git a/tests/wpt/meta/preload/link-header-preload-delay-onload.html.ini b/tests/wpt/meta/preload/link-header-preload-delay-onload.html.ini deleted file mode 100644 index cd896c20cb8..00000000000 --- a/tests/wpt/meta/preload/link-header-preload-delay-onload.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[link-header-preload-delay-onload.html] - [Makes sure that Link headers preload resources and block window.onload after resource discovery] - expected: FAIL diff --git a/tests/wpt/meta/preload/link-header-preload-non-html.html.ini b/tests/wpt/meta/preload/link-header-preload-non-html.html.ini index a69945adc84..a81640da277 100644 --- a/tests/wpt/meta/preload/link-header-preload-non-html.html.ini +++ b/tests/wpt/meta/preload/link-header-preload-non-html.html.ini @@ -1,7 +1,4 @@ [link-header-preload-non-html.html] - [XHTML documents should respect preload Link headers] - expected: FAIL - [plain text documents should respect preload Link headers] expected: FAIL diff --git a/tests/wpt/meta/preload/link-header-preload-nonce.html.ini b/tests/wpt/meta/preload/link-header-preload-nonce.html.ini deleted file mode 100644 index 39ba1868a86..00000000000 --- a/tests/wpt/meta/preload/link-header-preload-nonce.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[link-header-preload-nonce.html] - [with nonce] - expected: FAIL diff --git a/tests/wpt/meta/preload/preload-referrer-policy.html.ini b/tests/wpt/meta/preload/preload-referrer-policy.html.ini index 6d6da88ec21..ee130854fd6 100644 --- a/tests/wpt/meta/preload/preload-referrer-policy.html.ini +++ b/tests/wpt/meta/preload/preload-referrer-policy.html.ini @@ -1,583 +1,300 @@ [preload-referrer-policy.html] - expected: TIMEOUT - [referrer policy ( -> , header, cross-origin)] - expected: TIMEOUT - - [referrer policy ( -> , header, same-origin)] - expected: NOTRUN - [referrer policy ( -> no-referrer, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy ( -> no-referrer, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy ( -> no-referrer, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy ( -> no-referrer, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy ( -> same-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy ( -> same-origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy ( -> same-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy ( -> same-origin, header, same-origin)] - expected: NOTRUN - - [referrer policy ( -> origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy ( -> origin, element, same-origin)] - expected: NOTRUN - - [referrer policy ( -> origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy ( -> origin, header, same-origin)] - expected: NOTRUN - - [referrer policy ( -> origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy ( -> origin-when-cross-origin, element, same-origin)] - expected: NOTRUN - - [referrer policy ( -> origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy ( -> origin-when-cross-origin, header, same-origin)] - expected: NOTRUN - - [referrer policy ( -> strict-origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy ( -> strict-origin-when-cross-origin, element, same-origin)] - expected: NOTRUN - - [referrer policy ( -> strict-origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy ( -> strict-origin-when-cross-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy ( -> unsafe-url, element, cross-origin)] - expected: NOTRUN - - [referrer policy ( -> unsafe-url, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy ( -> unsafe-url, header, cross-origin)] - expected: NOTRUN - - [referrer policy ( -> unsafe-url, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> , element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> , element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> , header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> , header, same-origin)] - expected: NOTRUN - - [referrer policy (no-referrer -> no-referrer, element, cross-origin)] - expected: NOTRUN - - [referrer policy (no-referrer -> no-referrer, element, same-origin)] - expected: NOTRUN - - [referrer policy (no-referrer -> no-referrer, header, cross-origin)] - expected: NOTRUN - - [referrer policy (no-referrer -> no-referrer, header, same-origin)] - expected: NOTRUN - - [referrer policy (no-referrer -> same-origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> same-origin, element, same-origin)] - expected: NOTRUN - - [referrer policy (no-referrer -> same-origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> same-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> origin-when-cross-origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> origin-when-cross-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> strict-origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> strict-origin-when-cross-origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> strict-origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> strict-origin-when-cross-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> unsafe-url, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> unsafe-url, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> unsafe-url, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (no-referrer -> unsafe-url, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> , element, cross-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> , element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> , header, cross-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> , header, same-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> no-referrer, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> no-referrer, element, same-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> no-referrer, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> no-referrer, header, same-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> same-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> same-origin, element, same-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> same-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> same-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> origin-when-cross-origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> origin-when-cross-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> strict-origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> strict-origin-when-cross-origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> strict-origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> strict-origin-when-cross-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> unsafe-url, element, cross-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> unsafe-url, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (same-origin -> unsafe-url, header, cross-origin)] - expected: NOTRUN - - [referrer policy (same-origin -> unsafe-url, header, same-origin)] - expected: NOTRUN - - [referrer policy (origin -> , element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> , element, same-origin)] - expected: NOTRUN - - [referrer policy (origin -> , header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> , header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> no-referrer, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> no-referrer, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> no-referrer, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> no-referrer, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> same-origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> same-origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> same-origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> same-origin, header, same-origin)] - expected: NOTRUN - - [referrer policy (origin -> origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (origin -> origin, element, same-origin)] - expected: NOTRUN - - [referrer policy (origin -> origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (origin -> origin, header, same-origin)] - expected: NOTRUN - - [referrer policy (origin -> origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> origin-when-cross-origin, element, same-origin)] - expected: NOTRUN - - [referrer policy (origin -> origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> origin-when-cross-origin, header, same-origin)] - expected: NOTRUN - - [referrer policy (origin -> strict-origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> strict-origin-when-cross-origin, element, same-origin)] - expected: NOTRUN - - [referrer policy (origin -> strict-origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> strict-origin-when-cross-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> unsafe-url, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> unsafe-url, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> unsafe-url, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin -> unsafe-url, header, same-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> , element, cross-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> , element, same-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> , header, cross-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> , header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin-when-cross-origin -> no-referrer, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin-when-cross-origin -> no-referrer, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin-when-cross-origin -> no-referrer, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin-when-cross-origin -> no-referrer, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin-when-cross-origin -> same-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> same-origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin-when-cross-origin -> same-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> same-origin, header, same-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin-when-cross-origin -> origin, element, same-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin-when-cross-origin -> origin, header, same-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> origin-when-cross-origin, element, same-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> origin-when-cross-origin, header, same-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> strict-origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> strict-origin-when-cross-origin, element, same-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> strict-origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> strict-origin-when-cross-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin-when-cross-origin -> unsafe-url, element, cross-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> unsafe-url, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (origin-when-cross-origin -> unsafe-url, header, cross-origin)] - expected: NOTRUN - - [referrer policy (origin-when-cross-origin -> unsafe-url, header, same-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> , element, cross-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> , element, same-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> , header, cross-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> , header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (strict-origin-when-cross-origin -> no-referrer, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (strict-origin-when-cross-origin -> no-referrer, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (strict-origin-when-cross-origin -> no-referrer, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (strict-origin-when-cross-origin -> no-referrer, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (strict-origin-when-cross-origin -> same-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> same-origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (strict-origin-when-cross-origin -> same-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> same-origin, header, same-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (strict-origin-when-cross-origin -> origin, element, same-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (strict-origin-when-cross-origin -> origin, header, same-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> origin-when-cross-origin, element, same-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> origin-when-cross-origin, header, same-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> strict-origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> strict-origin-when-cross-origin, element, same-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> strict-origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> strict-origin-when-cross-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (strict-origin-when-cross-origin -> unsafe-url, element, cross-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> unsafe-url, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (strict-origin-when-cross-origin -> unsafe-url, header, cross-origin)] - expected: NOTRUN - - [referrer policy (strict-origin-when-cross-origin -> unsafe-url, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> , element, cross-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> , element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> , header, cross-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> , header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> no-referrer, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> no-referrer, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> no-referrer, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> no-referrer, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> same-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> same-origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> same-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> same-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> origin, element, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> origin, header, cross-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> origin-when-cross-origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> origin-when-cross-origin, header, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> strict-origin-when-cross-origin, element, cross-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> strict-origin-when-cross-origin, element, same-origin)] - expected: NOTRUN + expected: FAIL [referrer policy (unsafe-url -> strict-origin-when-cross-origin, header, cross-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> strict-origin-when-cross-origin, header, same-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> unsafe-url, element, cross-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> unsafe-url, element, same-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> unsafe-url, header, cross-origin)] - expected: NOTRUN - - [referrer policy (unsafe-url -> unsafe-url, header, same-origin)] - expected: NOTRUN + expected: FAIL diff --git a/tests/wpt/meta/preload/preload-resource-match.https.html.ini b/tests/wpt/meta/preload/preload-resource-match.https.html.ini index 047c69efb94..3d9c8e4a480 100644 --- a/tests/wpt/meta/preload/preload-resource-match.https.html.ini +++ b/tests/wpt/meta/preload/preload-resource-match.https.html.ini @@ -25,7 +25,7 @@ expected: FAIL [Loading style (use-credentials) with link (anonymous) should discard the preloaded response] - expected: TIMEOUT + expected: NOTRUN [Loading style (use-credentials) with link (use-credentials) should reuse the preloaded response] expected: NOTRUN @@ -47,3 +47,33 @@ [Loading backgroundImage (same-origin) with link (same-origin) should reuse the preloaded response] expected: FAIL + + [Loading module (use-credentials) with link (anonymous) should discard the preloaded response] + expected: TIMEOUT + + [Loading module (use-credentials) with link (use-credentials) should reuse the preloaded response] + expected: NOTRUN + + [Loading style (same-origin) with link (same-origin) should reuse the preloaded response] + expected: NOTRUN + + [Loading style (no-cors) with link (no-cors) should reuse the preloaded response] + expected: NOTRUN + + [Loading style (no-cors) with link (anonymous) should discard the preloaded response] + expected: NOTRUN + + [Loading style (no-cors) with link (use-credentials) should discard the preloaded response] + expected: NOTRUN + + [Loading style (anonymous) with link (no-cors) should discard the preloaded response] + expected: NOTRUN + + [Loading style (anonymous) with link (anonymous) should reuse the preloaded response] + expected: NOTRUN + + [Loading style (anonymous) with link (use-credentials) should discard the preloaded response] + expected: NOTRUN + + [Loading style (use-credentials) with link (no-cors) should discard the preloaded response] + expected: NOTRUN