Skip to content

Commit de5c48f

Browse files
committed
Implement basics of link preloading
These changes allow a minimal set of checks for font-src CSP checks to pass. Part of servo#4577 Part of servo#35035 Signed-off-by: Tim van der Lippe <[email protected]>
1 parent c52ce2d commit de5c48f

File tree

12 files changed

+206
-23
lines changed

12 files changed

+206
-23
lines changed

components/net/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ pub mod http_cache;
1616
pub mod http_loader;
1717
pub mod image_cache;
1818
pub mod local_directory_listing;
19-
pub mod mime_classifier;
2019
pub mod protocols;
2120
pub mod request_interceptor;
2221
pub mod resource_thread;

components/net/tests/main.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ mod filemanager_thread;
1414
mod hsts;
1515
mod http_cache;
1616
mod http_loader;
17-
mod mime_classifier;
1817
mod resource_thread;
1918
mod subresource_integrity;
2019

components/script/dom/htmllinkelement.rs

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
use std::borrow::{Borrow, ToOwned};
66
use std::cell::Cell;
77
use std::default::Default;
8+
use std::str::FromStr;
89

910
use base::id::WebViewId;
1011
use content_security_policy as csp;
1112
use dom_struct::dom_struct;
1213
use embedder_traits::EmbedderMsg;
1314
use html5ever::{LocalName, Prefix, local_name, ns};
1415
use js::rust::HandleObject;
16+
use mime::Mime;
17+
use net_traits::mime_classifier::{MediaType, MimeClassifier};
1518
use net_traits::policy_container::PolicyContainer;
1619
use net_traits::request::{
1720
CorsSettings, Destination, Initiator, InsecureRequestsPolicy, Referrer, RequestBuilder,
@@ -22,7 +25,7 @@ use net_traits::{
2225
ResourceTimingType,
2326
};
2427
use servo_arc::Arc;
25-
use servo_url::ServoUrl;
28+
use servo_url::{ImmutableOrigin, ServoUrl};
2629
use style::attr::AttrValue;
2730
use style::stylesheets::Stylesheet;
2831
use stylo_atoms::Atom;
@@ -78,6 +81,7 @@ struct LinkProcessingOptions {
7881
policy_container: PolicyContainer,
7982
source_set: Option<()>,
8083
base_url: ServoUrl,
84+
origin: ImmutableOrigin,
8185
insecure_requests_policy: InsecureRequestsPolicy,
8286
has_trustworthy_ancestor_origin: bool,
8387
// Some fields that we don't need yet are missing
@@ -257,6 +261,10 @@ impl VirtualMethods for HTMLLinkElement {
257261
if self.relations.get().contains(LinkRelations::PREFETCH) {
258262
self.fetch_and_process_prefetch_link(&attr.value());
259263
}
264+
265+
if self.relations.get().contains(LinkRelations::PRELOAD) {
266+
self.handle_preload_url();
267+
}
260268
},
261269
local_name!("sizes") if self.relations.get().contains(LinkRelations::ICON) => {
262270
if let Some(ref href) = get_attr(self.upcast(), &local_name!("href")) {
@@ -307,6 +315,10 @@ impl VirtualMethods for HTMLLinkElement {
307315
if relations.contains(LinkRelations::PREFETCH) {
308316
self.fetch_and_process_prefetch_link(&href);
309317
}
318+
319+
if relations.contains(LinkRelations::PRELOAD) {
320+
self.handle_preload_url();
321+
}
310322
}
311323
}
312324
}
@@ -348,6 +360,7 @@ impl HTMLLinkElement {
348360
referrer_policy: referrer_policy_for_element(element),
349361
policy_container: document.policy_container().to_owned(),
350362
source_set: None, // FIXME
363+
origin: document.borrow().origin().immutable().to_owned(),
351364
base_url: document.borrow().base_url(),
352365
insecure_requests_policy: document.insecure_requests_policy(),
353366
has_trustworthy_ancestor_origin: document.has_trustworthy_ancestor_or_current_origin(),
@@ -494,6 +507,91 @@ impl HTMLLinkElement {
494507
Err(e) => debug!("Parsing url {} failed: {}", href, e),
495508
}
496509
}
510+
511+
/// <https://html.spec.whatwg.org/multipage/links.html#link-type-preload:fetch-and-process-the-linked-resource-2>
512+
fn handle_preload_url(&self) {
513+
// Step 1. Update the source set for el.
514+
// TODO
515+
// Step 2. Let options be the result of creating link options from el.
516+
let options = self.processing_options();
517+
// Step 3. Preload options, with the following steps given a response response:
518+
// Step 3.1 If response is a network error, fire an event named error at el. Otherwise, fire an event named load at el.
519+
self.preload(options);
520+
}
521+
522+
/// <https://html.spec.whatwg.org/multipage/links.html#preload>
523+
fn preload(&self, options: LinkProcessingOptions) {
524+
// Step 1. If options's type doesn't match options's destination, then return.
525+
if !HTMLLinkElement::type_matches_destination(&options.link_type, options.destination) {
526+
return;
527+
}
528+
// Step 2. If options's destination is "image" and options's source set is not null,
529+
// then set options's href to the result of selecting an image source from options's source set.
530+
// TODO
531+
// Step 3. Let request be the result of creating a link request given options.
532+
let url = options.base_url.clone();
533+
let Some(mut request) = options.create_link_request(self.owner_window().webview_id())
534+
else {
535+
// Step 4. If request is null, then return.
536+
return;
537+
};
538+
let document = self.upcast::<Node>().owner_doc();
539+
// Step 5. Let unsafeEndTime be 0.
540+
// TODO
541+
// Step 6. Let entry be a new preload entry whose integrity metadata is options's integrity.
542+
// TODO
543+
// Step 7. Let key be the result of creating a preload key given request.
544+
// TODO
545+
// Step 8. If options's document is "pending", then set request's initiator type to "early hint".
546+
if document.has_pending_parsing_blocking_script() {
547+
request = request.initiator(Initiator::EarlyHint);
548+
}
549+
// Step 9. Let controller be null.
550+
// Step 10. Let reportTiming given a Document document be to report timing for controller given document's relevant global object.
551+
// Step 11. Set controller to the result of fetching request, with processResponseConsumeBody
552+
// set to the following steps given a response response and null, failure, or a byte sequence bodyBytes:
553+
let fetch_context = PreloadContext {
554+
url,
555+
link: Trusted::new(self),
556+
resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
557+
};
558+
document.fetch_background(request.clone(), fetch_context);
559+
}
560+
561+
fn type_matches_destination(mime_type: &str, destination: Option<Destination>) -> bool {
562+
// Step 1. If type is an empty string, then return true.
563+
if mime_type.is_empty() {
564+
return true;
565+
}
566+
let Some(destination) = destination else {
567+
return false;
568+
};
569+
// Step 2. If destination is "fetch", then return true.
570+
// TODO
571+
// Step 3. Let mimeTypeRecord be the result of parsing type.
572+
let Ok(mime_type_record) = Mime::from_str(mime_type) else {
573+
// Step 4. If mimeTypeRecord is failure, then return false.
574+
return false;
575+
};
576+
// Step 5. If mimeTypeRecord is not supported by the user agent, then return false.
577+
let Some(mime_type) = MimeClassifier::get_media_type(&mime_type_record) else {
578+
return false;
579+
};
580+
// Step 6. If any of the following are true:
581+
if (
582+
// destination is "audio" or "video", and mimeTypeRecord is an audio or video MIME type;
583+
(destination == Destination::Audio || destination == Destination::Video) &&
584+
mime_type == MediaType::AudioVideo)
585+
// destination is a script-like destination and mimeTypeRecord is a JavaScript MIME type;
586+
|| (destination.is_script_like() && mime_type == MediaType::Script)
587+
// destination is "image" and mimeTypeRecord is an image MIME type;
588+
|| (destination == Destination::Image && mime_type == MediaType::Image)
589+
{
590+
return true;
591+
}
592+
// Step 7. Return false.
593+
false
594+
}
497595
}
498596

499597
impl StylesheetOwner for HTMLLinkElement {
@@ -552,6 +650,18 @@ impl HTMLLinkElementMethods<crate::DomTypeHolder> for HTMLLinkElement {
552650
.set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
553651
}
554652

653+
// https://html.spec.whatwg.org/multipage/#dom-link-as
654+
make_enumerated_getter!(
655+
As,
656+
"as",
657+
"fetch" | "font" | "image" | "script" | "track",
658+
missing => "",
659+
invalid => ""
660+
);
661+
662+
// https://html.spec.whatwg.org/multipage/#dom-link-as
663+
make_setter!(SetAs, "as");
664+
555665
// https://html.spec.whatwg.org/multipage/#dom-link-media
556666
make_getter!(Media, "media");
557667

@@ -689,6 +799,7 @@ impl LinkProcessingOptions {
689799
self.has_trustworthy_ancestor_origin,
690800
self.policy_container,
691801
)
802+
.origin(self.origin)
692803
.integrity_metadata(self.integrity)
693804
.cryptographic_nonce_metadata(self.cryptographic_nonce_metadata)
694805
.referrer_policy(self.referrer_policy);
@@ -795,3 +906,89 @@ impl PreInvoke for PrefetchContext {
795906
true
796907
}
797908
}
909+
910+
struct PreloadContext {
911+
/// The `<link>` element that caused this prefetch operation
912+
link: Trusted<HTMLLinkElement>,
913+
914+
resource_timing: ResourceFetchTiming,
915+
916+
/// The url being prefetched
917+
url: ServoUrl,
918+
}
919+
920+
impl FetchResponseListener for PreloadContext {
921+
fn process_request_body(&mut self, _: RequestId) {}
922+
923+
fn process_request_eof(&mut self, _: RequestId) {}
924+
925+
fn process_response(
926+
&mut self,
927+
_: RequestId,
928+
fetch_metadata: Result<FetchMetadata, NetworkError>,
929+
) {
930+
_ = fetch_metadata;
931+
}
932+
933+
fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
934+
_ = chunk;
935+
}
936+
937+
// Step 3.1 of https://html.spec.whatwg.org/multipage/links.html#link-type-preload:fetch-and-process-the-linked-resource-2
938+
fn process_response_eof(
939+
&mut self,
940+
_: RequestId,
941+
response: Result<ResourceFetchTiming, NetworkError>,
942+
) {
943+
if response.is_err() {
944+
// Step 1. If response is a network error, fire an event named error at el.
945+
self.link
946+
.root()
947+
.upcast::<EventTarget>()
948+
.fire_event(atom!("error"), CanGc::note());
949+
} else {
950+
// Step 2. Otherwise, fire an event named load at el.
951+
self.link
952+
.root()
953+
.upcast::<EventTarget>()
954+
.fire_event(atom!("load"), CanGc::note());
955+
}
956+
}
957+
958+
fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
959+
&mut self.resource_timing
960+
}
961+
962+
fn resource_timing(&self) -> &ResourceFetchTiming {
963+
&self.resource_timing
964+
}
965+
966+
fn submit_resource_timing(&mut self) {
967+
submit_timing(self, CanGc::note())
968+
}
969+
970+
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
971+
let global = &self.resource_timing_global();
972+
global.report_csp_violations(violations, None);
973+
}
974+
}
975+
976+
impl ResourceTimingListener for PreloadContext {
977+
fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
978+
(
979+
InitiatorType::LocalName("preload".to_string()),
980+
self.url.clone(),
981+
)
982+
}
983+
984+
fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
985+
self.link.root().upcast::<Node>().owner_doc().global()
986+
}
987+
}
988+
989+
impl PreInvoke for PreloadContext {
990+
fn should_invoke(&self) -> bool {
991+
// Preload requests are never aborted.
992+
true
993+
}
994+
}

