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