fonts/
font_context.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::collections::{HashMap, HashSet};
6use std::default::Default;
7use std::hash::{Hash, Hasher};
8use std::ops::Deref;
9use std::sync::Arc;
10use std::sync::atomic::{AtomicBool, Ordering};
11
12use app_units::Au;
13use base::id::{RenderingGroupId, WebViewId};
14use compositing_traits::CrossProcessCompositorApi;
15use fonts_traits::{
16    CSSFontFaceDescriptors, FontDescriptor, FontIdentifier, FontTemplate, FontTemplateRef,
17    FontTemplateRefMethods, StylesheetWebFontLoadFinishedCallback,
18};
19use log::{debug, trace};
20use malloc_size_of_derive::MallocSizeOf;
21use net_traits::request::{Destination, Referrer, RequestBuilder};
22use net_traits::{CoreResourceThread, FetchResponseMsg, ResourceThreads, fetch_async};
23use parking_lot::{Mutex, RwLock};
24use rustc_hash::FxHashSet;
25use servo_arc::Arc as ServoArc;
26use servo_config::pref;
27use servo_url::ServoUrl;
28use style::Atom;
29use style::computed_values::font_variant_caps::T as FontVariantCaps;
30use style::font_face::{
31    FontFaceSourceFormat, FontFaceSourceFormatKeyword, Source, SourceList, UrlSource,
32};
33use style::media_queries::Device;
34use style::properties::style_structs::Font as FontStyleStruct;
35use style::shared_lock::SharedRwLockReadGuard;
36use style::stylesheets::{CssRule, DocumentStyleSheet, FontFaceRule, StylesheetInDocument};
37use style::values::computed::font::{FamilyName, FontFamilyNameSyntax, SingleFontFamily};
38use url::Url;
39use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey, FontVariation};
40
41use crate::font::{Font, FontFamilyDescriptor, FontGroup, FontRef, FontSearchScope};
42use crate::font_store::CrossThreadFontStore;
43use crate::platform::font::PlatformFont;
44use crate::{FontData, LowercaseFontFamilyName, PlatformFontMethods, SystemFontServiceProxy};
45
46static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h)
47
48pub(crate) type FontParameters = (FontKey, Au, Vec<FontVariation>);
49
50#[derive(MallocSizeOf)]
51struct FontGroupRef(#[conditional_malloc_size_of] Arc<RwLock<FontGroup>>);
52
53impl Deref for FontGroupRef {
54    type Target = Arc<RwLock<FontGroup>>;
55    fn deref(&self) -> &Self::Target {
56        &self.0
57    }
58}
59
60/// The FontContext represents the per-thread/thread state necessary for
61/// working with fonts. It is the public API used by the layout and
62/// paint code. It talks directly to the system font service where
63/// required.
64#[derive(MallocSizeOf)]
65pub struct FontContext {
66    #[conditional_malloc_size_of]
67    system_font_service_proxy: Arc<SystemFontServiceProxy>,
68
69    resource_threads: Mutex<CoreResourceThread>,
70
71    /// A sender that can send messages and receive replies from the compositor.
72    compositor_api: Mutex<CrossProcessCompositorApi>,
73
74    /// The actual instances of fonts ie a [`FontTemplate`] combined with a size and
75    /// other font properties, along with the font data and a platform font instance.
76    fonts: RwLock<HashMap<FontCacheKey, Option<FontRef>>>,
77
78    /// A caching map between the specification of a font in CSS style and
79    /// resolved [`FontGroup`] which contains information about all fonts that
80    /// can be selected with that style.
81    resolved_font_groups: RwLock<HashMap<FontGroupCacheKey, FontGroupRef>>,
82
83    web_fonts: CrossThreadFontStore,
84
85    /// A collection of WebRender [`FontKey`]s generated for the web fonts that this
86    /// [`FontContext`] controls.
87    webrender_font_keys: RwLock<HashMap<FontIdentifier, FontKey>>,
88
89    /// A collection of WebRender [`FontInstanceKey`]s generated for the web fonts that
90    /// this [`FontContext`] controls.
91    webrender_font_instance_keys: RwLock<HashMap<FontParameters, FontInstanceKey>>,
92
93    /// The data for each web font [`FontIdentifier`]. This data might be used by more than one
94    /// [`FontTemplate`] as each identifier refers to a URL.
95    font_data: RwLock<HashMap<FontIdentifier, FontData>>,
96
97    have_removed_web_fonts: AtomicBool,
98}
99
100impl FontContext {
101    pub fn new(
102        system_font_service_proxy: Arc<SystemFontServiceProxy>,
103        compositor_api: CrossProcessCompositorApi,
104        resource_threads: ResourceThreads,
105    ) -> Self {
106        #[allow(clippy::default_constructed_unit_structs)]
107        Self {
108            system_font_service_proxy,
109            resource_threads: Mutex::new(resource_threads.core_thread),
110            compositor_api: Mutex::new(compositor_api),
111            fonts: Default::default(),
112            resolved_font_groups: Default::default(),
113            web_fonts: Default::default(),
114            webrender_font_keys: RwLock::default(),
115            webrender_font_instance_keys: RwLock::default(),
116            have_removed_web_fonts: AtomicBool::new(false),
117            font_data: RwLock::default(),
118        }
119    }
120
121    pub fn web_fonts_still_loading(&self) -> usize {
122        self.web_fonts.read().number_of_fonts_still_loading()
123    }
124
125    fn get_font_data(&self, identifier: &FontIdentifier) -> Option<FontData> {
126        match identifier {
127            FontIdentifier::Web(_) => self.font_data.read().get(identifier).cloned(),
128            FontIdentifier::Local(_) => None,
129        }
130    }
131
132    /// Returns a `FontGroup` representing fonts which can be used for layout, given the `style`.
133    /// Font groups are cached, so subsequent calls with the same `style` will return a reference
134    /// to an existing `FontGroup`.
135    pub fn font_group(&self, style: ServoArc<FontStyleStruct>) -> Arc<RwLock<FontGroup>> {
136        let font_size = style.font_size.computed_size().into();
137        self.font_group_with_size(style, font_size)
138    }
139
140    /// Like [`Self::font_group`], but overriding the size found in the [`FontStyleStruct`] with the given size
141    /// in pixels.
142    pub fn font_group_with_size(
143        &self,
144        style: ServoArc<FontStyleStruct>,
145        size: Au,
146    ) -> Arc<RwLock<FontGroup>> {
147        let cache_key = FontGroupCacheKey { size, style };
148        if let Some(font_group) = self.resolved_font_groups.read().get(&cache_key) {
149            return font_group.0.clone();
150        }
151
152        let mut descriptor = FontDescriptor::from(&*cache_key.style);
153        descriptor.pt_size = size;
154
155        let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style, descriptor)));
156        self.resolved_font_groups
157            .write()
158            .insert(cache_key, FontGroupRef(font_group.clone()));
159        font_group
160    }
161
162    /// Returns a font matching the parameters. Fonts are cached, so repeated calls will return a
163    /// reference to the same underlying `Font`.
164    pub fn font(
165        &self,
166        font_template: FontTemplateRef,
167        font_descriptor: &FontDescriptor,
168    ) -> Option<FontRef> {
169        self.get_font_maybe_synthesizing_small_caps(
170            font_template,
171            font_descriptor,
172            true, /* synthesize_small_caps */
173        )
174    }
175
176    fn get_font_maybe_synthesizing_small_caps(
177        &self,
178        font_template: FontTemplateRef,
179        font_descriptor: &FontDescriptor,
180        synthesize_small_caps: bool,
181    ) -> Option<FontRef> {
182        // TODO: (Bug #3463): Currently we only support fake small-caps
183        // painting. We should also support true small-caps (where the
184        // font supports it) in the future.
185        let synthesized_small_caps_font =
186            if font_descriptor.variant == FontVariantCaps::SmallCaps && synthesize_small_caps {
187                let mut small_caps_descriptor = font_descriptor.clone();
188                small_caps_descriptor.pt_size =
189                    font_descriptor.pt_size.scale_by(SMALL_CAPS_SCALE_FACTOR);
190                self.get_font_maybe_synthesizing_small_caps(
191                    font_template.clone(),
192                    &small_caps_descriptor,
193                    false, /* synthesize_small_caps */
194                )
195            } else {
196                None
197            };
198
199        let cache_key = FontCacheKey {
200            font_identifier: font_template.identifier(),
201            font_descriptor: font_descriptor.clone(),
202        };
203
204        if let Some(font) = self.fonts.read().get(&cache_key).cloned() {
205            return font;
206        }
207
208        debug!(
209            "FontContext::font cache miss for font_template={:?} font_descriptor={:?}",
210            font_template, font_descriptor
211        );
212
213        // TODO: Inserting `None` into the cache here is a bit bogus. Instead we should somehow
214        // mark this template as invalid so it isn't tried again.
215        let font = self
216            .create_font(
217                font_template,
218                font_descriptor.to_owned(),
219                synthesized_small_caps_font,
220            )
221            .ok();
222        self.fonts.write().insert(cache_key, font.clone());
223        font
224    }
225
226    fn matching_web_font_templates(
227        &self,
228        descriptor_to_match: &FontDescriptor,
229        family_descriptor: &FontFamilyDescriptor,
230    ) -> Option<Vec<FontTemplateRef>> {
231        if family_descriptor.scope != FontSearchScope::Any {
232            return None;
233        }
234
235        // Do not look for generic fonts in our list of web fonts.
236        let SingleFontFamily::FamilyName(ref family_name) = family_descriptor.family else {
237            return None;
238        };
239
240        self.web_fonts
241            .read()
242            .families
243            .get(&family_name.name.clone().into())
244            .map(|templates| templates.find_for_descriptor(Some(descriptor_to_match)))
245    }
246
247    /// Try to find matching templates in this [`FontContext`], first looking in the list of web fonts and
248    /// falling back to asking the [`super::SystemFontService`] for a matching system font.
249    pub fn matching_templates(
250        &self,
251        descriptor_to_match: &FontDescriptor,
252        family_descriptor: &FontFamilyDescriptor,
253    ) -> Vec<FontTemplateRef> {
254        self.matching_web_font_templates(descriptor_to_match, family_descriptor)
255            .unwrap_or_else(|| {
256                self.system_font_service_proxy.find_matching_font_templates(
257                    Some(descriptor_to_match),
258                    &family_descriptor.family,
259                )
260            })
261    }
262
263    /// Create a `Font` for use in layout calculations, from a `FontTemplateData` returned by the
264    /// cache thread and a `FontDescriptor` which contains the styling parameters.
265    #[servo_tracing::instrument(skip_all)]
266    fn create_font(
267        &self,
268        font_template: FontTemplateRef,
269        font_descriptor: FontDescriptor,
270        synthesized_small_caps: Option<FontRef>,
271    ) -> Result<FontRef, &'static str> {
272        Ok(FontRef(Arc::new(Font::new(
273            font_template.clone(),
274            font_descriptor,
275            self.get_font_data(&font_template.identifier()),
276            synthesized_small_caps,
277        )?)))
278    }
279
280    pub(crate) fn create_font_instance_key(
281        &self,
282        font: &Font,
283        rendering_group_id: RenderingGroupId,
284    ) -> FontInstanceKey {
285        match font.template.identifier() {
286            FontIdentifier::Local(_) => self.system_font_service_proxy.get_system_font_instance(
287                font.template.identifier(),
288                font.descriptor.pt_size,
289                font.webrender_font_instance_flags(),
290                font.variations().to_owned(),
291                rendering_group_id,
292            ),
293            FontIdentifier::Web(_) => self.create_web_font_instance(
294                font.template.clone(),
295                font.descriptor.pt_size,
296                font.webrender_font_instance_flags(),
297                font.variations().to_owned(),
298                rendering_group_id,
299            ),
300        }
301    }
302
303    fn create_web_font_instance(
304        &self,
305        font_template: FontTemplateRef,
306        pt_size: Au,
307        flags: FontInstanceFlags,
308        variations: Vec<FontVariation>,
309        rendering_group_id: RenderingGroupId,
310    ) -> FontInstanceKey {
311        let identifier = font_template.identifier().clone();
312        let font_data = self
313            .get_font_data(&identifier)
314            .expect("Web font should have associated font data");
315        let font_key = *self
316            .webrender_font_keys
317            .write()
318            .entry(identifier.clone())
319            .or_insert_with(|| {
320                let font_key = self
321                    .system_font_service_proxy
322                    .generate_font_key(rendering_group_id);
323                self.compositor_api.lock().add_font(
324                    font_key,
325                    font_data.as_ipc_shared_memory(),
326                    identifier.index(),
327                );
328                font_key
329            });
330
331        *self
332            .webrender_font_instance_keys
333            .write()
334            .entry((font_key, pt_size, variations.clone()))
335            .or_insert_with(|| {
336                let font_instance_key = self
337                    .system_font_service_proxy
338                    .generate_font_instance_key(rendering_group_id);
339                self.compositor_api.lock().add_font_instance(
340                    font_instance_key,
341                    font_key,
342                    pt_size.to_f32_px(),
343                    flags,
344                    variations,
345                );
346                font_instance_key
347            })
348    }
349
350    fn invalidate_font_groups_after_web_font_load(&self) {
351        self.resolved_font_groups.write().clear();
352    }
353
354    pub fn is_supported_web_font_source(source: &&Source) -> bool {
355        let url_source = match &source {
356            Source::Url(url_source) => url_source,
357            Source::Local(_) => return true,
358        };
359        let format_hint = match url_source.format_hint {
360            Some(ref format_hint) => format_hint,
361            None => return true,
362        };
363
364        if matches!(
365            format_hint,
366            FontFaceSourceFormat::Keyword(
367                FontFaceSourceFormatKeyword::Truetype |
368                    FontFaceSourceFormatKeyword::Opentype |
369                    FontFaceSourceFormatKeyword::Woff |
370                    FontFaceSourceFormatKeyword::Woff2
371            )
372        ) {
373            return true;
374        }
375
376        if let FontFaceSourceFormat::String(string) = format_hint {
377            if string == "truetype" || string == "opentype" || string == "woff" || string == "woff2"
378            {
379                return true;
380            }
381
382            return pref!(layout_variable_fonts_enabled) &&
383                (string == "truetype-variations" ||
384                    string == "opentype-variations" ||
385                    string == "woff-variations" ||
386                    string == "woff2-variations");
387        }
388
389        false
390    }
391}
392
393pub(crate) struct WebFontDownloadState {
394    webview_id: Option<WebViewId>,
395    css_font_face_descriptors: CSSFontFaceDescriptors,
396    remaining_sources: Vec<Source>,
397    core_resource_thread: CoreResourceThread,
398    local_fonts: HashMap<Atom, Option<FontTemplateRef>>,
399    font_context: Arc<FontContext>,
400    initiator: WebFontLoadInitiator,
401}
402
403impl WebFontDownloadState {
404    fn new(
405        webview_id: Option<WebViewId>,
406        font_context: Arc<FontContext>,
407        css_font_face_descriptors: CSSFontFaceDescriptors,
408        initiator: WebFontLoadInitiator,
409        sources: Vec<Source>,
410        local_fonts: HashMap<Atom, Option<FontTemplateRef>>,
411    ) -> WebFontDownloadState {
412        match initiator {
413            WebFontLoadInitiator::Stylesheet(ref stylesheet, _) => {
414                font_context
415                    .web_fonts
416                    .write()
417                    .handle_web_font_load_started_for_stylesheet(stylesheet);
418            },
419            WebFontLoadInitiator::Script(_) => {
420                font_context
421                    .web_fonts
422                    .write()
423                    .handle_web_font_load_started_for_script();
424            },
425        };
426        let core_resource_thread = font_context.resource_threads.lock().clone();
427        WebFontDownloadState {
428            webview_id,
429            css_font_face_descriptors,
430            remaining_sources: sources,
431            core_resource_thread,
432            local_fonts,
433            font_context,
434            initiator,
435        }
436    }
437
438    fn handle_web_font_load_success(self, new_template: FontTemplate) {
439        let family_name = self.css_font_face_descriptors.family_name.clone();
440        match self.initiator {
441            WebFontLoadInitiator::Stylesheet(ref stylesheet, ref callback) => {
442                let not_cancelled = self
443                    .font_context
444                    .web_fonts
445                    .write()
446                    .handle_web_font_loaded_for_stylesheet(stylesheet, family_name, new_template);
447                self.font_context
448                    .invalidate_font_groups_after_web_font_load();
449                callback(not_cancelled);
450            },
451            WebFontLoadInitiator::Script(callback) => {
452                self.font_context
453                    .web_fonts
454                    .write()
455                    .handle_web_font_load_finished_for_script();
456                callback(family_name, Some(new_template));
457            },
458        }
459    }
460
461    fn handle_web_font_load_failure(self) {
462        let family_name = self.css_font_face_descriptors.family_name.clone();
463        match self.initiator {
464            WebFontLoadInitiator::Stylesheet(ref stylesheet, ref callback) => {
465                self.font_context
466                    .web_fonts
467                    .write()
468                    .handle_web_font_load_failed_for_stylesheet(stylesheet);
469                callback(false);
470            },
471            WebFontLoadInitiator::Script(callback) => {
472                self.font_context
473                    .web_fonts
474                    .write()
475                    .handle_web_font_load_finished_for_script();
476                callback(family_name, None);
477            },
478        }
479    }
480
481    fn font_load_cancelled(&self) -> bool {
482        match self.initiator {
483            WebFontLoadInitiator::Stylesheet(ref stylesheet, _) => self
484                .font_context
485                .web_fonts
486                .read()
487                .font_load_cancelled_for_stylesheet(stylesheet),
488            WebFontLoadInitiator::Script(_) => false,
489        }
490    }
491}
492
493pub trait FontContextWebFontMethods {
494    fn add_all_web_fonts_from_stylesheet(
495        &self,
496        webview_id: WebViewId,
497        stylesheet: &DocumentStyleSheet,
498        guard: &SharedRwLockReadGuard,
499        device: &Device,
500        finished_callback: StylesheetWebFontLoadFinishedCallback,
501    ) -> usize;
502    fn load_web_font_for_script(
503        &self,
504        webview_id: Option<WebViewId>,
505        source_list: SourceList,
506        descriptors: CSSFontFaceDescriptors,
507        finished_callback: ScriptWebFontLoadFinishedCallback,
508    );
509    fn add_template_to_font_context(
510        &self,
511        family_name: LowercaseFontFamilyName,
512        font_template: FontTemplate,
513    );
514    fn remove_all_web_fonts_from_stylesheet(&self, stylesheet: &DocumentStyleSheet);
515    fn collect_unused_webrender_resources(&self, all: bool)
516    -> (Vec<FontKey>, Vec<FontInstanceKey>);
517}
518
519impl FontContextWebFontMethods for Arc<FontContext> {
520    fn add_all_web_fonts_from_stylesheet(
521        &self,
522        webview_id: WebViewId,
523        stylesheet: &DocumentStyleSheet,
524        guard: &SharedRwLockReadGuard,
525        device: &Device,
526        finished_callback: StylesheetWebFontLoadFinishedCallback,
527    ) -> usize {
528        let mut number_loading = 0;
529        for rule in stylesheet.effective_rules(device, guard) {
530            let CssRule::FontFace(ref lock) = *rule else {
531                continue;
532            };
533
534            let rule: &FontFaceRule = lock.read_with(guard);
535            let Some(font_face) = rule.font_face() else {
536                continue;
537            };
538
539            let css_font_face_descriptors = rule.into();
540            let completion_handler =
541                WebFontLoadInitiator::Stylesheet(stylesheet.clone(), finished_callback.clone());
542
543            number_loading += 1;
544            self.start_loading_one_web_font(
545                Some(webview_id),
546                font_face.sources(),
547                css_font_face_descriptors,
548                completion_handler,
549            );
550        }
551
552        number_loading
553    }
554
555    fn remove_all_web_fonts_from_stylesheet(&self, stylesheet: &DocumentStyleSheet) {
556        let mut web_fonts = self.web_fonts.write();
557        let mut fonts = self.fonts.write();
558        let mut font_groups = self.resolved_font_groups.write();
559
560        // Cancel any currently in-progress web font loads.
561        web_fonts.handle_stylesheet_removed(stylesheet);
562
563        let mut removed_any = false;
564        for family in web_fonts.families.values_mut() {
565            removed_any |= family.remove_templates_for_stylesheet(stylesheet);
566        }
567        if !removed_any {
568            return;
569        };
570
571        fonts.retain(|_, font| match font {
572            Some(font) => font.template.borrow().stylesheet.as_ref() != Some(stylesheet),
573            _ => true,
574        });
575
576        // Removing this stylesheet modified the available fonts, so invalidate the cache
577        // of resolved font groups.
578        font_groups.clear();
579
580        // Ensure that we clean up any WebRender resources on the next display list update.
581        self.have_removed_web_fonts.store(true, Ordering::Relaxed);
582    }
583
584    fn collect_unused_webrender_resources(
585        &self,
586        all: bool,
587    ) -> (Vec<FontKey>, Vec<FontInstanceKey>) {
588        if all {
589            let mut webrender_font_keys = self.webrender_font_keys.write();
590            let mut webrender_font_instance_keys = self.webrender_font_instance_keys.write();
591            self.have_removed_web_fonts.store(false, Ordering::Relaxed);
592            return (
593                webrender_font_keys.drain().map(|(_, key)| key).collect(),
594                webrender_font_instance_keys
595                    .drain()
596                    .map(|(_, key)| key)
597                    .collect(),
598            );
599        }
600
601        if !self.have_removed_web_fonts.load(Ordering::Relaxed) {
602            return (Vec::new(), Vec::new());
603        }
604
605        // Lock everything to prevent adding new fonts while we are cleaning up the old ones.
606        let web_fonts = self.web_fonts.write();
607        let mut font_data = self.font_data.write();
608        let _fonts = self.fonts.write();
609        let _font_groups = self.resolved_font_groups.write();
610        let mut webrender_font_keys = self.webrender_font_keys.write();
611        let mut webrender_font_instance_keys = self.webrender_font_instance_keys.write();
612
613        let mut unused_identifiers: HashSet<FontIdentifier> =
614            webrender_font_keys.keys().cloned().collect();
615        for templates in web_fonts.families.values() {
616            templates.for_all_identifiers(|identifier| {
617                unused_identifiers.remove(identifier);
618            });
619        }
620
621        font_data.retain(|font_identifier, _| !unused_identifiers.contains(font_identifier));
622
623        self.have_removed_web_fonts.store(false, Ordering::Relaxed);
624
625        let mut removed_keys: FxHashSet<FontKey> = FxHashSet::default();
626        webrender_font_keys.retain(|identifier, font_key| {
627            if unused_identifiers.contains(identifier) {
628                removed_keys.insert(*font_key);
629                false
630            } else {
631                true
632            }
633        });
634
635        let mut removed_instance_keys: HashSet<FontInstanceKey> = HashSet::new();
636        webrender_font_instance_keys.retain(|(font_key, _, _), instance_key| {
637            if removed_keys.contains(font_key) {
638                removed_instance_keys.insert(*instance_key);
639                false
640            } else {
641                true
642            }
643        });
644
645        (
646            removed_keys.into_iter().collect(),
647            removed_instance_keys.into_iter().collect(),
648        )
649    }
650
651    fn load_web_font_for_script(
652        &self,
653        webview_id: Option<WebViewId>,
654        sources: SourceList,
655        descriptors: CSSFontFaceDescriptors,
656        finished_callback: ScriptWebFontLoadFinishedCallback,
657    ) {
658        let completion_handler = WebFontLoadInitiator::Script(finished_callback);
659        self.start_loading_one_web_font(webview_id, &sources, descriptors, completion_handler);
660    }
661
662    fn add_template_to_font_context(
663        &self,
664        family_name: LowercaseFontFamilyName,
665        new_template: FontTemplate,
666    ) {
667        self.web_fonts
668            .write()
669            .add_new_template(family_name, new_template);
670        self.invalidate_font_groups_after_web_font_load();
671    }
672}
673
674impl FontContext {
675    fn start_loading_one_web_font(
676        self: &Arc<FontContext>,
677        webview_id: Option<WebViewId>,
678        source_list: &SourceList,
679        css_font_face_descriptors: CSSFontFaceDescriptors,
680        completion_handler: WebFontLoadInitiator,
681    ) {
682        let sources: Vec<Source> = source_list
683            .0
684            .iter()
685            .rev()
686            .filter(Self::is_supported_web_font_source)
687            .cloned()
688            .collect();
689
690        // Fetch all local fonts first, beacause if we try to fetch them later on during the process of
691        // loading the list of web font `src`s we may be running in the context of the router thread, which
692        // means we won't be able to seend IPC messages to the FontCacheThread.
693        //
694        // TODO: This is completely wrong. The specification says that `local()` font-family should match
695        // against full PostScript names, but this is matching against font family names. This works...
696        // sometimes.
697        let mut local_fonts = HashMap::new();
698        for source in sources.iter() {
699            if let Source::Local(family_name) = source {
700                local_fonts
701                    .entry(family_name.name.clone())
702                    .or_insert_with(|| {
703                        let family = SingleFontFamily::FamilyName(FamilyName {
704                            name: family_name.name.clone(),
705                            syntax: FontFamilyNameSyntax::Quoted,
706                        });
707                        self.system_font_service_proxy
708                            .find_matching_font_templates(None, &family)
709                            .first()
710                            .cloned()
711                    });
712            }
713        }
714
715        self.process_next_web_font_source(WebFontDownloadState::new(
716            webview_id,
717            self.clone(),
718            css_font_face_descriptors,
719            completion_handler,
720            sources,
721            local_fonts,
722        ));
723    }
724
725    fn process_next_web_font_source(self: &Arc<FontContext>, mut state: WebFontDownloadState) {
726        let Some(source) = state.remaining_sources.pop() else {
727            state.handle_web_font_load_failure();
728            return;
729        };
730
731        let this = self.clone();
732        let web_font_family_name = state.css_font_face_descriptors.family_name.clone();
733        match source {
734            Source::Url(url_source) => {
735                RemoteWebFontDownloader::download(url_source, this, web_font_family_name, state)
736            },
737            Source::Local(ref local_family_name) => {
738                if let Some(new_template) = state
739                    .local_fonts
740                    .get(&local_family_name.name)
741                    .cloned()
742                    .flatten()
743                    .and_then(|local_template| {
744                        let template = FontTemplate::new_for_local_web_font(
745                            local_template.clone(),
746                            &state.css_font_face_descriptors,
747                            state.initiator.stylesheet().cloned(),
748                        )
749                        .ok()?;
750                        Some(template)
751                    })
752                {
753                    state.handle_web_font_load_success(new_template);
754                } else {
755                    this.process_next_web_font_source(state);
756                }
757            },
758        }
759    }
760}
761
762pub(crate) type ScriptWebFontLoadFinishedCallback =
763    Box<dyn FnOnce(LowercaseFontFamilyName, Option<FontTemplate>) + Send>;
764
765pub(crate) enum WebFontLoadInitiator {
766    Stylesheet(DocumentStyleSheet, StylesheetWebFontLoadFinishedCallback),
767    Script(ScriptWebFontLoadFinishedCallback),
768}
769
770impl WebFontLoadInitiator {
771    pub(crate) fn stylesheet(&self) -> Option<&DocumentStyleSheet> {
772        match self {
773            Self::Stylesheet(stylesheet, _) => Some(stylesheet),
774            Self::Script(_) => None,
775        }
776    }
777}
778
779struct RemoteWebFontDownloader {
780    state: Option<WebFontDownloadState>,
781    url: ServoArc<Url>,
782    web_font_family_name: LowercaseFontFamilyName,
783    response_valid: bool,
784    response_data: Vec<u8>,
785}
786
787enum DownloaderResponseResult {
788    InProcess,
789    Finished,
790    Failure,
791}
792
793impl RemoteWebFontDownloader {
794    fn download(
795        url_source: UrlSource,
796        font_context: Arc<FontContext>,
797        web_font_family_name: LowercaseFontFamilyName,
798        state: WebFontDownloadState,
799    ) {
800        // https://drafts.csswg.org/css-fonts/#font-fetching-requirements
801        let url = match url_source.url.url() {
802            Some(url) => url.clone(),
803            None => return,
804        };
805
806        // FIXME: This shouldn't use NoReferrer, but the current documents url
807        let request =
808            RequestBuilder::new(state.webview_id, url.clone().into(), Referrer::NoReferrer)
809                .destination(Destination::Font);
810
811        let core_resource_thread_clone = state.core_resource_thread.clone();
812
813        debug!("Loading @font-face {} from {}", web_font_family_name, url);
814        let mut downloader = Self {
815            url,
816            web_font_family_name,
817            response_valid: false,
818            response_data: Vec::new(),
819            state: Some(state),
820        };
821
822        fetch_async(
823            &core_resource_thread_clone,
824            request,
825            None,
826            Box::new(move |response_message| {
827                match downloader.handle_web_font_fetch_message(response_message) {
828                    DownloaderResponseResult::InProcess => {},
829                    DownloaderResponseResult::Finished => {
830                        if !downloader.process_downloaded_font_and_signal_completion() {
831                            font_context.process_next_web_font_source(downloader.take_state())
832                        }
833                    },
834                    DownloaderResponseResult::Failure => {
835                        font_context.process_next_web_font_source(downloader.take_state())
836                    },
837                }
838            }),
839        )
840    }
841
842    fn take_state(&mut self) -> WebFontDownloadState {
843        self.state
844            .take()
845            .expect("must be non-None until download either succeeds or fails")
846    }
847
848    /// After a download finishes, try to process the downloaded data, returning true if
849    /// the font is added successfully to the [`FontContext`] or false if it isn't.
850    fn process_downloaded_font_and_signal_completion(&mut self) -> bool {
851        let state = self
852            .state
853            .as_ref()
854            .expect("must be non-None until processing is completed");
855        if state.font_load_cancelled() {
856            self.take_state().handle_web_font_load_failure();
857            // Returning true here prevents trying to load the next font on the source list.
858            return true;
859        }
860
861        let font_data = std::mem::take(&mut self.response_data);
862        trace!(
863            "Downloaded @font-face {} ({} bytes)",
864            self.web_font_family_name,
865            font_data.len()
866        );
867
868        let font_data = match fontsan::process(&font_data) {
869            Ok(bytes) => FontData::from_bytes(&bytes),
870            Err(error) => {
871                debug!(
872                    "Sanitiser rejected web font: family={} url={:?} with {error:?}",
873                    self.web_font_family_name, self.url,
874                );
875                return false;
876            },
877        };
878
879        let url: ServoUrl = self.url.clone().into();
880        let identifier = FontIdentifier::Web(url.clone());
881        let Ok(handle) = PlatformFont::new_from_data(identifier, &font_data, None, &[], false)
882        else {
883            return false;
884        };
885        let state = self.take_state();
886
887        let mut descriptor = handle.descriptor();
888        descriptor
889            .override_values_with_css_font_template_descriptors(&state.css_font_face_descriptors);
890
891        let new_template = FontTemplate::new(
892            FontIdentifier::Web(url),
893            descriptor,
894            state.initiator.stylesheet().cloned(),
895        );
896
897        state
898            .font_context
899            .font_data
900            .write()
901            .insert(new_template.identifier.clone(), font_data);
902
903        state.handle_web_font_load_success(new_template);
904
905        // If the load was canceled above, then we still want to return true from this function in
906        // order to halt any attempt to load sources that come later on the source list.
907        true
908    }
909
910    fn handle_web_font_fetch_message(
911        &mut self,
912        response_message: FetchResponseMsg,
913    ) -> DownloaderResponseResult {
914        match response_message {
915            FetchResponseMsg::ProcessRequestBody(..) |
916            FetchResponseMsg::ProcessRequestEOF(..) |
917            FetchResponseMsg::ProcessCspViolations(..) => DownloaderResponseResult::InProcess,
918            FetchResponseMsg::ProcessResponse(_, meta_result) => {
919                trace!(
920                    "@font-face {} metadata ok={:?}",
921                    self.web_font_family_name,
922                    meta_result.is_ok()
923                );
924                self.response_valid = meta_result.is_ok();
925                DownloaderResponseResult::InProcess
926            },
927            FetchResponseMsg::ProcessResponseChunk(_, new_bytes) => {
928                trace!(
929                    "@font-face {} chunk={:?}",
930                    self.web_font_family_name, new_bytes
931                );
932                if self.response_valid {
933                    self.response_data.extend(new_bytes)
934                }
935                DownloaderResponseResult::InProcess
936            },
937            FetchResponseMsg::ProcessResponseEOF(_, response) => {
938                trace!(
939                    "@font-face {} EOF={:?}",
940                    self.web_font_family_name, response
941                );
942                if response.is_err() || !self.response_valid {
943                    return DownloaderResponseResult::Failure;
944                }
945                DownloaderResponseResult::Finished
946            },
947        }
948    }
949}
950
951#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)]
952struct FontCacheKey {
953    font_identifier: FontIdentifier,
954    font_descriptor: FontDescriptor,
955}
956
957#[derive(Debug, MallocSizeOf)]
958struct FontGroupCacheKey {
959    #[ignore_malloc_size_of = "This is also stored as part of styling."]
960    style: ServoArc<FontStyleStruct>,
961    size: Au,
962}
963
964impl PartialEq for FontGroupCacheKey {
965    fn eq(&self, other: &FontGroupCacheKey) -> bool {
966        self.style == other.style && self.size == other.size
967    }
968}
969
970impl Eq for FontGroupCacheKey {}
971
972impl Hash for FontGroupCacheKey {
973    fn hash<H>(&self, hasher: &mut H)
974    where
975        H: Hasher,
976    {
977        self.style.hash.hash(hasher)
978    }
979}