Skip to content

Commit e4ff039

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 e4ff039

32 files changed

+349
-348
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: 206 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,99 @@ 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+
// destination is "font" and mimeTypeRecord is a font MIME type;
590+
// TODO
591+
// destination is "json" and mimeTypeRecord is a JSON MIME type;
592+
// TODO
593+
// destination is "style" and mimeTypeRecord's essence is text/css; or
594+
// TODO
595+
// destination is "track" and mimeTypeRecord's essence is text/vtt,
596+
// TODO
597+
{
598+
return true;
599+
}
600+
// Step 7. Return false.
601+
false
602+
}
497603
}
498604

499605
impl StylesheetOwner for HTMLLinkElement {
@@ -552,6 +658,18 @@ impl HTMLLinkElementMethods<crate::DomTypeHolder> for HTMLLinkElement {
552658
.set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
553659
}
554660

661+
// https://html.spec.whatwg.org/multipage/#dom-link-as
662+
make_enumerated_getter!(
663+
As,
664+
"as",
665+
"fetch" | "font" | "image" | "script" | "track",
666+
missing => "",
667+
invalid => ""
668+
);
669+
670+
// https://html.spec.whatwg.org/multipage/#dom-link-as
671+
make_setter!(SetAs, "as");
672+
555673
// https://html.spec.whatwg.org/multipage/#dom-link-media
556674
make_getter!(Media, "media");
557675

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

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: 14 additions & 3 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,11 +17,13 @@ pub struct MimeClassifier {
1617
font_classifier: GroupedClassifier,
1718
}
1819

20+
#[derive(PartialEq)]
1921
pub enum MediaType {
2022
Xml,
2123
Html,
2224
AudioVideo,
2325
Image,
26+
Script,
2427
}
2528

2629
pub enum ApacheBugFlag {
@@ -99,7 +102,9 @@ impl MimeClassifier {
99102
Some(MediaType::AudioVideo) => {
100103
self.audio_video_classifier.classify(data)
101104
},
102-
Some(MediaType::Xml) | None => None,
105+
Some(MediaType::Script) | Some(MediaType::Xml) | None => {
106+
None
107+
},
103108
}
104109
.unwrap_or(supplied_type.clone())
105110
},
@@ -241,7 +246,11 @@ impl MimeClassifier {
241246
mt.type_() == mime::STAR && mt.subtype() == mime::STAR
242247
}
243248

244-
fn get_media_type(mime: &Mime) -> Option<MediaType> {
249+
fn is_script(mt: &Mime) -> bool {
250+
mt.type_() == mime::JAVASCRIPT
251+
}
252+
253+
pub fn get_media_type(mime: &Mime) -> Option<MediaType> {
245254
if MimeClassifier::is_xml(mime) {
246255
Some(MediaType::Xml)
247256
} else if MimeClassifier::is_html(mime) {
@@ -250,6 +259,8 @@ impl MimeClassifier {
250259
Some(MediaType::Image)
251260
} else if MimeClassifier::is_audio_video(mime) {
252261
Some(MediaType::AudioVideo)
262+
} else if MimeClassifier::is_script(mime) {
263+
Some(MediaType::Script)
253264
} else {
254265
None
255266
}

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.

0 commit comments

Comments
 (0)