1use std::borrow::{Borrow, ToOwned};
6use std::cell::Cell;
7use std::default::Default;
8use std::str::FromStr;
9
10use dom_struct::dom_struct;
11use html5ever::{LocalName, Prefix, local_name};
12use js::context::{JSContext, NoGC};
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, ParserMetadata, RequestBuilder, RequestId};
19use net_traits::{
20 FetchMetadata, FetchResponseMsg, NetworkError, ReferrerPolicy, ResourceFetchTiming,
21};
22use pixels::PixelFormat;
23use script_bindings::cell::DomRefCell;
24use script_bindings::root::Dom;
25use servo_arc::Arc;
26use servo_base::generic_channel::GenericSharedMemory;
27use servo_url::ServoUrl;
28use style::attr::AttrValue;
29use style::media_queries::MediaList as StyleMediaList;
30use style::stylesheets::Stylesheet;
31use stylo_atoms::Atom;
32use webrender_api::units::DeviceIntSize;
33
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::attributes::storage::AttrRef;
48use crate::dom::element::{
49 AttributeMutation, Element, ElementCreator, cors_setting_for_element,
50 cors_settings_attribute_credential_mode, referrer_policy_for_element,
51 reflect_cross_origin_attribute, reflect_referrer_policy_attribute, set_cross_origin_attribute,
52};
53use crate::dom::html::documentmetadata::processingoptions::{
54 LinkFetchContext, LinkFetchContextType, LinkProcessingOptions,
55};
56use crate::dom::html::htmlelement::HTMLElement;
57use crate::dom::medialist::MediaList;
58use crate::dom::node::virtualmethods::VirtualMethods;
59use crate::dom::node::{BindContext, Node, NodeTraits, UnbindContext};
60use crate::dom::performance::performanceresourcetiming::InitiatorType;
61use crate::dom::types::{EventTarget, GlobalScope};
62use crate::links::LinkRelations;
63use crate::modules::script_module::{ScriptFetchOptions, fetch_a_modulepreload_module};
64use crate::network_listener::{FetchResponseListener, ResourceTimingListener, submit_timing};
65use crate::stylesheet_loader::{ElementStylesheetLoader, StylesheetContextSource, StylesheetOwner};
66use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
67
68#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
69pub(crate) struct RequestGenerationId(u32);
70
71impl RequestGenerationId {
72 fn increment(self) -> RequestGenerationId {
73 RequestGenerationId(self.0 + 1)
74 }
75}
76
77#[dom_struct]
78pub(crate) struct HTMLLinkElement {
79 htmlelement: HTMLElement,
80 rel_list: MutNullableDom<DOMTokenList>,
82
83 #[no_trace]
89 relations: Cell<LinkRelations>,
90
91 #[conditional_malloc_size_of]
92 #[no_trace]
93 stylesheet: DomRefCell<Option<Arc<Stylesheet>>>,
94 cssom_stylesheet: MutNullableDom<CSSStyleSheet>,
95
96 parser_inserted: Cell<bool>,
98 pending_loads: Cell<u32>,
101 any_failed_load: Cell<bool>,
103 request_generation_id: Cell<RequestGenerationId>,
105 is_explicitly_enabled: Cell<bool>,
107 previous_type_matched: Cell<bool>,
109 previous_media_environment_matched: Cell<bool>,
111 line_number: u64,
113 blocking: MutNullableDom<DOMTokenList>,
115}
116
117impl HTMLLinkElement {
118 fn new_inherited(
119 local_name: LocalName,
120 prefix: Option<Prefix>,
121 document: &Document,
122 creator: ElementCreator,
123 ) -> HTMLLinkElement {
124 HTMLLinkElement {
125 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
126 rel_list: Default::default(),
127 relations: Cell::new(LinkRelations::empty()),
128 parser_inserted: Cell::new(creator.is_parser_created()),
129 stylesheet: DomRefCell::new(None),
130 cssom_stylesheet: MutNullableDom::new(None),
131 pending_loads: Cell::new(0),
132 any_failed_load: Cell::new(false),
133 request_generation_id: Cell::new(RequestGenerationId(0)),
134 is_explicitly_enabled: Cell::new(false),
135 previous_type_matched: Cell::new(true),
136 previous_media_environment_matched: Cell::new(true),
137 line_number: creator.return_line_number(),
138 blocking: Default::default(),
139 }
140 }
141
142 pub(crate) fn new(
143 cx: &mut js::context::JSContext,
144 local_name: LocalName,
145 prefix: Option<Prefix>,
146 document: &Document,
147 proto: Option<HandleObject>,
148 creator: ElementCreator,
149 ) -> DomRoot<HTMLLinkElement> {
150 Node::reflect_node_with_proto(
151 cx,
152 Box::new(HTMLLinkElement::new_inherited(
153 local_name, prefix, document, creator,
154 )),
155 document,
156 proto,
157 )
158 }
159
160 pub(crate) fn get_request_generation_id(&self) -> RequestGenerationId {
161 self.request_generation_id.get()
162 }
163
164 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
165 fn remove_stylesheet(&self, no_gc: &NoGC) {
166 if let Some(stylesheet) = self.stylesheet.borrow_mut().take() {
167 let owner = self.stylesheet_list_owner();
168 owner.remove_stylesheet(
169 StylesheetSource::Element(Dom::from_ref(self.upcast())),
170 &stylesheet,
171 );
172 self.clean_stylesheet_ownership();
173 owner.invalidate_stylesheets(no_gc);
174 }
175 }
176
177 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
180 pub(crate) fn set_stylesheet(&self, new_stylesheet: Arc<Stylesheet>) {
181 let owner = self.stylesheet_list_owner();
182 if let Some(old_stylesheet) = self.stylesheet.borrow_mut().replace(new_stylesheet.clone()) {
183 owner.remove_stylesheet(
184 StylesheetSource::Element(Dom::from_ref(self.upcast())),
185 &old_stylesheet,
186 );
187 }
188 owner.add_owned_stylesheet(self.upcast(), new_stylesheet);
189 }
190
191 pub(crate) fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
192 self.stylesheet.borrow().clone()
193 }
194
195 pub(crate) fn get_cssom_stylesheet(
196 &self,
197 cx: &mut JSContext,
198 ) -> Option<DomRoot<CSSStyleSheet>> {
199 self.get_stylesheet().map(|sheet| {
200 self.cssom_stylesheet.or_init(|| {
201 CSSStyleSheet::new(
202 cx,
203 &self.owner_window(),
204 Some(self.upcast::<Element>()),
205 "text/css".into(),
206 Some(self.Href().into()),
207 None, sheet,
209 None, )
211 })
212 })
213 }
214
215 pub(crate) fn is_alternate(&self) -> bool {
216 self.relations.get().contains(LinkRelations::ALTERNATE) &&
217 !self
218 .upcast::<Element>()
219 .get_string_attribute(&local_name!("title"))
220 .is_empty()
221 }
222
223 pub(crate) fn is_effectively_disabled(&self) -> bool {
224 (self.is_alternate() && !self.is_explicitly_enabled.get()) ||
225 self.upcast::<Element>()
226 .has_attribute(&local_name!("disabled"))
227 }
228
229 fn clean_stylesheet_ownership(&self) {
230 if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() {
231 cssom_stylesheet.set_owner_node(None);
232 }
233 self.cssom_stylesheet.set(None);
234 }
235}
236
237impl VirtualMethods for HTMLLinkElement {
238 fn super_type(&self) -> Option<&dyn VirtualMethods> {
239 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
240 }
241
242 fn attribute_mutated(
243 &self,
244 cx: &mut js::context::JSContext,
245 attr: AttrRef<'_>,
246 mutation: AttributeMutation,
247 ) {
248 self.super_type()
249 .unwrap()
250 .attribute_mutated(cx, attr, mutation);
251
252 let local_name = attr.local_name();
253 let is_removal = mutation.is_removal();
254 match *local_name {
255 local_name!("disabled") => {
256 self.handle_disabled_attribute_change(cx.no_gc(), is_removal);
257 return;
258 },
259 local_name!("rel") | local_name!("rev") => {
260 let previous_relations = self.relations.get();
261 self.relations
262 .set(LinkRelations::for_element(self.upcast()));
263
264 if previous_relations == self.relations.get() {
266 return;
267 }
268 },
269 _ => {},
270 }
271
272 let node = self.upcast::<Node>();
273 if !node.is_connected() {
274 return;
275 }
276
277 if self.relations.get().contains(LinkRelations::STYLESHEET) &&
280 let AttributeMutation::Set(Some(previous_value), _) = mutation &&
281 **previous_value == **attr.value()
282 {
283 return;
284 }
285
286 match *local_name {
287 local_name!("rel") | local_name!("rev") => {
288 if self.relations.get().contains(LinkRelations::STYLESHEET) {
291 self.handle_stylesheet_url(cx);
292 } else {
293 self.remove_stylesheet(cx.no_gc());
294 }
295
296 if self.relations.get().contains(LinkRelations::MODULE_PRELOAD) {
297 self.fetch_and_process_modulepreload(cx);
298 }
299 },
300 local_name!("href") => {
301 if is_removal {
304 if self.relations.get().contains(LinkRelations::STYLESHEET) {
305 self.remove_stylesheet(cx.no_gc());
306 }
307 return;
308 }
309 if self.relations.get().contains(LinkRelations::STYLESHEET) {
313 self.handle_stylesheet_url(cx);
314 }
315
316 if self.relations.get().contains(LinkRelations::ICON) {
317 self.handle_favicon_url(&attr.value());
318 }
319
320 if self.relations.get().contains(LinkRelations::PREFETCH) {
324 self.fetch_and_process_prefetch_link(&attr.value());
325 }
326
327 if self.relations.get().contains(LinkRelations::PRELOAD) {
331 self.handle_preload_url();
332 }
333
334 if self.relations.get().contains(LinkRelations::MODULE_PRELOAD) {
336 self.fetch_and_process_modulepreload(cx);
337 }
338 },
339 local_name!("sizes") if self.relations.get().contains(LinkRelations::ICON) => {
340 self.handle_favicon_url(&attr.value());
341 },
342 local_name!("crossorigin") => {
343 if self.relations.get().contains(LinkRelations::PREFETCH) {
347 self.fetch_and_process_prefetch_link(&attr.value());
348 }
349
350 if self.relations.get().contains(LinkRelations::STYLESHEET) {
354 self.handle_stylesheet_url(cx);
355 }
356 },
357 local_name!("as") => {
358 if self.relations.get().contains(LinkRelations::PRELOAD) &&
362 let AttributeMutation::Set(Some(_), _) = mutation
363 {
364 self.handle_preload_url();
365 }
366 },
367 local_name!("type") => {
368 if self.relations.get().contains(LinkRelations::STYLESHEET) {
376 self.handle_stylesheet_url(cx);
377 }
378
379 if self.relations.get().contains(LinkRelations::PRELOAD) &&
385 !self.previous_type_matched.get()
386 {
387 self.handle_preload_url();
388 }
389 },
390 local_name!("media") => {
391 if self.relations.get().contains(LinkRelations::PRELOAD) &&
396 !self.previous_media_environment_matched.get()
397 {
398 match mutation {
399 AttributeMutation::Removed | AttributeMutation::Set(Some(_), _) => {
400 self.handle_preload_url()
401 },
402 _ => {},
403 };
404 } else if self.relations.get().contains(LinkRelations::STYLESHEET) &&
405 let Some(ref stylesheet) = *self.stylesheet.borrow_mut()
406 {
407 let document = self.owner_document();
408 let shared_lock = document.style_shared_author_lock().clone();
409 let mut guard = shared_lock.write();
410 let media = stylesheet.media.write_with(&mut guard);
411 match mutation {
412 AttributeMutation::Set(..) => {
413 *media = MediaList::parse_media_list(&attr.value(), document.window())
414 },
415 AttributeMutation::Removed => *media = StyleMediaList::empty(),
416 };
417 self.owner_document().invalidate_stylesheets(cx.no_gc());
418 }
419
420 let matches_media_environment =
421 MediaList::matches_environment(&self.owner_document(), &attr.value());
422 self.previous_media_environment_matched
423 .set(matches_media_environment);
424 },
425 _ => {},
426 }
427 }
428
429 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
430 match name {
431 &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
432 _ => self
433 .super_type()
434 .unwrap()
435 .parse_plain_attribute(name, value),
436 }
437 }
438
439 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
440 if let Some(s) = self.super_type() {
441 s.bind_to_tree(cx, context);
442 }
443
444 if context.tree_connected &&
445 let Some(href) = self
446 .upcast::<Element>()
447 .get_attribute_string_value(&local_name!("href"))
448 {
449 let relations = self.relations.get();
450 if relations.contains(LinkRelations::STYLESHEET) {
453 self.handle_stylesheet_url(cx);
454 }
455
456 if relations.contains(LinkRelations::ICON) {
457 self.handle_favicon_url(&href);
458 }
459
460 if relations.contains(LinkRelations::PREFETCH) {
461 self.fetch_and_process_prefetch_link(&href);
462 }
463
464 if relations.contains(LinkRelations::PRELOAD) {
465 self.handle_preload_url();
466 }
467
468 if relations.contains(LinkRelations::MODULE_PRELOAD) {
470 let link = DomRoot::from_ref(self);
471 self.owner_document().add_delayed_task(
472 task!(FetchModulePreload: |cx, link: DomRoot<HTMLLinkElement>| {
473 link.fetch_and_process_modulepreload(cx);
474 }),
475 );
476 }
477 }
478 }
479
480 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
481 if let Some(s) = self.super_type() {
482 s.unbind_from_tree(cx, context);
483 }
484
485 self.remove_stylesheet(cx.no_gc());
486 }
487}
488
489impl HTMLLinkElement {
490 fn compute_destination_for_attribute(&self) -> Option<Destination> {
491 let element = self.upcast::<Element>();
494 element
495 .get_attribute_string_value(&local_name!("as"))
496 .and_then(|attr| LinkProcessingOptions::translate_a_preload_destination(&attr))
497 }
498
499 fn processing_options(&self) -> LinkProcessingOptions {
501 let element = self.upcast::<Element>();
502
503 let document = self.upcast::<Node>().owner_doc();
505 let global = document.owner_global();
506
507 let mut options = LinkProcessingOptions {
509 href: String::new(),
510 destination: Destination::None,
511 integrity: String::new(),
512 link_type: String::new(),
513 cryptographic_nonce_metadata: self.upcast::<Element>().nonce_value(),
514 cross_origin: cors_setting_for_element(element),
515 referrer_policy: referrer_policy_for_element(element),
516 policy_container: document.policy_container().to_owned(),
517 source_set: None, origin: document.borrow().origin().immutable().to_owned(),
519 base_url: document.borrow().base_url(),
520 request_client: global.request_client(None),
521 referrer: global.get_referrer(),
522 };
523
524 if let Some(href_attribute) = element.get_attribute_string_value(&local_name!("href")) {
526 options.href = href_attribute;
527 }
528
529 if let Some(integrity_attribute) =
532 element.get_attribute_string_value(&local_name!("integrity"))
533 {
534 options.integrity = integrity_attribute;
535 }
536
537 if let Some(type_attribute) = element.get_attribute_string_value(&local_name!("type")) {
539 options.link_type = type_attribute;
540 }
541
542 assert!(!options.href.is_empty() || options.source_set.is_some());
544
545 options
547 }
548
549 fn default_fetch_and_process_the_linked_resource(&self) -> Option<RequestBuilder> {
554 let options = self.processing_options();
556
557 let Some(request) = options.create_link_request(self.owner_window().webview_id()) else {
559 return None;
561 };
562 let mut request = request.synchronous(true);
564
565 if !self.linked_resource_fetch_setup(&mut request) {
567 return None;
568 }
569
570 Some(request)
576 }
577
578 fn linked_resource_fetch_setup(&self, request: &mut RequestBuilder) -> bool {
580 if self.relations.get().contains(LinkRelations::ICON) {
582 request.destination = Destination::Image;
584
585 }
589
590 if self.relations.get().contains(LinkRelations::STYLESHEET) {
592 if self
594 .upcast::<Element>()
595 .has_attribute(&local_name!("disabled"))
596 {
597 return false;
598 }
599 }
614
615 true
616 }
617
618 fn fetch_and_process_prefetch_link(&self, href: &str) {
620 if href.is_empty() {
622 return;
623 }
624
625 let mut options = self.processing_options();
627
628 options.destination = Destination::None;
630
631 let Some(request) = options.create_link_request(self.owner_window().webview_id()) else {
633 return;
635 };
636 let url = request.url.url();
637
638 let request = request.initiator(Initiator::Prefetch);
640
641 let document = self.upcast::<Node>().owner_doc();
645 let fetch_context = LinkFetchContext {
646 url,
647 link: Some(Trusted::new(self)),
648 global: Trusted::new(&document.global()),
649 type_: LinkFetchContextType::Prefetch,
650 response_body: vec![],
651 };
652
653 document.fetch_background(request, fetch_context);
654 }
655
656 fn handle_stylesheet_url(&self, cx: &mut js::context::JSContext) {
658 let document = self.owner_document();
659 if document.browsing_context().is_none() {
660 return;
661 }
662
663 let element = self.upcast::<Element>();
664
665 let type_ = element.get_string_attribute(&local_name!("type"));
672 if !type_.is_empty() && type_ != "text/css" {
673 return;
674 }
675
676 let href = element.get_string_attribute(&local_name!("href"));
678 if href.is_empty() {
679 return;
680 }
681
682 let link_url = match document.base_url().join(&href.str()) {
684 Ok(url) => url,
685 Err(e) => {
686 debug!("Parsing url {} failed: {}", href, e);
687 return;
688 },
689 };
690
691 let cors_setting = cors_setting_for_element(element);
693
694 let mq_str = element
695 .get_attribute_string_value(&local_name!("media"))
696 .unwrap_or_default();
697 let media = MediaList::parse_media_list(&mq_str, document.window());
698 let media = Arc::new(document.style_shared_author_lock().wrap(media));
699
700 let integrity_metadata = element
701 .get_attribute_string_value(&local_name!("integrity"))
702 .unwrap_or_default();
703
704 self.request_generation_id
705 .set(self.request_generation_id.get().increment());
706 self.pending_loads.set(0);
707
708 ElementStylesheetLoader::load_with_element(
709 cx,
710 self.upcast(),
711 StylesheetContextSource::LinkElement,
712 media,
713 link_url,
714 cors_setting,
715 integrity_metadata,
716 );
717 }
718
719 fn handle_disabled_attribute_change(&self, no_gc: &NoGC, is_removal: bool) {
721 if is_removal {
723 self.is_explicitly_enabled.set(true);
724 }
725 if let Some(stylesheet) = self.get_stylesheet() &&
726 stylesheet.set_disabled(!is_removal)
727 {
728 self.stylesheet_list_owner().invalidate_stylesheets(no_gc);
729 }
730 }
731
732 fn handle_favicon_url(&self, href: &str) {
733 if href.is_empty() {
735 return;
736 }
737
738 let window = self.owner_window();
741 if !window.is_top_level() {
742 return;
743 }
744 let Ok(href) = self.Href().parse() else {
745 return;
746 };
747
748 self.request_generation_id
750 .set(self.request_generation_id.get().increment());
751
752 let cache_result = window.image_cache().get_cached_image_status(
753 href,
754 window.origin().immutable().clone(),
755 cors_setting_for_element(self.upcast()),
756 );
757
758 match cache_result {
759 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
760 image, ..
761 }) => {
762 self.process_favicon_response(image);
763 },
764 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(_, id)) |
765 ImageCacheResult::Pending(id) => {
766 let sender = self.register_image_cache_callback(id);
767 window.image_cache().add_listener(ImageLoadListener::new(
768 sender,
769 window.pipeline_id(),
770 id,
771 ));
772 },
773 ImageCacheResult::ReadyForRequest(id) => {
774 let Some(request) = self.default_fetch_and_process_the_linked_resource() else {
775 return;
776 };
777
778 let sender = self.register_image_cache_callback(id);
779 window.image_cache().add_listener(ImageLoadListener::new(
780 sender,
781 window.pipeline_id(),
782 id,
783 ));
784
785 let document = self.upcast::<Node>().owner_doc();
786 let fetch_context = FaviconFetchContext {
787 url: self.owner_document().base_url(),
788 image_cache: window.image_cache(),
789 id,
790 link: Trusted::new(self),
791 };
792 document.fetch_background(request, fetch_context);
793 },
794 ImageCacheResult::FailedToLoadOrDecode => {},
795 };
796 }
797
798 fn register_image_cache_callback(&self, id: PendingImageId) -> ImageCacheResponseCallback {
799 let trusted_node = Trusted::new(self);
800 let window = self.owner_window();
801 let request_generation_id = self.get_request_generation_id();
802 window.register_image_cache_listener(id, move |response, _| {
803 let trusted_node = trusted_node.clone();
804 let link_element = trusted_node.root();
805 let window = link_element.owner_window();
806
807 let ImageResponse::Loaded(image, _) = response.response else {
808 return;
810 };
811
812 if request_generation_id != link_element.get_request_generation_id() {
813 return;
815 };
816
817 window
818 .as_global_scope()
819 .task_manager()
820 .networking_task_source()
821 .queue(task!(process_favicon_response: move || {
822 let element = trusted_node.root();
823
824 if request_generation_id != element.get_request_generation_id() {
825 return;
827 };
828
829 element.process_favicon_response(image);
830 }));
831 })
832 }
833
834 fn process_favicon_response(&self, image: Image) {
836 let window = self.owner_window();
838 let document = self.owner_document();
839
840 let send_rasterized_favicon_to_embedder = |raster_image: &pixels::RasterImage| {
841 let frame = raster_image.first_frame();
843
844 let format = match raster_image.format {
845 PixelFormat::K8 => embedder_traits::PixelFormat::K8,
846 PixelFormat::KA8 => embedder_traits::PixelFormat::KA8,
847 PixelFormat::RGB8 => embedder_traits::PixelFormat::RGB8,
848 PixelFormat::RGBA8 => embedder_traits::PixelFormat::RGBA8,
849 PixelFormat::BGRA8 => embedder_traits::PixelFormat::BGRA8,
850 };
851
852 let embedder_image = embedder_traits::Image::new(
853 frame.width,
854 frame.height,
855 std::sync::Arc::new(GenericSharedMemory::from_arc_vec(
856 raster_image.bytes.clone(),
857 )),
858 raster_image.frames[0].byte_range.clone(),
859 format,
860 );
861 document.set_favicon(embedder_image);
862 };
863
864 match image {
865 Image::Raster(raster_image) => send_rasterized_favicon_to_embedder(&raster_image),
866 Image::Vector(vector_image) => {
867 let size = DeviceIntSize::new(250, 250);
869
870 let image_cache = window.image_cache();
871 if let Some(raster_image) =
872 image_cache.rasterize_vector_image(vector_image.id, size, None)
873 {
874 send_rasterized_favicon_to_embedder(&raster_image);
875 } else {
876 let image_cache_sender = self.register_image_cache_callback(vector_image.id);
879 image_cache.add_rasterization_complete_listener(
880 window.pipeline_id(),
881 vector_image.id,
882 size,
883 image_cache_sender,
884 );
885 }
886 },
887 }
888 }
889
890 fn handle_preload_url(&self) {
893 let mut options = self.processing_options();
897 let Some(destination) = self.compute_destination_for_attribute() else {
900 return;
902 };
903 options.destination = destination;
905 {
907 let type_matches_destination = options.type_matches_destination();
909 self.previous_type_matched.set(type_matches_destination);
910 if !type_matches_destination {
911 return;
912 }
913 }
914 let document = self.upcast::<Node>().owner_doc();
916 options.preload(
917 self.owner_window().webview_id(),
918 Some(Trusted::new(self)),
919 &document,
920 );
921 }
922
923 pub(crate) fn fire_event_after_response(
925 &self,
926 cx: &mut JSContext,
927 response: Result<(), NetworkError>,
928 ) {
929 if response.is_err() {
932 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
933 } else {
934 self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
935 }
936 }
937
938 fn fetch_and_process_modulepreload(&self, cx: &mut JSContext) {
940 let el = self.upcast::<Element>();
941 let href_attribute_value = el.get_string_attribute(&local_name!("href"));
942
943 if href_attribute_value.is_empty() {
945 return;
946 }
947
948 let destination = el
950 .get_attribute_string_value(&local_name!("as"))
951 .map(|value| value.to_ascii_lowercase())
952 .and_then(|value| match value.as_str() {
953 "" => None,
955 "fetch" => Some(Destination::None),
957 _ => Destination::from_str(&value).ok(),
958 })
959 .unwrap_or(Destination::Script);
960
961 let document = self.owner_document();
962 let global = document.global();
963
964 let is_a_modulepreload_destination = match destination {
966 Destination::Json | Destination::Style => true,
967 Destination::Xslt => false,
970 d => d.is_script_like(),
971 };
972
973 if !is_a_modulepreload_destination {
976 return global
977 .task_manager()
978 .networking_task_source()
979 .queue_simple_event(self.upcast(), atom!("error"));
980 }
981
982 let Ok(url) = document.encoding_parse_a_url(&href_attribute_value.str()) else {
985 return;
986 };
987 let url = ensure_blob_referenced_by_url_is_kept_alive(&global, url);
988
989 let credentials_mode = cors_settings_attribute_credential_mode(el);
993
994 let cryptographic_nonce = el.nonce_value();
996
997 let integrity_metadata = el
1001 .get_attribute_string_value(&local_name!("integrity"))
1002 .unwrap_or_else(|| {
1003 global
1004 .import_map()
1005 .resolve_a_module_integrity_metadata(&url.url())
1006 });
1007
1008 let referrer_policy = referrer_policy_for_element(el);
1010
1011 let options = ScriptFetchOptions {
1017 cryptographic_nonce,
1018 integrity_metadata,
1019 parser_metadata: ParserMetadata::NotParserInserted,
1020 credentials_mode,
1021 referrer_policy,
1022 render_blocking: false,
1023 };
1024
1025 let link = DomRoot::from_ref(self);
1026
1027 fetch_a_modulepreload_module(
1030 cx,
1031 url,
1032 destination,
1033 &global,
1034 options,
1035 move |cx, fetch_failed| {
1036 let event = match fetch_failed {
1039 true => atom!("error"),
1040 false => atom!("load"),
1041 };
1042
1043 link.upcast::<EventTarget>().fire_event(cx, event);
1044 },
1045 );
1046 }
1047}
1048
1049impl StylesheetOwner for HTMLLinkElement {
1050 fn increment_pending_loads_count(&self) {
1051 self.pending_loads.set(self.pending_loads.get() + 1)
1052 }
1053
1054 fn load_finished(&self, succeeded: bool) -> Option<bool> {
1055 assert!(self.pending_loads.get() > 0, "What finished?");
1056 if !succeeded {
1057 self.any_failed_load.set(true);
1058 }
1059
1060 self.pending_loads.set(self.pending_loads.get() - 1);
1061 if self.pending_loads.get() != 0 {
1062 return None;
1063 }
1064
1065 let any_failed = self.any_failed_load.get();
1066 self.any_failed_load.set(false);
1067 Some(any_failed)
1068 }
1069
1070 fn parser_inserted(&self) -> bool {
1071 self.parser_inserted.get()
1072 }
1073
1074 fn potentially_render_blocking(&self) -> bool {
1076 self.parser_inserted() ||
1083 self.blocking
1084 .get()
1085 .is_some_and(|list| list.Contains("render".into()))
1086 }
1087
1088 fn referrer_policy(&self, cx: &mut js::context::JSContext) -> ReferrerPolicy {
1089 if self.RelList(cx).Contains("noreferrer".into()) {
1090 return ReferrerPolicy::NoReferrer;
1091 }
1092
1093 ReferrerPolicy::EmptyString
1094 }
1095
1096 fn set_origin_clean(&self, cx: &mut js::context::JSContext, origin_clean: bool) {
1097 if let Some(stylesheet) = self.get_cssom_stylesheet(cx) {
1098 stylesheet.set_origin_clean(origin_clean);
1099 }
1100 }
1101}
1102
1103impl HTMLLinkElementMethods<crate::DomTypeHolder> for HTMLLinkElement {
1104 make_url_getter!(Href, "href");
1106
1107 make_url_setter!(SetHref, "href");
1109
1110 make_getter!(Rel, "rel");
1112
1113 fn SetRel(&self, cx: &mut JSContext, rel: DOMString) {
1115 self.upcast::<Element>()
1116 .set_tokenlist_attribute(cx, &local_name!("rel"), rel);
1117 }
1118
1119 make_enumerated_getter!(
1121 As,
1122 "as",
1123 "fetch" | "audio" | "audioworklet" | "document" | "embed" | "font" | "frame"
1124 | "iframe" | "image" | "json" | "manifest" | "object" | "paintworklet"
1125 | "report" | "script" | "serviceworker" | "sharedworker" | "style" | "track"
1126 | "video" | "webidentity" | "worker" | "xslt",
1127 missing => "",
1128 invalid => ""
1129 );
1130
1131 make_setter!(SetAs, "as");
1133
1134 make_getter!(Media, "media");
1136
1137 make_setter!(SetMedia, "media");
1139
1140 make_getter!(Integrity, "integrity");
1142
1143 make_setter!(SetIntegrity, "integrity");
1145
1146 make_getter!(Hreflang, "hreflang");
1148
1149 make_setter!(SetHreflang, "hreflang");
1151
1152 make_getter!(Type, "type");
1154
1155 make_setter!(SetType, "type");
1157
1158 make_bool_getter!(Disabled, "disabled");
1160
1161 make_bool_setter!(SetDisabled, "disabled");
1163
1164 fn RelList(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMTokenList> {
1166 self.rel_list.or_init(|| {
1167 DOMTokenList::new(
1168 cx,
1169 self.upcast(),
1170 &local_name!("rel"),
1171 Some(vec![
1172 Atom::from("alternate"),
1173 Atom::from("apple-touch-icon"),
1174 Atom::from("apple-touch-icon-precomposed"),
1175 Atom::from("canonical"),
1176 Atom::from("dns-prefetch"),
1177 Atom::from("icon"),
1178 Atom::from("import"),
1179 Atom::from("manifest"),
1180 Atom::from("modulepreload"),
1181 Atom::from("next"),
1182 Atom::from("preconnect"),
1183 Atom::from("prefetch"),
1184 Atom::from("preload"),
1185 Atom::from("prerender"),
1186 Atom::from("stylesheet"),
1187 ]),
1188 )
1189 })
1190 }
1191
1192 make_getter!(Charset, "charset");
1194
1195 make_setter!(SetCharset, "charset");
1197
1198 make_getter!(Rev, "rev");
1200
1201 make_setter!(SetRev, "rev");
1203
1204 make_getter!(Target, "target");
1206
1207 make_setter!(SetTarget, "target");
1209
1210 fn Blocking(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMTokenList> {
1212 self.blocking.or_init(|| {
1213 DOMTokenList::new(
1214 cx,
1215 self.upcast(),
1216 &local_name!("blocking"),
1217 Some(vec![Atom::from("render")]),
1218 )
1219 })
1220 }
1221
1222 fn GetCrossOrigin(&self) -> Option<DOMString> {
1224 reflect_cross_origin_attribute(self.upcast::<Element>())
1225 }
1226
1227 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1229 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1230 }
1231
1232 fn ReferrerPolicy(&self) -> DOMString {
1234 reflect_referrer_policy_attribute(self.upcast::<Element>())
1235 }
1236
1237 make_setter!(SetReferrerPolicy, "referrerpolicy");
1239
1240 fn GetSheet(&self, cx: &mut JSContext) -> Option<DomRoot<DOMStyleSheet>> {
1242 self.get_cssom_stylesheet(cx).map(DomRoot::upcast)
1243 }
1244}
1245
1246struct FaviconFetchContext {
1247 link: Trusted<HTMLLinkElement>,
1249 image_cache: std::sync::Arc<dyn ImageCache>,
1250 id: PendingImageId,
1251
1252 url: ServoUrl,
1254}
1255
1256impl FetchResponseListener for FaviconFetchContext {
1257 fn process_request_body(&mut self, _: RequestId) {}
1258
1259 fn process_response(
1260 &mut self,
1261 _: &mut js::context::JSContext,
1262 request_id: RequestId,
1263 metadata: Result<FetchMetadata, NetworkError>,
1264 ) {
1265 self.image_cache.notify_pending_response(
1266 self.id,
1267 FetchResponseMsg::ProcessResponse(request_id, metadata),
1268 );
1269 }
1270
1271 fn process_response_chunk(
1272 &mut self,
1273 _: &mut js::context::JSContext,
1274 request_id: RequestId,
1275 chunk: Vec<u8>,
1276 ) {
1277 self.image_cache.notify_pending_response(
1278 self.id,
1279 FetchResponseMsg::ProcessResponseChunk(request_id, chunk.into()),
1280 );
1281 }
1282
1283 fn process_response_eof(
1284 self,
1285 cx: &mut js::context::JSContext,
1286 request_id: RequestId,
1287 response: Result<(), NetworkError>,
1288 timing: ResourceFetchTiming,
1289 ) {
1290 self.image_cache.notify_pending_response(
1291 self.id,
1292 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
1293 );
1294 submit_timing(cx, &self, &response, &timing);
1295 }
1296
1297 fn process_csp_violations(
1298 &mut self,
1299 cx: &mut js::context::JSContext,
1300 _request_id: RequestId,
1301 violations: Vec<Violation>,
1302 ) {
1303 let global = &self.resource_timing_global();
1304 global.report_csp_violations(cx, violations, None, None);
1305 }
1306
1307 fn process_content_length(&mut self, request_id: RequestId, size: usize) {
1308 self.image_cache.notify_pending_response(
1309 self.id,
1310 FetchResponseMsg::ProcessContentLength(request_id, size),
1311 )
1312 }
1313}
1314
1315impl ResourceTimingListener for FaviconFetchContext {
1316 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1317 (
1318 InitiatorType::LocalName("link".to_string()),
1319 self.url.clone(),
1320 )
1321 }
1322
1323 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
1324 self.link.root().upcast::<Node>().owner_doc().global()
1325 }
1326}