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