script/dom/html/
htmllinkelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::borrow::{Borrow, ToOwned};
6use std::cell::Cell;
7use std::default::Default;
8use std::str::FromStr;
9
10use base::id::WebViewId;
11use dom_struct::dom_struct;
12use embedder_traits::EmbedderMsg;
13use html5ever::{LocalName, Prefix, local_name, ns};
14use ipc_channel::ipc::IpcSender;
15use js::rust::HandleObject;
16use mime::Mime;
17use net_traits::image_cache::{
18    Image, ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
19    ImageOrMetadataAvailable, ImageResponse, PendingImageId, UsePlaceholder,
20};
21use net_traits::mime_classifier::{MediaType, MimeClassifier};
22use net_traits::policy_container::PolicyContainer;
23use net_traits::request::{
24    CorsSettings, Destination, Initiator, InsecureRequestsPolicy, Referrer, RequestBuilder,
25    RequestId,
26};
27use net_traits::{
28    FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError, ReferrerPolicy,
29    ResourceFetchTiming, ResourceTimingType,
30};
31use pixels::PixelFormat;
32use script_bindings::root::Dom;
33use servo_arc::Arc;
34use servo_url::{ImmutableOrigin, ServoUrl};
35use style::attr::AttrValue;
36use style::stylesheets::Stylesheet;
37use stylo_atoms::Atom;
38use webrender_api::units::DeviceIntSize;
39
40use crate::dom::attr::Attr;
41use crate::dom::bindings::cell::DomRefCell;
42use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenList_Binding::DOMTokenListMethods;
43use crate::dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods;
44use crate::dom::bindings::inheritance::Castable;
45use crate::dom::bindings::refcounted::Trusted;
46use crate::dom::bindings::reflector::DomGlobal;
47use crate::dom::bindings::root::{DomRoot, MutNullableDom};
48use crate::dom::bindings::str::{DOMString, USVString};
49use crate::dom::csp::{GlobalCspReporting, Violation};
50use crate::dom::cssstylesheet::CSSStyleSheet;
51use crate::dom::document::Document;
52use crate::dom::documentorshadowroot::StylesheetSource;
53use crate::dom::domtokenlist::DOMTokenList;
54use crate::dom::element::{
55    AttributeMutation, Element, ElementCreator, cors_setting_for_element,
56    referrer_policy_for_element, reflect_cross_origin_attribute, reflect_referrer_policy_attribute,
57    set_cross_origin_attribute,
58};
59use crate::dom::html::htmlelement::HTMLElement;
60use crate::dom::medialist::MediaList;
61use crate::dom::node::{BindContext, Node, NodeTraits, UnbindContext};
62use crate::dom::performanceresourcetiming::InitiatorType;
63use crate::dom::stylesheet::StyleSheet as DOMStyleSheet;
64use crate::dom::types::{EventTarget, GlobalScope};
65use crate::dom::virtualmethods::VirtualMethods;
66use crate::fetch::create_a_potential_cors_request;
67use crate::links::LinkRelations;
68use crate::network_listener::{PreInvoke, ResourceTimingListener, submit_timing};
69use crate::script_runtime::CanGc;
70use crate::stylesheet_loader::{StylesheetContextSource, StylesheetLoader, StylesheetOwner};
71
72#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
73pub(crate) struct RequestGenerationId(u32);
74
75impl RequestGenerationId {
76    fn increment(self) -> RequestGenerationId {
77        RequestGenerationId(self.0 + 1)
78    }
79}
80
81/// <https://html.spec.whatwg.org/multipage/#link-processing-options>
82struct LinkProcessingOptions {
83    href: String,
84    destination: Option<Destination>,
85    integrity: String,
86    link_type: String,
87    cryptographic_nonce_metadata: String,
88    cross_origin: Option<CorsSettings>,
89    referrer_policy: ReferrerPolicy,
90    policy_container: PolicyContainer,
91    source_set: Option<()>,
92    base_url: ServoUrl,
93    origin: ImmutableOrigin,
94    insecure_requests_policy: InsecureRequestsPolicy,
95    has_trustworthy_ancestor_origin: bool,
96    // Some fields that we don't need yet are missing
97}
98
99#[dom_struct]
100pub(crate) struct HTMLLinkElement {
101    htmlelement: HTMLElement,
102    /// The relations as specified by the "rel" attribute
103    rel_list: MutNullableDom<DOMTokenList>,
104
105    /// The link relations as they are used in practice.
106    ///
107    /// The reason this is seperate from [HTMLLinkElement::rel_list] is that
108    /// a literal list is a bit unwieldy and that there are corner cases to consider
109    /// (Like `rev="made"` implying an author relationship that is not represented in rel_list)
110    #[no_trace]
111    relations: Cell<LinkRelations>,
112
113    #[conditional_malloc_size_of]
114    #[no_trace]
115    stylesheet: DomRefCell<Option<Arc<Stylesheet>>>,
116    cssom_stylesheet: MutNullableDom<CSSStyleSheet>,
117
118    /// <https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts>
119    parser_inserted: Cell<bool>,
120    /// The number of loads that this link element has triggered (could be more
121    /// than one because of imports) and have not yet finished.
122    pending_loads: Cell<u32>,
123    /// Whether any of the loads have failed.
124    any_failed_load: Cell<bool>,
125    /// A monotonically increasing counter that keeps track of which stylesheet to apply.
126    request_generation_id: Cell<RequestGenerationId>,
127    /// <https://html.spec.whatwg.org/multipage/#explicitly-enabled>
128    is_explicitly_enabled: Cell<bool>,
129    /// Whether the previous type matched with the destination
130    previous_type_matched: Cell<bool>,
131    /// Whether the previous media environment matched with the media query
132    previous_media_environment_matched: Cell<bool>,
133    /// Line number this element was created on
134    line_number: u64,
135}
136
137impl HTMLLinkElement {
138    fn new_inherited(
139        local_name: LocalName,
140        prefix: Option<Prefix>,
141        document: &Document,
142        creator: ElementCreator,
143    ) -> HTMLLinkElement {
144        HTMLLinkElement {
145            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
146            rel_list: Default::default(),
147            relations: Cell::new(LinkRelations::empty()),
148            parser_inserted: Cell::new(creator.is_parser_created()),
149            stylesheet: DomRefCell::new(None),
150            cssom_stylesheet: MutNullableDom::new(None),
151            pending_loads: Cell::new(0),
152            any_failed_load: Cell::new(false),
153            request_generation_id: Cell::new(RequestGenerationId(0)),
154            is_explicitly_enabled: Cell::new(false),
155            previous_type_matched: Cell::new(true),
156            previous_media_environment_matched: Cell::new(true),
157            line_number: creator.return_line_number(),
158        }
159    }
160
161    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
162    pub(crate) fn new(
163        local_name: LocalName,
164        prefix: Option<Prefix>,
165        document: &Document,
166        proto: Option<HandleObject>,
167        creator: ElementCreator,
168        can_gc: CanGc,
169    ) -> DomRoot<HTMLLinkElement> {
170        Node::reflect_node_with_proto(
171            Box::new(HTMLLinkElement::new_inherited(
172                local_name, prefix, document, creator,
173            )),
174            document,
175            proto,
176            can_gc,
177        )
178    }
179
180    pub(crate) fn get_request_generation_id(&self) -> RequestGenerationId {
181        self.request_generation_id.get()
182    }
183
184    // FIXME(emilio): These methods are duplicated with
185    // HTMLStyleElement::set_stylesheet.
186    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
187    pub(crate) fn set_stylesheet(&self, s: Arc<Stylesheet>) {
188        let stylesheets_owner = self.stylesheet_list_owner();
189        if let Some(ref s) = *self.stylesheet.borrow() {
190            stylesheets_owner
191                .remove_stylesheet(StylesheetSource::Element(Dom::from_ref(self.upcast())), s)
192        }
193        *self.stylesheet.borrow_mut() = Some(s.clone());
194        self.clean_stylesheet_ownership();
195        stylesheets_owner.add_owned_stylesheet(self.upcast(), s);
196    }
197
198    pub(crate) fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
199        self.stylesheet.borrow().clone()
200    }
201
202    pub(crate) fn get_cssom_stylesheet(&self, can_gc: CanGc) -> Option<DomRoot<CSSStyleSheet>> {
203        self.get_stylesheet().map(|sheet| {
204            self.cssom_stylesheet.or_init(|| {
205                CSSStyleSheet::new(
206                    &self.owner_window(),
207                    Some(self.upcast::<Element>()),
208                    "text/css".into(),
209                    None, // todo handle location
210                    None, // todo handle title
211                    sheet,
212                    None, // constructor_document
213                    can_gc,
214                )
215            })
216        })
217    }
218
219    pub(crate) fn is_alternate(&self) -> bool {
220        self.relations.get().contains(LinkRelations::ALTERNATE)
221    }
222
223    pub(crate) fn is_effectively_disabled(&self) -> bool {
224        (self.is_alternate() && !self.is_explicitly_enabled.get()) ||
225            self.upcast::<Element>()
226                .has_attribute(&local_name!("disabled"))
227    }
228
229    fn clean_stylesheet_ownership(&self) {
230        if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() {
231            cssom_stylesheet.set_owner_node(None);
232        }
233        self.cssom_stylesheet.set(None);
234    }
235}
236
237fn get_attr(element: &Element, local_name: &LocalName) -> Option<String> {
238    let elem = element.get_attribute(&ns!(), local_name);
239    elem.map(|e| {
240        let value = e.value();
241        (**value).to_owned()
242    })
243}
244
245impl VirtualMethods for HTMLLinkElement {
246    fn super_type(&self) -> Option<&dyn VirtualMethods> {
247        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
248    }
249
250    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
251        self.super_type()
252            .unwrap()
253            .attribute_mutated(attr, mutation, can_gc);
254
255        let local_name = attr.local_name();
256        let is_removal = mutation.is_removal();
257        if *local_name == local_name!("disabled") {
258            self.handle_disabled_attribute_change(!is_removal);
259            return;
260        }
261
262        if !self.upcast::<Node>().is_connected() {
263            return;
264        }
265        match *local_name {
266            local_name!("rel") | local_name!("rev") => {
267                self.relations
268                    .set(LinkRelations::for_element(self.upcast()));
269            },
270            local_name!("href") => {
271                if is_removal {
272                    return;
273                }
274                // https://html.spec.whatwg.org/multipage/#link-type-stylesheet
275                // When the href attribute of the link element of an external resource link
276                // that is already browsing-context connected is changed.
277                if self.relations.get().contains(LinkRelations::STYLESHEET) {
278                    self.handle_stylesheet_url(&attr.value());
279                }
280
281                if self.relations.get().contains(LinkRelations::ICON) {
282                    self.handle_favicon_url();
283                }
284
285                // https://html.spec.whatwg.org/multipage/#link-type-prefetch
286                // When the href attribute of the link element of an external resource link
287                // that is already browsing-context connected is changed.
288                if self.relations.get().contains(LinkRelations::PREFETCH) {
289                    self.fetch_and_process_prefetch_link(&attr.value());
290                }
291
292                // https://html.spec.whatwg.org/multipage/#link-type-preload
293                // When the href attribute of the link element of an external resource link
294                // that is already browsing-context connected is changed.
295                if self.relations.get().contains(LinkRelations::PRELOAD) {
296                    self.handle_preload_url();
297                }
298            },
299            local_name!("sizes") if self.relations.get().contains(LinkRelations::ICON) => {
300                self.handle_favicon_url();
301            },
302            local_name!("crossorigin") => {
303                // https://html.spec.whatwg.org/multipage/#link-type-prefetch
304                // When the crossorigin attribute of the link element of an external resource link
305                // that is already browsing-context connected is set, changed, or removed.
306                if self.relations.get().contains(LinkRelations::PREFETCH) {
307                    self.fetch_and_process_prefetch_link(&attr.value());
308                }
309
310                // https://html.spec.whatwg.org/multipage/#link-type-stylesheet
311                // When the crossorigin attribute of the link element of an external resource link
312                // that is already browsing-context connected is set, changed, or removed.
313                if self.relations.get().contains(LinkRelations::STYLESHEET) {
314                    self.handle_stylesheet_url(&attr.value());
315                }
316            },
317            local_name!("as") => {
318                // https://html.spec.whatwg.org/multipage/#link-type-preload
319                // When the as attribute of the link element of an external resource link
320                // that is already browsing-context connected is changed.
321                if self.relations.get().contains(LinkRelations::PRELOAD) {
322                    if let AttributeMutation::Set(Some(_)) = mutation {
323                        self.handle_preload_url();
324                    }
325                }
326            },
327            local_name!("type") => {
328                // https://html.spec.whatwg.org/multipage/#link-type-stylesheet
329                // When the type attribute of the link element of an external resource link that
330                // is already browsing-context connected is set or changed to a value that does
331                // not or no longer matches the Content-Type metadata of the previous obtained
332                // external resource, if any.
333                //
334                // TODO: Match Content-Type metadata to check if it needs to be updated
335                if self.relations.get().contains(LinkRelations::STYLESHEET) {
336                    self.handle_stylesheet_url(&attr.value());
337                }
338
339                // https://html.spec.whatwg.org/multipage/#link-type-preload
340                // When the type attribute of the link element of an external resource link that
341                // is already browsing-context connected, but was previously not obtained due to
342                // the type attribute specifying an unsupported type for the request destination,
343                // is set, removed, or changed.
344                if self.relations.get().contains(LinkRelations::PRELOAD) &&
345                    !self.previous_type_matched.get()
346                {
347                    self.handle_preload_url();
348                }
349            },
350            local_name!("media") => {
351                // https://html.spec.whatwg.org/multipage/#link-type-preload
352                // When the media attribute of the link element of an external resource link that
353                // is already browsing-context connected, but was previously not obtained due to
354                // the media attribute not matching the environment, is changed or removed.
355                if self.relations.get().contains(LinkRelations::PRELOAD) &&
356                    !self.previous_media_environment_matched.get()
357                {
358                    match mutation {
359                        AttributeMutation::Removed | AttributeMutation::Set(Some(_)) => {
360                            self.handle_preload_url()
361                        },
362                        _ => {},
363                    };
364                }
365
366                let matches_media_environment =
367                    self.upcast::<Element>().matches_environment(&attr.value());
368                self.previous_media_environment_matched
369                    .set(matches_media_environment);
370            },
371            _ => {},
372        }
373    }
374
375    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
376        match name {
377            &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
378            _ => self
379                .super_type()
380                .unwrap()
381                .parse_plain_attribute(name, value),
382        }
383    }
384
385    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
386        if let Some(s) = self.super_type() {
387            s.bind_to_tree(context, can_gc);
388        }
389
390        self.relations
391            .set(LinkRelations::for_element(self.upcast()));
392
393        if context.tree_connected {
394            let element = self.upcast();
395
396            if let Some(href) = get_attr(element, &local_name!("href")) {
397                let relations = self.relations.get();
398                if relations.contains(LinkRelations::STYLESHEET) {
399                    self.handle_stylesheet_url(&href);
400                }
401
402                if relations.contains(LinkRelations::ICON) {
403                    self.handle_favicon_url();
404                }
405
406                if relations.contains(LinkRelations::PREFETCH) {
407                    self.fetch_and_process_prefetch_link(&href);
408                }
409
410                if relations.contains(LinkRelations::PRELOAD) {
411                    self.handle_preload_url();
412                }
413            }
414        }
415    }
416
417    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
418        if let Some(s) = self.super_type() {
419            s.unbind_from_tree(context, can_gc);
420        }
421
422        if let Some(s) = self.stylesheet.borrow_mut().take() {
423            self.clean_stylesheet_ownership();
424            self.stylesheet_list_owner()
425                .remove_stylesheet(StylesheetSource::Element(Dom::from_ref(self.upcast())), &s);
426        }
427    }
428}
429
430impl HTMLLinkElement {
431    fn compute_destination_for_attribute(&self) -> Destination {
432        let element = self.upcast::<Element>();
433        element
434            .get_attribute(&ns!(), &local_name!("as"))
435            .map(|attr| translate_a_preload_destination(&attr.value()))
436            .unwrap_or(Destination::None)
437    }
438
439    /// <https://html.spec.whatwg.org/multipage/#create-link-options-from-element>
440    fn processing_options(&self) -> LinkProcessingOptions {
441        let element = self.upcast::<Element>();
442
443        // Step 1. Let document be el's node document.
444        let document = self.upcast::<Node>().owner_doc();
445
446        // Step 2. Let options be a new link processing options
447        let destination = self.compute_destination_for_attribute();
448
449        let mut options = LinkProcessingOptions {
450            href: String::new(),
451            destination: Some(destination),
452            integrity: String::new(),
453            link_type: String::new(),
454            cryptographic_nonce_metadata: self.upcast::<Element>().nonce_value(),
455            cross_origin: cors_setting_for_element(element),
456            referrer_policy: referrer_policy_for_element(element),
457            policy_container: document.policy_container().to_owned(),
458            source_set: None, // FIXME
459            origin: document.borrow().origin().immutable().to_owned(),
460            base_url: document.borrow().base_url(),
461            insecure_requests_policy: document.insecure_requests_policy(),
462            has_trustworthy_ancestor_origin: document.has_trustworthy_ancestor_or_current_origin(),
463        };
464
465        // Step 3. If el has an href attribute, then set options's href to the value of el's href attribute.
466        if let Some(href_attribute) = element.get_attribute(&ns!(), &local_name!("href")) {
467            options.href = (**href_attribute.value()).to_owned();
468        }
469
470        // Step 4. If el has an integrity attribute, then set options's integrity
471        //         to the value of el's integrity content attribute.
472        if let Some(integrity_attribute) = element.get_attribute(&ns!(), &local_name!("integrity"))
473        {
474            options.integrity = (**integrity_attribute.value()).to_owned();
475        }
476
477        // Step 5. If el has a type attribute, then set options's type to the value of el's type attribute.
478        if let Some(type_attribute) = element.get_attribute(&ns!(), &local_name!("type")) {
479            options.link_type = (**type_attribute.value()).to_owned();
480        }
481
482        // Step 6. Assert: options's href is not the empty string, or options's source set is not null.
483        assert!(!options.href.is_empty() || options.source_set.is_some());
484
485        // Step 7. Return options.
486        options
487    }
488
489    /// <https://html.spec.whatwg.org/multipage/#default-fetch-and-process-the-linked-resource>
490    ///
491    /// This method does not implement Step 7 (fetching the request) and instead returns the [RequestBuilder],
492    /// as the fetch context that should be used depends on the link type.
493    fn default_fetch_and_process_the_linked_resource(&self) -> Option<RequestBuilder> {
494        // Step 1. Let options be the result of creating link options from el.
495        let options = self.processing_options();
496
497        // Step 2. Let request be the result of creating a link request given options.
498        let Some(request) = options.create_link_request(self.owner_window().webview_id()) else {
499            // Step 3. If request is null, then return.
500            return None;
501        };
502        // Step 4. Set request's synchronous flag.
503        let mut request = request.synchronous(true);
504
505        // Step 5. Run the linked resource fetch setup steps, given el and request. If the result is false, then return.
506        if !self.linked_resource_fetch_setup(&mut request) {
507            return None;
508        }
509
510        // TODO Step 6. Set request's initiator type to "css" if el's rel attribute
511        // contains the keyword stylesheet; "link" otherwise.
512
513        // Step 7. Fetch request with processResponseConsumeBody set to the following steps given response response and null,
514        // failure, or a byte sequence bodyBytes: [..]
515        Some(request)
516    }
517
518    /// <https://html.spec.whatwg.org/multipage/#linked-resource-fetch-setup-steps>
519    fn linked_resource_fetch_setup(&self, request: &mut RequestBuilder) -> bool {
520        if self.relations.get().contains(LinkRelations::ICON) {
521            // Step 1. Set request's destination to "image".
522            request.destination = Destination::Image;
523
524            // Step 2. Return true.
525            return true;
526        }
527
528        true
529    }
530
531    /// The `fetch and process the linked resource` algorithm for [`rel="prefetch"`](https://html.spec.whatwg.org/multipage/#link-type-prefetch)
532    fn fetch_and_process_prefetch_link(&self, href: &str) {
533        // Step 1. If el's href attribute's value is the empty string, then return.
534        if href.is_empty() {
535            return;
536        }
537
538        // Step 2. Let options be the result of creating link options from el.
539        let mut options = self.processing_options();
540
541        // Step 3. Set options's destination to the empty string.
542        options.destination = Some(Destination::None);
543
544        // Step 4. Let request be the result of creating a link request given options.
545        let url = options.base_url.clone();
546        let Some(request) = options.create_link_request(self.owner_window().webview_id()) else {
547            // Step 5. If request is null, then return.
548            return;
549        };
550
551        // Step 6. Set request's initiator to "prefetch".
552        let request = request.initiator(Initiator::Prefetch);
553
554        // (Step 7, firing load/error events is handled in the FetchResponseListener impl for PrefetchContext)
555
556        // Step 8. The user agent should fetch request, with processResponseConsumeBody set to processPrefetchResponse.
557        let document = self.upcast::<Node>().owner_doc();
558        let fetch_context = PrefetchContext {
559            url,
560            link: Trusted::new(self),
561            resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
562        };
563
564        document.fetch_background(request, fetch_context);
565    }
566
567    /// <https://html.spec.whatwg.org/multipage/#concept-link-obtain>
568    fn handle_stylesheet_url(&self, href: &str) {
569        let document = self.owner_document();
570        if document.browsing_context().is_none() {
571            return;
572        }
573
574        // Step 1.
575        if href.is_empty() {
576            return;
577        }
578
579        // Step 2.
580        let link_url = match document.base_url().join(href) {
581            Ok(url) => url,
582            Err(e) => {
583                debug!("Parsing url {} failed: {}", href, e);
584                return;
585            },
586        };
587
588        let element = self.upcast::<Element>();
589
590        // Step 3
591        let cors_setting = cors_setting_for_element(element);
592
593        let mq_attribute = element.get_attribute(&ns!(), &local_name!("media"));
594        let value = mq_attribute.as_ref().map(|a| a.value());
595        let mq_str = match value {
596            Some(ref value) => &***value,
597            None => "",
598        };
599
600        if !element.matches_environment(mq_str) {
601            return;
602        }
603
604        let media = MediaList::parse_media_list(mq_str, document.window());
605
606        let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
607        let integrity_val = im_attribute.as_ref().map(|a| a.value());
608        let integrity_metadata = match integrity_val {
609            Some(ref value) => &***value,
610            None => "",
611        };
612
613        self.request_generation_id
614            .set(self.request_generation_id.get().increment());
615
616        let loader = StylesheetLoader::for_element(self.upcast());
617        loader.load(
618            StylesheetContextSource::LinkElement { media: Some(media) },
619            link_url,
620            cors_setting,
621            integrity_metadata.to_owned(),
622        );
623    }
624
625    /// <https://html.spec.whatwg.org/multipage/#attr-link-disabled>
626    fn handle_disabled_attribute_change(&self, disabled: bool) {
627        if !disabled {
628            self.is_explicitly_enabled.set(true);
629        }
630        if let Some(stylesheet) = self.get_stylesheet() {
631            if stylesheet.set_disabled(disabled) {
632                self.stylesheet_list_owner().invalidate_stylesheets();
633            }
634        }
635    }
636
637    fn handle_favicon_url(&self) {
638        // The spec does not specify this, but we don't fetch favicons for iframes, as
639        // they won't be displayed anyways.
640        let window = self.owner_window();
641        if !window.is_top_level() {
642            return;
643        }
644        let Ok(href) = self.Href().parse() else {
645            return;
646        };
647
648        // Ignore all previous fetch operations
649        self.request_generation_id
650            .set(self.request_generation_id.get().increment());
651
652        let cache_result = window.image_cache().get_cached_image_status(
653            href,
654            window.origin().immutable().clone(),
655            cors_setting_for_element(self.upcast()),
656            UsePlaceholder::No,
657        );
658
659        match cache_result {
660            ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
661                image,
662                is_placeholder,
663                ..
664            }) => {
665                debug_assert!(!is_placeholder);
666
667                self.process_favicon_response(image);
668            },
669            ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(_, id)) |
670            ImageCacheResult::Pending(id) => {
671                let sender = self.register_image_cache_callback(id);
672                window.image_cache().add_listener(ImageLoadListener::new(
673                    sender,
674                    window.pipeline_id(),
675                    id,
676                ));
677            },
678            ImageCacheResult::ReadyForRequest(id) => {
679                let Some(request) = self.default_fetch_and_process_the_linked_resource() else {
680                    return;
681                };
682
683                let sender = self.register_image_cache_callback(id);
684                window.image_cache().add_listener(ImageLoadListener::new(
685                    sender,
686                    window.pipeline_id(),
687                    id,
688                ));
689
690                let document = self.upcast::<Node>().owner_doc();
691                let fetch_context = FaviconFetchContext {
692                    url: self.owner_document().base_url(),
693                    image_cache: window.image_cache(),
694                    id,
695                    link: Trusted::new(self),
696                    resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
697                };
698                document.fetch_background(request, fetch_context);
699            },
700            ImageCacheResult::LoadError => {},
701        };
702    }
703
704    fn register_image_cache_callback(
705        &self,
706        id: PendingImageId,
707    ) -> IpcSender<ImageCacheResponseMessage> {
708        let trusted_node = Trusted::new(self);
709        let window = self.owner_window();
710        let request_generation_id = self.get_request_generation_id();
711        window.register_image_cache_listener(id, move |response| {
712            let trusted_node = trusted_node.clone();
713            let link_element = trusted_node.root();
714            let window = link_element.owner_window();
715
716            let ImageResponse::Loaded(image, _) = response.response else {
717                // We don't care about metadata and such for favicons.
718                return;
719            };
720
721            if request_generation_id != link_element.get_request_generation_id() {
722                // This load is no longer relevant.
723                return;
724            };
725
726            window
727                .as_global_scope()
728                .task_manager()
729                .networking_task_source()
730                .queue(task!(process_favicon_response: move || {
731                    let element = trusted_node.root();
732
733                    if request_generation_id != element.get_request_generation_id() {
734                        // This load is no longer relevant.
735                        return;
736                    };
737
738                    element.process_favicon_response(image);
739                }));
740        })
741    }
742
743    /// Rasterizes a loaded favicon file if necessary and notifies the embedder about it.
744    fn process_favicon_response(&self, image: Image) {
745        // TODO: Include the size attribute here
746        let window = self.owner_window();
747
748        let send_rasterized_favicon_to_embedder = |raster_image: &pixels::RasterImage| {
749            // Let's not worry about animated favicons...
750            let frame = raster_image.first_frame();
751
752            let format = match raster_image.format {
753                PixelFormat::K8 => embedder_traits::PixelFormat::K8,
754                PixelFormat::KA8 => embedder_traits::PixelFormat::KA8,
755                PixelFormat::RGB8 => embedder_traits::PixelFormat::RGB8,
756                PixelFormat::RGBA8 => embedder_traits::PixelFormat::RGBA8,
757                PixelFormat::BGRA8 => embedder_traits::PixelFormat::BGRA8,
758            };
759
760            let embedder_image = embedder_traits::Image::new(
761                frame.width,
762                frame.height,
763                raster_image.bytes.clone(),
764                raster_image.frames[0].byte_range.clone(),
765                format,
766            );
767            window.send_to_embedder(EmbedderMsg::NewFavicon(window.webview_id(), embedder_image));
768        };
769
770        match image {
771            Image::Raster(raster_image) => send_rasterized_favicon_to_embedder(&raster_image),
772            Image::Vector(vector_image) => {
773                // This size is completely arbitrary.
774                let size = DeviceIntSize::new(250, 250);
775
776                let image_cache = window.image_cache();
777                if let Some(raster_image) =
778                    image_cache.rasterize_vector_image(vector_image.id, size)
779                {
780                    send_rasterized_favicon_to_embedder(&raster_image);
781                } else {
782                    // The rasterization callback will end up calling "process_favicon_response" again,
783                    // but this time with a raster image.
784                    let image_cache_sender = self.register_image_cache_callback(vector_image.id);
785                    image_cache.add_rasterization_complete_listener(
786                        window.pipeline_id(),
787                        vector_image.id,
788                        size,
789                        image_cache_sender,
790                    );
791                }
792            },
793        }
794    }
795
796    /// <https://html.spec.whatwg.org/multipage/#link-type-preload:fetch-and-process-the-linked-resource-2>
797    fn handle_preload_url(&self) {
798        // Step 1. Update the source set for el.
799        // TODO
800        // Step 2. Let options be the result of creating link options from el.
801        let options = self.processing_options();
802        // Step 3. Preload options, with the following steps given a response response:
803        // Step 3.1 If response is a network error, fire an event named error at el.
804        // Otherwise, fire an event named load at el.
805        self.preload(options);
806    }
807
808    /// <https://html.spec.whatwg.org/multipage/#preload>
809    fn preload(&self, options: LinkProcessingOptions) {
810        // Step 1. If options's type doesn't match options's destination, then return.
811        let type_matches_destination: bool =
812            HTMLLinkElement::type_matches_destination(&options.link_type, options.destination);
813        self.previous_type_matched.set(type_matches_destination);
814        if !type_matches_destination {
815            return;
816        }
817        // Step 2. If options's destination is "image" and options's source set is not null,
818        // then set options's href to the result of selecting an image source from options's source set.
819        // TODO
820        // Step 3. Let request be the result of creating a link request given options.
821        let url = options.base_url.clone();
822        let Some(request) = options.create_link_request(self.owner_window().webview_id()) else {
823            // Step 4. If request is null, then return.
824            return;
825        };
826        let document = self.upcast::<Node>().owner_doc();
827        // Step 5. Let unsafeEndTime be 0.
828        // TODO
829        // Step 6. Let entry be a new preload entry whose integrity metadata is options's integrity.
830        // TODO
831        // Step 7. Let key be the result of creating a preload key given request.
832        // TODO
833        // Step 8. If options's document is "pending", then set request's initiator type to "early hint".
834        // TODO
835        // Step 9. Let controller be null.
836        // Step 10. Let reportTiming given a Document document be to report timing for controller
837        // given document's relevant global object.
838        // Step 11. Set controller to the result of fetching request, with processResponseConsumeBody
839        // set to the following steps given a response response and null, failure, or a byte sequence bodyBytes:
840        let fetch_context = PreloadContext {
841            url,
842            link: Trusted::new(self),
843            resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
844        };
845        document.fetch_background(request.clone(), fetch_context);
846    }
847
848    /// <https://html.spec.whatwg.org/multipage/#match-preload-type>
849    fn type_matches_destination(mime_type: &str, destination: Option<Destination>) -> bool {
850        // Step 1. If type is an empty string, then return true.
851        if mime_type.is_empty() {
852            return true;
853        }
854        // Step 2. If destination is "fetch", then return true.
855        //
856        // Fetch is handled as an empty string destination in the spec:
857        // https://fetch.spec.whatwg.org/#concept-potential-destination-translate
858        let Some(destination) = destination else {
859            return false;
860        };
861        if destination == Destination::None {
862            return true;
863        }
864        // Step 3. Let mimeTypeRecord be the result of parsing type.
865        let Ok(mime_type_record) = Mime::from_str(mime_type) else {
866            // Step 4. If mimeTypeRecord is failure, then return false.
867            return false;
868        };
869        // Step 5. If mimeTypeRecord is not supported by the user agent, then return false.
870        //
871        // We currently don't check if we actually support the mime type. Only if we can classify
872        // it according to the spec.
873        let Some(mime_type) = MimeClassifier::get_media_type(&mime_type_record) else {
874            return false;
875        };
876        // Step 6. If any of the following are true:
877        if
878        // destination is "audio" or "video", and mimeTypeRecord is an audio or video MIME type;
879        ((destination == Destination::Audio || destination == Destination::Video) &&
880            mime_type == MediaType::AudioVideo)
881            // destination is a script-like destination and mimeTypeRecord is a JavaScript MIME type;
882            || (destination.is_script_like() && mime_type == MediaType::JavaScript)
883            // destination is "image" and mimeTypeRecord is an image MIME type;
884            || (destination == Destination::Image && mime_type == MediaType::Image)
885            // destination is "font" and mimeTypeRecord is a font MIME type;
886            || (destination == Destination::Font && mime_type == MediaType::Font)
887            // destination is "json" and mimeTypeRecord is a JSON MIME type;
888            || (destination == Destination::Json && mime_type == MediaType::Json)
889            // destination is "style" and mimeTypeRecord's essence is text/css; or
890            || (destination == Destination::Style && mime_type_record == mime::TEXT_CSS)
891            // destination is "track" and mimeTypeRecord's essence is text/vtt,
892            || (destination == Destination::Track && mime_type_record.essence_str() == "text/vtt")
893        {
894            // then return true.
895            return true;
896        }
897        // Step 7. Return false.
898        false
899    }
900
901    fn fire_event_after_response(&self, response: Result<ResourceFetchTiming, NetworkError>) {
902        if response.is_err() {
903            self.upcast::<EventTarget>()
904                .fire_event(atom!("error"), CanGc::note());
905        } else {
906            // TODO(35035): Figure out why we need to queue a task for the load event. Otherwise
907            // the performance timing data hasn't been saved yet, which fails several preload
908            // WPT tests that assume that performance timing information is available when
909            // the load event is fired.
910            let this = Trusted::new(self);
911            self.owner_global()
912                .task_manager()
913                .performance_timeline_task_source()
914                .queue(task!(preload_load_event: move || {
915                    let this = this.root();
916                    this
917                        .upcast::<EventTarget>()
918                        .fire_event(atom!("load"), CanGc::note());
919                }));
920        }
921    }
922}
923
924impl StylesheetOwner for HTMLLinkElement {
925    fn increment_pending_loads_count(&self) {
926        self.pending_loads.set(self.pending_loads.get() + 1)
927    }
928
929    fn load_finished(&self, succeeded: bool) -> Option<bool> {
930        assert!(self.pending_loads.get() > 0, "What finished?");
931        if !succeeded {
932            self.any_failed_load.set(true);
933        }
934
935        self.pending_loads.set(self.pending_loads.get() - 1);
936        if self.pending_loads.get() != 0 {
937            return None;
938        }
939
940        let any_failed = self.any_failed_load.get();
941        self.any_failed_load.set(false);
942        Some(any_failed)
943    }
944
945    fn parser_inserted(&self) -> bool {
946        self.parser_inserted.get()
947    }
948
949    fn referrer_policy(&self) -> ReferrerPolicy {
950        if self.RelList(CanGc::note()).Contains("noreferrer".into()) {
951            return ReferrerPolicy::NoReferrer;
952        }
953
954        ReferrerPolicy::EmptyString
955    }
956
957    fn set_origin_clean(&self, origin_clean: bool) {
958        if let Some(stylesheet) = self.get_cssom_stylesheet(CanGc::note()) {
959            stylesheet.set_origin_clean(origin_clean);
960        }
961    }
962}
963
964impl HTMLLinkElementMethods<crate::DomTypeHolder> for HTMLLinkElement {
965    // https://html.spec.whatwg.org/multipage/#dom-link-href
966    make_url_getter!(Href, "href");
967
968    // https://html.spec.whatwg.org/multipage/#dom-link-href
969    make_url_setter!(SetHref, "href");
970
971    // https://html.spec.whatwg.org/multipage/#dom-link-rel
972    make_getter!(Rel, "rel");
973
974    // https://html.spec.whatwg.org/multipage/#dom-link-rel
975    fn SetRel(&self, rel: DOMString, can_gc: CanGc) {
976        self.upcast::<Element>()
977            .set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
978    }
979
980    // https://html.spec.whatwg.org/multipage/#dom-link-as
981    make_enumerated_getter!(
982        As,
983        "as",
984        "fetch" | "audio" | "audioworklet" | "document" | "embed" | "font" | "frame"
985            | "iframe" | "image" | "json" | "manifest" | "object" | "paintworklet"
986            | "report" | "script" | "serviceworker" | "sharedworker" | "style" | "track"
987            | "video" | "webidentity" | "worker" | "xslt",
988        missing => "",
989        invalid => ""
990    );
991
992    // https://html.spec.whatwg.org/multipage/#dom-link-as
993    make_setter!(SetAs, "as");
994
995    // https://html.spec.whatwg.org/multipage/#dom-link-media
996    make_getter!(Media, "media");
997
998    // https://html.spec.whatwg.org/multipage/#dom-link-media
999    make_setter!(SetMedia, "media");
1000
1001    // https://html.spec.whatwg.org/multipage/#dom-link-integrity
1002    make_getter!(Integrity, "integrity");
1003
1004    // https://html.spec.whatwg.org/multipage/#dom-link-integrity
1005    make_setter!(SetIntegrity, "integrity");
1006
1007    // https://html.spec.whatwg.org/multipage/#dom-link-hreflang
1008    make_getter!(Hreflang, "hreflang");
1009
1010    // https://html.spec.whatwg.org/multipage/#dom-link-hreflang
1011    make_setter!(SetHreflang, "hreflang");
1012
1013    // https://html.spec.whatwg.org/multipage/#dom-link-type
1014    make_getter!(Type, "type");
1015
1016    // https://html.spec.whatwg.org/multipage/#dom-link-type
1017    make_setter!(SetType, "type");
1018
1019    // https://html.spec.whatwg.org/multipage/#dom-link-disabled
1020    make_bool_getter!(Disabled, "disabled");
1021
1022    // https://html.spec.whatwg.org/multipage/#dom-link-disabled
1023    make_bool_setter!(SetDisabled, "disabled");
1024
1025    // https://html.spec.whatwg.org/multipage/#dom-link-rellist
1026    fn RelList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
1027        self.rel_list.or_init(|| {
1028            DOMTokenList::new(
1029                self.upcast(),
1030                &local_name!("rel"),
1031                Some(vec![
1032                    Atom::from("alternate"),
1033                    Atom::from("apple-touch-icon"),
1034                    Atom::from("apple-touch-icon-precomposed"),
1035                    Atom::from("canonical"),
1036                    Atom::from("dns-prefetch"),
1037                    Atom::from("icon"),
1038                    Atom::from("import"),
1039                    Atom::from("manifest"),
1040                    Atom::from("modulepreload"),
1041                    Atom::from("next"),
1042                    Atom::from("preconnect"),
1043                    Atom::from("prefetch"),
1044                    Atom::from("preload"),
1045                    Atom::from("prerender"),
1046                    Atom::from("stylesheet"),
1047                ]),
1048                can_gc,
1049            )
1050        })
1051    }
1052
1053    // https://html.spec.whatwg.org/multipage/#dom-link-charset
1054    make_getter!(Charset, "charset");
1055
1056    // https://html.spec.whatwg.org/multipage/#dom-link-charset
1057    make_setter!(SetCharset, "charset");
1058
1059    // https://html.spec.whatwg.org/multipage/#dom-link-rev
1060    make_getter!(Rev, "rev");
1061
1062    // https://html.spec.whatwg.org/multipage/#dom-link-rev
1063    make_setter!(SetRev, "rev");
1064
1065    // https://html.spec.whatwg.org/multipage/#dom-link-target
1066    make_getter!(Target, "target");
1067
1068    // https://html.spec.whatwg.org/multipage/#dom-link-target
1069    make_setter!(SetTarget, "target");
1070
1071    // https://html.spec.whatwg.org/multipage/#dom-link-crossorigin
1072    fn GetCrossOrigin(&self) -> Option<DOMString> {
1073        reflect_cross_origin_attribute(self.upcast::<Element>())
1074    }
1075
1076    // https://html.spec.whatwg.org/multipage/#dom-link-crossorigin
1077    fn SetCrossOrigin(&self, value: Option<DOMString>, can_gc: CanGc) {
1078        set_cross_origin_attribute(self.upcast::<Element>(), value, can_gc);
1079    }
1080
1081    // https://html.spec.whatwg.org/multipage/#dom-link-referrerpolicy
1082    fn ReferrerPolicy(&self) -> DOMString {
1083        reflect_referrer_policy_attribute(self.upcast::<Element>())
1084    }
1085
1086    // https://html.spec.whatwg.org/multipage/#dom-link-referrerpolicy
1087    make_setter!(SetReferrerPolicy, "referrerpolicy");
1088
1089    // https://drafts.csswg.org/cssom/#dom-linkstyle-sheet
1090    fn GetSheet(&self, can_gc: CanGc) -> Option<DomRoot<DOMStyleSheet>> {
1091        self.get_cssom_stylesheet(can_gc).map(DomRoot::upcast)
1092    }
1093}
1094
1095impl LinkProcessingOptions {
1096    /// <https://html.spec.whatwg.org/multipage/#create-a-link-request>
1097    fn create_link_request(self, webview_id: WebViewId) -> Option<RequestBuilder> {
1098        // Step 1. Assert: options's href is not the empty string.
1099        assert!(!self.href.is_empty());
1100
1101        // Step 2. If options's destination is null, then return null.
1102        let destination = self.destination?;
1103
1104        // Step 3. Let url be the result of encoding-parsing a URL given options's href, relative to options's base URL.
1105        // TODO: The spec passes a base url which is incompatible with the
1106        //       "encoding-parse a URL" algorithm.
1107        let Ok(url) = self.base_url.join(&self.href) else {
1108            // Step 4. If url is failure, then return null.
1109            return None;
1110        };
1111
1112        // Step 5. Let request be the result of creating a potential-CORS request given
1113        //         url, options's destination, and options's crossorigin.
1114        // Step 6. Set request's policy container to options's policy container.
1115        // Step 7. Set request's integrity metadata to options's integrity.
1116        // Step 8. Set request's cryptographic nonce metadata to options's cryptographic nonce metadata.
1117        // Step 9. Set request's referrer policy to options's referrer policy.
1118        // FIXME: Step 10. Set request's client to options's environment.
1119        // FIXME: Step 11. Set request's priority to options's fetch priority.
1120        // FIXME: Use correct referrer
1121        let builder = create_a_potential_cors_request(
1122            Some(webview_id),
1123            url,
1124            destination,
1125            self.cross_origin,
1126            None,
1127            Referrer::NoReferrer,
1128            self.insecure_requests_policy,
1129            self.has_trustworthy_ancestor_origin,
1130            self.policy_container,
1131        )
1132        .initiator(Initiator::Link)
1133        .origin(self.origin)
1134        .integrity_metadata(self.integrity)
1135        .cryptographic_nonce_metadata(self.cryptographic_nonce_metadata)
1136        .referrer_policy(self.referrer_policy);
1137
1138        // Step 12. Return request.
1139        Some(builder)
1140    }
1141}
1142
1143/// <https://html.spec.whatwg.org/multipage/#translate-a-preload-destination>
1144fn translate_a_preload_destination(potential_destination: &str) -> Destination {
1145    match potential_destination {
1146        "fetch" => Destination::None,
1147        "font" => Destination::Font,
1148        "image" => Destination::Image,
1149        "script" => Destination::Script,
1150        "track" => Destination::Track,
1151        _ => Destination::None,
1152    }
1153}
1154
1155struct FaviconFetchContext {
1156    /// The `<link>` element that caused this fetch operation
1157    link: Trusted<HTMLLinkElement>,
1158    image_cache: std::sync::Arc<dyn ImageCache>,
1159    id: PendingImageId,
1160
1161    /// The base url of the document that the `<link>` element belongs to.
1162    url: ServoUrl,
1163
1164    resource_timing: ResourceFetchTiming,
1165}
1166
1167impl FetchResponseListener for FaviconFetchContext {
1168    fn process_request_body(&mut self, _: RequestId) {}
1169
1170    fn process_request_eof(&mut self, _: RequestId) {}
1171
1172    fn process_response(
1173        &mut self,
1174        request_id: RequestId,
1175        metadata: Result<FetchMetadata, NetworkError>,
1176    ) {
1177        self.image_cache.notify_pending_response(
1178            self.id,
1179            FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
1180        );
1181    }
1182
1183    fn process_response_chunk(&mut self, request_id: RequestId, chunk: Vec<u8>) {
1184        self.image_cache.notify_pending_response(
1185            self.id,
1186            FetchResponseMsg::ProcessResponseChunk(request_id, chunk),
1187        );
1188    }
1189
1190    fn process_response_eof(
1191        &mut self,
1192        request_id: RequestId,
1193        response: Result<ResourceFetchTiming, NetworkError>,
1194    ) {
1195        self.image_cache.notify_pending_response(
1196            self.id,
1197            FetchResponseMsg::ProcessResponseEOF(request_id, response),
1198        );
1199    }
1200
1201    fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
1202        &mut self.resource_timing
1203    }
1204
1205    fn resource_timing(&self) -> &ResourceFetchTiming {
1206        &self.resource_timing
1207    }
1208
1209    fn submit_resource_timing(&mut self) {
1210        submit_timing(self, CanGc::note())
1211    }
1212
1213    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
1214        let global = &self.resource_timing_global();
1215        let link = self.link.root();
1216        let source_position = link
1217            .upcast::<Element>()
1218            .compute_source_position(link.line_number as u32);
1219        global.report_csp_violations(violations, None, Some(source_position));
1220    }
1221}
1222
1223impl ResourceTimingListener for FaviconFetchContext {
1224    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1225        (
1226            InitiatorType::LocalName("link".to_string()),
1227            self.url.clone(),
1228        )
1229    }
1230
1231    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
1232        self.link.root().upcast::<Node>().owner_doc().global()
1233    }
1234}
1235
1236impl PreInvoke for FaviconFetchContext {
1237    fn should_invoke(&self) -> bool {
1238        true
1239    }
1240}
1241
1242struct PrefetchContext {
1243    /// The `<link>` element that caused this prefetch operation
1244    link: Trusted<HTMLLinkElement>,
1245
1246    resource_timing: ResourceFetchTiming,
1247
1248    /// The url being prefetched
1249    url: ServoUrl,
1250}
1251
1252impl FetchResponseListener for PrefetchContext {
1253    fn process_request_body(&mut self, _: RequestId) {}
1254
1255    fn process_request_eof(&mut self, _: RequestId) {}
1256
1257    fn process_response(
1258        &mut self,
1259        _: RequestId,
1260        fetch_metadata: Result<FetchMetadata, NetworkError>,
1261    ) {
1262        _ = fetch_metadata;
1263    }
1264
1265    fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
1266        _ = chunk;
1267    }
1268
1269    // Step 7 of `fetch and process the linked resource` in https://html.spec.whatwg.org/multipage/#link-type-prefetch
1270    fn process_response_eof(
1271        &mut self,
1272        _: RequestId,
1273        response: Result<ResourceFetchTiming, NetworkError>,
1274    ) {
1275        if response.is_err() {
1276            // Step 1. If response is a network error, fire an event named error at el.
1277            self.link
1278                .root()
1279                .upcast::<EventTarget>()
1280                .fire_event(atom!("error"), CanGc::note());
1281        } else {
1282            // Step 2. Otherwise, fire an event named load at el.
1283            self.link
1284                .root()
1285                .upcast::<EventTarget>()
1286                .fire_event(atom!("load"), CanGc::note());
1287        }
1288    }
1289
1290    fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
1291        &mut self.resource_timing
1292    }
1293
1294    fn resource_timing(&self) -> &ResourceFetchTiming {
1295        &self.resource_timing
1296    }
1297
1298    fn submit_resource_timing(&mut self) {
1299        submit_timing(self, CanGc::note())
1300    }
1301
1302    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
1303        let global = &self.resource_timing_global();
1304        let link = self.link.root();
1305        let source_position = link
1306            .upcast::<Element>()
1307            .compute_source_position(link.line_number as u32);
1308        global.report_csp_violations(violations, None, Some(source_position));
1309    }
1310}
1311
1312impl ResourceTimingListener for PrefetchContext {
1313    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1314        (
1315            InitiatorType::LocalName("prefetch".to_string()),
1316            self.url.clone(),
1317        )
1318    }
1319
1320    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
1321        self.link.root().upcast::<Node>().owner_doc().global()
1322    }
1323}
1324
1325impl PreInvoke for PrefetchContext {
1326    fn should_invoke(&self) -> bool {
1327        // Prefetch requests are never aborted.
1328        true
1329    }
1330}
1331
1332struct PreloadContext {
1333    /// The `<link>` element that caused this preload operation
1334    link: Trusted<HTMLLinkElement>,
1335
1336    resource_timing: ResourceFetchTiming,
1337
1338    /// The url being preloaded
1339    url: ServoUrl,
1340}
1341
1342impl FetchResponseListener for PreloadContext {
1343    fn process_request_body(&mut self, _: RequestId) {}
1344
1345    fn process_request_eof(&mut self, _: RequestId) {}
1346
1347    fn process_response(
1348        &mut self,
1349        _: RequestId,
1350        fetch_metadata: Result<FetchMetadata, NetworkError>,
1351    ) {
1352        _ = fetch_metadata;
1353    }
1354
1355    fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
1356        _ = chunk;
1357    }
1358
1359    /// Step 3.1 of <https://html.spec.whatwg.org/multipage/#link-type-preload:fetch-and-process-the-linked-resource-2>
1360    fn process_response_eof(
1361        &mut self,
1362        _: RequestId,
1363        response: Result<ResourceFetchTiming, NetworkError>,
1364    ) {
1365        self.link.root().fire_event_after_response(response);
1366    }
1367
1368    fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
1369        &mut self.resource_timing
1370    }
1371
1372    fn resource_timing(&self) -> &ResourceFetchTiming {
1373        &self.resource_timing
1374    }
1375
1376    fn submit_resource_timing(&mut self) {
1377        submit_timing(self, CanGc::note())
1378    }
1379
1380    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
1381        let global = &self.resource_timing_global();
1382        let link = self.link.root();
1383        let source_position = link
1384            .upcast::<Element>()
1385            .compute_source_position(link.line_number as u32);
1386        global.report_csp_violations(violations, None, Some(source_position));
1387    }
1388}
1389
1390impl ResourceTimingListener for PreloadContext {
1391    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1392        (
1393            InitiatorType::LocalName(self.url.clone().into_string()),
1394            self.url.clone(),
1395        )
1396    }
1397
1398    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
1399        self.link.root().upcast::<Node>().owner_doc().global()
1400    }
1401}
1402
1403impl PreInvoke for PreloadContext {
1404    fn should_invoke(&self) -> bool {
1405        // Preload requests are never aborted.
1406        true
1407    }
1408}