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