components/script_bindings/webidls/HTMLLinkElement.webidl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface HTMLLinkElement : HTMLElement {
1313
attribute DOMString? crossOrigin;
1414
[CEReactions]
1515
attribute DOMString rel;
16-
// [CEReactions] attribute DOMString as;
16+
[CEReactions] attribute DOMString as;
1717
[SameObject, PutForwards=value] readonly attribute DOMTokenList relList;
1818
[CEReactions]
1919
attribute DOMString media;

components/shared/net/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub mod blob_url_store;
4040
pub mod filemanager_thread;
4141
pub mod http_status;
4242
pub mod image_cache;
43+
pub mod mime_classifier;
4344
pub mod policy_container;
4445
pub mod pub_domains;
4546
pub mod quality;

components/net/mime_classifier.rs renamed to components/shared/net/mime_classifier.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
44

55
use mime::{self, Mime};
6-
use net_traits::LoadContext;
6+
7+
use crate::LoadContext;
78

89
pub struct MimeClassifier {
910
image_classifier: GroupedClassifier,
@@ -16,6 +17,7 @@ pub struct MimeClassifier {
1617
font_classifier: GroupedClassifier,
1718
}
1819

20+
#[derive(PartialEq)]
1921
pub enum MediaType {
2022
Xml,
2123
Html,
@@ -241,7 +243,7 @@ impl MimeClassifier {
241243
mt.type_() == mime::STAR && mt.subtype() == mime::STAR
242244
}
243245

244-
fn get_media_type(mime: &Mime) -> Option<MediaType> {
246+
pub fn get_media_type(mime: &Mime) -> Option<MediaType> {
245247
if MimeClassifier::is_xml(mime) {
246248
Some(MediaType::Xml)
247249
} else if MimeClassifier::is_html(mime) {

components/shared/net/request.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub enum Initiator {
3939
XSLT,
4040
Prefetch,
4141
Link,
42+
EarlyHint,
4243
}
4344

4445
/// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination)

components/net/tests/mime_classifier.rs renamed to components/shared/net/tests/mime_classifier.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use std::io::{self, Read};
88
use std::path::{self, PathBuf};
99

1010
use mime::{self, Mime};
11-
use net::mime_classifier::{ApacheBugFlag, MimeClassifier, Mp4Matcher, NoSniffFlag};
1211
use net_traits::LoadContext;
12+
use net_traits::mime_classifier::{ApacheBugFlag, MimeClassifier, Mp4Matcher, NoSniffFlag};
1313

1414
fn read_file(path: &path::Path) -> io::Result<Vec<u8>> {
1515
let mut file = File::open(path)?;

tests/wpt/meta/content-security-policy/font-src/font-match-allowed.sub.html.ini

Lines changed: 0 additions & 4 deletions
This file was deleted.

tests/wpt/meta/content-security-policy/font-src/font-mismatch-blocked.sub.html.ini

Lines changed: 0 additions & 4 deletions
This file was deleted.

tests/wpt/meta/content-security-policy/font-src/font-none-blocked.sub.html.ini

Lines changed: 0 additions & 4 deletions
This file was deleted.

tests/wpt/meta/content-security-policy/font-src/font-self-allowed.html.ini

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)