1use std::collections::hash_map::Entry;
6use std::collections::{HashMap, HashSet};
7use std::default::Default;
8use std::hash::{Hash, Hasher};
9use std::iter;
10use std::sync::Arc;
11use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
12
13use app_units::Au;
14use atomic_refcell::AtomicRefCell;
15use content_security_policy::Violation;
16use fonts_traits::{
17 CSSFontFaceDescriptors, FontDescriptor, FontFaceRuleWithOrigin, FontIdentifier, FontTemplate,
18 FontTemplateRef, FontTemplateRefMethods, StylesheetWebFontLoadFinishedCallback,
19 WebFontLoadEvent, WebFontSetDifference,
20};
21use log::{debug, trace};
22use malloc_size_of::MallocSizeOf;
23use malloc_size_of_derive::MallocSizeOf;
24use net_traits::blob_url_store::UrlWithBlobClaim;
25use net_traits::policy_container::PolicyContainer;
26use net_traits::request::{
27 CredentialsMode, Destination, Referrer, RequestBuilder, RequestClient, RequestMode,
28 ServiceWorkersMode,
29};
30use net_traits::{
31 CoreResourceThread, FetchResponseMsg, ResourceFetchTiming, ResourceThreads, fetch_async,
32};
33use paint_api::CrossProcessPaintApi;
34use parking_lot::{Mutex, RwLock};
35use rustc_hash::{FxHashMap, FxHashSet};
36use servo_arc::Arc as ServoArc;
37use servo_base::id::{PainterId, WebViewId};
38use servo_config::pref;
39use servo_url::ServoUrl;
40use style::Atom;
41use style::computed_values::font_variant_caps::T as FontVariantCaps;
42use style::font_face::{
43 FontFaceSourceFormat, FontFaceSourceFormatKeyword, Source, SourceList, UrlSource,
44};
45use style::properties::generated::font_face::Descriptors as FontFaceRuleDescriptors;
46use style::properties::style_structs::Font as FontStyleStruct;
47use style::shared_lock::StylesheetGuards;
48use style::stylesheets::LockedFontFaceRule;
49use style::stylist::Stylist;
50use style::values::computed::FontVariantAlternates;
51use style::values::computed::font::{FamilyName, FontFamilyNameSyntax, SingleFontFamily};
52use style::values::specified::font::VariantAlternates;
53use url::Url;
54use uuid::Uuid;
55use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey, FontVariation};
56
57use crate::font::{Font, FontFamilyDescriptor, FontGroup, FontRef, FontSearchScope};
58use crate::font_feature_values::{
59 AlternateKindRequiringResolution, FontFeatureValue, FontFeatureValueMap,
60 ResolvedFontVariantAlternates,
61};
62use crate::font_store::{CrossThreadFontStore, FontStore};
63use crate::platform::font::PlatformFont;
64use crate::{FontData, LowercaseFontFamilyName, PlatformFontMethods, SystemFontServiceProxy};
65
66static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; #[derive(Eq, Hash, MallocSizeOf, PartialEq)]
69pub(crate) struct FontParameters {
70 pub(crate) font_key: FontKey,
71 pub(crate) pt_size: Au,
72 pub(crate) variations: Vec<FontVariation>,
73 pub(crate) flags: FontInstanceFlags,
74}
75
76pub type FontGroupRef = Arc<FontGroup>;
77
78#[derive(MallocSizeOf)]
83pub struct FontContext {
84 #[conditional_malloc_size_of]
85 system_font_service_proxy: Arc<SystemFontServiceProxy>,
86
87 resource_threads: Mutex<CoreResourceThread>,
88
89 paint_api: Mutex<CrossProcessPaintApi>,
91
92 fonts: RwLock<HashMap<FontCacheKey, Option<FontRef>>>,
95
96 #[conditional_malloc_size_of]
100 resolved_font_groups: RwLock<HashMap<FontGroupCacheKey, FontGroupRef>>,
101
102 web_fonts: CrossThreadFontStore,
103
104 webrender_font_keys: RwLock<HashMap<FontIdentifier, FontKey>>,
107
108 webrender_font_instance_keys: RwLock<HashMap<FontParameters, FontInstanceKey>>,
111
112 font_data: RwLock<HashMap<FontIdentifier, FontData>>,
115
116 have_removed_web_fonts: AtomicBool,
117
118 currently_downloading_fonts: Mutex<HashMap<ServoUrl, Vec<WebFontDownloadState>>>,
121
122 known_font_face_rules: Mutex<KnownFontFaceRules>,
126
127 font_feature_value_map: AtomicRefCell<Option<FontFeatureValueMap>>,
129
130 number_of_loading_web_fonts: AtomicUsize,
132}
133
134pub trait CspViolationHandler: Send + std::fmt::Debug + MallocSizeOf {
138 fn process_violations(&self, violations: Vec<Violation>);
139 fn clone(&self) -> Box<dyn CspViolationHandler>;
140}
141
142pub trait NetworkTimingHandler: Send + std::fmt::Debug + MallocSizeOf {
145 fn submit_timing(&self, url: ServoUrl, response: ResourceFetchTiming);
146 fn clone(&self) -> Box<dyn NetworkTimingHandler>;
147}
148
149#[derive(Debug, MallocSizeOf)]
151pub struct WebFontDocumentContext {
152 pub policy_container: PolicyContainer,
153 pub request_client: RequestClient,
154 pub document_url: ServoUrl,
155 pub csp_handler: Box<dyn CspViolationHandler>,
156 pub network_timing_handler: Box<dyn NetworkTimingHandler>,
157}
158
159impl Clone for WebFontDocumentContext {
160 fn clone(&self) -> WebFontDocumentContext {
161 Self {
162 policy_container: self.policy_container.clone(),
163 request_client: self.request_client.clone(),
164 document_url: self.document_url.clone(),
165 csp_handler: self.csp_handler.clone(),
166 network_timing_handler: self.network_timing_handler.clone(),
167 }
168 }
169}
170
171impl FontContext {
172 pub fn new(
173 system_font_service_proxy: Arc<SystemFontServiceProxy>,
174 paint_api: CrossProcessPaintApi,
175 resource_threads: ResourceThreads,
176 ) -> Self {
177 Self {
178 system_font_service_proxy,
179 resource_threads: Mutex::new(resource_threads.core_thread),
180 paint_api: Mutex::new(paint_api),
181 fonts: Default::default(),
182 resolved_font_groups: Default::default(),
183 web_fonts: Default::default(),
184 webrender_font_keys: RwLock::default(),
185 webrender_font_instance_keys: RwLock::default(),
186 have_removed_web_fonts: AtomicBool::new(false),
187 font_data: RwLock::default(),
188 currently_downloading_fonts: Default::default(),
189 known_font_face_rules: Default::default(),
190 font_feature_value_map: Default::default(),
191 number_of_loading_web_fonts: Default::default(),
192 }
193 }
194
195 pub fn web_fonts_still_loading(&self) -> usize {
196 self.number_of_loading_web_fonts.load(Ordering::SeqCst)
197 }
198
199 fn get_font_data(&self, identifier: &FontIdentifier) -> Option<FontData> {
200 match identifier {
201 FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => {
202 self.font_data.read().get(identifier).cloned()
203 },
204 FontIdentifier::Local(_) => None,
205 }
206 }
207
208 pub fn font_group(&self, style: ServoArc<FontStyleStruct>) -> FontGroupRef {
212 let font_size = style.font_size.computed_size().into();
213 self.font_group_with_size(style, font_size)
214 }
215
216 pub fn font_group_with_size(
219 &self,
220 style: ServoArc<FontStyleStruct>,
221 size: Au,
222 ) -> Arc<FontGroup> {
223 let cache_key = FontGroupCacheKey { size, style };
224 if let Some(font_group) = self.resolved_font_groups.read().get(&cache_key) {
225 return font_group.clone();
226 }
227
228 let mut descriptor = FontDescriptor::from(&*cache_key.style);
229 descriptor.pt_size = size;
230
231 let font_group = Arc::new(FontGroup::new(&cache_key.style, descriptor));
232 self.resolved_font_groups
233 .write()
234 .insert(cache_key, font_group.clone());
235 font_group
236 }
237
238 pub fn font(
241 &self,
242 font_template: FontTemplateRef,
243 font_descriptor: &FontDescriptor,
244 ) -> Option<FontRef> {
245 let font_descriptor = if servo_config::pref!(layout_variable_fonts_enabled) {
246 let variation_settings = font_template.borrow().compute_variations(font_descriptor);
247 &font_descriptor.with_variation_settings(variation_settings)
248 } else {
249 font_descriptor
250 };
251
252 self.get_font_maybe_synthesizing_small_caps(
253 font_template,
254 font_descriptor,
255 true, )
257 }
258
259 fn get_font_maybe_synthesizing_small_caps(
260 &self,
261 font_template: FontTemplateRef,
262 font_descriptor: &FontDescriptor,
263 synthesize_small_caps: bool,
264 ) -> Option<FontRef> {
265 let synthesized_small_caps_font =
269 if font_descriptor.variant == FontVariantCaps::SmallCaps && synthesize_small_caps {
270 let mut small_caps_descriptor = font_descriptor.clone();
271 small_caps_descriptor.pt_size =
272 font_descriptor.pt_size.scale_by(SMALL_CAPS_SCALE_FACTOR);
273 self.get_font_maybe_synthesizing_small_caps(
274 font_template.clone(),
275 &small_caps_descriptor,
276 false, )
278 } else {
279 None
280 };
281
282 let cache_key = FontCacheKey {
283 font_identifier: font_template.identifier().to_owned(),
284 font_descriptor: font_descriptor.clone(),
285 };
286
287 if let Some(font) = self.fonts.read().get(&cache_key).cloned() {
288 return font;
289 }
290
291 debug!(
292 "FontContext::font cache miss for font_template={:?} font_descriptor={:?}",
293 font_template, font_descriptor
294 );
295
296 let mut fonts = self.fonts.write();
301 if let Some(font) = fonts.get(&cache_key).cloned() {
302 return font;
303 }
304
305 let font = self
308 .create_font(
309 font_template,
310 font_descriptor.to_owned(),
311 synthesized_small_caps_font,
312 )
313 .ok();
314 fonts.insert(cache_key, font.clone());
315 font
316 }
317
318 fn matching_web_font_templates(
319 &self,
320 descriptor_to_match: &FontDescriptor,
321 family_descriptor: &FontFamilyDescriptor,
322 ) -> Option<Vec<FontTemplateRef>> {
323 if family_descriptor.scope != FontSearchScope::Any {
324 return None;
325 }
326
327 let SingleFontFamily::FamilyName(ref family_name) = family_descriptor.family else {
329 return None;
330 };
331
332 self.web_fonts
333 .read()
334 .families
335 .get(&family_name.name.clone().into())
336 .map(|templates| templates.find_for_descriptor(Some(descriptor_to_match)))
337 }
338
339 pub fn matching_templates(
342 &self,
343 descriptor_to_match: &FontDescriptor,
344 family_descriptor: &FontFamilyDescriptor,
345 ) -> Vec<FontTemplateRef> {
346 self.matching_web_font_templates(descriptor_to_match, family_descriptor)
347 .unwrap_or_else(|| {
348 self.system_font_service_proxy.find_matching_font_templates(
349 Some(descriptor_to_match),
350 &family_descriptor.family,
351 )
352 })
353 }
354
355 #[servo_tracing::instrument(skip_all)]
358 fn create_font(
359 &self,
360 font_template: FontTemplateRef,
361 font_descriptor: FontDescriptor,
362 synthesized_small_caps: Option<FontRef>,
363 ) -> Result<FontRef, &'static str> {
364 Ok(FontRef(Arc::new(Font::new(
365 font_template.clone(),
366 font_descriptor,
367 self.get_font_data(&font_template.identifier()),
368 synthesized_small_caps,
369 )?)))
370 }
371
372 pub(crate) fn create_font_instance_key(
373 &self,
374 font: &Font,
375 painter_id: PainterId,
376 ) -> FontInstanceKey {
377 let font_template_identifier = font.template.identifier();
378 match &*font_template_identifier {
379 FontIdentifier::Local(_) => self.system_font_service_proxy.get_system_font_instance(
380 font.template.identifier().to_owned(),
381 font.descriptor.pt_size,
382 font.webrender_font_instance_flags(),
383 font.variations().to_owned(),
384 painter_id,
385 ),
386 FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => self
387 .create_web_font_instance(
388 font.template.clone(),
389 font.descriptor.pt_size,
390 font.webrender_font_instance_flags(),
391 font.variations().to_owned(),
392 painter_id,
393 ),
394 }
395 }
396
397 fn create_web_font_instance(
398 &self,
399 font_template: FontTemplateRef,
400 pt_size: Au,
401 flags: FontInstanceFlags,
402 variations: Vec<FontVariation>,
403 painter_id: PainterId,
404 ) -> FontInstanceKey {
405 let identifier = font_template.identifier();
406 let font_data = self
407 .get_font_data(&identifier)
408 .expect("Web font should have associated font data");
409 let font_key = *self
410 .webrender_font_keys
411 .write()
412 .entry(identifier.clone())
413 .or_insert_with(|| {
414 let font_key = self.system_font_service_proxy.generate_font_key(painter_id);
415 self.paint_api.lock().add_font(
416 font_key,
417 font_data.as_ipc_shared_memory(),
418 identifier.index(),
419 );
420 font_key
421 });
422
423 let entry_key = FontParameters {
424 font_key,
425 pt_size,
426 variations: variations.clone(),
427 flags,
428 };
429 *self
430 .webrender_font_instance_keys
431 .write()
432 .entry(entry_key)
433 .or_insert_with(|| {
434 let font_instance_key = self
435 .system_font_service_proxy
436 .generate_font_instance_key(painter_id);
437 self.paint_api.lock().add_font_instance(
438 font_instance_key,
439 font_key,
440 pt_size.to_f32_px(),
441 flags,
442 variations,
443 );
444 font_instance_key
445 })
446 }
447
448 fn invalidate_font_groups_after_web_font_load(&self) {
449 self.resolved_font_groups.write().clear();
450 }
451
452 pub fn is_supported_web_font_source(source: &&Source) -> bool {
453 let url_source = match &source {
454 Source::Url(url_source) => url_source,
455 Source::Local(_) => return true,
456 };
457 let format_hint = match url_source.format_hint {
458 Some(ref format_hint) => format_hint,
459 None => return true,
460 };
461
462 if matches!(
463 format_hint,
464 FontFaceSourceFormat::Keyword(
465 FontFaceSourceFormatKeyword::Truetype |
466 FontFaceSourceFormatKeyword::Opentype |
467 FontFaceSourceFormatKeyword::Woff |
468 FontFaceSourceFormatKeyword::Woff2
469 )
470 ) {
471 return true;
472 }
473
474 if let FontFaceSourceFormat::String(string) = format_hint {
475 if string == "truetype" || string == "opentype" || string == "woff" || string == "woff2"
476 {
477 return true;
478 }
479
480 return pref!(layout_variable_fonts_enabled) &&
481 (string == "truetype-variations" ||
482 string == "opentype-variations" ||
483 string == "woff-variations" ||
484 string == "woff2-variations");
485 }
486
487 false
488 }
489
490 fn is_local_or_unknown_url_font(
491 &self,
492 family_name: &LowercaseFontFamilyName,
493 source: &Source,
494 ) -> bool {
495 match source {
496 Source::Url(url) => !url
497 .url
498 .url()
499 .cloned()
500 .map(ServoUrl::from)
501 .map(FontIdentifier::Web)
502 .filter(|font_identifier| self.font_data.read().contains_key(font_identifier))
503 .is_some_and(|font_identifier| {
504 self.web_fonts
505 .read()
506 .families
507 .get(family_name)
508 .is_some_and(|templates| {
509 templates
510 .templates
511 .iter()
512 .any(|template| template.borrow().identifier == font_identifier)
513 })
514 }),
515 Source::Local(_) => true,
516 }
517 }
518
519 pub(crate) fn handle_web_font_request_started(
524 &self,
525 url: ServoUrl,
526 state: WebFontDownloadState,
527 ) -> bool {
528 let mut downloading_fonts = self.currently_downloading_fonts.lock();
529 let entry = downloading_fonts.entry(url);
530
531 let needs_new_fetch_request = matches!(entry, Entry::Vacant(_));
533
534 entry.or_default().push(state);
535
536 needs_new_fetch_request
537 }
538
539 pub(crate) fn handle_web_font_request_succeeded(
545 &self,
546 font_data: FontData,
547 url: ServoUrl,
548 ) -> bool {
549 let Some(download_states) = self.currently_downloading_fonts.lock().remove(&url) else {
550 return false;
552 };
553 debug_assert!(
554 !download_states.is_empty(),
555 "Should have removed this entry"
556 );
557
558 let identifier = FontIdentifier::Web(url);
559 let Ok(handle) =
560 PlatformFont::new_from_data(identifier.clone(), &font_data, None, &[], false)
561 else {
562 return false;
563 };
564
565 self.font_data.write().insert(identifier.clone(), font_data);
566 let descriptor = handle.descriptor();
567 for download_state in download_states {
568 let mut descriptor = descriptor.clone();
569 descriptor.override_values_with_css_font_template_descriptors(
570 &download_state.css_font_face_descriptors,
571 );
572
573 let new_template = FontTemplate::new(
574 identifier.clone(),
575 descriptor,
576 download_state.initiator.font_face_rule().cloned(),
577 );
578
579 download_state.handle_web_font_load_success(new_template);
580 }
581
582 true
583 }
584
585 pub fn decrement_count_of_loading_fonts_by_one(&self) {
587 self.number_of_loading_web_fonts
588 .fetch_sub(1, Ordering::SeqCst);
589 }
590
591 pub(crate) fn is_font_face_rule_active(
595 &self,
596 target_rule: &ServoArc<LockedFontFaceRule>,
597 ) -> bool {
598 self.known_font_face_rules
599 .lock()
600 .contents
601 .values()
602 .flat_map(|bucket| bucket.iter())
603 .any(|known_rule| ServoArc::ptr_eq(&known_rule.rule_with_origin.rule, target_rule))
604 }
605}
606
607#[derive(MallocSizeOf)]
610pub(crate) struct WebFontDownloadState {
611 webview_id: Option<WebViewId>,
612 css_font_face_descriptors: CSSFontFaceDescriptors,
613 remaining_sources: Vec<Source>,
614 local_fonts: FxHashMap<Atom, Option<FontTemplateRef>>,
615 #[conditional_malloc_size_of]
616 pub(crate) font_context: Arc<FontContext>,
617 initiator: WebFontLoadInitiator,
618 document_context: WebFontDocumentContext,
619}
620
621impl WebFontDownloadState {
622 fn new(
623 webview_id: Option<WebViewId>,
624 font_context: Arc<FontContext>,
625 css_font_face_descriptors: CSSFontFaceDescriptors,
626 initiator: WebFontLoadInitiator,
627 sources: Vec<Source>,
628 local_fonts: FxHashMap<Atom, Option<FontTemplateRef>>,
629 document_context: WebFontDocumentContext,
630 ) -> WebFontDownloadState {
631 WebFontDownloadState {
632 webview_id,
633 css_font_face_descriptors,
634 remaining_sources: sources,
635 local_fonts,
636 font_context,
637 initiator,
638 document_context,
639 }
640 }
641
642 pub(crate) fn handle_web_font_load_success(self, new_template: FontTemplate) {
643 let family_name = self.css_font_face_descriptors.family_name.clone();
644 match self.initiator {
645 WebFontLoadInitiator::Stylesheet(initiator) => {
646 if !self
647 .font_context
648 .is_font_face_rule_active(&initiator.created_by)
649 {
650 if self
652 .font_context
653 .number_of_loading_web_fonts
654 .fetch_sub(1, Ordering::SeqCst) ==
655 1
656 {
657 (initiator.callback)(WebFontLoadEvent::UnblockedFontReadyPromise);
660 }
661 return;
662 }
663
664 self.font_context
665 .web_fonts
666 .write()
667 .add_new_template(family_name, new_template);
668 self.font_context
669 .invalidate_font_groups_after_web_font_load();
670
671 (initiator.callback)(WebFontLoadEvent::LoadedSuccessfully);
675 },
676 WebFontLoadInitiator::Script(callback) => {
677 self.font_context.decrement_count_of_loading_fonts_by_one();
678 callback(family_name, Some(new_template));
679 },
680 }
681 }
682
683 pub(crate) fn handle_web_font_load_failure(self) {
685 let family_name = self.css_font_face_descriptors.family_name.clone();
686 match self.initiator {
687 WebFontLoadInitiator::Stylesheet(initiator) => {
688 if self
689 .font_context
690 .number_of_loading_web_fonts
691 .fetch_sub(1, Ordering::SeqCst) ==
692 1
693 {
694 (initiator.callback)(WebFontLoadEvent::UnblockedFontReadyPromise);
697 }
698 },
699 WebFontLoadInitiator::Script(callback) => {
700 self.font_context.decrement_count_of_loading_fonts_by_one();
701 callback(family_name, None);
702 },
703 }
704 }
705}
706
707pub trait FontContextWebFontMethods {
708 fn rebuild_font_face_set(
709 &self,
710 webview_id: WebViewId,
711 stylist: &Stylist,
712 guards: &StylesheetGuards<'_>,
713 callback: StylesheetWebFontLoadFinishedCallback,
714 document_context: &WebFontDocumentContext,
715 ) -> WebFontSetDifference;
716 fn load_single_font_face_rule(
717 &self,
718 webview_id: WebViewId,
719 locked_font_face_rule: &FontFaceRuleWithOrigin,
720 guards: &StylesheetGuards<'_>,
721 callback: StylesheetWebFontLoadFinishedCallback,
722 document_context: &WebFontDocumentContext,
723 );
724 fn load_web_font_for_script(
725 &self,
726 webview_id: Option<WebViewId>,
727 sources: SourceList,
728 descriptors: CSSFontFaceDescriptors,
729 finished_callback: ScriptWebFontLoadFinishedCallback,
730 document_context: &WebFontDocumentContext,
731 );
732 fn handle_web_font_request_failed(&self, url: ServoUrl);
733}
734
735impl FontContextWebFontMethods for Arc<FontContext> {
736 fn load_single_font_face_rule(
737 &self,
738 webview_id: WebViewId,
739 locked_font_face_rule: &FontFaceRuleWithOrigin,
740 guards: &StylesheetGuards<'_>,
741 callback: StylesheetWebFontLoadFinishedCallback,
742 document_context: &WebFontDocumentContext,
743 ) {
744 let font_face_rule = locked_font_face_rule.read_with(guards);
745 let Some(ref sources) = font_face_rule.descriptors.src else {
746 return;
747 };
748
749 let css_font_face_descriptors = font_face_rule.into();
750
751 let initiator = FontFaceRuleInitiator {
752 created_by: locked_font_face_rule.rule.clone(),
753 font_face_rule: font_face_rule.descriptors.clone(),
754 callback: callback.clone(),
755 };
756
757 self.start_loading_one_web_font(
758 Some(webview_id),
759 sources,
760 css_font_face_descriptors,
761 WebFontLoadInitiator::Stylesheet(Box::new(initiator)),
762 document_context,
763 );
764 }
765 fn rebuild_font_face_set(
766 &self,
767 webview_id: WebViewId,
768 stylist: &Stylist,
769 guards: &StylesheetGuards<'_>,
770 callback: StylesheetWebFontLoadFinishedCallback,
771 document_context: &WebFontDocumentContext,
772 ) -> WebFontSetDifference {
773 let difference = self
774 .known_font_face_rules
775 .lock()
776 .diff_old_and_new_font_face_rules(stylist, guards);
777
778 for added_rule in &difference.added_font_faces {
779 self.load_single_font_face_rule(
780 webview_id,
781 added_rule,
782 guards,
783 callback.clone(),
784 document_context,
785 );
786 }
787 for removed_rule in &difference.removed_font_faces {
788 let removed_rule = removed_rule.read_with(guards);
789 self.remove_single_font_face_rule(
790 &removed_rule.descriptors,
791 &mut self.web_fonts.write(),
792 );
793 }
794
795 if !difference.removed_font_faces.is_empty() {
796 self.resolved_font_groups.write().clear();
798
799 self.have_removed_web_fonts.store(true, Ordering::Relaxed);
801 }
802
803 difference
804 }
805
806 fn load_web_font_for_script(
807 &self,
808 webview_id: Option<WebViewId>,
809 sources: SourceList,
810 descriptors: CSSFontFaceDescriptors,
811 finished_callback: ScriptWebFontLoadFinishedCallback,
812 document_context: &WebFontDocumentContext,
813 ) {
814 let completion_handler = WebFontLoadInitiator::Script(finished_callback);
815 self.start_loading_one_web_font(
816 webview_id,
817 &sources,
818 descriptors,
819 completion_handler,
820 document_context,
821 );
822 }
823
824 fn handle_web_font_request_failed(&self, url: ServoUrl) {
826 let Some(subscribers) = self.currently_downloading_fonts.lock().remove(&url) else {
827 return;
828 };
829
830 for subscriber in subscribers {
831 if let WebFontLoadInitiator::Stylesheet(stylesheet_initiator) = &subscriber.initiator &&
833 !self.is_font_face_rule_active(&stylesheet_initiator.created_by)
834 {
835 if self
837 .number_of_loading_web_fonts
838 .fetch_sub(1, Ordering::SeqCst) ==
839 1
840 {
841 (stylesheet_initiator.callback)(WebFontLoadEvent::UnblockedFontReadyPromise);
844 }
845 return;
846 }
847
848 self.process_next_web_font_source(subscriber);
849 }
850 }
851}
852
853impl FontContext {
854 pub fn collect_unused_webrender_resources(
855 &self,
856 all: bool,
857 ) -> (Vec<FontKey>, Vec<FontInstanceKey>) {
858 if all {
859 let mut webrender_font_keys = self.webrender_font_keys.write();
860 let mut webrender_font_instance_keys = self.webrender_font_instance_keys.write();
861 self.have_removed_web_fonts.store(false, Ordering::Relaxed);
862 return (
863 webrender_font_keys.drain().map(|(_, key)| key).collect(),
864 webrender_font_instance_keys
865 .drain()
866 .map(|(_, key)| key)
867 .collect(),
868 );
869 }
870
871 if !self.have_removed_web_fonts.load(Ordering::Relaxed) {
872 return (Vec::new(), Vec::new());
873 }
874
875 let web_fonts = self.web_fonts.write();
877 let mut font_data = self.font_data.write();
878 let _fonts = self.fonts.write();
879 let _font_groups = self.resolved_font_groups.write();
880 let mut webrender_font_keys = self.webrender_font_keys.write();
881 let mut webrender_font_instance_keys = self.webrender_font_instance_keys.write();
882
883 let mut unused_identifiers: HashSet<FontIdentifier> =
884 webrender_font_keys.keys().cloned().collect();
885 for templates in web_fonts.families.values() {
886 templates.for_all_identifiers(|identifier| {
887 unused_identifiers.remove(identifier);
888 });
889 }
890
891 font_data.retain(|font_identifier, _| !unused_identifiers.contains(font_identifier));
892
893 self.have_removed_web_fonts.store(false, Ordering::Relaxed);
894
895 let mut removed_keys: FxHashSet<FontKey> = FxHashSet::default();
896 webrender_font_keys.retain(|identifier, font_key| {
897 if unused_identifiers.contains(identifier) {
898 removed_keys.insert(*font_key);
899 false
900 } else {
901 true
902 }
903 });
904
905 let mut removed_instance_keys: HashSet<FontInstanceKey> = HashSet::new();
906 webrender_font_instance_keys.retain(|font_param, instance_key| {
907 if removed_keys.contains(&font_param.font_key) {
908 removed_instance_keys.insert(*instance_key);
909 false
910 } else {
911 true
912 }
913 });
914
915 (
916 removed_keys.into_iter().collect(),
917 removed_instance_keys.into_iter().collect(),
918 )
919 }
920
921 fn remove_single_font_face_rule(
923 &self,
924 font_face_rule: &FontFaceRuleDescriptors,
925 font_store: &mut FontStore,
926 ) -> bool {
927 let Some(family) = font_face_rule.font_family.as_ref() else {
928 return false;
929 };
930
931 let lowercase_family_name: LowercaseFontFamilyName = family.name.clone().into();
932 let Some(known_family) = font_store.families.get_mut(&lowercase_family_name) else {
933 return false;
934 };
935 if !known_family.remove_template_for_font_face_rule(font_face_rule) {
936 return false;
937 }
938 self.fonts.write().retain(|_, font| match font {
939 Some(font) => !font
940 .template
941 .borrow()
942 .is_defined_by_font_face_rule(font_face_rule),
943 _ => true,
944 });
945
946 true
947 }
948
949 pub fn add_template_to_font_context(
950 &self,
951 family_name: LowercaseFontFamilyName,
952 new_template: FontTemplate,
953 ) {
954 self.web_fonts
955 .write()
956 .add_new_template(family_name, new_template);
957 self.invalidate_font_groups_after_web_font_load();
958 }
959
960 pub fn construct_web_font_from_data(
961 &self,
962 data: &[u8],
963 descriptors: CSSFontFaceDescriptors,
964 ) -> Option<(LowercaseFontFamilyName, FontTemplate)> {
965 let bytes = fontsan::process(data)
966 .inspect_err(|error| {
967 debug!(
968 "Sanitiser rejected FontFace font: family={} with {error:?}",
969 descriptors.family_name,
970 );
971 })
972 .ok()?;
973 let font_data = FontData::from_bytes(&bytes);
974
975 let identifier = FontIdentifier::ArrayBuffer(Uuid::new_v4());
976 let handle =
977 PlatformFont::new_from_data(identifier.clone(), &font_data, None, &[], false).ok()?;
978
979 let new_template = FontTemplate::new(identifier.clone(), handle.descriptor(), None);
980
981 self.font_data.write().insert(identifier, font_data);
982
983 Some((descriptors.family_name, new_template))
984 }
985
986 fn start_loading_one_web_font(
987 self: &Arc<FontContext>,
988 webview_id: Option<WebViewId>,
989 source_list: &SourceList,
990 css_font_face_descriptors: CSSFontFaceDescriptors,
991 completion_handler: WebFontLoadInitiator,
992 document_context: &WebFontDocumentContext,
993 ) {
994 self.number_of_loading_web_fonts
995 .fetch_add(1, Ordering::SeqCst);
996
997 let sources: Vec<Source> = source_list
998 .0
999 .iter()
1000 .rev()
1001 .filter(Self::is_supported_web_font_source)
1002 .filter(|source| {
1003 self.is_local_or_unknown_url_font(&css_font_face_descriptors.family_name, source)
1004 })
1005 .cloned()
1006 .collect();
1007
1008 let sources_transformed = sources
1016 .iter()
1017 .filter_map(|source| {
1018 if let Source::Local(family_name) = source {
1019 Some(family_name)
1020 } else {
1021 None
1022 }
1023 })
1024 .map(|family_name| {
1025 let family = SingleFontFamily::FamilyName(FamilyName {
1026 name: family_name.name.clone(),
1027 syntax: FontFamilyNameSyntax::Quoted,
1028 });
1029 let matching_font_templates = self
1030 .system_font_service_proxy
1031 .find_matching_font_templates(None, &family);
1032 let value = matching_font_templates.first();
1033 (family_name.name.clone(), value.cloned())
1034 });
1035
1036 let local_fonts = FxHashMap::from_iter(sources_transformed);
1037
1038 self.process_next_web_font_source(WebFontDownloadState::new(
1039 webview_id,
1040 self.clone(),
1041 css_font_face_descriptors,
1042 completion_handler,
1043 sources,
1044 local_fonts,
1045 document_context.clone(),
1046 ));
1047 }
1048
1049 pub(crate) fn process_next_web_font_source(
1050 self: &Arc<FontContext>,
1051 mut state: WebFontDownloadState,
1052 ) {
1053 let Some(source) = state.remaining_sources.pop() else {
1054 state.handle_web_font_load_failure();
1055 return;
1056 };
1057
1058 let this = self.clone();
1059 let web_font_family_name = state.css_font_face_descriptors.family_name.clone();
1060 match source {
1061 Source::Url(url_source) => {
1062 RemoteWebFontDownloader::download(url_source, this, web_font_family_name, state)
1063 },
1064 Source::Local(ref local_family_name) => {
1065 if let Some(new_template) = state
1066 .local_fonts
1067 .get(&local_family_name.name)
1068 .cloned()
1069 .flatten()
1070 .and_then(|local_template| {
1071 let template = FontTemplate::new_for_local_web_font(
1072 local_template,
1073 &state.css_font_face_descriptors,
1074 state.initiator.font_face_rule().cloned(),
1075 )
1076 .ok()?;
1077 Some(template)
1078 })
1079 {
1080 state.handle_web_font_load_success(new_template);
1081 } else {
1082 this.process_next_web_font_source(state);
1083 }
1084 },
1085 }
1086 }
1087
1088 pub fn resolve_font_variant_alternate_identifiers_for(
1090 &self,
1091 font: &FontRef,
1092 alternates: &FontVariantAlternates,
1093 stylist: &Stylist,
1094 ) -> ResolvedFontVariantAlternates {
1095 let mut resolved_alternates = ResolvedFontVariantAlternates::default();
1096 if alternates.is_empty() {
1097 return resolved_alternates;
1098 }
1099 let Some(family_name) = font.family_name() else {
1100 return resolved_alternates;
1101 };
1102
1103 for alternate in alternates.iter() {
1104 match alternate {
1105 VariantAlternates::Stylistic(stylistic) => {
1106 let Some(FontFeatureValue::Single(value)) = self
1107 .look_up_font_feature_alternate_name(
1108 family_name.clone(),
1109 AlternateKindRequiringResolution::Stylistic,
1110 stylistic.0.clone(),
1111 stylist,
1112 )
1113 else {
1114 continue;
1115 };
1116
1117 resolved_alternates.stylistic = Some(value);
1118 },
1119 VariantAlternates::Styleset(styleset_list) => {
1120 for styleset in styleset_list.iter() {
1121 let Some(FontFeatureValue::Vector(value)) = self
1122 .look_up_font_feature_alternate_name(
1123 family_name.clone(),
1124 AlternateKindRequiringResolution::Styleset,
1125 styleset.0.clone(),
1126 stylist,
1127 )
1128 else {
1129 continue;
1130 };
1131
1132 resolved_alternates.styleset.extend(value.0.iter());
1133 }
1134 },
1135 VariantAlternates::CharacterVariant(character_variant_list) => {
1136 for character_variant in character_variant_list.iter() {
1137 let Some(FontFeatureValue::Pair(value)) = self
1138 .look_up_font_feature_alternate_name(
1139 family_name.clone(),
1140 AlternateKindRequiringResolution::CharacterVariant,
1141 character_variant.0.clone(),
1142 stylist,
1143 )
1144 else {
1145 continue;
1146 };
1147
1148 resolved_alternates.character_variant.push(value);
1149 }
1150 },
1151 VariantAlternates::Swash(swash) => {
1152 let Some(FontFeatureValue::Single(value)) = self
1153 .look_up_font_feature_alternate_name(
1154 family_name.clone(),
1155 AlternateKindRequiringResolution::Swash,
1156 swash.0.clone(),
1157 stylist,
1158 )
1159 else {
1160 continue;
1161 };
1162
1163 resolved_alternates.swash = Some(value);
1164 },
1165 VariantAlternates::Ornaments(ornaments) => {
1166 let Some(FontFeatureValue::Single(value)) = self
1167 .look_up_font_feature_alternate_name(
1168 family_name.clone(),
1169 AlternateKindRequiringResolution::Ornaments,
1170 ornaments.0.clone(),
1171 stylist,
1172 )
1173 else {
1174 continue;
1175 };
1176
1177 resolved_alternates.ornaments = Some(value);
1178 },
1179 VariantAlternates::Annotation(annotation) => {
1180 let Some(FontFeatureValue::Single(value)) = self
1181 .look_up_font_feature_alternate_name(
1182 family_name.clone(),
1183 AlternateKindRequiringResolution::Annotation,
1184 annotation.0.clone(),
1185 stylist,
1186 )
1187 else {
1188 continue;
1189 };
1190
1191 resolved_alternates.annotation = Some(value);
1192 },
1193 VariantAlternates::HistoricalForms => {
1194 resolved_alternates.historical_forms = true;
1195 },
1196 }
1197 }
1198
1199 resolved_alternates
1200 }
1201
1202 fn look_up_font_feature_alternate_name(
1208 &self,
1209 family_name: Atom,
1210 kind: AlternateKindRequiringResolution,
1211 name: Atom,
1212 stylist: &Stylist,
1213 ) -> Option<FontFeatureValue> {
1214 let read_guard = self.font_feature_value_map.borrow();
1216 if let Some(map) = &*read_guard {
1217 map.lookup(family_name, kind, name)
1219 } else {
1220 drop(read_guard);
1222 let mut write_guard = self.font_feature_value_map.borrow_mut();
1223 if let Some(map) = &*write_guard {
1224 return map.lookup(family_name, kind, name);
1227 }
1228
1229 log::debug!("Initializing @font-feature-values map");
1230 let mut map = FontFeatureValueMap::default();
1231 stylist
1232 .iter_extra_data_origins_rev()
1233 .flat_map(|(extra_data, _)| extra_data.font_feature_values.iter())
1234 .for_each(|(rule, _)| map.add_rule(rule));
1235 let map = &*write_guard.insert(map);
1236 map.lookup(family_name, kind, name)
1238 }
1239 }
1240
1241 pub fn invalidate_font_feature_values_map(&self) {
1242 self.font_feature_value_map.borrow_mut().take();
1243 }
1244}
1245
1246pub(crate) type ScriptWebFontLoadFinishedCallback =
1247 Box<dyn FnOnce(LowercaseFontFamilyName, Option<FontTemplate>) + Send>;
1248
1249#[derive(MallocSizeOf)]
1250pub(crate) struct FontFaceRuleInitiator {
1251 #[conditional_malloc_size_of]
1257 created_by: ServoArc<LockedFontFaceRule>,
1258 font_face_rule: FontFaceRuleDescriptors,
1259 #[ignore_malloc_size_of = "dyn Fn"]
1260 callback: StylesheetWebFontLoadFinishedCallback,
1261}
1262
1263#[derive(MallocSizeOf)]
1264pub(crate) enum WebFontLoadInitiator {
1265 Stylesheet(Box<FontFaceRuleInitiator>),
1266 Script(#[ignore_malloc_size_of = "dyn Fn"] ScriptWebFontLoadFinishedCallback),
1267}
1268
1269impl WebFontLoadInitiator {
1270 pub(crate) fn font_face_rule(&self) -> Option<&FontFaceRuleDescriptors> {
1271 match self {
1272 Self::Stylesheet(initiator) => Some(&initiator.font_face_rule),
1273 Self::Script(_) => None,
1274 }
1275 }
1276}
1277
1278struct RemoteWebFontDownloader {
1279 url: ServoArc<Url>,
1281 web_font_family_name: LowercaseFontFamilyName,
1282 response_valid: bool,
1283 response_data: Vec<u8>,
1285 document_context: WebFontDocumentContext,
1286 font_context: Arc<FontContext>,
1287}
1288
1289enum DownloaderResponseResult {
1290 InProcess,
1291 Finished,
1292 Failure,
1293}
1294
1295impl RemoteWebFontDownloader {
1296 fn download(
1297 url_source: UrlSource,
1298 font_context: Arc<FontContext>,
1299 web_font_family_name: LowercaseFontFamilyName,
1300 state: WebFontDownloadState,
1301 ) {
1302 let url = match url_source.url.url() {
1304 Some(url) => url.clone(),
1305 None => return,
1306 };
1307
1308 let webview_id = state.webview_id;
1309 let document_context = state.document_context.clone();
1310 if !font_context.handle_web_font_request_started(url.clone().into(), state) {
1311 return;
1314 }
1315
1316 let request = RequestBuilder::new(
1317 webview_id,
1318 UrlWithBlobClaim::from_url_without_having_claimed_blob(url.clone().into()),
1319 Referrer::ReferrerUrl(document_context.document_url.clone()),
1320 )
1321 .destination(Destination::Font)
1322 .mode(RequestMode::CorsMode)
1323 .credentials_mode(CredentialsMode::CredentialsSameOrigin)
1324 .service_workers_mode(ServiceWorkersMode::All)
1325 .policy_container(document_context.policy_container.clone())
1326 .client(document_context.request_client.clone());
1327
1328 let core_resource_thread_clone = font_context.resource_threads.lock().clone();
1329
1330 debug!("Loading @font-face {} from {}", web_font_family_name, url);
1331 let mut downloader = Self {
1332 url,
1333 web_font_family_name,
1334 response_valid: false,
1335 response_data: Vec::new(),
1336 document_context,
1337 font_context: font_context.clone(),
1338 };
1339
1340 fetch_async(
1341 &core_resource_thread_clone,
1342 request,
1343 None,
1344 Box::new(move |response_message| {
1345 match downloader.handle_web_font_fetch_message(response_message) {
1346 DownloaderResponseResult::InProcess => {},
1347 DownloaderResponseResult::Finished => {
1348 downloader.process_downloaded_font_and_signal_completion()
1349 },
1350 DownloaderResponseResult::Failure => {
1351 font_context.handle_web_font_request_failed(downloader.url.clone().into());
1352 },
1353 }
1354 }),
1355 )
1356 }
1357
1358 fn process_downloaded_font_and_signal_completion(&mut self) {
1361 let font_data = std::mem::take(&mut self.response_data);
1362 trace!(
1363 "Downloaded @font-face {} ({} bytes)",
1364 self.web_font_family_name,
1365 font_data.len()
1366 );
1367
1368 let font_data = match fontsan::process(&font_data) {
1369 Ok(bytes) => FontData::from_bytes(&bytes),
1370 Err(error) => {
1371 debug!(
1372 "Sanitiser rejected web font url={:?} with {error:?}",
1373 self.url.as_str(),
1374 );
1375 return self
1376 .font_context
1377 .handle_web_font_request_failed(self.url.clone().into());
1378 },
1379 };
1380
1381 let url: ServoUrl = self.url.clone().into();
1382 self.font_context
1383 .handle_web_font_request_succeeded(font_data, url);
1384 }
1385
1386 fn handle_web_font_fetch_message(
1387 &mut self,
1388 response_message: FetchResponseMsg,
1389 ) -> DownloaderResponseResult {
1390 match response_message {
1391 FetchResponseMsg::ProcessRequestBody(..) => DownloaderResponseResult::InProcess,
1392 FetchResponseMsg::ProcessCspViolations(_request_id, violations) => {
1393 self.document_context
1394 .csp_handler
1395 .process_violations(violations);
1396 DownloaderResponseResult::InProcess
1397 },
1398 FetchResponseMsg::ProcessResponse(_, meta_result) => {
1399 trace!(
1400 "@font-face {} metadata ok={:?}",
1401 self.web_font_family_name,
1402 meta_result.is_ok()
1403 );
1404 self.response_valid = meta_result.is_ok();
1405 DownloaderResponseResult::InProcess
1406 },
1407 FetchResponseMsg::ProcessResponseChunk(_, new_bytes) => {
1408 trace!(
1409 "@font-face {} chunk={:?}",
1410 self.web_font_family_name, new_bytes
1411 );
1412 if self.response_valid {
1413 self.response_data.extend(new_bytes.0)
1414 }
1415 DownloaderResponseResult::InProcess
1416 },
1417 FetchResponseMsg::ProcessResponseEOF(_, response, timing) => {
1418 trace!(
1419 "@font-face {} EOF={:?}",
1420 self.web_font_family_name, response
1421 );
1422 if response.is_err() || !self.response_valid {
1423 return DownloaderResponseResult::Failure;
1424 }
1425 self.document_context
1426 .network_timing_handler
1427 .submit_timing(ServoUrl::from_url(self.url.as_ref().clone()), timing);
1428 DownloaderResponseResult::Finished
1429 },
1430 FetchResponseMsg::ProcessContentLength(_request_id, size) => {
1431 self.response_data.reserve(size - self.response_data.len());
1432 DownloaderResponseResult::InProcess
1433 },
1434 }
1435 }
1436}
1437
1438#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)]
1439struct FontCacheKey {
1440 font_identifier: FontIdentifier,
1441 font_descriptor: FontDescriptor,
1442}
1443
1444#[derive(Debug, MallocSizeOf)]
1445struct FontGroupCacheKey {
1446 #[ignore_malloc_size_of = "This is also stored as part of styling."]
1447 style: ServoArc<FontStyleStruct>,
1448 size: Au,
1449}
1450
1451impl PartialEq for FontGroupCacheKey {
1452 fn eq(&self, other: &FontGroupCacheKey) -> bool {
1453 self.style == other.style && self.size == other.size
1454 }
1455}
1456
1457impl Eq for FontGroupCacheKey {}
1458
1459impl Hash for FontGroupCacheKey {
1460 fn hash<H>(&self, hasher: &mut H)
1461 where
1462 H: Hasher,
1463 {
1464 self.style.hash.hash(hasher)
1465 }
1466}
1467
1468#[derive(Default, MallocSizeOf)]
1469struct KnownFontFaceRules {
1470 generation: bool,
1475 contents: HashMap<Atom, Vec<KnownFontFaceRule>>,
1478}
1479
1480#[derive(MallocSizeOf)]
1481struct KnownFontFaceRule {
1482 rule_with_origin: FontFaceRuleWithOrigin,
1483 generation: bool,
1484}
1485
1486impl KnownFontFaceRules {
1487 fn diff_old_and_new_font_face_rules(
1491 &mut self,
1492 stylist: &Stylist,
1493 guards: &StylesheetGuards<'_>,
1494 ) -> WebFontSetDifference {
1495 let mut difference = WebFontSetDifference::default();
1496 self.generation = !self.generation;
1497
1498 let font_face_rules_in_cascade_order = stylist
1499 .iter_extra_data_origins()
1500 .flat_map(|(extra_data, origin)| {
1501 extra_data.font_faces.iter().rev().zip(iter::repeat(origin))
1502 })
1503 .map(|((rule, _layer), origin)| FontFaceRuleWithOrigin::new(rule.clone(), origin));
1504
1505 let mut number_of_unchanged_rules = 0;
1507 let number_of_previously_known_rules: usize = self
1508 .contents
1509 .values()
1510 .map(|fonts_from_family| fonts_from_family.len())
1511 .sum();
1512 for rule_with_origin in font_face_rules_in_cascade_order {
1513 let borrowed_rule = rule_with_origin.read_with(guards);
1514
1515 let Some(font_family) = borrowed_rule.descriptors.font_family.as_ref() else {
1516 continue;
1521 };
1522 if borrowed_rule.descriptors.src.is_none() {
1523 continue;
1525 }
1526
1527 let known_font_faces_for_family =
1528 self.contents.entry(font_family.name.clone()).or_default();
1529
1530 let mut conflicting_declaration_with_higher_priority_exists = false;
1531 let mut index_of_existing_entry_for_this_rule = None;
1532 for (index, known_font_face) in known_font_faces_for_family.iter().enumerate() {
1533 if FontFaceRuleWithOrigin::ptr_eq(
1535 &known_font_face.rule_with_origin,
1536 &rule_with_origin,
1537 ) {
1538 index_of_existing_entry_for_this_rule = Some(index);
1539 }
1540
1541 if conflicting_declaration_with_higher_priority_exists {
1543 continue;
1545 }
1546 if known_font_face.generation != self.generation {
1547 continue;
1550 }
1551 if font_face_rules_conflict(
1552 &known_font_face
1553 .rule_with_origin
1554 .read_with(guards)
1555 .descriptors,
1556 &borrowed_rule.descriptors,
1557 ) {
1558 conflicting_declaration_with_higher_priority_exists = true;
1559 }
1560 }
1561
1562 if let Some(index_of_existing_entry_for_this_rule) =
1563 index_of_existing_entry_for_this_rule
1564 {
1565 if conflicting_declaration_with_higher_priority_exists {
1569 let stale_rule =
1570 known_font_faces_for_family.remove(index_of_existing_entry_for_this_rule);
1571 difference
1572 .removed_font_faces
1573 .push(stale_rule.rule_with_origin);
1574 } else {
1575 number_of_unchanged_rules += 1;
1576 known_font_faces_for_family[index_of_existing_entry_for_this_rule].generation =
1577 self.generation;
1578 }
1579 } else if conflicting_declaration_with_higher_priority_exists {
1580 continue;
1583 } else {
1584 difference.added_font_faces.push(rule_with_origin.clone());
1586 known_font_faces_for_family.push(KnownFontFaceRule {
1587 rule_with_origin,
1588 generation: self.generation,
1589 });
1590 }
1591 }
1592
1593 if number_of_unchanged_rules == number_of_previously_known_rules {
1594 return difference;
1598 }
1599
1600 self.contents.retain(|_, known_font_faces_for_family| {
1602 known_font_faces_for_family
1603 .extract_if(.., |rule| rule.generation != self.generation)
1604 .for_each(|removed_rule| {
1605 difference
1606 .removed_font_faces
1607 .push(removed_rule.rule_with_origin);
1608 });
1609
1610 !known_font_faces_for_family.is_empty()
1611 });
1612
1613 difference
1614 }
1615}
1616
1617fn font_face_rules_conflict(
1624 first_rule: &FontFaceRuleDescriptors,
1625 second_rule: &FontFaceRuleDescriptors,
1626) -> bool {
1627 first_rule.font_stretch == second_rule.font_stretch &&
1628 first_rule.font_style == second_rule.font_style &&
1629 first_rule.font_weight == second_rule.font_weight &&
1630 first_rule.unicode_range == second_rule.unicode_range
1631}