1use std::borrow::{Borrow, ToOwned};
6use std::cell::Cell;
7use std::default::Default;
8use std::str::FromStr;
9
10use bytes::{Bytes, BytesMut};
11use dom_struct::dom_struct;
12use html5ever::{LocalName, Prefix, local_name};
13use js::context::{JSContext, NoGC};
14use js::rust::HandleObject;
15use net_traits::image_cache::{
16 Image, ImageCache, ImageCacheResponseCallback, ImageCacheResult, ImageLoadListener,
17 ImageOrMetadataAvailable, ImageResponse, PendingImageId,
18};
19use net_traits::request::{Destination, Initiator, ParserMetadata, RequestBuilder, RequestId};
20use net_traits::{
21 FetchMetadata, FetchResponseMsg, NetworkError, ReferrerPolicy, ResourceFetchTiming,
22};
23use pixels::PixelFormat;
24use script_bindings::cell::DomRefCell;
25use script_bindings::root::Dom;
26use servo_arc::Arc;
27use servo_base::generic_channel::GenericSharedMemory;
28use servo_url::ServoUrl;
29use style::attr::AttrValue;
30use style::media_queries::MediaList as StyleMediaList;
31use style::stylesheets::Stylesheet;
32use stylo_atoms::Atom;
33use webrender_api::units::DeviceIntSize;
34
35use crate::css::stylesheet_loader::{
36 ElementStylesheetLoader, StylesheetContextSource, StylesheetOwner,
37};
38use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenList_Binding::DOMTokenListMethods;
39use crate::dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods;
40use crate::dom::bindings::inheritance::Castable;
41use crate::dom::bindings::refcounted::Trusted;
42use crate::dom::bindings::reflector::DomGlobal;
43use crate::dom::bindings::root::{DomRoot, MutNullableDom};
44use crate::dom::bindings::str::{DOMString, USVString};
45use crate::dom::csp::{GlobalCspReporting, Violation};
46use crate::dom::css::cssstylesheet::CSSStyleSheet;
47use crate::dom::css::stylesheet::StyleSheet as DOMStyleSheet;
48use crate::dom::document::Document;
49use crate::dom::documentorshadowroot::StylesheetSource;
50use crate::dom::domtokenlist::DOMTokenList;
51use crate::dom::element::attributes::storage::AttrRef;
52use crate::dom::element::{
53 AttributeMutation, Element, ElementCreator, cors_setting_for_element,
54 cors_settings_attribute_credential_mode, referrer_policy_for_element,
55 reflect_cross_origin_attribute, reflect_referrer_policy_attribute, set_cross_origin_attribute,
56};
57use crate::dom::html::document_metadata::processingoptions::{
58 LinkFetchContext, LinkFetchContextType, LinkProcessingOptions,
59};
60use crate::dom::html::htmlelement::HTMLElement;
61use crate::dom::html::links::relations::LinkRelations;
62use crate::dom::medialist::MediaList;
63use crate::dom::node::virtualmethods::VirtualMethods;
64use crate::dom::node::{BindContext, Node, NodeTraits, UnbindContext};
65use crate::dom::performance::performanceresourcetiming::InitiatorType;
66use crate::dom::srcset::SourceSet;
67use crate::dom::types::{EventTarget, GlobalScope};
68use crate::fetch::network_listener::{
69 FetchResponseListener, ResourceTimingListener, submit_timing,
70};
71use crate::scripting::fetching_options::ScriptFetchOptions;
72use crate::scripting::script_module::fetch_a_modulepreload_module;
73use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
74
75#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
76pub(crate) struct RequestGenerationId(u32);
77
78impl RequestGenerationId {
79 fn increment(self) -> RequestGenerationId {
80 RequestGenerationId(self.0 + 1)
81 }
82}
83
84#[dom_struct]
85pub(crate) struct HTMLLinkElement {
86 htmlelement: HTMLElement,
87 rel_list: MutNullableDom<DOMTokenList>,
89
90 #[no_trace]
96 relations: Cell<LinkRelations>,
97
98 #[conditional_malloc_size_of]
99 #[no_trace]
100 stylesheet: DomRefCell<Option<Arc<Stylesheet>>>,
101 cssom_stylesheet: MutNullableDom<CSSStyleSheet>,
102
103 parser_inserted: Cell<bool>,
105 any_failed_load: Cell<bool>,
107 is_explicitly_enabled: Cell<bool>,
109 previous_type_matched: Cell<bool>,
111 pending_loads: Cell<u32>,
114 request_generation_id: Cell<RequestGenerationId>,
116
117 previous_media_environment_matched: Cell<bool>,
119 line_number: u64,
121 source_set: DomRefCell<SourceSet>,
123 blocking: MutNullableDom<DOMTokenList>,
125}
126
127impl HTMLLinkElement {
128 fn new_inherited(
129 local_name: LocalName,
130 prefix: Option<Prefix>,
131 document: &Document,
132 creator: ElementCreator,
133 ) -> HTMLLinkElement {
134 HTMLLinkElement {
135 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
136 rel_list: Default::default(),
137 relations: Cell::new(LinkRelations::empty()),
138 parser_inserted: Cell::new(creator.is_parser_created()),
139 stylesheet: DomRefCell::new(None),
140 cssom_stylesheet: MutNullableDom::new(None),
141 pending_loads: Cell::new(0),
142 any_failed_load: Cell::new(false),
143 request_generation_id: Cell::new(RequestGenerationId(0)),
144 is_explicitly_enabled: Cell::new(false),
145 previous_type_matched: Cell::new(true),
146 previous_media_environment_matched: Cell::new(true),
147 line_number: creator.return_line_number(),
148 source_set: DomRefCell::new(SourceSet::new()),
149 blocking: Default::default(),
150 }
151 }
152
153 pub(crate) fn new(
154 cx: &mut js::context::JSContext,
155 local_name: LocalName,
156 prefix: Option<Prefix>,
157 document: &Document,
158 proto: Option<HandleObject>,
159 creator: ElementCreator,
160 ) -> DomRoot<HTMLLinkElement> {
161 Node::reflect_node_with_proto(
162 cx,
163 Box::new(HTMLLinkElement::new_inherited(
164 local_name, prefix, document, creator,
165 )),
166 document,
167 proto,
168 )
169 }
170
171 pub(crate) fn get_request_generation_id(&self) -> RequestGenerationId {
172 self.request_generation_id.get()
173 }
174
175 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
176 fn remove_stylesheet(&self, no_gc: &NoGC) {
177 if let Some(stylesheet) = self.stylesheet.safe_borrow_mut(no_gc).take() {
178 let owner = self.stylesheet_list_owner();
179 owner.remove_stylesheet(
180 StylesheetSource::Element(Dom::from_ref(self.upcast())),
181 &stylesheet,
182 );
183 self.clean_stylesheet_ownership();
184 owner.invalidate_stylesheets(no_gc);
185 }
186 }
187
188 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
191 pub(crate) fn set_stylesheet(&self, no_gc: &NoGC, new_stylesheet: Arc<Stylesheet>) {
192 let owner = self.stylesheet_list_owner();
193 if let Some(old_stylesheet) = self
194 .stylesheet
195 .safe_borrow_mut(no_gc)
196 .replace(new_stylesheet.clone())
197 {
198 owner.remove_stylesheet(
199 StylesheetSource::Element(Dom::from_ref(self.upcast())),
200 &old_stylesheet,
201 );
202 }
203 owner.add_owned_stylesheet(no_gc, self.upcast(), new_stylesheet);
204 }
205
206 pub(crate) fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
207 self.stylesheet.borrow().clone()
208 }
209
210 pub(crate) fn get_cssom_stylesheet(
211 &self,
212 cx: &mut JSContext,
213 ) -> Option<DomRoot<CSSStyleSheet>> {
214 self.get_stylesheet().map(|sheet| {
215 self.cssom_stylesheet.or_init(|| {
216 CSSStyleSheet::new(
217 cx,
218 &self.owner_window(),
219 Some(self.upcast::<Element>()),
220 DOMString::from_static("text/css"),
221 Some(self.Href().into()),
222 sheet,
223 None, )
225 })
226 })
227 }
228
229 pub(crate) fn is_alternate(&self) -> bool {
230 self.relations.get().contains(LinkRelations::ALTERNATE) &&
231 !self
232 .upcast::<Element>()
233 .get_string_attribute(&local_name!("title"))
234 .is_empty()
235 }
236
237 pub(crate) fn is_effectively_disabled(&self) -> bool {
238 (self.is_alternate() && !self.is_explicitly_enabled.get()) ||
239 self.upcast::<Element>()
240 .has_attribute(&local_name!("disabled"))
241 }
242
243 fn clean_stylesheet_ownership(&self) {
244 if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() {
245 cssom_stylesheet.set_owner_node(None);
246 }
247 self.cssom_stylesheet.set(None);
248 }
249}
250
251impl VirtualMethods for HTMLLinkElement {
252 fn super_type(&self) -> Option<&dyn VirtualMethods> {
253 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
254 }
255
256 fn attribute_mutated(
257 &self,
258 cx: &mut js::context::JSContext,
259 attr: AttrRef<'_>,
260 mutation: AttributeMutation,
261 ) {
262 self.super_type()
263 .unwrap()
264 .attribute_mutated(cx, attr, mutation);
265
266 let local_name = attr.local_name();
267 let is_removal = mutation.is_removal();
268 match *local_name {
269 local_name!("disabled") => {
270 self.handle_disabled_attribute_change(cx.no_gc(), is_removal);
271 return;
272 },
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 _ => {},
284 }
285
286 let node = self.upcast::<Node>();
287 if !node.is_connected() {
288 return;
289 }
290
291 if self.relations.get().contains(LinkRelations::STYLESHEET) &&
294 let AttributeMutation::Set(Some(previous_value)) = mutation &&
295 **previous_value == **attr.value()
296 {
297 return;
298 }
299
300 match *local_name {
301 local_name!("rel") | local_name!("rev") => {
302 if self.relations.get().contains(LinkRelations::STYLESHEET) {
305 self.handle_stylesheet_url(cx);
306 } else {
307 self.remove_stylesheet(cx.no_gc());
308 }
309
310 if self.relations.get().contains(LinkRelations::MODULE_PRELOAD) {
311 self.fetch_and_process_modulepreload(cx);
312 }
313 },
314 local_name!("href") => {
315 if is_removal {
318 if self.relations.get().contains(LinkRelations::STYLESHEET) {
319 self.remove_stylesheet(cx.no_gc());
320 }
321 return;
322 }
323 if self.relations.get().contains(LinkRelations::STYLESHEET) {
327 self.handle_stylesheet_url(cx);
328 }
329
330 if self.relations.get().contains(LinkRelations::ICON) {
331 self.handle_favicon_url(&attr.value());
332 }
333
334 if self.relations.get().contains(LinkRelations::PREFETCH) {
338 self.fetch_and_process_prefetch_link(&attr.value());
339 }
340
341 if self.relations.get().contains(LinkRelations::PRELOAD) {
345 self.handle_preload_url(cx.no_gc());
346 }
347
348 if self.relations.get().contains(LinkRelations::MODULE_PRELOAD) {
350 self.fetch_and_process_modulepreload(cx);
351 }
352 },
353 local_name!("imagesrcset") => {
354 self.source_set
355 .safe_borrow_mut(cx.no_gc())
356 .update_source_set(cx.no_gc(), self.upcast::<Element>());
357 },
358 local_name!("imagesizes") => {
359 if self
360 .upcast::<Element>()
361 .has_attribute(&local_name!("imagesrcset"))
362 {
363 self.source_set
364 .safe_borrow_mut(cx.no_gc())
365 .update_source_set(cx.no_gc(), self.upcast::<Element>());
366 }
367 },
368 local_name!("sizes") if self.relations.get().contains(LinkRelations::ICON) => {
369 self.handle_favicon_url(&attr.value());
370 },
371 local_name!("crossorigin") => {
372 if self.relations.get().contains(LinkRelations::PREFETCH) {
376 self.fetch_and_process_prefetch_link(&attr.value());
377 }
378
379 if self.relations.get().contains(LinkRelations::STYLESHEET) {
383 self.handle_stylesheet_url(cx);
384 }
385 },
386 local_name!("as") => {
387 if self.relations.get().contains(LinkRelations::PRELOAD) &&
391 let AttributeMutation::Set(Some(_)) = mutation
392 {
393 self.handle_preload_url(cx.no_gc());
394 }
395 },
396 local_name!("type") => {
397 if self.relations.get().contains(LinkRelations::STYLESHEET) {
405 self.handle_stylesheet_url(cx);
406 }
407
408 if self.relations.get().contains(LinkRelations::PRELOAD) &&
414 !self.previous_type_matched.get()
415 {
416 self.handle_preload_url(cx.no_gc());
417 }
418 },
419 local_name!("media") => {
420 if self.relations.get().contains(LinkRelations::PRELOAD) &&
425 !self.previous_media_environment_matched.get()
426 {
427 match mutation {
428 AttributeMutation::Removed | AttributeMutation::Set(Some(_)) => {
429 self.handle_preload_url(cx.no_gc())
430 },
431 _ => {},
432 };
433 } else if self.relations.get().contains(LinkRelations::STYLESHEET) &&
434 let Some(ref stylesheet) = *self.stylesheet.safe_borrow_mut(cx.no_gc())
435 {
436 let document = self.owner_document();
437 let shared_lock = document.style_shared_author_lock().clone();
438 let mut guard = shared_lock.write();
439 let media = stylesheet.media.write_with(&mut guard);
440 match mutation {
441 AttributeMutation::Set(_) => {
442 *media = MediaList::parse_media_list(&attr.value(), document.window())
443 },
444 AttributeMutation::Removed => *media = StyleMediaList::empty(),
445 };
446 self.owner_document().invalidate_stylesheets(cx.no_gc());
447 }
448
449 let matches_media_environment =
450 MediaList::matches_environment(&self.owner_document(), &attr.value());
451 self.previous_media_environment_matched
452 .set(matches_media_environment);
453 },
454 _ => {},
455 }
456 }
457
458 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
459 match name {
460 &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
461 _ => self
462 .super_type()
463 .unwrap()
464 .parse_plain_attribute(name, value),
465 }
466 }
467
468 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
469 if let Some(s) = self.super_type() {
470 s.bind_to_tree(cx, context);
471 }
472
473 let element = self.upcast::<Element>();
474 let href = element.get_attribute_string_value(&local_name!("href"));
475
476 if context.tree_connected &&
477 (href.as_ref().is_some_and(|x| !x.is_empty()) ||
478 element.has_attribute(&local_name!("imagesrcset")))
479 {
480 let relations = self.relations.get();
481 if let Some(href) = href {
484 if relations.contains(LinkRelations::STYLESHEET) {
485 self.handle_stylesheet_url(cx);
486 }
487
488 if relations.contains(LinkRelations::ICON) {
489 self.handle_favicon_url(&href);
490 }
491
492 if relations.contains(LinkRelations::PREFETCH) {
493 self.fetch_and_process_prefetch_link(&href);
494 }
495
496 if relations.contains(LinkRelations::MODULE_PRELOAD) {
498 let link = DomRoot::from_ref(self);
499 self.owner_document().add_delayed_task(
500 task!(FetchModulePreload: |cx, link: DomRoot<HTMLLinkElement>| {
501 link.fetch_and_process_modulepreload(cx);
502 }),
503 );
504 }
505 }
506
507 if relations.contains(LinkRelations::PRELOAD) {
508 self.handle_preload_url(cx.no_gc());
509 }
510 }
511 }
512
513 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
514 if let Some(s) = self.super_type() {
515 s.unbind_from_tree(cx, context);
516 }
517
518 self.remove_stylesheet(cx.no_gc());
519 }
520}
521
522impl HTMLLinkElement {
523 fn compute_destination_for_attribute(&self) -> Option<Destination> {
524 let element = self.upcast::<Element>();
527 element
528 .get_attribute_string_ref(&local_name!("as"))
529 .and_then(|attr| {
530 let attr = attr.as_attr_ref().value();
531 LinkProcessingOptions::translate_a_preload_destination(&attr)
532 })
533 }
534
535 fn processing_options(&self) -> LinkProcessingOptions {
537 let element = self.upcast::<Element>();
538
539 let document = self.upcast::<Node>().owner_doc();
541 let global = document.owner_global();
542
543 let mut options = LinkProcessingOptions {
545 href: String::new(),
546 destination: Destination::None,
547 integrity: String::new(),
548 link_type: String::new(),
549 cryptographic_nonce_metadata: self.upcast::<Element>().nonce_value(),
550 cross_origin: cors_setting_for_element(element),
551 referrer_policy: referrer_policy_for_element(element),
552 policy_container: document.policy_container().clone(),
553 source_set: Some(self.source_set.borrow().clone()),
554 origin: document.borrow().origin().immutable().to_owned(),
555 base_url: document.borrow().base_url(),
556 request_client: global.request_client(None),
557 referrer: global.get_referrer(),
558 };
559
560 if let Some(href_attribute) = element.get_attribute_string_value(&local_name!("href")) {
562 options.href = href_attribute;
563 }
564
565 if let Some(integrity_attribute) =
568 element.get_attribute_string_value(&local_name!("integrity"))
569 {
570 options.integrity = integrity_attribute;
571 }
572
573 if let Some(type_attribute) = element.get_attribute_string_value(&local_name!("type")) {
575 options.link_type = type_attribute;
576 }
577
578 assert!(!options.href.is_empty() || options.source_set.is_some());
580
581 options
583 }
584
585 fn default_fetch_and_process_the_linked_resource(&self) -> Option<RequestBuilder> {
590 let options = self.processing_options();
592
593 let Some(request) =
595 options.create_link_request(self.owner_window().webview_id(), &self.owner_global())
596 else {
597 return None;
599 };
600 let mut request = request.synchronous(true);
602
603 if !self.linked_resource_fetch_setup(&mut request) {
605 return None;
606 }
607
608 Some(request)
614 }
615
616 fn linked_resource_fetch_setup(&self, request: &mut RequestBuilder) -> bool {
618 if self.relations.get().contains(LinkRelations::ICON) {
620 request.destination = Destination::Image;
622
623 }
627
628 if self.relations.get().contains(LinkRelations::STYLESHEET) {
630 if self
632 .upcast::<Element>()
633 .has_attribute(&local_name!("disabled"))
634 {
635 return false;
636 }
637 }
652
653 true
654 }
655
656 fn fetch_and_process_prefetch_link(&self, href: &str) {
658 if href.is_empty() {
660 return;
661 }
662
663 let mut options = self.processing_options();
665
666 options.destination = Destination::None;
668
669 let Some(request) =
671 options.create_link_request(self.owner_window().webview_id(), &self.owner_global())
672 else {
673 return;
675 };
676 let url = request.url.url();
677
678 let request = request.initiator(Initiator::Prefetch);
680
681 let document = self.upcast::<Node>().owner_doc();
685 let fetch_context = LinkFetchContext {
686 url,
687 link: Some(Trusted::new(self)),
688 type_: LinkFetchContextType::Prefetch,
689 response_body: BytesMut::new(),
690 };
691
692 document.fetch_background(request, fetch_context);
693 }
694
695 fn handle_stylesheet_url(&self, cx: &mut js::context::JSContext) {
697 let document = self.owner_document();
698 if document.browsing_context().is_none() {
699 return;
700 }
701
702 let element = self.upcast::<Element>();
703
704 let type_ = element.get_string_attribute(&local_name!("type"));
711 if !type_.is_empty() && type_ != "text/css" {
712 return;
713 }
714
715 let href = element.get_string_attribute(&local_name!("href"));
717 if href.is_empty() {
718 return;
719 }
720
721 let link_url = match document.base_url().join(&href.str()) {
723 Ok(url) => url,
724 Err(e) => {
725 debug!("Parsing url {} failed: {}", href, e);
726 return;
727 },
728 };
729
730 let cors_setting = cors_setting_for_element(element);
732
733 let media_query_string = element
734 .get_attribute_string_ref(&local_name!("media"))
735 .unwrap_or_default();
736 let media = MediaList::parse_media_list(
737 &media_query_string.as_attr_ref().value(),
738 document.window(),
739 );
740 let media = Arc::new(document.style_shared_author_lock().wrap(media));
741
742 let integrity_metadata = element
743 .get_attribute_string_value(&local_name!("integrity"))
744 .unwrap_or_default();
745
746 self.request_generation_id
747 .set(self.request_generation_id.get().increment());
748 self.pending_loads.set(0);
749
750 ElementStylesheetLoader::load_with_element(
751 cx,
752 self.upcast(),
753 StylesheetContextSource::LinkElement,
754 media,
755 link_url,
756 cors_setting,
757 integrity_metadata,
758 );
759 }
760
761 fn handle_disabled_attribute_change(&self, no_gc: &NoGC, is_removal: bool) {
763 if is_removal {
765 self.is_explicitly_enabled.set(true);
766 }
767 if let Some(stylesheet) = self.get_stylesheet() &&
768 stylesheet.set_disabled(!is_removal)
769 {
770 self.stylesheet_list_owner().invalidate_stylesheets(no_gc);
771 }
772 }
773
774 fn handle_favicon_url(&self, href: &str) {
775 if href.is_empty() {
777 return;
778 }
779
780 let window = self.owner_window();
783 if !window.is_top_level() {
784 return;
785 }
786 let Ok(href) = self.Href().parse() else {
787 return;
788 };
789
790 self.request_generation_id
792 .set(self.request_generation_id.get().increment());
793
794 let cache_result = window.image_cache().get_cached_image_status(
795 href,
796 window.origin().immutable(),
797 cors_setting_for_element(self.upcast()),
798 );
799
800 match cache_result {
801 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
802 image, ..
803 }) => {
804 self.process_favicon_response(image);
805 },
806 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(_, id)) |
807 ImageCacheResult::Pending(id) => {
808 let sender = self.register_image_cache_callback(id);
809 window.image_cache().add_listener(ImageLoadListener::new(
810 sender,
811 window.pipeline_id(),
812 id,
813 ));
814 },
815 ImageCacheResult::ReadyForRequest(id) => {
816 let Some(request) = self.default_fetch_and_process_the_linked_resource() else {
817 return;
818 };
819
820 let sender = self.register_image_cache_callback(id);
821 window.image_cache().add_listener(ImageLoadListener::new(
822 sender,
823 window.pipeline_id(),
824 id,
825 ));
826
827 let document = self.upcast::<Node>().owner_doc();
828 let fetch_context = FaviconFetchContext {
829 url: self.owner_document().base_url(),
830 image_cache: window.image_cache(),
831 id,
832 };
833 document.fetch_background(request, fetch_context);
834 },
835 ImageCacheResult::FailedToLoadOrDecode => {},
836 };
837 }
838
839 fn register_image_cache_callback(&self, id: PendingImageId) -> ImageCacheResponseCallback {
840 let trusted_node = Trusted::new(self);
841 let window = self.owner_window();
842 let request_generation_id = self.get_request_generation_id();
843 window.register_image_cache_listener(id, move |response, _| {
844 let trusted_node = trusted_node.clone();
845 let link_element = trusted_node.root();
846 let window = link_element.owner_window();
847
848 let ImageResponse::Loaded(image, _) = response.response else {
849 return;
851 };
852
853 if request_generation_id != link_element.get_request_generation_id() {
854 return;
856 };
857
858 window
859 .as_global_scope()
860 .task_manager()
861 .networking_task_source()
862 .queue(task!(process_favicon_response: move || {
863 let element = trusted_node.root();
864
865 if request_generation_id != element.get_request_generation_id() {
866 return;
868 };
869
870 element.process_favicon_response(image);
871 }));
872 })
873 }
874
875 fn process_favicon_response(&self, image: Image) {
877 let window = self.owner_window();
879 let document = self.owner_document();
880
881 let send_rasterized_favicon_to_embedder = |raster_image: &pixels::RasterImage| {
882 let frame = raster_image.first_frame();
884
885 let format = match raster_image.format {
886 PixelFormat::K8 => embedder_traits::PixelFormat::K8,
887 PixelFormat::KA8 => embedder_traits::PixelFormat::KA8,
888 PixelFormat::RGB8 => embedder_traits::PixelFormat::RGB8,
889 PixelFormat::RGBA8 => embedder_traits::PixelFormat::RGBA8,
890 PixelFormat::BGRA8 => embedder_traits::PixelFormat::BGRA8,
891 };
892
893 let embedder_image = embedder_traits::Image::new(
894 frame.width,
895 frame.height,
896 std::sync::Arc::new(GenericSharedMemory::from_arc_vec(
897 raster_image.bytes.clone(),
898 )),
899 raster_image.frames[0].byte_range.clone(),
900 format,
901 );
902 document.set_favicon(embedder_image);
903 };
904
905 match image {
906 Image::Raster(raster_image) => send_rasterized_favicon_to_embedder(&raster_image),
907 Image::Vector(vector_image) => {
908 let size = DeviceIntSize::new(250, 250);
910
911 let image_cache = window.image_cache();
912 if let Some(raster_image) =
913 image_cache.rasterize_vector_image(vector_image.id, size, None)
914 {
915 send_rasterized_favicon_to_embedder(&raster_image);
916 } else {
917 let image_cache_sender = self.register_image_cache_callback(vector_image.id);
920 image_cache.add_rasterization_complete_listener(
921 window.pipeline_id(),
922 vector_image.id,
923 size,
924 image_cache_sender,
925 );
926 }
927 },
928 }
929 }
930
931 fn handle_preload_url(&self, no_gc: &NoGC) {
934 self.source_set
936 .safe_borrow_mut(no_gc)
937 .update_source_set(no_gc, self.upcast::<Element>());
938 let mut options = self.processing_options();
940 let Some(destination) = self.compute_destination_for_attribute() else {
943 return;
945 };
946 options.destination = destination;
948 {
950 let type_matches_destination = options.type_matches_destination();
952 self.previous_type_matched.set(type_matches_destination);
953 if !type_matches_destination {
954 return;
955 }
956 }
957 let document = self.upcast::<Node>().owner_doc();
959 options.preload(
960 self.owner_window_unrooted(no_gc).webview_id(),
961 Some(Trusted::new(self)),
962 &document,
963 );
964 }
965
966 pub(crate) fn fire_event_after_response(
968 &self,
969 cx: &mut JSContext,
970 response: Result<(), NetworkError>,
971 ) {
972 if response.is_err() {
975 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
976 } else {
977 self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
978 }
979 }
980
981 fn fetch_and_process_modulepreload(&self, cx: &mut JSContext) {
983 let el = self.upcast::<Element>();
984 let href_attribute_value = el.get_string_attribute(&local_name!("href"));
985
986 if href_attribute_value.is_empty() {
988 return;
989 }
990
991 let destination = el
993 .get_attribute_string_value(&local_name!("as"))
994 .map(|mut value| {
995 value.make_ascii_lowercase();
996 value
997 })
998 .and_then(|value| match value.as_str() {
999 "" => None,
1001 "fetch" => Some(Destination::None),
1003 _ => Destination::from_str(&value).ok(),
1004 })
1005 .unwrap_or(Destination::Script);
1006
1007 let document = self.owner_document();
1008 let global = document.global();
1009
1010 let is_a_modulepreload_destination = match destination {
1012 Destination::Json | Destination::Style | Destination::Text => true,
1013 Destination::Xslt => false,
1016 d => d.is_script_like(),
1017 };
1018
1019 if !is_a_modulepreload_destination {
1022 return global
1023 .task_manager()
1024 .networking_task_source()
1025 .queue_simple_event(self.upcast(), atom!("error"));
1026 }
1027
1028 let Ok(url) = document.encoding_parse_a_url(&href_attribute_value.str()) else {
1031 return;
1032 };
1033 let url = ensure_blob_referenced_by_url_is_kept_alive(&global, url);
1034
1035 let credentials_mode = cors_settings_attribute_credential_mode(el);
1039
1040 let cryptographic_nonce = el.nonce_value();
1042
1043 let integrity_metadata = el
1047 .get_attribute_string_value(&local_name!("integrity"))
1048 .unwrap_or_else(|| {
1049 global
1050 .import_map()
1051 .resolve_a_module_integrity_metadata(&url.url())
1052 });
1053
1054 let referrer_policy = referrer_policy_for_element(el);
1056
1057 let options = ScriptFetchOptions {
1063 cryptographic_nonce,
1064 integrity_metadata,
1065 parser_metadata: ParserMetadata::NotParserInserted,
1066 credentials_mode,
1067 referrer_policy,
1068 render_blocking: false,
1069 };
1070
1071 let link = DomRoot::from_ref(self);
1072
1073 fetch_a_modulepreload_module(
1076 cx,
1077 url,
1078 destination,
1079 &global,
1080 options,
1081 move |cx, fetch_failed| {
1082 let event = match fetch_failed {
1085 true => atom!("error"),
1086 false => atom!("load"),
1087 };
1088
1089 link.upcast::<EventTarget>().fire_event(cx, event);
1090 },
1091 );
1092 }
1093}
1094
1095impl StylesheetOwner for HTMLLinkElement {
1096 fn increment_pending_loads_count(&self) {
1097 self.pending_loads.set(self.pending_loads.get() + 1)
1098 }
1099
1100 fn load_finished(&self, succeeded: bool) -> Option<bool> {
1101 assert!(self.pending_loads.get() > 0, "What finished?");
1102 if !succeeded {
1103 self.any_failed_load.set(true);
1104 }
1105
1106 self.pending_loads.set(self.pending_loads.get() - 1);
1107 if self.pending_loads.get() != 0 {
1108 return None;
1109 }
1110
1111 let any_failed = self.any_failed_load.get();
1112 self.any_failed_load.set(false);
1113 Some(any_failed)
1114 }
1115
1116 fn parser_inserted(&self) -> bool {
1117 self.parser_inserted.get()
1118 }
1119
1120 fn potentially_render_blocking(&self) -> bool {
1122 self.parser_inserted() ||
1129 self.blocking
1130 .get()
1131 .is_some_and(|list| list.Contains(DOMString::from_static("render")))
1132 }
1133
1134 fn referrer_policy(&self, cx: &mut js::context::JSContext) -> ReferrerPolicy {
1135 if self
1136 .RelList(cx)
1137 .Contains(DOMString::from_static("noreferrer"))
1138 {
1139 return ReferrerPolicy::NoReferrer;
1140 }
1141
1142 ReferrerPolicy::EmptyString
1143 }
1144
1145 fn set_origin_clean(&self, cx: &mut js::context::JSContext, origin_clean: bool) {
1146 if let Some(stylesheet) = self.get_cssom_stylesheet(cx) {
1147 stylesheet.set_origin_clean(origin_clean);
1148 }
1149 }
1150}
1151
1152impl HTMLLinkElementMethods<crate::DomTypeHolder> for HTMLLinkElement {
1153 make_url_getter!(Href, "href");
1155
1156 make_url_setter!(SetHref, "href");
1158
1159 make_getter!(Rel, "rel");
1161
1162 fn SetRel(&self, cx: &mut JSContext, rel: DOMString) {
1164 self.upcast::<Element>()
1165 .set_tokenlist_attribute(cx, &local_name!("rel"), rel);
1166 }
1167
1168 make_enumerated_getter!(
1170 As,
1171 "as",
1172 "fetch" | "audio" | "audioworklet" | "document" | "embed" | "font" | "frame"
1173 | "iframe" | "image" | "json" | "manifest" | "object" | "paintworklet"
1174 | "report" | "script" | "serviceworker" | "sharedworker" | "style" | "track"
1175 | "video" | "webidentity" | "worker" | "xslt",
1176 missing => "",
1177 invalid => ""
1178 );
1179
1180 make_setter!(SetAs, "as");
1182
1183 make_getter!(Media, "media");
1185
1186 make_setter!(SetMedia, "media");
1188
1189 make_getter!(Integrity, "integrity");
1191
1192 make_setter!(SetIntegrity, "integrity");
1194
1195 make_getter!(Hreflang, "hreflang");
1197
1198 make_setter!(SetHreflang, "hreflang");
1200
1201 make_getter!(Type, "type");
1203
1204 make_setter!(SetType, "type");
1206
1207 make_url_getter!(ImageSrcset, "imagesrcset");
1209
1210 make_url_setter!(SetImageSrcset, "imagesrcset");
1212
1213 make_getter!(ImageSizes, "imagesizes");
1215
1216 make_setter!(SetImageSizes, "imagesizes");
1218
1219 make_bool_getter!(Disabled, "disabled");
1221
1222 make_bool_setter!(SetDisabled, "disabled");
1224
1225 fn RelList(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMTokenList> {
1227 self.rel_list.or_init(|| {
1228 DOMTokenList::new(
1229 cx,
1230 self.upcast(),
1231 &local_name!("rel"),
1232 Some(vec![
1233 Atom::from("alternate"),
1234 Atom::from("apple-touch-icon"),
1235 Atom::from("apple-touch-icon-precomposed"),
1236 Atom::from("canonical"),
1237 Atom::from("dns-prefetch"),
1238 Atom::from("icon"),
1239 Atom::from("import"),
1240 Atom::from("manifest"),
1241 Atom::from("modulepreload"),
1242 Atom::from("next"),
1243 Atom::from("preconnect"),
1244 Atom::from("prefetch"),
1245 Atom::from("preload"),
1246 Atom::from("prerender"),
1247 Atom::from("stylesheet"),
1248 ]),
1249 )
1250 })
1251 }
1252
1253 make_getter!(Charset, "charset");
1255
1256 make_setter!(SetCharset, "charset");
1258
1259 make_getter!(Rev, "rev");
1261
1262 make_setter!(SetRev, "rev");
1264
1265 make_getter!(Target, "target");
1267
1268 make_setter!(SetTarget, "target");
1270
1271 fn Blocking(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMTokenList> {
1273 self.blocking.or_init(|| {
1274 DOMTokenList::new(
1275 cx,
1276 self.upcast(),
1277 &local_name!("blocking"),
1278 Some(vec![Atom::from("render")]),
1279 )
1280 })
1281 }
1282
1283 fn GetCrossOrigin(&self) -> Option<DOMString> {
1285 reflect_cross_origin_attribute(self.upcast::<Element>())
1286 }
1287
1288 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1290 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1291 }
1292
1293 fn ReferrerPolicy(&self) -> DOMString {
1295 reflect_referrer_policy_attribute(self.upcast::<Element>())
1296 }
1297
1298 make_setter!(SetReferrerPolicy, "referrerpolicy");
1300
1301 fn GetSheet(&self, cx: &mut JSContext) -> Option<DomRoot<DOMStyleSheet>> {
1303 self.get_cssom_stylesheet(cx).map(DomRoot::upcast)
1304 }
1305}
1306
1307struct FaviconFetchContext {
1308 image_cache: std::sync::Arc<dyn ImageCache>,
1309 id: PendingImageId,
1310
1311 url: ServoUrl,
1313}
1314
1315impl FetchResponseListener for FaviconFetchContext {
1316 fn process_request_body(&mut self, _: &GlobalScope, _: RequestId) {}
1317
1318 fn process_response(
1319 &mut self,
1320 _: &mut js::context::JSContext,
1321 _: &GlobalScope,
1322 request_id: RequestId,
1323 metadata: Result<FetchMetadata, NetworkError>,
1324 ) {
1325 self.image_cache.notify_pending_response(
1326 self.id,
1327 FetchResponseMsg::ProcessResponse(request_id, metadata),
1328 );
1329 }
1330
1331 fn process_response_chunk(
1332 &mut self,
1333 _: &mut js::context::JSContext,
1334 _: &GlobalScope,
1335 request_id: RequestId,
1336 chunk: Bytes,
1337 ) {
1338 self.image_cache.notify_pending_response(
1339 self.id,
1340 FetchResponseMsg::ProcessResponseChunk(request_id, chunk),
1341 );
1342 }
1343
1344 fn process_response_eof(
1345 self,
1346 cx: &mut js::context::JSContext,
1347 global: &GlobalScope,
1348 request_id: RequestId,
1349 response: Result<(), NetworkError>,
1350 timing: ResourceFetchTiming,
1351 ) {
1352 self.image_cache.notify_pending_response(
1353 self.id,
1354 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
1355 );
1356 submit_timing(cx, global, &self, &response, &timing);
1357 }
1358
1359 fn process_csp_violations(
1360 &mut self,
1361 cx: &mut js::context::JSContext,
1362 global: &GlobalScope,
1363 _request_id: RequestId,
1364 violations: Vec<Violation>,
1365 ) {
1366 global.report_csp_violations(cx, violations, None, None);
1367 }
1368
1369 fn process_content_length(&mut self, request_id: RequestId, size: usize) {
1370 self.image_cache.notify_pending_response(
1371 self.id,
1372 FetchResponseMsg::ProcessContentLength(request_id, size),
1373 )
1374 }
1375}
1376
1377impl ResourceTimingListener for FaviconFetchContext {
1378 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1379 (InitiatorType::LocalName("link".into()), self.url.clone())
1380 }
1381}