mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Initial support for <link rel="prefetch">
(#33345)
* Properly store link relations Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Send fetch request for prefetch links We don't actually *do* anything with the response yet (handle errors etc) but its a first step. Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Fire load/error events for prefetch loads Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Set prefetch destination/cors setting correctly Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Update WPT expectations Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Fix ./mach test-tidy errors Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Set correct "Accept" value for prefetch requests Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Add spec text to individual steps Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> --------- Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
parent
2993577ac0
commit
10e5bb72d9
11 changed files with 531 additions and 182 deletions
|
@ -8,12 +8,12 @@
|
|||
|
||||
use base::id::PipelineId;
|
||||
use crossbeam_channel::Sender;
|
||||
use http::HeaderMap;
|
||||
use http::{header, HeaderMap};
|
||||
use ipc_channel::ipc;
|
||||
use ipc_channel::router::ROUTER;
|
||||
use log::warn;
|
||||
use net::http_loader::{set_default_accept, set_default_accept_language};
|
||||
use net_traits::request::{Destination, Referrer, RequestBuilder};
|
||||
use net::http_loader::{set_default_accept_language, DOCUMENT_ACCEPT_HEADER_VALUE};
|
||||
use net_traits::request::{Referrer, RequestBuilder};
|
||||
use net_traits::response::ResponseInit;
|
||||
use net_traits::{
|
||||
CoreResourceMsg, FetchChannels, FetchMetadata, FetchResponseMsg, IpcSend, NetworkError,
|
||||
|
@ -66,7 +66,17 @@ impl NetworkListener {
|
|||
None,
|
||||
),
|
||||
None => {
|
||||
set_default_accept(Destination::Document, &mut listener.request_builder.headers);
|
||||
if !listener
|
||||
.request_builder
|
||||
.headers
|
||||
.contains_key(header::ACCEPT)
|
||||
{
|
||||
listener
|
||||
.request_builder
|
||||
.headers
|
||||
.insert(header::ACCEPT, DOCUMENT_ACCEPT_HEADER_VALUE);
|
||||
}
|
||||
|
||||
set_default_accept_language(&mut listener.request_builder.headers);
|
||||
|
||||
CoreResourceMsg::Fetch(
|
||||
|
|
|
@ -133,7 +133,7 @@ pub async fn fetch_with_cors_cache(
|
|||
}
|
||||
|
||||
// Step 3.
|
||||
set_default_accept(request.destination, &mut request.headers);
|
||||
set_default_accept(request);
|
||||
|
||||
// Step 4.
|
||||
set_default_accept_language(&mut request.headers);
|
||||
|
|
|
@ -35,13 +35,12 @@ use ipc_channel::ipc::{self, IpcSender};
|
|||
use ipc_channel::router::ROUTER;
|
||||
use log::{debug, error, info, log_enabled, warn};
|
||||
use net_traits::pub_domains::reg_suffix;
|
||||
use net_traits::quality::{quality_to_value, Quality, QualityItem};
|
||||
use net_traits::request::Origin::Origin as SpecificOrigin;
|
||||
use net_traits::request::{
|
||||
get_cors_unsafe_header_names, is_cors_non_wildcard_request_header_name,
|
||||
is_cors_safelisted_method, is_cors_safelisted_request_header, BodyChunkRequest,
|
||||
BodyChunkResponse, CacheMode, CredentialsMode, Destination, Origin, RedirectMode, Referrer,
|
||||
Request, RequestBuilder, RequestMode, ResponseTainting, ServiceWorkersMode,
|
||||
BodyChunkResponse, CacheMode, CredentialsMode, Destination, Initiator, Origin, RedirectMode,
|
||||
Referrer, Request, RequestBuilder, RequestMode, ResponseTainting, ServiceWorkersMode,
|
||||
};
|
||||
use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType};
|
||||
use net_traits::{
|
||||
|
@ -70,6 +69,10 @@ use crate::hsts::HstsList;
|
|||
use crate::http_cache::{CacheKey, HttpCache};
|
||||
use crate::resource_thread::AuthCache;
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#document-accept-header-value>
|
||||
pub const DOCUMENT_ACCEPT_HEADER_VALUE: HeaderValue =
|
||||
HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
|
||||
/// The various states an entry of the HttpCache can be in.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum HttpCacheEntryState {
|
||||
|
@ -117,41 +120,29 @@ impl Default for HttpState {
|
|||
}
|
||||
}
|
||||
|
||||
// Step 3 of https://fetch.spec.whatwg.org/#concept-fetch.
|
||||
pub fn set_default_accept(destination: Destination, headers: &mut HeaderMap) {
|
||||
if headers.contains_key(header::ACCEPT) {
|
||||
/// Step 13 of <https://fetch.spec.whatwg.org/#concept-fetch>.
|
||||
pub fn set_default_accept(request: &mut Request) {
|
||||
if request.headers.contains_key(header::ACCEPT) {
|
||||
return;
|
||||
}
|
||||
let value = match destination {
|
||||
// Step 3.2.
|
||||
Destination::Document => vec![
|
||||
QualityItem::new(mime::TEXT_HTML, Quality::from_u16(1000)),
|
||||
QualityItem::new(
|
||||
"application/xhtml+xml".parse().unwrap(),
|
||||
Quality::from_u16(1000),
|
||||
),
|
||||
QualityItem::new("application/xml".parse().unwrap(), Quality::from_u16(900)),
|
||||
QualityItem::new(mime::STAR_STAR, Quality::from_u16(800)),
|
||||
],
|
||||
// Step 3.3.
|
||||
Destination::Image => vec![
|
||||
QualityItem::new(mime::IMAGE_PNG, Quality::from_u16(1000)),
|
||||
QualityItem::new(mime::IMAGE_SVG, Quality::from_u16(1000)),
|
||||
QualityItem::new(mime::IMAGE_STAR, Quality::from_u16(800)),
|
||||
QualityItem::new(mime::STAR_STAR, Quality::from_u16(500)),
|
||||
],
|
||||
// Step 3.3.
|
||||
Destination::Style => vec![
|
||||
QualityItem::new(mime::TEXT_CSS, Quality::from_u16(1000)),
|
||||
QualityItem::new(mime::STAR_STAR, Quality::from_u16(100)),
|
||||
],
|
||||
// Step 3.1.
|
||||
_ => vec![QualityItem::new(mime::STAR_STAR, Quality::from_u16(1000))],
|
||||
|
||||
let value = if request.initiator == Initiator::Prefetch {
|
||||
DOCUMENT_ACCEPT_HEADER_VALUE
|
||||
} else {
|
||||
match request.destination {
|
||||
Destination::Document | Destination::Frame | Destination::IFrame => {
|
||||
DOCUMENT_ACCEPT_HEADER_VALUE
|
||||
},
|
||||
Destination::Image => {
|
||||
HeaderValue::from_static("image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5")
|
||||
},
|
||||
Destination::Json => HeaderValue::from_static("application/json,*/*;q=0.5"),
|
||||
Destination::Style => HeaderValue::from_static("text/css,*/*;q=0.1"),
|
||||
_ => HeaderValue::from_static("*/*"),
|
||||
}
|
||||
};
|
||||
|
||||
// Step 3.4.
|
||||
// TODO(eijebong): Change this once typed headers are done
|
||||
headers.insert(header::ACCEPT, quality_to_value(value));
|
||||
request.headers.insert(header::ACCEPT, value);
|
||||
}
|
||||
|
||||
fn set_default_accept_encoding(headers: &mut HeaderMap) {
|
||||
|
|
|
@ -2,46 +2,61 @@
|
|||
* 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::borrow::ToOwned;
|
||||
use std::borrow::{Borrow, ToOwned};
|
||||
use std::cell::Cell;
|
||||
use std::default::Default;
|
||||
use std::sync;
|
||||
|
||||
use cssparser::{Parser as CssParser, ParserInput};
|
||||
use dom_struct::dom_struct;
|
||||
use embedder_traits::EmbedderMsg;
|
||||
use html5ever::{local_name, namespace_url, ns, LocalName, Prefix};
|
||||
use ipc_channel::ipc;
|
||||
use ipc_channel::router::ROUTER;
|
||||
use js::rust::HandleObject;
|
||||
use net_traits::ReferrerPolicy;
|
||||
use net_traits::request::{CorsSettings, Destination, Initiator, Referrer, RequestBuilder};
|
||||
use net_traits::{
|
||||
CoreResourceMsg, FetchChannels, FetchMetadata, FetchResponseListener, IpcSend, NetworkError,
|
||||
ReferrerPolicy, ResourceFetchTiming, ResourceTimingType,
|
||||
};
|
||||
use servo_arc::Arc;
|
||||
use servo_atoms::Atom;
|
||||
use servo_url::ServoUrl;
|
||||
use style::attr::AttrValue;
|
||||
use style::media_queries::MediaList;
|
||||
use style::parser::ParserContext as CssParserContext;
|
||||
use style::str::HTML_SPACE_CHARACTERS;
|
||||
use style::stylesheets::{CssRuleType, Origin, Stylesheet, UrlExtraData};
|
||||
use style_traits::ParsingMode;
|
||||
|
||||
use super::types::{EventTarget, GlobalScope};
|
||||
use crate::dom::attr::Attr;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenList_Binding::DOMTokenListMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::DomObject;
|
||||
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
||||
use crate::dom::bindings::str::{DOMString, USVString};
|
||||
use crate::dom::cssstylesheet::CSSStyleSheet;
|
||||
use crate::dom::document::Document;
|
||||
use crate::dom::domtokenlist::DOMTokenList;
|
||||
use crate::dom::element::{
|
||||
cors_setting_for_element, reflect_cross_origin_attribute, reflect_referrer_policy_attribute,
|
||||
set_cross_origin_attribute, AttributeMutation, Element, ElementCreator,
|
||||
cors_setting_for_element, referrer_policy_for_element, reflect_cross_origin_attribute,
|
||||
reflect_referrer_policy_attribute, set_cross_origin_attribute, AttributeMutation, Element,
|
||||
ElementCreator,
|
||||
};
|
||||
use crate::dom::htmlelement::HTMLElement;
|
||||
use crate::dom::node::{
|
||||
document_from_node, stylesheets_owner_from_node, window_from_node, BindContext, Node,
|
||||
UnbindContext,
|
||||
};
|
||||
use crate::dom::performanceresourcetiming::InitiatorType;
|
||||
use crate::dom::stylesheet::StyleSheet as DOMStyleSheet;
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use crate::fetch::create_a_potential_cors_request;
|
||||
use crate::link_relations::LinkRelations;
|
||||
use crate::network_listener::{submit_timing, NetworkListener, PreInvoke, ResourceTimingListener};
|
||||
use crate::stylesheet_loader::{StylesheetContextSource, StylesheetLoader, StylesheetOwner};
|
||||
|
||||
#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
|
||||
|
@ -53,10 +68,33 @@ impl RequestGenerationId {
|
|||
}
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#link-processing-options>
|
||||
struct LinkProcessingOptions {
|
||||
href: String,
|
||||
destination: Option<Destination>,
|
||||
integrity: String,
|
||||
link_type: String,
|
||||
cross_origin: Option<CorsSettings>,
|
||||
referrer_policy: Option<ReferrerPolicy>,
|
||||
source_set: Option<()>,
|
||||
base_url: ServoUrl,
|
||||
// Some fields that we don't need yet are missing
|
||||
}
|
||||
|
||||
#[dom_struct]
|
||||
pub struct HTMLLinkElement {
|
||||
htmlelement: HTMLElement,
|
||||
/// The relations as specified by the "rel" attribute
|
||||
rel_list: MutNullableDom<DOMTokenList>,
|
||||
|
||||
/// The link relations as they are used in practice.
|
||||
///
|
||||
/// The reason this is seperate from [HTMLLinkElement::rel_list] is that
|
||||
/// a literal list is a bit unwieldy and that there are corner cases to consider
|
||||
/// (Like `rev="made"` implying an author relationship that is not represented in rel_list)
|
||||
#[no_trace]
|
||||
relations: Cell<LinkRelations>,
|
||||
|
||||
#[ignore_malloc_size_of = "Arc"]
|
||||
#[no_trace]
|
||||
stylesheet: DomRefCell<Option<Arc<Stylesheet>>>,
|
||||
|
@ -83,6 +121,7 @@ impl HTMLLinkElement {
|
|||
HTMLLinkElement {
|
||||
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
|
||||
rel_list: Default::default(),
|
||||
relations: Cell::new(LinkRelations::empty()),
|
||||
parser_inserted: Cell::new(creator.is_parser_created()),
|
||||
stylesheet: DomRefCell::new(None),
|
||||
cssom_stylesheet: MutNullableDom::new(None),
|
||||
|
@ -146,13 +185,7 @@ impl HTMLLinkElement {
|
|||
}
|
||||
|
||||
pub fn is_alternate(&self) -> bool {
|
||||
let rel = get_attr(self.upcast(), &local_name!("rel"));
|
||||
match rel {
|
||||
Some(ref value) => value
|
||||
.split(HTML_SPACE_CHARACTERS)
|
||||
.any(|s| s.eq_ignore_ascii_case("alternate")),
|
||||
None => false,
|
||||
}
|
||||
self.relations.get().contains(LinkRelations::ALTERNATE)
|
||||
}
|
||||
|
||||
fn clean_stylesheet_ownership(&self) {
|
||||
|
@ -171,27 +204,6 @@ fn get_attr(element: &Element, local_name: &LocalName) -> Option<String> {
|
|||
})
|
||||
}
|
||||
|
||||
fn string_is_stylesheet(value: &Option<String>) -> bool {
|
||||
match *value {
|
||||
Some(ref value) => value
|
||||
.split(HTML_SPACE_CHARACTERS)
|
||||
.any(|s| s.eq_ignore_ascii_case("stylesheet")),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Favicon spec usage in accordance with CEF implementation:
|
||||
/// only url of icon is required/used
|
||||
/// <https://html.spec.whatwg.org/multipage/#rel-icon>
|
||||
fn is_favicon(value: &Option<String>) -> bool {
|
||||
match *value {
|
||||
Some(ref value) => value
|
||||
.split(HTML_SPACE_CHARACTERS)
|
||||
.any(|s| s.eq_ignore_ascii_case("icon") || s.eq_ignore_ascii_case("apple-touch-icon")),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualMethods for HTMLLinkElement {
|
||||
fn super_type(&self) -> Option<&dyn VirtualMethods> {
|
||||
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
|
||||
|
@ -203,25 +215,33 @@ impl VirtualMethods for HTMLLinkElement {
|
|||
return;
|
||||
}
|
||||
|
||||
let rel = get_attr(self.upcast(), &local_name!("rel"));
|
||||
match *attr.local_name() {
|
||||
local_name!("rel") | local_name!("rev") => {
|
||||
self.relations
|
||||
.set(LinkRelations::for_element(self.upcast()));
|
||||
},
|
||||
local_name!("href") => {
|
||||
if string_is_stylesheet(&rel) {
|
||||
if self.relations.get().contains(LinkRelations::STYLESHEET) {
|
||||
self.handle_stylesheet_url(&attr.value());
|
||||
} else if is_favicon(&rel) {
|
||||
}
|
||||
|
||||
if self.relations.get().contains(LinkRelations::ICON) {
|
||||
let sizes = get_attr(self.upcast(), &local_name!("sizes"));
|
||||
self.handle_favicon_url(rel.as_ref().unwrap(), &attr.value(), &sizes);
|
||||
self.handle_favicon_url(&attr.value(), &sizes);
|
||||
}
|
||||
|
||||
if self.relations.get().contains(LinkRelations::PREFETCH) {
|
||||
self.fetch_and_process_prefetch_link(&attr.value());
|
||||
}
|
||||
},
|
||||
local_name!("sizes") => {
|
||||
if is_favicon(&rel) {
|
||||
if let Some(ref href) = get_attr(self.upcast(), &local_name!("href")) {
|
||||
self.handle_favicon_url(
|
||||
rel.as_ref().unwrap(),
|
||||
href,
|
||||
&Some(attr.value().to_string()),
|
||||
);
|
||||
}
|
||||
local_name!("sizes") if self.relations.get().contains(LinkRelations::ICON) => {
|
||||
if let Some(ref href) = get_attr(self.upcast(), &local_name!("href")) {
|
||||
self.handle_favicon_url(href, &Some(attr.value().to_string()));
|
||||
}
|
||||
},
|
||||
local_name!("crossorigin") => {
|
||||
if self.relations.get().contains(LinkRelations::PREFETCH) {
|
||||
self.fetch_and_process_prefetch_link(&attr.value());
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
|
@ -243,21 +263,26 @@ impl VirtualMethods for HTMLLinkElement {
|
|||
s.bind_to_tree(context);
|
||||
}
|
||||
|
||||
self.relations
|
||||
.set(LinkRelations::for_element(self.upcast()));
|
||||
|
||||
if context.tree_connected {
|
||||
let element = self.upcast();
|
||||
|
||||
let rel = get_attr(element, &local_name!("rel"));
|
||||
let href = get_attr(element, &local_name!("href"));
|
||||
let sizes = get_attr(self.upcast(), &local_name!("sizes"));
|
||||
if let Some(href) = get_attr(element, &local_name!("href")) {
|
||||
let relations = self.relations.get();
|
||||
if relations.contains(LinkRelations::STYLESHEET) {
|
||||
self.handle_stylesheet_url(&href);
|
||||
}
|
||||
|
||||
match href {
|
||||
Some(ref href) if string_is_stylesheet(&rel) => {
|
||||
self.handle_stylesheet_url(href);
|
||||
},
|
||||
Some(ref href) if is_favicon(&rel) => {
|
||||
self.handle_favicon_url(rel.as_ref().unwrap(), href, &sizes);
|
||||
},
|
||||
_ => {},
|
||||
if relations.contains(LinkRelations::ICON) {
|
||||
let sizes = get_attr(self.upcast(), &local_name!("sizes"));
|
||||
self.handle_favicon_url(&href, &sizes);
|
||||
}
|
||||
|
||||
if relations.contains(LinkRelations::PREFETCH) {
|
||||
self.fetch_and_process_prefetch_link(&href);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,6 +300,118 @@ impl VirtualMethods for HTMLLinkElement {
|
|||
}
|
||||
|
||||
impl HTMLLinkElement {
|
||||
/// <https://html.spec.whatwg.org/multipage/#create-link-options-from-element>
|
||||
fn processing_options(&self) -> LinkProcessingOptions {
|
||||
let element = self.upcast::<Element>();
|
||||
|
||||
// Step 1. Let document be el's node document.
|
||||
let document = self.upcast::<Node>().owner_doc();
|
||||
|
||||
// Step 2. Let options be a new link processing options
|
||||
let destination = element
|
||||
.get_attribute(&ns!(), &local_name!("as"))
|
||||
.map(|attr| translate_a_preload_destination(&*attr.value()))
|
||||
.unwrap_or(Destination::None);
|
||||
|
||||
let mut options = LinkProcessingOptions {
|
||||
href: String::new(),
|
||||
destination: Some(destination),
|
||||
integrity: String::new(),
|
||||
link_type: String::new(),
|
||||
cross_origin: cors_setting_for_element(element),
|
||||
referrer_policy: referrer_policy_for_element(element),
|
||||
source_set: None, // FIXME
|
||||
base_url: document.borrow().base_url(),
|
||||
};
|
||||
|
||||
// Step 3. If el has an href attribute, then set options's href to the value of el's href attribute.
|
||||
if let Some(href_attribute) = element.get_attribute(&ns!(), &local_name!("href")) {
|
||||
options.href = (**href_attribute.value()).to_owned();
|
||||
}
|
||||
|
||||
// Step 4. If el has an integrity attribute, then set options's integrity
|
||||
// to the value of el's integrity content attribute.
|
||||
if let Some(integrity_attribute) = element.get_attribute(&ns!(), &local_name!("integrity"))
|
||||
{
|
||||
options.integrity = (**integrity_attribute.value()).to_owned();
|
||||
}
|
||||
|
||||
// Step 5. If el has a type attribute, then set options's type to the value of el's type attribute.
|
||||
if let Some(type_attribute) = element.get_attribute(&ns!(), &local_name!("type")) {
|
||||
options.link_type = (**type_attribute.value()).to_owned();
|
||||
}
|
||||
|
||||
// Step 6. Assert: options's href is not the empty string, or options's source set is not null.
|
||||
assert!(!options.href.is_empty() || options.source_set.is_some());
|
||||
|
||||
// Step 7. Return options.
|
||||
options
|
||||
}
|
||||
|
||||
/// The `fetch and process the linked resource` algorithm for [`rel="prefetch"`](https://html.spec.whatwg.org/multipage/#link-type-prefetch)
|
||||
fn fetch_and_process_prefetch_link(&self, href: &str) {
|
||||
// Step 1. If el's href attribute's value is the empty string, then return.
|
||||
if href.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2. Let options be the result of creating link options from el.
|
||||
let mut options = self.processing_options();
|
||||
|
||||
// Step 3. Set options's destination to the empty string.
|
||||
options.destination = Some(Destination::None);
|
||||
|
||||
// Step 4. Let request be the result of creating a link request given options.
|
||||
let url = options.base_url.clone();
|
||||
let Some(request) = options.create_link_request() else {
|
||||
// Step 5. If request is null, then return.
|
||||
return;
|
||||
};
|
||||
|
||||
// Step 6. Set request's initiator to "prefetch".
|
||||
let request = request.initiator(Initiator::Prefetch);
|
||||
|
||||
// (Step 7, firing load/error events is handled in the FetchResponseListener impl for PrefetchContext)
|
||||
|
||||
// Step 8. The user agent should fetch request, with processResponseConsumeBody set to processPrefetchResponse.
|
||||
let (action_sender, action_receiver) = ipc::channel().unwrap();
|
||||
let document = self.upcast::<Node>().owner_doc();
|
||||
let window = document.window();
|
||||
|
||||
let (task_source, canceller) = window
|
||||
.task_manager()
|
||||
.networking_task_source_with_canceller();
|
||||
|
||||
let fetch_context = sync::Arc::new(sync::Mutex::new(PrefetchContext {
|
||||
url: url,
|
||||
link: Trusted::new(self),
|
||||
resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
|
||||
}));
|
||||
|
||||
let listener = NetworkListener {
|
||||
context: fetch_context,
|
||||
task_source,
|
||||
canceller: Some(canceller),
|
||||
};
|
||||
|
||||
ROUTER.add_route(
|
||||
action_receiver.to_opaque(),
|
||||
Box::new(move |message| {
|
||||
listener.notify_fetch(message.to().unwrap());
|
||||
}),
|
||||
);
|
||||
|
||||
window
|
||||
.upcast::<GlobalScope>()
|
||||
.resource_threads()
|
||||
.sender()
|
||||
.send(CoreResourceMsg::Fetch(
|
||||
request,
|
||||
FetchChannels::ResponseMsg(action_sender, None),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#concept-link-obtain>
|
||||
fn handle_stylesheet_url(&self, href: &str) {
|
||||
let document = document_from_node(self);
|
||||
|
@ -348,7 +485,7 @@ impl HTMLLinkElement {
|
|||
);
|
||||
}
|
||||
|
||||
fn handle_favicon_url(&self, _rel: &str, href: &str, _sizes: &Option<String>) {
|
||||
fn handle_favicon_url(&self, href: &str, _sizes: &Option<String>) {
|
||||
let document = document_from_node(self);
|
||||
match document.base_url().join(href) {
|
||||
Ok(url) => {
|
||||
|
@ -511,3 +648,131 @@ impl HTMLLinkElementMethods for HTMLLinkElement {
|
|||
self.get_cssom_stylesheet().map(DomRoot::upcast)
|
||||
}
|
||||
}
|
||||
|
||||
impl LinkProcessingOptions {
|
||||
/// <https://html.spec.whatwg.org/multipage/#create-a-link-request>
|
||||
fn create_link_request(self) -> Option<RequestBuilder> {
|
||||
// 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 Some(destination) = self.destination else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Step 3. Let url be the result of encoding-parsing a URL given options's href, relative to options's base URL.
|
||||
// TODO: The spec passes a base url which is incompatible with the
|
||||
// "encoding-parse a URL" algorithm.
|
||||
let Ok(url) = self.base_url.join(&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.
|
||||
// FIXME: Step 6. Set request's policy container to options's policy container.
|
||||
// Step 7. Set request's integrity metadata to options's integrity.
|
||||
// FIXME: 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(
|
||||
url,
|
||||
destination,
|
||||
self.cross_origin,
|
||||
None,
|
||||
Referrer::NoReferrer,
|
||||
)
|
||||
.integrity_metadata(self.integrity)
|
||||
.referrer_policy(self.referrer_policy);
|
||||
|
||||
// Step 12. Return request.
|
||||
Some(builder)
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#translate-a-preload-destination>
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
struct PrefetchContext {
|
||||
/// The `<link>` element that caused this prefetch operation
|
||||
link: Trusted<HTMLLinkElement>,
|
||||
|
||||
resource_timing: ResourceFetchTiming,
|
||||
|
||||
/// The url being prefetched
|
||||
url: ServoUrl,
|
||||
}
|
||||
|
||||
impl FetchResponseListener for PrefetchContext {
|
||||
fn process_request_body(&mut self) {}
|
||||
|
||||
fn process_request_eof(&mut self) {}
|
||||
|
||||
fn process_response(&mut self, fetch_metadata: Result<FetchMetadata, NetworkError>) {
|
||||
_ = fetch_metadata;
|
||||
}
|
||||
|
||||
fn process_response_chunk(&mut self, chunk: Vec<u8>) {
|
||||
_ = chunk;
|
||||
}
|
||||
|
||||
// Step 7 of `fetch and process the linked resource` in https://html.spec.whatwg.org/multipage/#link-type-prefetch
|
||||
fn process_response_eof(&mut self, response: Result<ResourceFetchTiming, NetworkError>) {
|
||||
if response.is_err() {
|
||||
// Step 1. If response is a network error, fire an event named error at el.
|
||||
self.link
|
||||
.root()
|
||||
.upcast::<EventTarget>()
|
||||
.fire_event(atom!("error"));
|
||||
} else {
|
||||
// Step 2. Otherwise, fire an event named load at el.
|
||||
self.link
|
||||
.root()
|
||||
.upcast::<EventTarget>()
|
||||
.fire_event(atom!("load"));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceTimingListener for PrefetchContext {
|
||||
fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
|
||||
(
|
||||
InitiatorType::LocalName("prefetch".to_string()),
|
||||
self.url.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
|
||||
self.link.root().upcast::<Node>().owner_doc().global()
|
||||
}
|
||||
}
|
||||
|
||||
impl PreInvoke for PrefetchContext {
|
||||
fn should_invoke(&self) -> bool {
|
||||
// Prefetch requests are never aborted.
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,8 @@ mod webdriver_handlers;
|
|||
#[warn(deprecated)]
|
||||
mod window_named_properties;
|
||||
|
||||
mod link_relations;
|
||||
|
||||
pub use init::init;
|
||||
pub use script_runtime::JSEngineSetup;
|
||||
|
||||
|
|
131
components/script/link_relations.rs
Normal file
131
components/script/link_relations.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
/* 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 html5ever::{local_name, namespace_url, ns};
|
||||
use malloc_size_of::malloc_size_of_is_0;
|
||||
use style::str::HTML_SPACE_CHARACTERS;
|
||||
|
||||
use crate::dom::types::Element;
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct LinkRelations: u32 {
|
||||
const ALTERNATE = 1;
|
||||
const AUTHOR = 1 << 1;
|
||||
const BOOKMARK = 1 << 2;
|
||||
const CANONICAL = 1 << 3;
|
||||
const DNS_PREFETCH = 1 << 4;
|
||||
const EXPECT = 1 << 5;
|
||||
const EXTERNAL = 1 << 6;
|
||||
const HELP = 1 << 7;
|
||||
const ICON = 1 << 8;
|
||||
const LICENSE = 1 << 9;
|
||||
const NEXT = 1 << 10;
|
||||
const MANIFEST = 1 << 11;
|
||||
const MODULE_PRELOAD = 1 << 12;
|
||||
const NO_FOLLOW = 1 << 13;
|
||||
const NO_OPENER = 1 << 14;
|
||||
const NO_REFERRER = 1 << 15;
|
||||
const OPENER = 1 << 16;
|
||||
const PING_BACK = 1 << 17;
|
||||
const PRECONNECT = 1 << 18;
|
||||
const PREFETCH = 1 << 19;
|
||||
const PRELOAD = 1 << 20;
|
||||
const PREV = 1 << 21;
|
||||
const PRIVACY_POLICY = 1 << 22;
|
||||
const SEARCH = 1 << 23;
|
||||
const STYLESHEET = 1 << 24;
|
||||
const TAG = 1 << 25;
|
||||
const TermsOfService = 1 << 26;
|
||||
}
|
||||
}
|
||||
|
||||
impl LinkRelations {
|
||||
pub fn for_element(element: &Element) -> Self {
|
||||
let rel = element.get_attribute(&ns!(), &local_name!("rel")).map(|e| {
|
||||
let value = e.value();
|
||||
(**value).to_owned()
|
||||
});
|
||||
|
||||
// FIXME: for a, area and form elements we need to allow a different
|
||||
// set of attributes
|
||||
let mut relations = rel
|
||||
.map(|attribute| {
|
||||
attribute
|
||||
.split(HTML_SPACE_CHARACTERS)
|
||||
.map(Self::from_single_keyword_for_link_element)
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or(Self::empty());
|
||||
|
||||
// For historical reasons, "rev=made" is treated as if the "author" relation was specified
|
||||
let has_legacy_author_relation = element
|
||||
.get_attribute(&ns!(), &local_name!("rev"))
|
||||
.is_some_and(|rev| &**rev.value() == "made");
|
||||
if has_legacy_author_relation {
|
||||
relations |= Self::AUTHOR;
|
||||
}
|
||||
|
||||
relations
|
||||
}
|
||||
|
||||
/// Parse one of the relations allowed for the `<link>` element
|
||||
///
|
||||
/// If the keyword is invalid then `Self::empty` is returned.
|
||||
fn from_single_keyword_for_link_element(keyword: &str) -> Self {
|
||||
if keyword.eq_ignore_ascii_case("alternate") {
|
||||
Self::ALTERNATE
|
||||
} else if keyword.eq_ignore_ascii_case("canonical") {
|
||||
Self::CANONICAL
|
||||
} else if keyword.eq_ignore_ascii_case("author") {
|
||||
Self::AUTHOR
|
||||
} else if keyword.eq_ignore_ascii_case("dns-prefetch") {
|
||||
Self::DNS_PREFETCH
|
||||
} else if keyword.eq_ignore_ascii_case("expect") {
|
||||
Self::EXPECT
|
||||
} else if keyword.eq_ignore_ascii_case("help") {
|
||||
Self::HELP
|
||||
} else if keyword.eq_ignore_ascii_case("icon") ||
|
||||
keyword.eq_ignore_ascii_case("shortcut icon") ||
|
||||
keyword.eq_ignore_ascii_case("apple-touch-icon")
|
||||
{
|
||||
// TODO: "apple-touch-icon" is not in the spec. Where did it come from? Do we need it?
|
||||
// There is also "apple-touch-icon-precomposed" listed in
|
||||
// https://github.com/servo/servo/blob/e43e4778421be8ea30db9d5c553780c042161522/components/script/dom/htmllinkelement.rs#L452-L467
|
||||
Self::ICON
|
||||
} else if keyword.eq_ignore_ascii_case("manifest") {
|
||||
Self::MANIFEST
|
||||
} else if keyword.eq_ignore_ascii_case("modulepreload") {
|
||||
Self::MODULE_PRELOAD
|
||||
} else if keyword.eq_ignore_ascii_case("license") ||
|
||||
keyword.eq_ignore_ascii_case("copyright")
|
||||
{
|
||||
Self::LICENSE
|
||||
} else if keyword.eq_ignore_ascii_case("next") {
|
||||
Self::NEXT
|
||||
} else if keyword.eq_ignore_ascii_case("pingback") {
|
||||
Self::PING_BACK
|
||||
} else if keyword.eq_ignore_ascii_case("preconnect") {
|
||||
Self::PRECONNECT
|
||||
} else if keyword.eq_ignore_ascii_case("prefetch") {
|
||||
Self::PREFETCH
|
||||
} else if keyword.eq_ignore_ascii_case("preload") {
|
||||
Self::PRELOAD
|
||||
} else if keyword.eq_ignore_ascii_case("prev") || keyword.eq_ignore_ascii_case("previous") {
|
||||
Self::PREV
|
||||
} else if keyword.eq_ignore_ascii_case("privacy-policy") {
|
||||
Self::PRIVACY_POLICY
|
||||
} else if keyword.eq_ignore_ascii_case("search") {
|
||||
Self::SEARCH
|
||||
} else if keyword.eq_ignore_ascii_case("stylesheet") {
|
||||
Self::STYLESHEET
|
||||
} else if keyword.eq_ignore_ascii_case("terms-of-service") {
|
||||
Self::TermsOfService
|
||||
} else {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
malloc_size_of_is_0!(LinkRelations);
|
|
@ -25,6 +25,8 @@ pub enum Initiator {
|
|||
ImageSet,
|
||||
Manifest,
|
||||
XSLT,
|
||||
Prefetch,
|
||||
Link,
|
||||
}
|
||||
|
||||
/// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination)
|
||||
|
|
|
@ -1,97 +1,93 @@
|
|||
[element-link-prefetch.https.optional.sub.html]
|
||||
expected: TIMEOUT
|
||||
[sec-fetch-site - Same origin no attributes]
|
||||
expected: TIMEOUT
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Cross-site no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Same site no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Same-Origin -> Cross-Site -> Same-Origin redirect no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Same-Origin -> Same-Site -> Same-Origin redirect no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Cross-Site -> Same Origin no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Cross-Site -> Same-Site no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Cross-Site -> Cross-Site no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Same-Origin -> Same Origin no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Same-Origin -> Same-Site no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Same-Origin -> Cross-Site no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Same-Site -> Same Origin no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Same-Site -> Same-Site no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - Same-Site -> Cross-Site no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-mode no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-mode attributes: crossorigin]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-mode attributes: crossorigin=anonymous]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-mode attributes: crossorigin=use-credentials]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=audio]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=document]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=embed]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=fetch]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=font]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=image]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=object]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=script]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=style]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=track]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=video]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-dest attributes: as=worker]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-user no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,46 +1,6 @@
|
|||
[element-link-prefetch.optional.sub.html]
|
||||
expected: TIMEOUT
|
||||
[sec-fetch-site - Not sent to non-trustworthy same-origin destination no attributes]
|
||||
expected: TIMEOUT
|
||||
|
||||
[sec-fetch-site - Not sent to non-trustworthy same-site destination no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-site - Not sent to non-trustworthy cross-site destination no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-mode - Not sent to non-trustworthy same-origin destination no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-mode - Not sent to non-trustworthy same-site destination no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-mode - Not sent to non-trustworthy cross-site destination no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-dest - Not sent to non-trustworthy same-origin destination no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-dest - Not sent to non-trustworthy same-site destination no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-dest - Not sent to non-trustworthy cross-site destination no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-user - Not sent to non-trustworthy same-origin destination no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-user - Not sent to non-trustworthy same-site destination no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-user - Not sent to non-trustworthy cross-site destination no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-site - HTTPS downgrade (header not sent) no attributes]
|
||||
expected: NOTRUN
|
||||
|
||||
[sec-fetch-site - HTTPS upgrade no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[sec-fetch-site - HTTPS downgrade-upgrade no attributes]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[prefetch-transfer-size-executor.html]
|
||||
expected: TIMEOUT
|
||||
[Navigation timing transfer size for a prefetched navigation should be 0.]
|
||||
expected: TIMEOUT
|
|
@ -1,4 +0,0 @@
|
|||
[prefetch-transfer-size-iframe.html]
|
||||
expected: TIMEOUT
|
||||
[Navigation timing transfer size for a prefetched navigation should be 0.]
|
||||
expected: TIMEOUT
|
Loading…
Add table
Add a link
Reference in a new issue