5
5
use std:: borrow:: { Borrow , ToOwned } ;
6
6
use std:: cell:: Cell ;
7
7
use std:: default:: Default ;
8
+ use std:: str:: FromStr ;
8
9
9
10
use base:: id:: WebViewId ;
10
11
use content_security_policy as csp;
11
12
use dom_struct:: dom_struct;
12
13
use embedder_traits:: EmbedderMsg ;
13
14
use html5ever:: { LocalName , Prefix , local_name, ns} ;
14
15
use js:: rust:: HandleObject ;
16
+ use mime:: Mime ;
17
+ use net_traits:: mime_classifier:: { MediaType , MimeClassifier } ;
15
18
use net_traits:: policy_container:: PolicyContainer ;
16
19
use net_traits:: request:: {
17
20
CorsSettings , Destination , Initiator , InsecureRequestsPolicy , Referrer , RequestBuilder ,
@@ -22,7 +25,7 @@ use net_traits::{
22
25
ResourceTimingType ,
23
26
} ;
24
27
use servo_arc:: Arc ;
25
- use servo_url:: ServoUrl ;
28
+ use servo_url:: { ImmutableOrigin , ServoUrl } ;
26
29
use style:: attr:: AttrValue ;
27
30
use style:: stylesheets:: Stylesheet ;
28
31
use stylo_atoms:: Atom ;
@@ -78,6 +81,7 @@ struct LinkProcessingOptions {
78
81
policy_container : PolicyContainer ,
79
82
source_set : Option < ( ) > ,
80
83
base_url : ServoUrl ,
84
+ origin : ImmutableOrigin ,
81
85
insecure_requests_policy : InsecureRequestsPolicy ,
82
86
has_trustworthy_ancestor_origin : bool ,
83
87
// Some fields that we don't need yet are missing
@@ -257,6 +261,10 @@ impl VirtualMethods for HTMLLinkElement {
257
261
if self . relations . get ( ) . contains ( LinkRelations :: PREFETCH ) {
258
262
self . fetch_and_process_prefetch_link ( & attr. value ( ) ) ;
259
263
}
264
+
265
+ if self . relations . get ( ) . contains ( LinkRelations :: PRELOAD ) {
266
+ self . handle_preload_url ( ) ;
267
+ }
260
268
} ,
261
269
local_name ! ( "sizes" ) if self . relations . get ( ) . contains ( LinkRelations :: ICON ) => {
262
270
if let Some ( ref href) = get_attr ( self . upcast ( ) , & local_name ! ( "href" ) ) {
@@ -307,6 +315,10 @@ impl VirtualMethods for HTMLLinkElement {
307
315
if relations. contains ( LinkRelations :: PREFETCH ) {
308
316
self . fetch_and_process_prefetch_link ( & href) ;
309
317
}
318
+
319
+ if relations. contains ( LinkRelations :: PRELOAD ) {
320
+ self . handle_preload_url ( ) ;
321
+ }
310
322
}
311
323
}
312
324
}
@@ -348,6 +360,7 @@ impl HTMLLinkElement {
348
360
referrer_policy : referrer_policy_for_element ( element) ,
349
361
policy_container : document. policy_container ( ) . to_owned ( ) ,
350
362
source_set : None , // FIXME
363
+ origin : document. borrow ( ) . origin ( ) . immutable ( ) . to_owned ( ) ,
351
364
base_url : document. borrow ( ) . base_url ( ) ,
352
365
insecure_requests_policy : document. insecure_requests_policy ( ) ,
353
366
has_trustworthy_ancestor_origin : document. has_trustworthy_ancestor_or_current_origin ( ) ,
@@ -494,6 +507,128 @@ impl HTMLLinkElement {
494
507
Err ( e) => debug ! ( "Parsing url {} failed: {}" , href, e) ,
495
508
}
496
509
}
510
+
511
+ /// <https://html.spec.whatwg.org/multipage/#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.
519
+ // Otherwise, fire an event named load at el.
520
+ self . preload ( options) ;
521
+ }
522
+
523
+ /// <https://html.spec.whatwg.org/multipage/#preload>
524
+ fn preload ( & self , options : LinkProcessingOptions ) {
525
+ // Step 1. If options's type doesn't match options's destination, then return.
526
+ if !HTMLLinkElement :: type_matches_destination ( & options. link_type , options. destination ) {
527
+ return ;
528
+ }
529
+ // Step 2. If options's destination is "image" and options's source set is not null,
530
+ // then set options's href to the result of selecting an image source from options's source set.
531
+ // TODO
532
+ // Step 3. Let request be the result of creating a link request given options.
533
+ let url = options. base_url . clone ( ) ;
534
+ let Some ( mut request) = options. create_link_request ( self . owner_window ( ) . webview_id ( ) )
535
+ else {
536
+ // Step 4. If request is null, then return.
537
+ return ;
538
+ } ;
539
+ let document = self . upcast :: < Node > ( ) . owner_doc ( ) ;
540
+ // Step 5. Let unsafeEndTime be 0.
541
+ // TODO
542
+ // Step 6. Let entry be a new preload entry whose integrity metadata is options's integrity.
543
+ // TODO
544
+ // Step 7. Let key be the result of creating a preload key given request.
545
+ // TODO
546
+ // Step 8. If options's document is "pending", then set request's initiator type to "early hint".
547
+ if document. has_pending_parsing_blocking_script ( ) {
548
+ request = request. initiator ( Initiator :: EarlyHint ) ;
549
+ }
550
+ // Step 9. Let controller be null.
551
+ // Step 10. Let reportTiming given a Document document be to report timing for controller
552
+ // given document's relevant global object.
553
+ // Step 11. Set controller to the result of fetching request, with processResponseConsumeBody
554
+ // set to the following steps given a response response and null, failure, or a byte sequence bodyBytes:
555
+ let fetch_context = PreloadContext {
556
+ url,
557
+ link : Trusted :: new ( self ) ,
558
+ resource_timing : ResourceFetchTiming :: new ( ResourceTimingType :: Resource ) ,
559
+ } ;
560
+ document. fetch_background ( request. clone ( ) , fetch_context) ;
561
+ }
562
+
563
+ /// <https://html.spec.whatwg.org/multipage/#match-preload-type>
564
+ fn type_matches_destination ( mime_type : & str , destination : Option < Destination > ) -> bool {
565
+ // Step 1. If type is an empty string, then return true.
566
+ if mime_type. is_empty ( ) {
567
+ return true ;
568
+ }
569
+ let Some ( destination) = destination else {
570
+ return false ;
571
+ } ;
572
+ // Step 2. If destination is "fetch", then return true.
573
+ //
574
+ // Fetch is handled as an empty string destination in the spec
575
+ if destination == Destination :: None {
576
+ return true ;
577
+ }
578
+ // Step 3. Let mimeTypeRecord be the result of parsing type.
579
+ let Ok ( mime_type_record) = Mime :: from_str ( mime_type) else {
580
+ // Step 4. If mimeTypeRecord is failure, then return false.
581
+ return false ;
582
+ } ;
583
+ // Step 5. If mimeTypeRecord is not supported by the user agent, then return false.
584
+ let Some ( mime_type) = MimeClassifier :: get_media_type ( & mime_type_record) else {
585
+ return false ;
586
+ } ;
587
+ // Step 6. If any of the following are true:
588
+ if
589
+ // destination is "audio" or "video", and mimeTypeRecord is an audio or video MIME type;
590
+ ( ( destination == Destination :: Audio || destination == Destination :: Video ) &&
591
+ mime_type == MediaType :: AudioVideo )
592
+ // destination is a script-like destination and mimeTypeRecord is a JavaScript MIME type;
593
+ || ( destination. is_script_like ( ) && mime_type == MediaType :: JavaScript )
594
+ // destination is "image" and mimeTypeRecord is an image MIME type;
595
+ || ( destination == Destination :: Image && mime_type == MediaType :: Image )
596
+ // destination is "font" and mimeTypeRecord is a font MIME type;
597
+ || ( destination == Destination :: Font && mime_type == MediaType :: Font )
598
+ // destination is "json" and mimeTypeRecord is a JSON MIME type;
599
+ || ( destination == Destination :: Json && mime_type == MediaType :: Json )
600
+ // destination is "style" and mimeTypeRecord's essence is text/css; or
601
+ || ( destination == Destination :: Style && mime_type_record == mime:: TEXT_CSS )
602
+ // destination is "track" and mimeTypeRecord's essence is text/vtt,
603
+ // TODO
604
+ {
605
+ return true ;
606
+ }
607
+ // Step 7. Return false.
608
+ false
609
+ }
610
+
611
+ fn fire_event_after_response ( & self , response : Result < ResourceFetchTiming , NetworkError > ) {
612
+ if response. is_err ( ) {
613
+ self . upcast :: < EventTarget > ( )
614
+ . fire_event ( atom ! ( "error" ) , CanGc :: note ( ) ) ;
615
+ } else {
616
+ // TODO(35035): Figure out why we need to queue a task for the load event. Otherwise
617
+ // the performance timing data hasn't been saved yet, which fails several preload
618
+ // WPT tests that assume that performance timing information is available when
619
+ // the load event is fired.
620
+ let this = Trusted :: new ( self ) ;
621
+ self . owner_global ( )
622
+ . task_manager ( )
623
+ . performance_timeline_task_source ( )
624
+ . queue ( task ! ( preload_load_event: move || {
625
+ let this = this. root( ) ;
626
+ this
627
+ . upcast:: <EventTarget >( )
628
+ . fire_event( atom!( "load" ) , CanGc :: note( ) ) ;
629
+ } ) ) ;
630
+ }
631
+ }
497
632
}
498
633
499
634
impl StylesheetOwner for HTMLLinkElement {
@@ -552,6 +687,21 @@ impl HTMLLinkElementMethods<crate::DomTypeHolder> for HTMLLinkElement {
552
687
. set_tokenlist_attribute ( & local_name ! ( "rel" ) , rel, can_gc) ;
553
688
}
554
689
690
+ // https://html.spec.whatwg.org/multipage/#dom-link-as
691
+ make_enumerated_getter ! (
692
+ As ,
693
+ "as" ,
694
+ "fetch" | "audio" | "audioworklet" | "document" | "embed" | "font" | "frame"
695
+ | "iframe" | "image" | "json" | "manifest" | "object" | "paintworklet"
696
+ | "report" | "script" | "serviceworker" | "sharedworker" | "style" | "track"
697
+ | "video" | "webidentity" | "worker" | "xslt" ,
698
+ missing => "" ,
699
+ invalid => ""
700
+ ) ;
701
+
702
+ // https://html.spec.whatwg.org/multipage/#dom-link-as
703
+ make_setter ! ( SetAs , "as" ) ;
704
+
555
705
// https://html.spec.whatwg.org/multipage/#dom-link-media
556
706
make_getter ! ( Media , "media" ) ;
557
707
@@ -689,6 +839,7 @@ impl LinkProcessingOptions {
689
839
self . has_trustworthy_ancestor_origin ,
690
840
self . policy_container ,
691
841
)
842
+ . origin ( self . origin )
692
843
. integrity_metadata ( self . integrity )
693
844
. cryptographic_nonce_metadata ( self . cryptographic_nonce_metadata )
694
845
. referrer_policy ( self . referrer_policy ) ;
@@ -795,3 +946,77 @@ impl PreInvoke for PrefetchContext {
795
946
true
796
947
}
797
948
}
949
+
950
+ struct PreloadContext {
951
+ /// The `<link>` element that caused this prefetch operation
952
+ link : Trusted < HTMLLinkElement > ,
953
+
954
+ resource_timing : ResourceFetchTiming ,
955
+
956
+ /// The url being prefetched
957
+ url : ServoUrl ,
958
+ }
959
+
960
+ impl FetchResponseListener for PreloadContext {
961
+ fn process_request_body ( & mut self , _: RequestId ) { }
962
+
963
+ fn process_request_eof ( & mut self , _: RequestId ) { }
964
+
965
+ fn process_response (
966
+ & mut self ,
967
+ _: RequestId ,
968
+ fetch_metadata : Result < FetchMetadata , NetworkError > ,
969
+ ) {
970
+ _ = fetch_metadata;
971
+ }
972
+
973
+ fn process_response_chunk ( & mut self , _: RequestId , chunk : Vec < u8 > ) {
974
+ _ = chunk;
975
+ }
976
+
977
+ /// Step 3.1 of <https://html.spec.whatwg.org/multipage/#link-type-preload:fetch-and-process-the-linked-resource-2>
978
+ fn process_response_eof (
979
+ & mut self ,
980
+ _: RequestId ,
981
+ response : Result < ResourceFetchTiming , NetworkError > ,
982
+ ) {
983
+ self . link . root ( ) . fire_event_after_response ( response) ;
984
+ }
985
+
986
+ fn resource_timing_mut ( & mut self ) -> & mut ResourceFetchTiming {
987
+ & mut self . resource_timing
988
+ }
989
+
990
+ fn resource_timing ( & self ) -> & ResourceFetchTiming {
991
+ & self . resource_timing
992
+ }
993
+
994
+ fn submit_resource_timing ( & mut self ) {
995
+ submit_timing ( self , CanGc :: note ( ) )
996
+ }
997
+
998
+ fn process_csp_violations ( & mut self , _request_id : RequestId , violations : Vec < csp:: Violation > ) {
999
+ let global = & self . resource_timing_global ( ) ;
1000
+ global. report_csp_violations ( violations, None ) ;
1001
+ }
1002
+ }
1003
+
1004
+ impl ResourceTimingListener for PreloadContext {
1005
+ fn resource_timing_information ( & self ) -> ( InitiatorType , ServoUrl ) {
1006
+ (
1007
+ InitiatorType :: LocalName ( self . url . clone ( ) . into_string ( ) ) ,
1008
+ self . url . clone ( ) ,
1009
+ )
1010
+ }
1011
+
1012
+ fn resource_timing_global ( & self ) -> DomRoot < GlobalScope > {
1013
+ self . link . root ( ) . upcast :: < Node > ( ) . owner_doc ( ) . global ( )
1014
+ }
1015
+ }
1016
+
1017
+ impl PreInvoke for PreloadContext {
1018
+ fn should_invoke ( & self ) -> bool {
1019
+ // Preload requests are never aborted.
1020
+ true
1021
+ }
1022
+ }
0 commit comments