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(),
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 match font.template.identifier() {
378 FontIdentifier::Local(_) => self.system_font_service_proxy.get_system_font_instance(
379 font.template.identifier(),
380 font.descriptor.pt_size,
381 font.webrender_font_instance_flags(),
382 font.variations().to_owned(),
383 painter_id,
384 ),
385 FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => self
386 .create_web_font_instance(
387 font.template.clone(),
388 font.descriptor.pt_size,
389 font.webrender_font_instance_flags(),
390 font.variations().to_owned(),
391 painter_id,
392 ),
393 }
394 }
395
396 fn create_web_font_instance(
397 &self,
398 font_template: FontTemplateRef,
399 pt_size: Au,
400 flags: FontInstanceFlags,
401 variations: Vec<FontVariation>,
402 painter_id: PainterId,
403 ) -> FontInstanceKey {
404 let identifier = font_template.identifier();
405 let font_data = self
406 .get_font_data(&identifier)
407 .expect("Web font should have associated font data");
408 let font_key = *self
409 .webrender_font_keys
410 .write()
411 .entry(identifier.clone())
412 .or_insert_with(|| {
413 let font_key = self.system_font_service_proxy.generate_font_key(painter_id);
414 self.paint_api.lock().add_font(
415 font_key,
416 font_data.as_ipc_shared_memory(),
417 identifier.index(),
418 );
419 font_key
420 });
421
422 let entry_key = FontParameters {
423 font_key,
424 pt_size,
425 variations: variations.clone(),
426 flags,
427 };
428 *self
429 .webrender_font_instance_keys
430 .write()
431 .entry(entry_key)
432 .or_insert_with(|| {
433 let font_instance_key = self
434 .system_font_service_proxy
435 .generate_font_instance_key(painter_id);
436 self.paint_api.lock().add_font_instance(
437 font_instance_key,
438 font_key,
439 pt_size.to_f32_px(),
440 flags,
441 variations,
442 );
443 font_instance_key
444 })
445 }
446
447 fn invalidate_font_groups_after_web_font_load(&self) {
448 self.resolved_font_groups.write().clear();
449 }
450
451 pub fn is_supported_web_font_source(source: &&Source) -> bool {
452 let url_source = match &source {
453 Source::Url(url_source) => url_source,
454 Source::Local(_) => return true,
455 };
456 let format_hint = match url_source.format_hint {
457 Some(ref format_hint) => format_hint,
458 None => return true,
459 };
460
461 if matches!(
462 format_hint,
463 FontFaceSourceFormat::Keyword(
464 FontFaceSourceFormatKeyword::Truetype |
465 FontFaceSourceFormatKeyword::Opentype |
466 FontFaceSourceFormatKeyword::Woff |
467 FontFaceSourceFormatKeyword::Woff2
468 )
469 ) {
470 return true;
471 }
472
473 if let FontFaceSourceFormat::String(string) = format_hint {
474 if string == "truetype" || string == "opentype" || string == "woff" || string == "woff2"
475 {
476 return true;
477 }
478
479 return pref!(layout_variable_fonts_enabled) &&
480 (string == "truetype-variations" ||
481 string == "opentype-variations" ||
482 string == "woff-variations" ||
483 string == "woff2-variations");
484 }
485
486 false
487 }
488
489 fn is_local_or_unknown_url_font(
490 &self,
491 family_name: &LowercaseFontFamilyName,
492 source: &Source,
493 ) -> bool {
494 match source {
495 Source::Url(url) => !url
496 .url
497 .url()
498 .cloned()
499 .map(ServoUrl::from)
500 .map(FontIdentifier::Web)
501 .filter(|font_identifier| self.font_data.read().contains_key(font_identifier))
502 .is_some_and(|font_identifier| {
503 self.web_fonts
504 .read()
505 .families
506 .get(family_name)
507 .is_some_and(|templates| {
508 templates
509 .templates
510 .iter()
511 .any(|template| template.borrow().identifier == font_identifier)
512 })
513 }),
514 Source::Local(_) => true,
515 }
516 }
517
518 pub(crate) fn handle_web_font_request_started(
523 &self,
524 url: ServoUrl,
525 state: WebFontDownloadState,
526 ) -> bool {
527 let mut downloading_fonts = self.currently_downloading_fonts.lock();
528 let entry = downloading_fonts.entry(url);
529
530 let needs_new_fetch_request = matches!(entry, Entry::Vacant(_));
532
533 entry.or_default().push(state);
534
535 needs_new_fetch_request
536 }
537
538 pub(crate) fn handle_web_font_request_succeeded(
544 &self,
545 font_data: FontData,
546 url: ServoUrl,
547 ) -> bool {
548 let Some(download_states) = self.currently_downloading_fonts.lock().remove(&url) else {
549 return false;
551 };
552 debug_assert!(
553 !download_states.is_empty(),
554 "Should have removed this entry"
555 );
556
557 let identifier = FontIdentifier::Web(url);
558 let Ok(handle) =
559 PlatformFont::new_from_data(identifier.clone(), &font_data, None, &[], false)
560 else {
561 return false;
562 };
563
564 self.font_data.write().insert(identifier.clone(), font_data);
565 let descriptor = handle.descriptor();
566 for download_state in download_states {
567 let mut descriptor = descriptor.clone();
568 descriptor.override_values_with_css_font_template_descriptors(
569 &download_state.css_font_face_descriptors,
570 );
571
572 let new_template = FontTemplate::new(
573 identifier.clone(),
574 descriptor,
575 download_state.initiator.font_face_rule().cloned(),
576 );
577
578 download_state.handle_web_font_load_success(new_template);
579 }
580
581 true
582 }
583
584 pub fn decrement_count_of_loading_fonts_by_one(&self) {
586 self.number_of_loading_web_fonts
587 .fetch_sub(1, Ordering::SeqCst);
588 }
589
590 pub(crate) fn is_font_face_rule_active(
594 &self,
595 target_rule: &ServoArc<LockedFontFaceRule>,
596 ) -> bool {
597 self.known_font_face_rules
598 .lock()
599 .contents
600 .values()
601 .flat_map(|bucket| bucket.iter())
602 .any(|known_rule| ServoArc::ptr_eq(&known_rule.rule_with_origin.rule, target_rule))
603 }
604}
605
606#[derive(MallocSizeOf)]
609pub(crate) struct WebFontDownloadState {
610 webview_id: Option<WebViewId>,
611 css_font_face_descriptors: CSSFontFaceDescriptors,
612 remaining_sources: Vec<Source>,
613 local_fonts: FxHashMap<Atom, Option<FontTemplateRef>>,
614 #[conditional_malloc_size_of]
615 pub(crate) font_context: Arc<FontContext>,
616 initiator: WebFontLoadInitiator,
617 document_context: WebFontDocumentContext,
618}
619
620impl WebFontDownloadState {
621 fn new(
622 webview_id: Option<WebViewId>,
623 font_context: Arc<FontContext>,
624 css_font_face_descriptors: CSSFontFaceDescriptors,
625 initiator: WebFontLoadInitiator,
626 sources: Vec<Source>,
627 local_fonts: FxHashMap<Atom, Option<FontTemplateRef>>,
628 document_context: WebFontDocumentContext,
629 ) -> WebFontDownloadState {
630 WebFontDownloadState {
631 webview_id,
632 css_font_face_descriptors,
633 remaining_sources: sources,
634 local_fonts,
635 font_context,
636 initiator,
637 document_context,
638 }
639 }
640
641 pub(crate) fn handle_web_font_load_success(self, new_template: FontTemplate) {
642 let family_name = self.css_font_face_descriptors.family_name.clone();
643 match self.initiator {
644 WebFontLoadInitiator::Stylesheet(initiator) => {
645 if !self
646 .font_context
647 .is_font_face_rule_active(&initiator.created_by)
648 {
649 if self
651 .font_context
652 .number_of_loading_web_fonts
653 .fetch_sub(1, Ordering::SeqCst) ==
654 1
655 {
656 (initiator.callback)(WebFontLoadEvent::UnblockedFontReadyPromise);
659 }
660 return;
661 }
662
663 self.font_context
664 .web_fonts
665 .write()
666 .add_new_template(family_name, new_template);
667 self.font_context
668 .invalidate_font_groups_after_web_font_load();
669
670 (initiator.callback)(WebFontLoadEvent::LoadedSuccessfully);
674 },
675 WebFontLoadInitiator::Script(callback) => {
676 self.font_context.decrement_count_of_loading_fonts_by_one();
677 callback(family_name, Some(new_template));
678 },
679 }
680 }
681
682 pub(crate) fn handle_web_font_load_failure(self) {
684 let family_name = self.css_font_face_descriptors.family_name.clone();
685 match self.initiator {
686 WebFontLoadInitiator::Stylesheet(initiator) => {
687 if self
688 .font_context
689 .number_of_loading_web_fonts
690 .fetch_sub(1, Ordering::SeqCst) ==
691 1
692 {
693 (initiator.callback)(WebFontLoadEvent::UnblockedFontReadyPromise);
696 }
697 },
698 WebFontLoadInitiator::Script(callback) => {
699 self.font_context.decrement_count_of_loading_fonts_by_one();
700 callback(family_name, None);
701 },
702 }
703 }
704}
705
706pub trait FontContextWebFontMethods {
707 fn rebuild_font_face_set(
708 &self,
709 webview_id: WebViewId,
710 stylist: &Stylist,
711 guards: &StylesheetGuards<'_>,
712 callback: StylesheetWebFontLoadFinishedCallback,
713 document_context: &WebFontDocumentContext,
714 ) -> WebFontSetDifference;
715 fn load_single_font_face_rule(
716 &self,
717 webview_id: WebViewId,
718 locked_font_face_rule: &FontFaceRuleWithOrigin,
719 guards: &StylesheetGuards<'_>,
720 callback: StylesheetWebFontLoadFinishedCallback,
721 document_context: &WebFontDocumentContext,
722 );
723 fn load_web_font_for_script(
724 &self,
725 webview_id: Option<WebViewId>,
726 sources: SourceList,
727 descriptors: CSSFontFaceDescriptors,
728 finished_callback: ScriptWebFontLoadFinishedCallback,
729 document_context: &WebFontDocumentContext,
730 );
731 fn handle_web_font_request_failed(&self, url: ServoUrl);
732}
733
734impl FontContextWebFontMethods for Arc<FontContext> {
735 fn load_single_font_face_rule(
736 &self,
737 webview_id: WebViewId,
738 locked_font_face_rule: &FontFaceRuleWithOrigin,
739 guards: &StylesheetGuards<'_>,
740 callback: StylesheetWebFontLoadFinishedCallback,
741 document_context: &WebFontDocumentContext,
742 ) {
743 let font_face_rule = locked_font_face_rule.read_with(guards);
744 let Some(ref sources) = font_face_rule.descriptors.src else {
745 return;
746 };
747
748 let css_font_face_descriptors = font_face_rule.into();
749
750 let initiator = FontFaceRuleInitiator {
751 created_by: locked_font_face_rule.rule.clone(),
752 font_face_rule: font_face_rule.descriptors.clone(),
753 callback: callback.clone(),
754 };
755
756 self.start_loading_one_web_font(
757 Some(webview_id),
758 sources,
759 css_font_face_descriptors,
760 WebFontLoadInitiator::Stylesheet(Box::new(initiator)),
761 document_context,
762 );
763 }
764 fn rebuild_font_face_set(
765 &self,
766 webview_id: WebViewId,
767 stylist: &Stylist,
768 guards: &StylesheetGuards<'_>,
769 callback: StylesheetWebFontLoadFinishedCallback,
770 document_context: &WebFontDocumentContext,
771 ) -> WebFontSetDifference {
772 let difference = self
773 .known_font_face_rules
774 .lock()
775 .diff_old_and_new_font_face_rules(stylist, guards);
776
777 for added_rule in &difference.added_font_faces {
778 self.load_single_font_face_rule(
779 webview_id,
780 added_rule,
781 guards,
782 callback.clone(),
783 document_context,
784 );
785 }
786 for removed_rule in &difference.removed_font_faces {
787 let removed_rule = removed_rule.read_with(guards);
788 self.remove_single_font_face_rule(
789 &removed_rule.descriptors,
790 &mut self.web_fonts.write(),
791 );
792 }
793
794 if !difference.removed_font_faces.is_empty() {
795 self.resolved_font_groups.write().clear();
797
798 self.have_removed_web_fonts.store(true, Ordering::Relaxed);
800 }
801
802 difference
803 }
804
805 fn load_web_font_for_script(
806 &self,
807 webview_id: Option<WebViewId>,
808 sources: SourceList,
809 descriptors: CSSFontFaceDescriptors,
810 finished_callback: ScriptWebFontLoadFinishedCallback,
811 document_context: &WebFontDocumentContext,
812 ) {
813 let completion_handler = WebFontLoadInitiator::Script(finished_callback);
814 self.start_loading_one_web_font(
815 webview_id,
816 &sources,
817 descriptors,
818 completion_handler,
819 document_context,
820 );
821 }
822
823 fn handle_web_font_request_failed(&self, url: ServoUrl) {
825 let Some(subscribers) = self.currently_downloading_fonts.lock().remove(&url) else {
826 return;
827 };
828
829 for subscriber in subscribers {
830 if let WebFontLoadInitiator::Stylesheet(stylesheet_initiator) = &subscriber.initiator &&
832 !self.is_font_face_rule_active(&stylesheet_initiator.created_by)
833 {
834 if self
836 .number_of_loading_web_fonts
837 .fetch_sub(1, Ordering::SeqCst) ==
838 1
839 {
840 (stylesheet_initiator.callback)(WebFontLoadEvent::UnblockedFontReadyPromise);
843 }
844 return;
845 }
846
847 self.process_next_web_font_source(subscriber);
848 }
849 }
850}
851
852impl FontContext {
853 pub fn collect_unused_webrender_resources(
854 &self,
855 all: bool,
856 ) -> (Vec<FontKey>, Vec<FontInstanceKey>) {
857 if all {
858 let mut webrender_font_keys = self.webrender_font_keys.write();
859 let mut webrender_font_instance_keys = self.webrender_font_instance_keys.write();
860 self.have_removed_web_fonts.store(false, Ordering::Relaxed);
861 return (
862 webrender_font_keys.drain().map(|(_, key)| key).collect(),
863 webrender_font_instance_keys
864 .drain()
865 .map(|(_, key)| key)
866 .collect(),
867 );
868 }
869
870 if !self.have_removed_web_fonts.load(Ordering::Relaxed) {
871 return (Vec::new(), Vec::new());
872 }
873
874 let web_fonts = self.web_fonts.write();
876 let mut font_data = self.font_data.write();
877 let _fonts = self.fonts.write();
878 let _font_groups = self.resolved_font_groups.write();
879 let mut webrender_font_keys = self.webrender_font_keys.write();
880 let mut webrender_font_instance_keys = self.webrender_font_instance_keys.write();
881
882 let mut unused_identifiers: HashSet<FontIdentifier> =
883 webrender_font_keys.keys().cloned().collect();
884 for templates in web_fonts.families.values() {
885 templates.for_all_identifiers(|identifier| {
886 unused_identifiers.remove(identifier);
887 });
888 }
889
890 font_data.retain(|font_identifier, _| !unused_identifiers.contains(font_identifier));
891
892 self.have_removed_web_fonts.store(false, Ordering::Relaxed);
893
894 let mut removed_keys: FxHashSet<FontKey> = FxHashSet::default();
895 webrender_font_keys.retain(|identifier, font_key| {
896 if unused_identifiers.contains(identifier) {
897 removed_keys.insert(*font_key);
898 false
899 } else {
900 true
901 }
902 });
903
904 let mut removed_instance_keys: HashSet<FontInstanceKey> = HashSet::new();
905 webrender_font_instance_keys.retain(|font_param, instance_key| {
906 if removed_keys.contains(&font_param.font_key) {
907 removed_instance_keys.insert(*instance_key);
908 false
909 } else {
910 true
911 }
912 });
913
914 (
915 removed_keys.into_iter().collect(),
916 removed_instance_keys.into_iter().collect(),
917 )
918 }
919
920 fn remove_single_font_face_rule(
922 &self,
923 font_face_rule: &FontFaceRuleDescriptors,
924 font_store: &mut FontStore,
925 ) -> bool {
926 let Some(family) = font_face_rule.font_family.as_ref() else {
927 return false;
928 };
929
930 let lowercase_family_name: LowercaseFontFamilyName = family.name.clone().into();
931 let Some(known_family) = font_store.families.get_mut(&lowercase_family_name) else {
932 return false;
933 };
934 if !known_family.remove_template_for_font_face_rule(font_face_rule) {
935 return false;
936 }
937 self.fonts.write().retain(|_, font| match font {
938 Some(font) => !font
939 .template
940 .borrow()
941 .is_defined_by_font_face_rule(font_face_rule),
942 _ => true,
943 });
944
945 true
946 }
947
948 pub fn add_template_to_font_context(
949 &self,
950 family_name: LowercaseFontFamilyName,
951 new_template: FontTemplate,
952 ) {
953 self.web_fonts
954 .write()
955 .add_new_template(family_name, new_template);
956 self.invalidate_font_groups_after_web_font_load();
957 }
958
959 pub fn construct_web_font_from_data(
960 &self,
961 data: &[u8],
962 descriptors: CSSFontFaceDescriptors,
963 ) -> Option<(LowercaseFontFamilyName, FontTemplate)> {
964 let bytes = fontsan::process(data)
965 .inspect_err(|error| {
966 debug!(
967 "Sanitiser rejected FontFace font: family={} with {error:?}",
968 descriptors.family_name,
969 );
970 })
971 .ok()?;
972 let font_data = FontData::from_bytes(&bytes);
973
974 let identifier = FontIdentifier::ArrayBuffer(Uuid::new_v4());
975 let handle =
976 PlatformFont::new_from_data(identifier.clone(), &font_data, None, &[], false).ok()?;
977
978 let new_template = FontTemplate::new(identifier.clone(), handle.descriptor(), None);
979
980 self.font_data.write().insert(identifier, font_data);
981
982 Some((descriptors.family_name, new_template))
983 }
984
985 fn start_loading_one_web_font(
986 self: &Arc<FontContext>,
987 webview_id: Option<WebViewId>,
988 source_list: &SourceList,
989 css_font_face_descriptors: CSSFontFaceDescriptors,
990 completion_handler: WebFontLoadInitiator,
991 document_context: &WebFontDocumentContext,
992 ) {
993 self.number_of_loading_web_fonts
994 .fetch_add(1, Ordering::SeqCst);
995
996 let sources: Vec<Source> = source_list
997 .0
998 .iter()
999 .rev()
1000 .filter(Self::is_supported_web_font_source)
1001 .filter(|source| {
1002 self.is_local_or_unknown_url_font(&css_font_face_descriptors.family_name, source)
1003 })
1004 .cloned()
1005 .collect();
1006
1007 let sources_transformed = sources
1015 .iter()
1016 .filter_map(|source| {
1017 if let Source::Local(family_name) = source {
1018 Some(family_name)
1019 } else {
1020 None
1021 }
1022 })
1023 .map(|family_name| {
1024 let family = SingleFontFamily::FamilyName(FamilyName {
1025 name: family_name.name.clone(),
1026 syntax: FontFamilyNameSyntax::Quoted,
1027 });
1028 let matching_font_templates = self
1029 .system_font_service_proxy
1030 .find_matching_font_templates(None, &family);
1031 let value = matching_font_templates.first();
1032 (family_name.name.clone(), value.cloned())
1033 });
1034
1035 let local_fonts = FxHashMap::from_iter(sources_transformed);
1036
1037 self.process_next_web_font_source(WebFontDownloadState::new(
1038 webview_id,
1039 self.clone(),
1040 css_font_face_descriptors,
1041 completion_handler,
1042 sources,
1043 local_fonts,
1044 document_context.clone(),
1045 ));
1046 }
1047
1048 pub(crate) fn process_next_web_font_source(
1049 self: &Arc<FontContext>,
1050 mut state: WebFontDownloadState,
1051 ) {
1052 let Some(source) = state.remaining_sources.pop() else {
1053 state.handle_web_font_load_failure();
1054 return;
1055 };
1056
1057 let this = self.clone();
1058 let web_font_family_name = state.css_font_face_descriptors.family_name.clone();
1059 match source {
1060 Source::Url(url_source) => {
1061 RemoteWebFontDownloader::download(url_source, this, web_font_family_name, state)
1062 },
1063 Source::Local(ref local_family_name) => {
1064 if let Some(new_template) = state
1065 .local_fonts
1066 .get(&local_family_name.name)
1067 .cloned()
1068 .flatten()
1069 .and_then(|local_template| {
1070 let template = FontTemplate::new_for_local_web_font(
1071 local_template,
1072 &state.css_font_face_descriptors,
1073 state.initiator.font_face_rule().cloned(),
1074 )
1075 .ok()?;
1076 Some(template)
1077 })
1078 {
1079 state.handle_web_font_load_success(new_template);
1080 } else {
1081 this.process_next_web_font_source(state);
1082 }
1083 },
1084 }
1085 }
1086
1087 pub fn resolve_font_variant_alternate_identifiers_for(
1089 &self,
1090 font: &FontRef,
1091 alternates: &FontVariantAlternates,
1092 stylist: &Stylist,
1093 ) -> ResolvedFontVariantAlternates {
1094 let mut resolved_alternates = ResolvedFontVariantAlternates::default();
1095 if alternates.is_empty() {
1096 return resolved_alternates;
1097 }
1098 let Some(family_name) = font.family_name() else {
1099 return resolved_alternates;
1100 };
1101
1102 for alternate in alternates.iter() {
1103 match alternate {
1104 VariantAlternates::Stylistic(stylistic) => {
1105 let Some(FontFeatureValue::Single(value)) = self
1106 .look_up_font_feature_alternate_name(
1107 family_name.clone(),
1108 AlternateKindRequiringResolution::Stylistic,
1109 stylistic.0.clone(),
1110 stylist,
1111 )
1112 else {
1113 continue;
1114 };
1115
1116 resolved_alternates.stylistic = Some(value);
1117 },
1118 VariantAlternates::Styleset(styleset_list) => {
1119 for styleset in styleset_list.iter() {
1120 let Some(FontFeatureValue::Vector(value)) = self
1121 .look_up_font_feature_alternate_name(
1122 family_name.clone(),
1123 AlternateKindRequiringResolution::Styleset,
1124 styleset.0.clone(),
1125 stylist,
1126 )
1127 else {
1128 continue;
1129 };
1130
1131 resolved_alternates.styleset.extend(value.0.iter());
1132 }
1133 },
1134 VariantAlternates::CharacterVariant(character_variant_list) => {
1135 for character_variant in character_variant_list.iter() {
1136 let Some(FontFeatureValue::Pair(value)) = self
1137 .look_up_font_feature_alternate_name(
1138 family_name.clone(),
1139 AlternateKindRequiringResolution::CharacterVariant,
1140 character_variant.0.clone(),
1141 stylist,
1142 )
1143 else {
1144 continue;
1145 };
1146
1147 resolved_alternates.character_variant.push(value);
1148 }
1149 },
1150 VariantAlternates::Swash(swash) => {
1151 let Some(FontFeatureValue::Single(value)) = self
1152 .look_up_font_feature_alternate_name(
1153 family_name.clone(),
1154 AlternateKindRequiringResolution::Swash,
1155 swash.0.clone(),
1156 stylist,
1157 )
1158 else {
1159 continue;
1160 };
1161
1162 resolved_alternates.swash = Some(value);
1163 },
1164 VariantAlternates::Ornaments(ornaments) => {
1165 let Some(FontFeatureValue::Single(value)) = self
1166 .look_up_font_feature_alternate_name(
1167 family_name.clone(),
1168 AlternateKindRequiringResolution::Ornaments,
1169 ornaments.0.clone(),
1170 stylist,
1171 )
1172 else {
1173 continue;
1174 };
1175
1176 resolved_alternates.ornaments = Some(value);
1177 },
1178 VariantAlternates::Annotation(annotation) => {
1179 let Some(FontFeatureValue::Single(value)) = self
1180 .look_up_font_feature_alternate_name(
1181 family_name.clone(),
1182 AlternateKindRequiringResolution::Annotation,
1183 annotation.0.clone(),
1184 stylist,
1185 )
1186 else {
1187 continue;
1188 };
1189
1190 resolved_alternates.annotation = Some(value);
1191 },
1192 VariantAlternates::HistoricalForms => {
1193 resolved_alternates.historical_forms = true;
1194 },
1195 }
1196 }
1197
1198 resolved_alternates
1199 }
1200
1201 fn look_up_font_feature_alternate_name(
1207 &self,
1208 family_name: Atom,
1209 kind: AlternateKindRequiringResolution,
1210 name: Atom,
1211 stylist: &Stylist,
1212 ) -> Option<FontFeatureValue> {
1213 let read_guard = self.font_feature_value_map.borrow();
1215 if let Some(map) = &*read_guard {
1216 map.lookup(family_name, kind, name)
1218 } else {
1219 drop(read_guard);
1221 let mut write_guard = self.font_feature_value_map.borrow_mut();
1222 if let Some(map) = &*write_guard {
1223 return map.lookup(family_name, kind, name);
1226 }
1227
1228 log::debug!("Initializing @font-feature-values map");
1229 let mut map = FontFeatureValueMap::default();
1230 stylist
1231 .iter_extra_data_origins_rev()
1232 .flat_map(|(extra_data, _)| extra_data.font_feature_values.iter())
1233 .for_each(|(rule, _)| map.add_rule(rule));
1234 let map = &*write_guard.insert(map);
1235 map.lookup(family_name, kind, name)
1237 }
1238 }
1239
1240 pub fn invalidate_font_feature_values_map(&self) {
1241 self.font_feature_value_map.borrow_mut().take();
1242 }
1243}
1244
1245pub(crate) type ScriptWebFontLoadFinishedCallback =
1246 Box<dyn FnOnce(LowercaseFontFamilyName, Option<FontTemplate>) + Send>;
1247
1248#[derive(MallocSizeOf)]
1249pub(crate) struct FontFaceRuleInitiator {
1250 #[conditional_malloc_size_of]
1256 created_by: ServoArc<LockedFontFaceRule>,
1257 font_face_rule: FontFaceRuleDescriptors,
1258 #[ignore_malloc_size_of = "dyn Fn"]
1259 callback: StylesheetWebFontLoadFinishedCallback,
1260}
1261
1262#[derive(MallocSizeOf)]
1263pub(crate) enum WebFontLoadInitiator {
1264 Stylesheet(Box<FontFaceRuleInitiator>),
1265 Script(#[ignore_malloc_size_of = "dyn Fn"] ScriptWebFontLoadFinishedCallback),
1266}
1267
1268impl WebFontLoadInitiator {
1269 pub(crate) fn font_face_rule(&self) -> Option<&FontFaceRuleDescriptors> {
1270 match self {
1271 Self::Stylesheet(initiator) => Some(&initiator.font_face_rule),
1272 Self::Script(_) => None,
1273 }
1274 }
1275}
1276
1277struct RemoteWebFontDownloader {
1278 url: ServoArc<Url>,
1280 web_font_family_name: LowercaseFontFamilyName,
1281 response_valid: bool,
1282 response_data: Vec<u8>,
1284 document_context: WebFontDocumentContext,
1285 font_context: Arc<FontContext>,
1286}
1287
1288enum DownloaderResponseResult {
1289 InProcess,
1290 Finished,
1291 Failure,
1292}
1293
1294impl RemoteWebFontDownloader {
1295 fn download(
1296 url_source: UrlSource,
1297 font_context: Arc<FontContext>,
1298 web_font_family_name: LowercaseFontFamilyName,
1299 state: WebFontDownloadState,
1300 ) {
1301 let url = match url_source.url.url() {
1303 Some(url) => url.clone(),
1304 None => return,
1305 };
1306
1307 let webview_id = state.webview_id;
1308 let document_context = state.document_context.clone();
1309 if !font_context.handle_web_font_request_started(url.clone().into(), state) {
1310 return;
1313 }
1314
1315 let request = RequestBuilder::new(
1316 webview_id,
1317 UrlWithBlobClaim::from_url_without_having_claimed_blob(url.clone().into()),
1318 Referrer::ReferrerUrl(document_context.document_url.clone()),
1319 )
1320 .destination(Destination::Font)
1321 .mode(RequestMode::CorsMode)
1322 .credentials_mode(CredentialsMode::CredentialsSameOrigin)
1323 .service_workers_mode(ServiceWorkersMode::All)
1324 .policy_container(document_context.policy_container.clone())
1325 .client(document_context.request_client.clone());
1326
1327 let core_resource_thread_clone = font_context.resource_threads.lock().clone();
1328
1329 debug!("Loading @font-face {} from {}", web_font_family_name, url);
1330 let mut downloader = Self {
1331 url,
1332 web_font_family_name,
1333 response_valid: false,
1334 response_data: Vec::new(),
1335 document_context,
1336 font_context: font_context.clone(),
1337 };
1338
1339 fetch_async(
1340 &core_resource_thread_clone,
1341 request,
1342 None,
1343 Box::new(move |response_message| {
1344 match downloader.handle_web_font_fetch_message(response_message) {
1345 DownloaderResponseResult::InProcess => {},
1346 DownloaderResponseResult::Finished => {
1347 downloader.process_downloaded_font_and_signal_completion()
1348 },
1349 DownloaderResponseResult::Failure => {
1350 font_context.handle_web_font_request_failed(downloader.url.clone().into());
1351 },
1352 }
1353 }),
1354 )
1355 }
1356
1357 fn process_downloaded_font_and_signal_completion(&mut self) {
1360 let font_data = std::mem::take(&mut self.response_data);
1361 trace!(
1362 "Downloaded @font-face {} ({} bytes)",
1363 self.web_font_family_name,
1364 font_data.len()
1365 );
1366
1367 let font_data = match fontsan::process(&font_data) {
1368 Ok(bytes) => FontData::from_bytes(&bytes),
1369 Err(error) => {
1370 debug!(
1371 "Sanitiser rejected web font url={:?} with {error:?}",
1372 self.url.as_str(),
1373 );
1374 return self
1375 .font_context
1376 .handle_web_font_request_failed(self.url.clone().into());
1377 },
1378 };
1379
1380 let url: ServoUrl = self.url.clone().into();
1381 self.font_context
1382 .handle_web_font_request_succeeded(font_data, url);
1383 }
1384
1385 fn handle_web_font_fetch_message(
1386 &mut self,
1387 response_message: FetchResponseMsg,
1388 ) -> DownloaderResponseResult {
1389 match response_message {
1390 FetchResponseMsg::ProcessRequestBody(..) => DownloaderResponseResult::InProcess,
1391 FetchResponseMsg::ProcessCspViolations(_request_id, violations) => {
1392 self.document_context
1393 .csp_handler
1394 .process_violations(violations);
1395 DownloaderResponseResult::InProcess
1396 },
1397 FetchResponseMsg::ProcessResponse(_, meta_result) => {
1398 trace!(
1399 "@font-face {} metadata ok={:?}",
1400 self.web_font_family_name,
1401 meta_result.is_ok()
1402 );
1403 self.response_valid = meta_result.is_ok();
1404 DownloaderResponseResult::InProcess
1405 },
1406 FetchResponseMsg::ProcessResponseChunk(_, new_bytes) => {
1407 trace!(
1408 "@font-face {} chunk={:?}",
1409 self.web_font_family_name, new_bytes
1410 );
1411 if self.response_valid {
1412 self.response_data.extend(new_bytes.0)
1413 }
1414 DownloaderResponseResult::InProcess
1415 },
1416 FetchResponseMsg::ProcessResponseEOF(_, response, timing) => {
1417 trace!(
1418 "@font-face {} EOF={:?}",
1419 self.web_font_family_name, response
1420 );
1421 if response.is_err() || !self.response_valid {
1422 return DownloaderResponseResult::Failure;
1423 }
1424 self.document_context
1425 .network_timing_handler
1426 .submit_timing(ServoUrl::from_url(self.url.as_ref().clone()), timing);
1427 DownloaderResponseResult::Finished
1428 },
1429 FetchResponseMsg::ProcessContentLength(_request_id, size) => {
1430 self.response_data.reserve(size - self.response_data.len());
1431 DownloaderResponseResult::InProcess
1432 },
1433 }
1434 }
1435}
1436
1437#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)]
1438struct FontCacheKey {
1439 font_identifier: FontIdentifier,
1440 font_descriptor: FontDescriptor,
1441}
1442
1443#[derive(Debug, MallocSizeOf)]
1444struct FontGroupCacheKey {
1445 #[ignore_malloc_size_of = "This is also stored as part of styling."]
1446 style: ServoArc<FontStyleStruct>,
1447 size: Au,
1448}
1449
1450impl PartialEq for FontGroupCacheKey {
1451 fn eq(&self, other: &FontGroupCacheKey) -> bool {
1452 self.style == other.style && self.size == other.size
1453 }
1454}
1455
1456impl Eq for FontGroupCacheKey {}
1457
1458impl Hash for FontGroupCacheKey {
1459 fn hash<H>(&self, hasher: &mut H)
1460 where
1461 H: Hasher,
1462 {
1463 self.style.hash.hash(hasher)
1464 }
1465}
1466
1467#[derive(Default, MallocSizeOf)]
1468struct KnownFontFaceRules {
1469 generation: bool,
1474 contents: HashMap<Atom, Vec<KnownFontFaceRule>>,
1477}
1478
1479#[derive(MallocSizeOf)]
1480struct KnownFontFaceRule {
1481 rule_with_origin: FontFaceRuleWithOrigin,
1482 generation: bool,
1483}
1484
1485impl KnownFontFaceRules {
1486 fn diff_old_and_new_font_face_rules(
1490 &mut self,
1491 stylist: &Stylist,
1492 guards: &StylesheetGuards<'_>,
1493 ) -> WebFontSetDifference {
1494 let mut difference = WebFontSetDifference::default();
1495 self.generation = !self.generation;
1496
1497 let font_face_rules_in_cascade_order = stylist
1498 .iter_extra_data_origins()
1499 .flat_map(|(extra_data, origin)| {
1500 extra_data.font_faces.iter().rev().zip(iter::repeat(origin))
1501 })
1502 .map(|((rule, _layer), origin)| FontFaceRuleWithOrigin::new(rule.clone(), origin));
1503
1504 let mut number_of_unchanged_rules = 0;
1506 let number_of_previously_known_rules: usize = self
1507 .contents
1508 .values()
1509 .map(|fonts_from_family| fonts_from_family.len())
1510 .sum();
1511 for rule_with_origin in font_face_rules_in_cascade_order {
1512 let borrowed_rule = rule_with_origin.read_with(guards);
1513
1514 let Some(font_family) = borrowed_rule.descriptors.font_family.as_ref() else {
1515 continue;
1520 };
1521 if borrowed_rule.descriptors.src.is_none() {
1522 continue;
1524 }
1525
1526 let known_font_faces_for_family =
1527 self.contents.entry(font_family.name.clone()).or_default();
1528
1529 let mut conflicting_declaration_with_higher_priority_exists = false;
1530 let mut index_of_existing_entry_for_this_rule = None;
1531 for (index, known_font_face) in known_font_faces_for_family.iter().enumerate() {
1532 if FontFaceRuleWithOrigin::ptr_eq(
1534 &known_font_face.rule_with_origin,
1535 &rule_with_origin,
1536 ) {
1537 index_of_existing_entry_for_this_rule = Some(index);
1538 }
1539
1540 if conflicting_declaration_with_higher_priority_exists {
1542 continue;
1544 }
1545 if known_font_face.generation != self.generation {
1546 continue;
1549 }
1550 if font_face_rules_conflict(
1551 &known_font_face
1552 .rule_with_origin
1553 .read_with(guards)
1554 .descriptors,
1555 &borrowed_rule.descriptors,
1556 ) {
1557 conflicting_declaration_with_higher_priority_exists = true;
1558 }
1559 }
1560
1561 if let Some(index_of_existing_entry_for_this_rule) =
1562 index_of_existing_entry_for_this_rule
1563 {
1564 if conflicting_declaration_with_higher_priority_exists {
1568 let stale_rule =
1569 known_font_faces_for_family.remove(index_of_existing_entry_for_this_rule);
1570 difference
1571 .removed_font_faces
1572 .push(stale_rule.rule_with_origin);
1573 } else {
1574 number_of_unchanged_rules += 1;
1575 known_font_faces_for_family[index_of_existing_entry_for_this_rule].generation =
1576 self.generation;
1577 }
1578 } else if conflicting_declaration_with_higher_priority_exists {
1579 continue;
1582 } else {
1583 difference.added_font_faces.push(rule_with_origin.clone());
1585 known_font_faces_for_family.push(KnownFontFaceRule {
1586 rule_with_origin,
1587 generation: self.generation,
1588 });
1589 }
1590 }
1591
1592 if number_of_unchanged_rules == number_of_previously_known_rules {
1593 return difference;
1597 }
1598
1599 self.contents.retain(|_, known_font_faces_for_family| {
1601 known_font_faces_for_family
1602 .extract_if(.., |rule| rule.generation != self.generation)
1603 .for_each(|removed_rule| {
1604 difference
1605 .removed_font_faces
1606 .push(removed_rule.rule_with_origin);
1607 });
1608
1609 !known_font_faces_for_family.is_empty()
1610 });
1611
1612 difference
1613 }
1614}
1615
1616fn font_face_rules_conflict(
1623 first_rule: &FontFaceRuleDescriptors,
1624 second_rule: &FontFaceRuleDescriptors,
1625) -> bool {
1626 first_rule.font_stretch == second_rule.font_stretch &&
1627 first_rule.font_style == second_rule.font_style &&
1628 first_rule.font_weight == second_rule.font_weight &&
1629 first_rule.unicode_range == second_rule.unicode_range
1630}