1use 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};
12use js::context::JSContext;
13use js::rust::HandleObject;
14use net_traits::image_cache::{
15 Image, ImageCache, ImageCacheResponseCallback, ImageCacheResult, ImageLoadListener,
16 ImageOrMetadataAvailable, ImageResponse, PendingImageId,
17};
18use net_traits::request::{Destination, Initiator, RequestBuilder, RequestId};
19use net_traits::{
20 FetchMetadata, FetchResponseMsg, NetworkError, ReferrerPolicy, ResourceFetchTiming,
21};
22use pixels::PixelFormat;
23use script_bindings::root::Dom;
24use servo_arc::Arc;
25use servo_url::ServoUrl;
26use style::attr::AttrValue;
27use style::media_queries::MediaList as StyleMediaList;
28use style::stylesheets::Stylesheet;
29use stylo_atoms::Atom;
30use webrender_api::units::DeviceIntSize;
31
32use crate::dom::attr::Attr;
33use crate::dom::bindings::cell::DomRefCell;
34use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenList_Binding::DOMTokenListMethods;
35use crate::dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods;
36use crate::dom::bindings::inheritance::Castable;
37use crate::dom::bindings::refcounted::Trusted;
38use crate::dom::bindings::reflector::DomGlobal;
39use crate::dom::bindings::root::{DomRoot, MutNullableDom};
40use crate::dom::bindings::str::{DOMString, USVString};
41use crate::dom::csp::{GlobalCspReporting, Violation};
42use crate::dom::css::cssstylesheet::CSSStyleSheet;
43use crate::dom::css::stylesheet::StyleSheet as DOMStyleSheet;
44use crate::dom::document::Document;
45use crate::dom::documentorshadowroot::StylesheetSource;
46use crate::dom::domtokenlist::DOMTokenList;
47use crate::dom::element::{
48 AttributeMutation, Element, ElementCreator, cors_setting_for_element,
49 referrer_policy_for_element, reflect_cross_origin_attribute, reflect_referrer_policy_attribute,
50 set_cross_origin_attribute,
51};
52use crate::dom::html::htmlelement::HTMLElement;
53use crate::dom::medialist::MediaList;
54use crate::dom::node::{BindContext, Node, NodeTraits, UnbindContext};
55use crate::dom::performance::performanceresourcetiming::InitiatorType;
56use crate::dom::processingoptions::{
57 LinkFetchContext, LinkFetchContextType, LinkProcessingOptions,
58};
59use crate::dom::types::{EventTarget, GlobalScope};
60use crate::dom::virtualmethods::VirtualMethods;
61use crate::links::LinkRelations;
62use crate::network_listener::{FetchResponseListener, ResourceTimingListener, submit_timing};
63use crate::script_runtime::CanGc;
64use crate::stylesheet_loader::{ElementStylesheetLoader, StylesheetContextSource, StylesheetOwner};
65
66#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
67pub(crate) struct RequestGenerationId(u32);
68
69impl RequestGenerationId {
70 fn increment(self) -> RequestGenerationId {
71 RequestGenerationId(self.0 + 1)
72 }
73}
74
75#[dom_struct]
76pub(crate) struct HTMLLinkElement {
77 htmlelement: HTMLElement,
78 rel_list: MutNullableDom<DOMTokenList>,
80
81 #[no_trace]
87 relations: Cell<LinkRelations>,
88
89 #[conditional_malloc_size_of]
90 #[no_trace]
91 stylesheet: DomRefCell<Option<Arc<Stylesheet>>>,
92 cssom_stylesheet: MutNullableDom<CSSStyleSheet>,
93
94 parser_inserted: Cell<bool>,
96 pending_loads: Cell<u32>,
99 any_failed_load: Cell<bool>,
101 request_generation_id: Cell<RequestGenerationId>,
103 is_explicitly_enabled: Cell<bool>,
105 previous_type_matched: Cell<bool>,
107 previous_media_environment_matched: Cell<bool>,
109 line_number: u64,
111 blocking: MutNullableDom<DOMTokenList>,
113}
114
115impl HTMLLinkElement {
116 fn new_inherited(
117 local_name: LocalName,
118 prefix: Option<Prefix>,
119 document: &Document,
120 creator: ElementCreator,
121 ) -> HTMLLinkElement {
122 HTMLLinkElement {
123 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
124 rel_list: Default::default(),
125 relations: Cell::new(LinkRelations::empty()),
126 parser_inserted: Cell::new(creator.is_parser_created()),
127 stylesheet: DomRefCell::new(None),
128 cssom_stylesheet: MutNullableDom::new(None),
129 pending_loads: Cell::new(0),
130 any_failed_load: Cell::new(false),
131 request_generation_id: Cell::new(RequestGenerationId(0)),
132 is_explicitly_enabled: Cell::new(false),
133 previous_type_matched: Cell::new(true),
134 previous_media_environment_matched: Cell::new(true),
135 line_number: creator.return_line_number(),
136 blocking: Default::default(),
137 }
138 }
139
140 pub(crate) fn new(
141 local_name: LocalName,
142 prefix: Option<Prefix>,
143 document: &Document,
144 proto: Option<HandleObject>,
145 creator: ElementCreator,
146 can_gc: CanGc,
147 ) -> DomRoot<HTMLLinkElement> {
148 Node::reflect_node_with_proto(
149 Box::new(HTMLLinkElement::new_inherited(
150 local_name, prefix, document, creator,
151 )),
152 document,
153 proto,
154 can_gc,
155 )
156 }
157
158 pub(crate) fn get_request_generation_id(&self) -> RequestGenerationId {
159 self.request_generation_id.get()
160 }
161
162 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
163 fn remove_stylesheet(&self) {
164 if let Some(stylesheet) = self.stylesheet.borrow_mut().take() {
165 let owner = self.stylesheet_list_owner();
166 owner.remove_stylesheet(
167 StylesheetSource::Element(Dom::from_ref(self.upcast())),
168 &stylesheet,
169 );
170 self.clean_stylesheet_ownership();
171 owner.invalidate_stylesheets();
172 }
173 }
174
175 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
178 pub(crate) fn set_stylesheet(&self, new_stylesheet: Arc<Stylesheet>) {
179 let owner = self.stylesheet_list_owner();
180 if let Some(old_stylesheet) = self.stylesheet.borrow_mut().replace(new_stylesheet.clone()) {
181 owner.remove_stylesheet(
182 StylesheetSource::Element(Dom::from_ref(self.upcast())),
183 &old_stylesheet,
184 );
185 }
186 owner.add_owned_stylesheet(self.upcast(), new_stylesheet);
187 }
188
189 pub(crate) fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
190 self.stylesheet.borrow().clone()
191 }
192
193 pub(crate) fn get_cssom_stylesheet(&self, can_gc: CanGc) -> Option<DomRoot<CSSStyleSheet>> {
194 self.get_stylesheet().map(|sheet| {
195 self.cssom_stylesheet.or_init(|| {
196 CSSStyleSheet::new(
197 &self.owner_window(),
198 Some(self.upcast::<Element>()),
199 "text/css".into(),
200 Some(self.Href().into()),
201 None, sheet,
203 None, can_gc,
205 )
206 })
207 })
208 }
209
210 pub(crate) fn is_alternate(&self) -> bool {
211 self.relations.get().contains(LinkRelations::ALTERNATE) &&
212 !self
213 .upcast::<Element>()
214 .get_string_attribute(&local_name!("title"))
215 .is_empty()
216 }
217
218 pub(crate) fn is_effectively_disabled(&self) -> bool {
219 (self.is_alternate() && !self.is_explicitly_enabled.get()) ||
220 self.upcast::<Element>()
221 .has_attribute(&local_name!("disabled"))
222 }
223
224 fn clean_stylesheet_ownership(&self) {
225 if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() {
226 cssom_stylesheet.set_owner_node(None);
227 }
228 self.cssom_stylesheet.set(None);
229 }
230}
231
232fn get_attr(element: &Element, local_name: &LocalName) -> Option<String> {
233 let elem = element.get_attribute(local_name);
234 elem.map(|e| {
235 let value = e.value();
236 (**value).to_owned()
237 })
238}
239
240impl VirtualMethods for HTMLLinkElement {
241 fn super_type(&self) -> Option<&dyn VirtualMethods> {
242 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
243 }
244
245 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
246 self.super_type()
247 .unwrap()
248 .attribute_mutated(attr, mutation, can_gc);
249
250 let local_name = attr.local_name();
251 let is_removal = mutation.is_removal();
252 if *local_name == local_name!("disabled") {
253 self.handle_disabled_attribute_change(is_removal);
254 return;
255 }
256 let node = self.upcast::<Node>();
257
258 if !node.is_connected() {
259 return;
260 }
261
262 if self.relations.get().contains(LinkRelations::STYLESHEET) {
265 if let AttributeMutation::Set(Some(previous_value), _) = mutation {
266 if **previous_value == **attr.value() {
267 return;
268 }
269 }
270 }
271
272 match *local_name {
273 local_name!("rel") | local_name!("rev") => {
274 let previous_relations = self.relations.get();
275 self.relations
276 .set(LinkRelations::for_element(self.upcast()));
277
278 if previous_relations == self.relations.get() {
280 return;
281 }
282
283 if self.relations.get().contains(LinkRelations::STYLESHEET) {
286 self.handle_stylesheet_url();
287 } else {
288 self.remove_stylesheet();
289 }
290 },
291 local_name!("href") => {
292 if is_removal {
295 if self.relations.get().contains(LinkRelations::STYLESHEET) {
296 self.remove_stylesheet();
297 }
298 return;
299 }
300 if self.relations.get().contains(LinkRelations::STYLESHEET) {
304 self.handle_stylesheet_url();
305 }
306
307 if self.relations.get().contains(LinkRelations::ICON) {
308 self.handle_favicon_url(&attr.value());
309 }
310
311 if self.relations.get().contains(LinkRelations::PREFETCH) {
315 self.fetch_and_process_prefetch_link(&attr.value());
316 }
317
318 if self.relations.get().contains(LinkRelations::PRELOAD) {
322 self.handle_preload_url();
323 }
324 },
325 local_name!("sizes") if self.relations.get().contains(LinkRelations::ICON) => {
326 self.handle_favicon_url(&attr.value());
327 },
328 local_name!("crossorigin") => {
329 if self.relations.get().contains(LinkRelations::PREFETCH) {
333 self.fetch_and_process_prefetch_link(&attr.value());
334 }
335
336 if self.relations.get().contains(LinkRelations::STYLESHEET) {
340 self.handle_stylesheet_url();
341 }
342 },
343 local_name!("as") => {
344 if self.relations.get().contains(LinkRelations::PRELOAD) {
348 if let AttributeMutation::Set(Some(_), _) = mutation {
349 self.handle_preload_url();
350 }
351 }
352 },
353 local_name!("type") => {
354 if self.relations.get().contains(LinkRelations::STYLESHEET) {
362 self.handle_stylesheet_url();
363 }
364
365 if self.relations.get().contains(LinkRelations::PRELOAD) &&
371 !self.previous_type_matched.get()
372 {
373 self.handle_preload_url();
374 }
375 },
376 local_name!("media") => {
377 if self.relations.get().contains(LinkRelations::PRELOAD) &&
382 !self.previous_media_environment_matched.get()
383 {
384 match mutation {
385 AttributeMutation::Removed | AttributeMutation::Set(Some(_), _) => {
386 self.handle_preload_url()
387 },
388 _ => {},
389 };
390 } else if self.relations.get().contains(LinkRelations::STYLESHEET) {
391 if let Some(ref stylesheet) = *self.stylesheet.borrow_mut() {
392 let document = self.owner_document();
393 let shared_lock = document.style_shared_lock().clone();
394 let mut guard = shared_lock.write();
395 let media = stylesheet.media.write_with(&mut guard);
396 match mutation {
397 AttributeMutation::Set(..) => {
398 *media =
399 MediaList::parse_media_list(&attr.value(), document.window())
400 },
401 AttributeMutation::Removed => *media = StyleMediaList::empty(),
402 };
403 self.owner_document().invalidate_stylesheets();
404 }
405 }
406
407 let matches_media_environment =
408 MediaList::matches_environment(&self.owner_document(), &attr.value());
409 self.previous_media_environment_matched
410 .set(matches_media_environment);
411 },
412 _ => {},
413 }
414 }
415
416 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
417 match name {
418 &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
419 _ => self
420 .super_type()
421 .unwrap()
422 .parse_plain_attribute(name, value),
423 }
424 }
425
426 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
427 if let Some(s) = self.super_type() {
428 s.bind_to_tree(context, can_gc);
429 }
430
431 self.relations
432 .set(LinkRelations::for_element(self.upcast()));
433
434 if context.tree_connected {
435 let element = self.upcast();
436
437 if let Some(href) = get_attr(element, &local_name!("href")) {
438 let relations = self.relations.get();
439 if relations.contains(LinkRelations::STYLESHEET) {
442 self.handle_stylesheet_url();
443 }
444
445 if relations.contains(LinkRelations::ICON) {
446 self.handle_favicon_url(&href);
447 }
448
449 if relations.contains(LinkRelations::PREFETCH) {
450 self.fetch_and_process_prefetch_link(&href);
451 }
452
453 if relations.contains(LinkRelations::PRELOAD) {
454 self.handle_preload_url();
455 }
456 }
457 }
458 }
459
460 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
461 if let Some(s) = self.super_type() {
462 s.unbind_from_tree(context, can_gc);
463 }
464
465 self.remove_stylesheet();
466 }
467}
468
469impl HTMLLinkElement {
470 fn compute_destination_for_attribute(&self) -> Option<Destination> {
471 let element = self.upcast::<Element>();
474 element
475 .get_attribute(&local_name!("as"))
476 .and_then(|attr| LinkProcessingOptions::translate_a_preload_destination(&attr.value()))
477 }
478
479 fn processing_options(&self) -> LinkProcessingOptions {
481 let element = self.upcast::<Element>();
482
483 let document = self.upcast::<Node>().owner_doc();
485 let global = document.owner_global();
486
487 let mut options = LinkProcessingOptions {
489 href: String::new(),
490 destination: Destination::None,
491 integrity: String::new(),
492 link_type: String::new(),
493 cryptographic_nonce_metadata: self.upcast::<Element>().nonce_value(),
494 cross_origin: cors_setting_for_element(element),
495 referrer_policy: referrer_policy_for_element(element),
496 policy_container: document.policy_container().to_owned(),
497 source_set: None, origin: document.borrow().origin().immutable().to_owned(),
499 base_url: document.borrow().base_url(),
500 insecure_requests_policy: document.insecure_requests_policy(),
501 has_trustworthy_ancestor_origin: document.has_trustworthy_ancestor_or_current_origin(),
502 request_client: global.request_client(),
503 referrer: global.get_referrer(),
504 };
505
506 if let Some(href_attribute) = element.get_attribute(&local_name!("href")) {
508 options.href = (**href_attribute.value()).to_owned();
509 }
510
511 if let Some(integrity_attribute) = element.get_attribute(&local_name!("integrity")) {
514 options.integrity = (**integrity_attribute.value()).to_owned();
515 }
516
517 if let Some(type_attribute) = element.get_attribute(&local_name!("type")) {
519 options.link_type = (**type_attribute.value()).to_owned();
520 }
521
522 assert!(!options.href.is_empty() || options.source_set.is_some());
524
525 options
527 }
528
529 fn default_fetch_and_process_the_linked_resource(&self) -> Option<RequestBuilder> {
534 let options = self.processing_options();
536
537 let Some(request) = options.create_link_request(self.owner_window().webview_id()) else {
539 return None;
541 };
542 let mut request = request.synchronous(true);
544
545 if !self.linked_resource_fetch_setup(&mut request) {
547 return None;
548 }
549
550 Some(request)
556 }
557
558 fn linked_resource_fetch_setup(&self, request: &mut RequestBuilder) -> bool {
560 if self.relations.get().contains(LinkRelations::ICON) {
562 request.destination = Destination::Image;
564
565 }
569
570 if self.relations.get().contains(LinkRelations::STYLESHEET) {
572 if self
574 .upcast::<Element>()
575 .has_attribute(&local_name!("disabled"))
576 {
577 return false;
578 }
579 }
594
595 true
596 }
597
598 fn fetch_and_process_prefetch_link(&self, href: &str) {
600 if href.is_empty() {
602 return;
603 }
604
605 let mut options = self.processing_options();
607
608 options.destination = Destination::None;
610
611 let Some(request) = options.create_link_request(self.owner_window().webview_id()) else {
613 return;
615 };
616 let url = request.url.clone();
617
618 let request = request.initiator(Initiator::Prefetch);
620
621 let document = self.upcast::<Node>().owner_doc();
625 let fetch_context = LinkFetchContext {
626 url,
627 link: Some(Trusted::new(self)),
628 document: Trusted::new(&document),
629 global: Trusted::new(&document.global()),
630 type_: LinkFetchContextType::Prefetch,
631 response_body: vec![],
632 };
633
634 document.fetch_background(request, fetch_context);
635 }
636
637 fn handle_stylesheet_url(&self) {
639 let document = self.owner_document();
640 if document.browsing_context().is_none() {
641 return;
642 }
643
644 let element = self.upcast::<Element>();
645
646 let type_ = element.get_string_attribute(&local_name!("type"));
653 if !type_.is_empty() && type_ != "text/css" {
654 return;
655 }
656
657 let href = element.get_string_attribute(&local_name!("href"));
659 if href.is_empty() {
660 return;
661 }
662
663 let link_url = match document.base_url().join(&href.str()) {
665 Ok(url) => url,
666 Err(e) => {
667 debug!("Parsing url {} failed: {}", href, e);
668 return;
669 },
670 };
671
672 let cors_setting = cors_setting_for_element(element);
674
675 let mq_attribute = element.get_attribute(&local_name!("media"));
676 let value = mq_attribute.as_ref().map(|a| a.value());
677 let mq_str = match value {
678 Some(ref value) => &***value,
679 None => "",
680 };
681
682 let media = MediaList::parse_media_list(mq_str, document.window());
683 let media = Arc::new(document.style_shared_lock().wrap(media));
684
685 let im_attribute = element.get_attribute(&local_name!("integrity"));
686 let integrity_val = im_attribute.as_ref().map(|a| a.value());
687 let integrity_metadata = match integrity_val {
688 Some(ref value) => &***value,
689 None => "",
690 };
691
692 self.request_generation_id
693 .set(self.request_generation_id.get().increment());
694 self.pending_loads.set(0);
695
696 ElementStylesheetLoader::load_with_element(
697 self.upcast(),
698 StylesheetContextSource::LinkElement,
699 media,
700 link_url,
701 cors_setting,
702 integrity_metadata.to_owned(),
703 );
704 }
705
706 fn handle_disabled_attribute_change(&self, is_removal: bool) {
708 if is_removal {
710 self.is_explicitly_enabled.set(true);
711 }
712 if let Some(stylesheet) = self.get_stylesheet() {
713 if stylesheet.set_disabled(!is_removal) {
714 self.stylesheet_list_owner().invalidate_stylesheets();
715 }
716 }
717 }
718
719 fn handle_favicon_url(&self, href: &str) {
720 if href.is_empty() {
722 return;
723 }
724
725 let window = self.owner_window();
728 if !window.is_top_level() {
729 return;
730 }
731 let Ok(href) = self.Href().parse() else {
732 return;
733 };
734
735 self.request_generation_id
737 .set(self.request_generation_id.get().increment());
738
739 let cache_result = window.image_cache().get_cached_image_status(
740 href,
741 window.origin().immutable().clone(),
742 cors_setting_for_element(self.upcast()),
743 );
744
745 match cache_result {
746 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
747 image, ..
748 }) => {
749 self.process_favicon_response(image);
750 },
751 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(_, id)) |
752 ImageCacheResult::Pending(id) => {
753 let sender = self.register_image_cache_callback(id);
754 window.image_cache().add_listener(ImageLoadListener::new(
755 sender,
756 window.pipeline_id(),
757 id,
758 ));
759 },
760 ImageCacheResult::ReadyForRequest(id) => {
761 let Some(request) = self.default_fetch_and_process_the_linked_resource() else {
762 return;
763 };
764
765 let sender = self.register_image_cache_callback(id);
766 window.image_cache().add_listener(ImageLoadListener::new(
767 sender,
768 window.pipeline_id(),
769 id,
770 ));
771
772 let document = self.upcast::<Node>().owner_doc();
773 let fetch_context = FaviconFetchContext {
774 url: self.owner_document().base_url(),
775 image_cache: window.image_cache(),
776 id,
777 link: Trusted::new(self),
778 };
779 document.fetch_background(request, fetch_context);
780 },
781 ImageCacheResult::FailedToLoadOrDecode => {},
782 };
783 }
784
785 fn register_image_cache_callback(&self, id: PendingImageId) -> ImageCacheResponseCallback {
786 let trusted_node = Trusted::new(self);
787 let window = self.owner_window();
788 let request_generation_id = self.get_request_generation_id();
789 window.register_image_cache_listener(id, move |response, _| {
790 let trusted_node = trusted_node.clone();
791 let link_element = trusted_node.root();
792 let window = link_element.owner_window();
793
794 let ImageResponse::Loaded(image, _) = response.response else {
795 return;
797 };
798
799 if request_generation_id != link_element.get_request_generation_id() {
800 return;
802 };
803
804 window
805 .as_global_scope()
806 .task_manager()
807 .networking_task_source()
808 .queue(task!(process_favicon_response: move || {
809 let element = trusted_node.root();
810
811 if request_generation_id != element.get_request_generation_id() {
812 return;
814 };
815
816 element.process_favicon_response(image);
817 }));
818 })
819 }
820
821 fn process_favicon_response(&self, image: Image) {
823 let window = self.owner_window();
825 let document = self.owner_document();
826
827 let send_rasterized_favicon_to_embedder = |raster_image: &pixels::RasterImage| {
828 let frame = raster_image.first_frame();
830
831 let format = match raster_image.format {
832 PixelFormat::K8 => embedder_traits::PixelFormat::K8,
833 PixelFormat::KA8 => embedder_traits::PixelFormat::KA8,
834 PixelFormat::RGB8 => embedder_traits::PixelFormat::RGB8,
835 PixelFormat::RGBA8 => embedder_traits::PixelFormat::RGBA8,
836 PixelFormat::BGRA8 => embedder_traits::PixelFormat::BGRA8,
837 };
838
839 let embedder_image = embedder_traits::Image::new(
840 frame.width,
841 frame.height,
842 std::sync::Arc::new(GenericSharedMemory::from_bytes(&raster_image.bytes)),
843 raster_image.frames[0].byte_range.clone(),
844 format,
845 );
846 document.set_favicon(embedder_image);
847 };
848
849 match image {
850 Image::Raster(raster_image) => send_rasterized_favicon_to_embedder(&raster_image),
851 Image::Vector(vector_image) => {
852 let size = DeviceIntSize::new(250, 250);
854
855 let image_cache = window.image_cache();
856 if let Some(raster_image) =
857 image_cache.rasterize_vector_image(vector_image.id, size, None)
858 {
859 send_rasterized_favicon_to_embedder(&raster_image);
860 } else {
861 let image_cache_sender = self.register_image_cache_callback(vector_image.id);
864 image_cache.add_rasterization_complete_listener(
865 window.pipeline_id(),
866 vector_image.id,
867 size,
868 image_cache_sender,
869 );
870 }
871 },
872 }
873 }
874
875 fn handle_preload_url(&self) {
878 let mut options = self.processing_options();
882 let Some(destination) = self.compute_destination_for_attribute() else {
885 return;
887 };
888 options.destination = destination;
890 {
892 let type_matches_destination = options.type_matches_destination();
894 self.previous_type_matched.set(type_matches_destination);
895 if !type_matches_destination {
896 return;
897 }
898 }
899 let document = self.upcast::<Node>().owner_doc();
901 options.preload(
902 self.owner_window().webview_id(),
903 Some(Trusted::new(self)),
904 &document,
905 );
906 }
907
908 pub(crate) fn fire_event_after_response(
910 &self,
911 response: Result<(), NetworkError>,
912 can_gc: CanGc,
913 ) {
914 if response.is_err() {
917 self.upcast::<EventTarget>()
918 .fire_event(atom!("error"), can_gc);
919 } else {
920 self.upcast::<EventTarget>()
921 .fire_event(atom!("load"), can_gc);
922 }
923 }
924}
925
926impl StylesheetOwner for HTMLLinkElement {
927 fn increment_pending_loads_count(&self) {
928 self.pending_loads.set(self.pending_loads.get() + 1)
929 }
930
931 fn load_finished(&self, succeeded: bool) -> Option<bool> {
932 assert!(self.pending_loads.get() > 0, "What finished?");
933 if !succeeded {
934 self.any_failed_load.set(true);
935 }
936
937 self.pending_loads.set(self.pending_loads.get() - 1);
938 if self.pending_loads.get() != 0 {
939 return None;
940 }
941
942 let any_failed = self.any_failed_load.get();
943 self.any_failed_load.set(false);
944 Some(any_failed)
945 }
946
947 fn parser_inserted(&self) -> bool {
948 self.parser_inserted.get()
949 }
950
951 fn potentially_render_blocking(&self) -> bool {
953 self.parser_inserted() ||
960 self.blocking
961 .get()
962 .is_some_and(|list| list.Contains("render".into()))
963 }
964
965 fn referrer_policy(&self) -> ReferrerPolicy {
966 if self.RelList(CanGc::note()).Contains("noreferrer".into()) {
967 return ReferrerPolicy::NoReferrer;
968 }
969
970 ReferrerPolicy::EmptyString
971 }
972
973 fn set_origin_clean(&self, origin_clean: bool) {
974 if let Some(stylesheet) = self.get_cssom_stylesheet(CanGc::note()) {
975 stylesheet.set_origin_clean(origin_clean);
976 }
977 }
978}
979
980impl HTMLLinkElementMethods<crate::DomTypeHolder> for HTMLLinkElement {
981 make_url_getter!(Href, "href");
983
984 make_url_setter!(SetHref, "href");
986
987 make_getter!(Rel, "rel");
989
990 fn SetRel(&self, rel: DOMString, can_gc: CanGc) {
992 self.upcast::<Element>()
993 .set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
994 }
995
996 make_enumerated_getter!(
998 As,
999 "as",
1000 "fetch" | "audio" | "audioworklet" | "document" | "embed" | "font" | "frame"
1001 | "iframe" | "image" | "json" | "manifest" | "object" | "paintworklet"
1002 | "report" | "script" | "serviceworker" | "sharedworker" | "style" | "track"
1003 | "video" | "webidentity" | "worker" | "xslt",
1004 missing => "",
1005 invalid => ""
1006 );
1007
1008 make_setter!(SetAs, "as");
1010
1011 make_getter!(Media, "media");
1013
1014 make_setter!(SetMedia, "media");
1016
1017 make_getter!(Integrity, "integrity");
1019
1020 make_setter!(SetIntegrity, "integrity");
1022
1023 make_getter!(Hreflang, "hreflang");
1025
1026 make_setter!(SetHreflang, "hreflang");
1028
1029 make_getter!(Type, "type");
1031
1032 make_setter!(SetType, "type");
1034
1035 make_bool_getter!(Disabled, "disabled");
1037
1038 make_bool_setter!(SetDisabled, "disabled");
1040
1041 fn RelList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
1043 self.rel_list.or_init(|| {
1044 DOMTokenList::new(
1045 self.upcast(),
1046 &local_name!("rel"),
1047 Some(vec![
1048 Atom::from("alternate"),
1049 Atom::from("apple-touch-icon"),
1050 Atom::from("apple-touch-icon-precomposed"),
1051 Atom::from("canonical"),
1052 Atom::from("dns-prefetch"),
1053 Atom::from("icon"),
1054 Atom::from("import"),
1055 Atom::from("manifest"),
1056 Atom::from("modulepreload"),
1057 Atom::from("next"),
1058 Atom::from("preconnect"),
1059 Atom::from("prefetch"),
1060 Atom::from("preload"),
1061 Atom::from("prerender"),
1062 Atom::from("stylesheet"),
1063 ]),
1064 can_gc,
1065 )
1066 })
1067 }
1068
1069 make_getter!(Charset, "charset");
1071
1072 make_setter!(SetCharset, "charset");
1074
1075 make_getter!(Rev, "rev");
1077
1078 make_setter!(SetRev, "rev");
1080
1081 make_getter!(Target, "target");
1083
1084 make_setter!(SetTarget, "target");
1086
1087 fn Blocking(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
1089 self.blocking.or_init(|| {
1090 DOMTokenList::new(
1091 self.upcast(),
1092 &local_name!("blocking"),
1093 Some(vec![Atom::from("render")]),
1094 can_gc,
1095 )
1096 })
1097 }
1098
1099 fn GetCrossOrigin(&self) -> Option<DOMString> {
1101 reflect_cross_origin_attribute(self.upcast::<Element>())
1102 }
1103
1104 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1106 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1107 }
1108
1109 fn ReferrerPolicy(&self) -> DOMString {
1111 reflect_referrer_policy_attribute(self.upcast::<Element>())
1112 }
1113
1114 make_setter!(SetReferrerPolicy, "referrerpolicy");
1116
1117 fn GetSheet(&self, can_gc: CanGc) -> Option<DomRoot<DOMStyleSheet>> {
1119 self.get_cssom_stylesheet(can_gc).map(DomRoot::upcast)
1120 }
1121}
1122
1123struct FaviconFetchContext {
1124 link: Trusted<HTMLLinkElement>,
1126 image_cache: std::sync::Arc<dyn ImageCache>,
1127 id: PendingImageId,
1128
1129 url: ServoUrl,
1131}
1132
1133impl FetchResponseListener for FaviconFetchContext {
1134 fn process_request_body(&mut self, _: RequestId) {}
1135
1136 fn process_request_eof(&mut self, _: RequestId) {}
1137
1138 fn process_response(
1139 &mut self,
1140 request_id: RequestId,
1141 metadata: Result<FetchMetadata, NetworkError>,
1142 ) {
1143 self.image_cache.notify_pending_response(
1144 self.id,
1145 FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
1146 );
1147 }
1148
1149 fn process_response_chunk(&mut self, request_id: RequestId, chunk: Vec<u8>) {
1150 self.image_cache.notify_pending_response(
1151 self.id,
1152 FetchResponseMsg::ProcessResponseChunk(request_id, chunk.into()),
1153 );
1154 }
1155
1156 fn process_response_eof(
1157 self,
1158 cx: &mut js::context::JSContext,
1159 request_id: RequestId,
1160 response: Result<(), NetworkError>,
1161 timing: ResourceFetchTiming,
1162 ) {
1163 self.image_cache.notify_pending_response(
1164 self.id,
1165 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
1166 );
1167 submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
1168 }
1169
1170 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
1171 let global = &self.resource_timing_global();
1172 global.report_csp_violations(violations, None, None);
1173 }
1174}
1175
1176impl ResourceTimingListener for FaviconFetchContext {
1177 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1178 (
1179 InitiatorType::LocalName("link".to_string()),
1180 self.url.clone(),
1181 )
1182 }
1183
1184 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
1185 self.link.root().upcast::<Node>().owner_doc().global()
1186 }
1187}