Skip to main content

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::sync::Arc;
9use std::sync::atomic::{AtomicBool, Ordering};
10
11use app_units::Au;
12use content_security_policy::Violation;
13use fonts_traits::{
14    CSSFontFaceDescriptors, FontDescriptor, FontIdentifier, FontTemplate, FontTemplateRef,
15    FontTemplateRefMethods, StylesheetWebFontLoadFinishedCallback,
16};
17use log::{debug, trace};
18use malloc_size_of::MallocSizeOf;
19use malloc_size_of_derive::MallocSizeOf;
20use net_traits::blob_url_store::UrlWithBlobClaim;
21use net_traits::policy_container::PolicyContainer;
22use net_traits::request::{
23    CredentialsMode, Destination, Referrer, RequestBuilder, RequestClient, RequestMode,
24    ServiceWorkersMode,
25};
26use net_traits::{
27    CoreResourceThread, FetchResponseMsg, ResourceFetchTiming, ResourceThreads, fetch_async,
28};
29use paint_api::CrossProcessPaintApi;
30use parking_lot::{Mutex, RwLock};
31use rustc_hash::FxHashSet;
32use servo_arc::Arc as ServoArc;
33use servo_base::id::{PainterId, WebViewId};
34use servo_config::pref;
35use servo_url::ServoUrl;
36use style::Atom;
37use style::computed_values::font_variant_caps::T as FontVariantCaps;
38use style::device::Device;
39use style::font_face::{
40    FontFaceSourceFormat, FontFaceSourceFormatKeyword, Source, SourceList, UrlSource,
41};
42use style::properties::generated::font_face::Descriptors as FontFaceRuleDescriptors;
43use style::properties::style_structs::Font as FontStyleStruct;
44use style::shared_lock::SharedRwLockReadGuard;
45use style::stylesheets::{
46    CssRule, CustomMediaMap, DocumentStyleSheet, FontFaceRule, StylesheetInDocument,
47};
48use style::values::computed::font::{FamilyName, FontFamilyNameSyntax, SingleFontFamily};
49use url::Url;
50use uuid::Uuid;
51use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey, FontVariation};
52
53use crate::font::{Font, FontFamilyDescriptor, FontGroup, FontRef, FontSearchScope};
54use crate::font_store::{CrossThreadFontStore, FontStore};
55use crate::platform::font::PlatformFont;
56use crate::{FontData, LowercaseFontFamilyName, PlatformFontMethods, SystemFontServiceProxy};
57
58static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h)
59
60#[derive(Eq, Hash, MallocSizeOf, PartialEq)]
61pub(crate) struct FontParameters {
62    pub(crate) font_key: FontKey,
63    pub(crate) pt_size: Au,
64    pub(crate) variations: Vec<FontVariation>,
65    pub(crate) flags: FontInstanceFlags,
66}
67
68pub type FontGroupRef = Arc<FontGroup>;
69
70/// The FontContext represents the per-thread/thread state necessary for
71/// working with fonts. It is the public API used by the layout and
72/// paint code. It talks directly to the system font service where
73/// required.
74#[derive(MallocSizeOf)]
75pub struct FontContext {
76    #[conditional_malloc_size_of]
77    system_font_service_proxy: Arc<SystemFontServiceProxy>,
78
79    resource_threads: Mutex<CoreResourceThread>,
80
81    /// A sender that can send messages and receive replies from `Paint`.
82    paint_api: Mutex<CrossProcessPaintApi>,
83
84    /// The actual instances of fonts ie a [`FontTemplate`] combined with a size and
85    /// other font properties, along with the font data and a platform font instance.
86    fonts: RwLock<HashMap<FontCacheKey, Option<FontRef>>>,
87
88    /// A caching map between the specification of a font in CSS style and
89    /// resolved [`FontGroup`] which contains information about all fonts that
90    /// can be selected with that style.
91    #[conditional_malloc_size_of]
92    resolved_font_groups: RwLock<HashMap<FontGroupCacheKey, FontGroupRef>>,
93
94    web_fonts: CrossThreadFontStore,
95
96    /// A collection of WebRender [`FontKey`]s generated for the web fonts that this
97    /// [`FontContext`] controls.
98    webrender_font_keys: RwLock<HashMap<FontIdentifier, FontKey>>,
99
100    /// A collection of WebRender [`FontInstanceKey`]s generated for the web fonts that
101    /// this [`FontContext`] controls.
102    webrender_font_instance_keys: RwLock<HashMap<FontParameters, FontInstanceKey>>,
103
104    /// The data for each web font [`FontIdentifier`]. This data might be used by more than one
105    /// [`FontTemplate`] as each identifier refers to a URL.
106    font_data: RwLock<HashMap<FontIdentifier, FontData>>,
107
108    have_removed_web_fonts: AtomicBool,
109
110    /// Maps from a URL to all the `@font-face` rules that are currently waiting for the load to
111    /// finish.
112    currently_downloading_fonts: Mutex<HashMap<ServoUrl, Vec<WebFontDownloadState>>>,
113}
114
115/// A callback that will be invoked on the Fetch thread if a web font download
116/// results in CSP violations. This handler will be cloned each time a new
117/// web font download is initiated.
118pub trait CspViolationHandler: Send + std::fmt::Debug + MallocSizeOf {
119    fn process_violations(&self, violations: Vec<Violation>);
120    fn clone(&self) -> Box<dyn CspViolationHandler>;
121}
122
123/// A callback that will be invoked on the Fetch thread when a web font
124/// download succeeds, providing timing information about the request.
125pub trait NetworkTimingHandler: Send + std::fmt::Debug + MallocSizeOf {
126    fn submit_timing(&self, url: ServoUrl, response: ResourceFetchTiming);
127    fn clone(&self) -> Box<dyn NetworkTimingHandler>;
128}
129
130/// Document-specific data required to fetch a web font.
131#[derive(Debug, MallocSizeOf)]
132pub struct WebFontDocumentContext {
133    pub policy_container: PolicyContainer,
134    pub request_client: RequestClient,
135    pub document_url: ServoUrl,
136    pub csp_handler: Box<dyn CspViolationHandler>,
137    pub network_timing_handler: Box<dyn NetworkTimingHandler>,
138}
139
140impl Clone for WebFontDocumentContext {
141    fn clone(&self) -> WebFontDocumentContext {
142        Self {
143            policy_container: self.policy_container.clone(),
144            request_client: self.request_client.clone(),
145            document_url: self.document_url.clone(),
146            csp_handler: self.csp_handler.clone(),
147            network_timing_handler: self.network_timing_handler.clone(),
148        }
149    }
150}
151
152impl FontContext {
153    pub fn new(
154        system_font_service_proxy: Arc<SystemFontServiceProxy>,
155        paint_api: CrossProcessPaintApi,
156        resource_threads: ResourceThreads,
157    ) -> Self {
158        Self {
159            system_font_service_proxy,
160            resource_threads: Mutex::new(resource_threads.core_thread),
161            paint_api: Mutex::new(paint_api),
162            fonts: Default::default(),
163            resolved_font_groups: Default::default(),
164            web_fonts: Default::default(),
165            webrender_font_keys: RwLock::default(),
166            webrender_font_instance_keys: RwLock::default(),
167            have_removed_web_fonts: AtomicBool::new(false),
168            font_data: RwLock::default(),
169            currently_downloading_fonts: Default::default(),
170        }
171    }
172
173    pub fn web_fonts_still_loading(&self) -> usize {
174        self.currently_downloading_fonts.lock().len()
175    }
176
177    fn get_font_data(&self, identifier: &FontIdentifier) -> Option<FontData> {
178        match identifier {
179            FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => {
180                self.font_data.read().get(identifier).cloned()
181            },
182            FontIdentifier::Local(_) => None,
183        }
184    }
185
186    /// Returns a `FontGroup` representing fonts which can be used for layout, given the `style`.
187    /// Font groups are cached, so subsequent calls with the same `style` will return a reference
188    /// to an existing `FontGroup`.
189    pub fn font_group(&self, style: ServoArc<FontStyleStruct>) -> FontGroupRef {
190        let font_size = style.font_size.computed_size().into();
191        self.font_group_with_size(style, font_size)
192    }
193
194    /// Like [`Self::font_group`], but overriding the size found in the [`FontStyleStruct`] with the given size
195    /// in pixels.
196    pub fn font_group_with_size(
197        &self,
198        style: ServoArc<FontStyleStruct>,
199        size: Au,
200    ) -> Arc<FontGroup> {
201        let cache_key = FontGroupCacheKey { size, style };
202        if let Some(font_group) = self.resolved_font_groups.read().get(&cache_key) {
203            return font_group.clone();
204        }
205
206        let mut descriptor = FontDescriptor::from(&*cache_key.style);
207        descriptor.pt_size = size;
208
209        let font_group = Arc::new(FontGroup::new(&cache_key.style, descriptor));
210        self.resolved_font_groups
211            .write()
212            .insert(cache_key, font_group.clone());
213        font_group
214    }
215
216    /// Returns a font matching the parameters. Fonts are cached, so repeated calls will return a
217    /// reference to the same underlying `Font`.
218    pub fn font(
219        &self,
220        font_template: FontTemplateRef,
221        font_descriptor: &FontDescriptor,
222    ) -> Option<FontRef> {
223        let font_descriptor = if servo_config::pref!(layout_variable_fonts_enabled) {
224            let variation_settings = font_template.borrow().compute_variations(font_descriptor);
225            &font_descriptor.with_variation_settings(variation_settings)
226        } else {
227            font_descriptor
228        };
229
230        self.get_font_maybe_synthesizing_small_caps(
231            font_template,
232            font_descriptor,
233            true, /* synthesize_small_caps */
234        )
235    }
236
237    fn get_font_maybe_synthesizing_small_caps(
238        &self,
239        font_template: FontTemplateRef,
240        font_descriptor: &FontDescriptor,
241        synthesize_small_caps: bool,
242    ) -> Option<FontRef> {
243        // TODO: (Bug #3463): Currently we only support fake small-caps
244        // painting. We should also support true small-caps (where the
245        // font supports it) in the future.
246        let synthesized_small_caps_font =
247            if font_descriptor.variant == FontVariantCaps::SmallCaps && synthesize_small_caps {
248                let mut small_caps_descriptor = font_descriptor.clone();
249                small_caps_descriptor.pt_size =
250                    font_descriptor.pt_size.scale_by(SMALL_CAPS_SCALE_FACTOR);
251                self.get_font_maybe_synthesizing_small_caps(
252                    font_template.clone(),
253                    &small_caps_descriptor,
254                    false, /* synthesize_small_caps */
255                )
256            } else {
257                None
258            };
259
260        let cache_key = FontCacheKey {
261            font_identifier: font_template.identifier(),
262            font_descriptor: font_descriptor.clone(),
263        };
264
265        if let Some(font) = self.fonts.read().get(&cache_key).cloned() {
266            return font;
267        }
268
269        debug!(
270            "FontContext::font cache miss for font_template={:?} font_descriptor={:?}",
271            font_template, font_descriptor
272        );
273
274        // Check one more time whether the font is cached or not. There's a potential race
275        // condition, where between the time we took the read lock above and now, another thread
276        // added the font to the cache. This check makes sense, because loading a font has memory
277        // implications and is much slower than checking the map again.
278        let mut fonts = self.fonts.write();
279        if let Some(font) = fonts.get(&cache_key).cloned() {
280            return font;
281        }
282
283        // TODO: Inserting `None` into the cache here is a bit bogus. Instead we should somehow
284        // mark this template as invalid so it isn't tried again.
285        let font = self
286            .create_font(
287                font_template,
288                font_descriptor.to_owned(),
289                synthesized_small_caps_font,
290            )
291            .ok();
292        fonts.insert(cache_key, font.clone());
293        font
294    }
295
296    fn matching_web_font_templates(
297        &self,
298        descriptor_to_match: &FontDescriptor,
299        family_descriptor: &FontFamilyDescriptor,
300    ) -> Option<Vec<FontTemplateRef>> {
301        if family_descriptor.scope != FontSearchScope::Any {
302            return None;
303        }
304
305        // Do not look for generic fonts in our list of web fonts.
306        let SingleFontFamily::FamilyName(ref family_name) = family_descriptor.family else {
307            return None;
308        };
309
310        self.web_fonts
311            .read()
312            .families
313            .get(&family_name.name.clone().into())
314            .map(|templates| templates.find_for_descriptor(Some(descriptor_to_match)))
315    }
316
317    /// Try to find matching templates in this [`FontContext`], first looking in the list of web fonts and
318    /// falling back to asking the [`super::SystemFontService`] for a matching system font.
319    pub fn matching_templates(
320        &self,
321        descriptor_to_match: &FontDescriptor,
322        family_descriptor: &FontFamilyDescriptor,
323    ) -> Vec<FontTemplateRef> {
324        self.matching_web_font_templates(descriptor_to_match, family_descriptor)
325            .unwrap_or_else(|| {
326                self.system_font_service_proxy.find_matching_font_templates(
327                    Some(descriptor_to_match),
328                    &family_descriptor.family,
329                )
330            })
331    }
332
333    /// Create a `Font` for use in layout calculations, from a `FontTemplateData` returned by the
334    /// cache thread and a `FontDescriptor` which contains the styling parameters.
335    #[servo_tracing::instrument(skip_all)]
336    fn create_font(
337        &self,
338        font_template: FontTemplateRef,
339        font_descriptor: FontDescriptor,
340        synthesized_small_caps: Option<FontRef>,
341    ) -> Result<FontRef, &'static str> {
342        Ok(FontRef(Arc::new(Font::new(
343            font_template.clone(),
344            font_descriptor,
345            self.get_font_data(&font_template.identifier()),
346            synthesized_small_caps,
347        )?)))
348    }
349
350    pub(crate) fn create_font_instance_key(
351        &self,
352        font: &Font,
353        painter_id: PainterId,
354    ) -> FontInstanceKey {
355        match font.template.identifier() {
356            FontIdentifier::Local(_) => self.system_font_service_proxy.get_system_font_instance(
357                font.template.identifier(),
358                font.descriptor.pt_size,
359                font.webrender_font_instance_flags(),
360                font.variations().to_owned(),
361                painter_id,
362            ),
363            FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => self
364                .create_web_font_instance(
365                    font.template.clone(),
366                    font.descriptor.pt_size,
367                    font.webrender_font_instance_flags(),
368                    font.variations().to_owned(),
369                    painter_id,
370                ),
371        }
372    }
373
374    fn create_web_font_instance(
375        &self,
376        font_template: FontTemplateRef,
377        pt_size: Au,
378        flags: FontInstanceFlags,
379        variations: Vec<FontVariation>,
380        painter_id: PainterId,
381    ) -> FontInstanceKey {
382        let identifier = font_template.identifier();
383        let font_data = self
384            .get_font_data(&identifier)
385            .expect("Web font should have associated font data");
386        let font_key = *self
387            .webrender_font_keys
388            .write()
389            .entry(identifier.clone())
390            .or_insert_with(|| {
391                let font_key = self.system_font_service_proxy.generate_font_key(painter_id);
392                self.paint_api.lock().add_font(
393                    font_key,
394                    font_data.as_ipc_shared_memory(),
395                    identifier.index(),
396                );
397                font_key
398            });
399
400        let entry_key = FontParameters {
401            font_key,
402            pt_size,
403            variations: variations.clone(),
404            flags,
405        };
406        *self
407            .webrender_font_instance_keys
408            .write()
409            .entry(entry_key)
410            .or_insert_with(|| {
411                let font_instance_key = self
412                    .system_font_service_proxy
413                    .generate_font_instance_key(painter_id);
414                self.paint_api.lock().add_font_instance(
415                    font_instance_key,
416                    font_key,
417                    pt_size.to_f32_px(),
418                    flags,
419                    variations,
420                );
421                font_instance_key
422            })
423    }
424
425    fn invalidate_font_groups_after_web_font_load(&self) {
426        self.resolved_font_groups.write().clear();
427    }
428
429    pub fn is_supported_web_font_source(source: &&Source) -> bool {
430        let url_source = match &source {
431            Source::Url(url_source) => url_source,
432            Source::Local(_) => return true,
433        };
434        let format_hint = match url_source.format_hint {
435            Some(ref format_hint) => format_hint,
436            None => return true,
437        };
438
439        if matches!(
440            format_hint,
441            FontFaceSourceFormat::Keyword(
442                FontFaceSourceFormatKeyword::Truetype |
443                    FontFaceSourceFormatKeyword::Opentype |
444                    FontFaceSourceFormatKeyword::Woff |
445                    FontFaceSourceFormatKeyword::Woff2
446            )
447        ) {
448            return true;
449        }
450
451        if let FontFaceSourceFormat::String(string) = format_hint {
452            if string == "truetype" || string == "opentype" || string == "woff" || string == "woff2"
453            {
454                return true;
455            }
456
457            return pref!(layout_variable_fonts_enabled) &&
458                (string == "truetype-variations" ||
459                    string == "opentype-variations" ||
460                    string == "woff-variations" ||
461                    string == "woff2-variations");
462        }
463
464        false
465    }
466
467    fn is_local_or_unknown_url_font(
468        &self,
469        family_name: &LowercaseFontFamilyName,
470        source: &Source,
471    ) -> bool {
472        match source {
473            Source::Url(url) => !url
474                .url
475                .url()
476                .cloned()
477                .map(ServoUrl::from)
478                .map(FontIdentifier::Web)
479                .filter(|font_identifier| self.font_data.read().contains_key(font_identifier))
480                .is_some_and(|font_identifier| {
481                    self.web_fonts
482                        .read()
483                        .families
484                        .get(family_name)
485                        .is_some_and(|templates| {
486                            templates
487                                .templates
488                                .iter()
489                                .any(|template| template.borrow().identifier == font_identifier)
490                        })
491                }),
492            Source::Local(_) => true,
493        }
494    }
495
496    pub(crate) fn handle_web_font_request_started(
497        &self,
498        url: ServoUrl,
499        state: WebFontDownloadState,
500    ) {
501        self.currently_downloading_fonts
502            .lock()
503            .entry(url)
504            .or_default()
505            .push(state);
506    }
507
508    /// Handle a web font load finishing, adding the new font to the [`FontStore`]. If the web font
509    /// load was canceled (for instance, if the stylesheet was removed), then do nothing and return
510    /// false.
511    ///
512    /// All download states waiting for this entry to load will have their promise fulfilled.
513    pub(crate) fn handle_web_font_request_succeeded(
514        &self,
515        font_data: FontData,
516        url: ServoUrl,
517    ) -> bool {
518        let Some(download_states) = self.currently_downloading_fonts.lock().remove(&url) else {
519            // No one is waiting for this web font to load ):
520            return false;
521        };
522        debug_assert!(
523            !download_states.is_empty(),
524            "Should have removed this entry"
525        );
526
527        let identifier = FontIdentifier::Web(url);
528        let Ok(handle) =
529            PlatformFont::new_from_data(identifier.clone(), &font_data, None, &[], false)
530        else {
531            return false;
532        };
533
534        self.font_data.write().insert(identifier.clone(), font_data);
535        let descriptor = handle.descriptor();
536        for download_state in download_states {
537            let mut descriptor = descriptor.clone();
538            descriptor.override_values_with_css_font_template_descriptors(
539                &download_state.css_font_face_descriptors,
540            );
541
542            let new_template = FontTemplate::new(
543                identifier.clone(),
544                descriptor,
545                download_state.initiator.font_face_rule().cloned(),
546            );
547
548            download_state.handle_web_font_load_success(new_template);
549        }
550
551        true
552    }
553
554    pub(crate) fn has_pending_font_requests_for_url(&self, url: ServoArc<Url>) -> bool {
555        self.currently_downloading_fonts
556            .lock()
557            .contains_key(&url.into())
558    }
559}
560
561/// Tracks the progress of loading a single `@font-face` rule by trying all specified
562/// sources in order.
563#[derive(MallocSizeOf)]
564pub(crate) struct WebFontDownloadState {
565    webview_id: Option<WebViewId>,
566    css_font_face_descriptors: CSSFontFaceDescriptors,
567    remaining_sources: Vec<Source>,
568    local_fonts: HashMap<Atom, Option<FontTemplateRef>>,
569    #[conditional_malloc_size_of]
570    pub(crate) font_context: Arc<FontContext>,
571    initiator: WebFontLoadInitiator,
572    document_context: WebFontDocumentContext,
573}
574
575impl WebFontDownloadState {
576    fn new(
577        webview_id: Option<WebViewId>,
578        font_context: Arc<FontContext>,
579        css_font_face_descriptors: CSSFontFaceDescriptors,
580        initiator: WebFontLoadInitiator,
581        sources: Vec<Source>,
582        local_fonts: HashMap<Atom, Option<FontTemplateRef>>,
583        document_context: WebFontDocumentContext,
584    ) -> WebFontDownloadState {
585        WebFontDownloadState {
586            webview_id,
587            css_font_face_descriptors,
588            remaining_sources: sources,
589            local_fonts,
590            font_context,
591            initiator,
592            document_context,
593        }
594    }
595
596    pub(crate) fn handle_web_font_load_success(self, new_template: FontTemplate) {
597        let family_name = self.css_font_face_descriptors.family_name.clone();
598        match self.initiator {
599            WebFontLoadInitiator::Stylesheet(initiator) => {
600                self.font_context
601                    .web_fonts
602                    .write()
603                    .add_new_template(family_name, new_template);
604                self.font_context
605                    .invalidate_font_groups_after_web_font_load();
606                (initiator.callback)(true);
607            },
608            WebFontLoadInitiator::Script(callback) => {
609                callback(family_name, Some(new_template));
610            },
611        }
612    }
613
614    /// Called when we've tried all available sources and none were usable.
615    pub(crate) fn handle_web_font_load_failure(self) {
616        let family_name = self.css_font_face_descriptors.family_name.clone();
617        match self.initiator {
618            WebFontLoadInitiator::Stylesheet(initiator) => {
619                (initiator.callback)(false);
620            },
621            WebFontLoadInitiator::Script(callback) => {
622                callback(family_name, None);
623            },
624        }
625    }
626
627    fn was_created_for_font_face_rule(&self, font_face_rule: &FontFaceRuleDescriptors) -> bool {
628        self.initiator
629            .font_face_rule()
630            .is_some_and(|initiating_rule| initiating_rule == font_face_rule)
631    }
632}
633
634pub trait FontContextWebFontMethods {
635    fn add_all_web_fonts_from_stylesheet(
636        &self,
637        webview_id: WebViewId,
638        stylesheet: &DocumentStyleSheet,
639        guard: &SharedRwLockReadGuard,
640        device: &Device,
641        finished_callback: StylesheetWebFontLoadFinishedCallback,
642        document_context: &WebFontDocumentContext,
643    ) -> usize;
644    fn load_web_font_for_script(
645        &self,
646        webview_id: Option<WebViewId>,
647        sources: SourceList,
648        descriptors: CSSFontFaceDescriptors,
649        finished_callback: ScriptWebFontLoadFinishedCallback,
650        document_context: &WebFontDocumentContext,
651    );
652    fn handle_web_font_request_failed(&self, url: ServoUrl);
653}
654
655impl FontContextWebFontMethods for Arc<FontContext> {
656    fn add_all_web_fonts_from_stylesheet(
657        &self,
658        webview_id: WebViewId,
659        stylesheet: &DocumentStyleSheet,
660        guard: &SharedRwLockReadGuard,
661        device: &Device,
662        finished_callback: StylesheetWebFontLoadFinishedCallback,
663        document_context: &WebFontDocumentContext,
664    ) -> usize {
665        let mut number_loading = 0;
666        let custom_media = &CustomMediaMap::default();
667        for rule in stylesheet
668            .contents(guard)
669            .effective_rules(device, custom_media, guard)
670        {
671            let CssRule::FontFace(ref lock) = *rule else {
672                continue;
673            };
674
675            let rule: &FontFaceRule = lock.read_with(guard);
676
677            // Per https://github.com/w3c/csswg-drafts/issues/1133 an @font-face rule
678            // is valid as far as the CSS parser is concerned even if it doesn’t have
679            // a font-family or src declaration.
680            // However, both are required for the rule to represent an actual font face.
681            if rule.descriptors.font_family.is_none() {
682                continue;
683            }
684            let Some(ref sources) = rule.descriptors.src else {
685                continue;
686            };
687
688            let css_font_face_descriptors = rule.into();
689
690            let initiator = FontFaceRuleInitiator {
691                font_face_rule: rule.descriptors.clone(),
692                callback: finished_callback.clone(),
693            };
694
695            number_loading += 1;
696            self.start_loading_one_web_font(
697                Some(webview_id),
698                sources,
699                css_font_face_descriptors,
700                WebFontLoadInitiator::Stylesheet(Box::new(initiator)),
701                document_context,
702            );
703        }
704
705        number_loading
706    }
707
708    fn load_web_font_for_script(
709        &self,
710        webview_id: Option<WebViewId>,
711        sources: SourceList,
712        descriptors: CSSFontFaceDescriptors,
713        finished_callback: ScriptWebFontLoadFinishedCallback,
714        document_context: &WebFontDocumentContext,
715    ) {
716        let completion_handler = WebFontLoadInitiator::Script(finished_callback);
717        self.start_loading_one_web_font(
718            webview_id,
719            &sources,
720            descriptors,
721            completion_handler,
722            document_context,
723        );
724    }
725
726    /// Called when a single URL for a `@font-face` failed to load.
727    fn handle_web_font_request_failed(&self, url: ServoUrl) {
728        let Some(subscribers) = self.currently_downloading_fonts.lock().remove(&url) else {
729            return;
730        };
731
732        for subscriber in subscribers {
733            self.process_next_web_font_source(subscriber);
734        }
735    }
736}
737
738impl FontContext {
739    pub fn collect_unused_webrender_resources(
740        &self,
741        all: bool,
742    ) -> (Vec<FontKey>, Vec<FontInstanceKey>) {
743        if all {
744            let mut webrender_font_keys = self.webrender_font_keys.write();
745            let mut webrender_font_instance_keys = self.webrender_font_instance_keys.write();
746            self.have_removed_web_fonts.store(false, Ordering::Relaxed);
747            return (
748                webrender_font_keys.drain().map(|(_, key)| key).collect(),
749                webrender_font_instance_keys
750                    .drain()
751                    .map(|(_, key)| key)
752                    .collect(),
753            );
754        }
755
756        if !self.have_removed_web_fonts.load(Ordering::Relaxed) {
757            return (Vec::new(), Vec::new());
758        }
759
760        // Lock everything to prevent adding new fonts while we are cleaning up the old ones.
761        let web_fonts = self.web_fonts.write();
762        let mut font_data = self.font_data.write();
763        let _fonts = self.fonts.write();
764        let _font_groups = self.resolved_font_groups.write();
765        let mut webrender_font_keys = self.webrender_font_keys.write();
766        let mut webrender_font_instance_keys = self.webrender_font_instance_keys.write();
767
768        let mut unused_identifiers: HashSet<FontIdentifier> =
769            webrender_font_keys.keys().cloned().collect();
770        for templates in web_fonts.families.values() {
771            templates.for_all_identifiers(|identifier| {
772                unused_identifiers.remove(identifier);
773            });
774        }
775
776        font_data.retain(|font_identifier, _| !unused_identifiers.contains(font_identifier));
777
778        self.have_removed_web_fonts.store(false, Ordering::Relaxed);
779
780        let mut removed_keys: FxHashSet<FontKey> = FxHashSet::default();
781        webrender_font_keys.retain(|identifier, font_key| {
782            if unused_identifiers.contains(identifier) {
783                removed_keys.insert(*font_key);
784                false
785            } else {
786                true
787            }
788        });
789
790        let mut removed_instance_keys: HashSet<FontInstanceKey> = HashSet::new();
791        webrender_font_instance_keys.retain(|font_param, instance_key| {
792            if removed_keys.contains(&font_param.font_key) {
793                removed_instance_keys.insert(*instance_key);
794                false
795            } else {
796                true
797            }
798        });
799
800        (
801            removed_keys.into_iter().collect(),
802            removed_instance_keys.into_iter().collect(),
803        )
804    }
805
806    /// Returns `true` if any font templates were removed.
807    fn remove_single_font_face_rule(
808        &self,
809        font_face_rule: &FontFaceRuleDescriptors,
810        font_store: &mut FontStore,
811    ) -> bool {
812        let Some(family) = font_face_rule.font_family.as_ref() else {
813            return false;
814        };
815
816        // Mark any ongoing load operations for this font as cancelled.
817        self.currently_downloading_fonts
818            .lock()
819            .retain(|_, download_states| {
820                download_states.retain(|download_state| {
821                    !download_state.was_created_for_font_face_rule(font_face_rule)
822                });
823
824                !download_states.is_empty()
825            });
826
827        let lowercase_family_name: LowercaseFontFamilyName = family.name.clone().into();
828        let Some(known_family) = font_store.families.get_mut(&lowercase_family_name) else {
829            return false;
830        };
831        if !known_family.remove_template_for_font_face_rule(font_face_rule) {
832            return false;
833        }
834        self.fonts.write().retain(|_, font| match font {
835            Some(font) => !font
836                .template
837                .borrow()
838                .is_defined_by_font_face_rule(font_face_rule),
839            _ => true,
840        });
841
842        true
843    }
844
845    pub fn remove_all_web_fonts_from_stylesheet(
846        &self,
847        stylesheet: &DocumentStyleSheet,
848        device: &Device,
849        guard: &SharedRwLockReadGuard,
850    ) {
851        let mut web_fonts = self.web_fonts.write();
852
853        // TODO: Walking the stylesheet should not be necessary here. Instead, we can diff
854        // the results from Stylist.iter_extra_data() and find out which font face rules were
855        // removed.
856        let mut removed_any = false;
857        let custom_media = &CustomMediaMap::default();
858        for rule in stylesheet
859            .contents(guard)
860            .effective_rules(device, custom_media, guard)
861        {
862            let CssRule::FontFace(ref lock) = *rule else {
863                continue;
864            };
865            let rule: &FontFaceRule = lock.read_with(guard);
866
867            removed_any |= self.remove_single_font_face_rule(&rule.descriptors, &mut web_fonts);
868        }
869
870        if !removed_any {
871            return;
872        };
873
874        // Removing this stylesheet modified the available fonts, so invalidate the cache
875        // of resolved font groups.
876        self.resolved_font_groups.write().clear();
877
878        // Ensure that we clean up any WebRender resources on the next display list update.
879        self.have_removed_web_fonts.store(true, Ordering::Relaxed);
880    }
881
882    pub fn add_template_to_font_context(
883        &self,
884        family_name: LowercaseFontFamilyName,
885        new_template: FontTemplate,
886    ) {
887        self.web_fonts
888            .write()
889            .add_new_template(family_name, new_template);
890        self.invalidate_font_groups_after_web_font_load();
891    }
892
893    pub fn construct_web_font_from_data(
894        &self,
895        data: &[u8],
896        descriptors: CSSFontFaceDescriptors,
897    ) -> Option<(LowercaseFontFamilyName, FontTemplate)> {
898        let bytes = fontsan::process(data)
899            .inspect_err(|error| {
900                debug!(
901                    "Sanitiser rejected FontFace font: family={} with {error:?}",
902                    descriptors.family_name,
903                );
904            })
905            .ok()?;
906        let font_data = FontData::from_bytes(&bytes);
907
908        let identifier = FontIdentifier::ArrayBuffer(Uuid::new_v4());
909        let handle =
910            PlatformFont::new_from_data(identifier.clone(), &font_data, None, &[], false).ok()?;
911
912        let new_template = FontTemplate::new(identifier.clone(), handle.descriptor(), None);
913
914        self.font_data.write().insert(identifier, font_data);
915
916        Some((descriptors.family_name, new_template))
917    }
918
919    fn start_loading_one_web_font(
920        self: &Arc<FontContext>,
921        webview_id: Option<WebViewId>,
922        source_list: &SourceList,
923        css_font_face_descriptors: CSSFontFaceDescriptors,
924        completion_handler: WebFontLoadInitiator,
925        document_context: &WebFontDocumentContext,
926    ) {
927        let sources: Vec<Source> = source_list
928            .0
929            .iter()
930            .rev()
931            .filter(Self::is_supported_web_font_source)
932            .filter(|source| {
933                self.is_local_or_unknown_url_font(&css_font_face_descriptors.family_name, source)
934            })
935            .cloned()
936            .collect();
937
938        // Fetch all local fonts first, beacause if we try to fetch them later on during the process of
939        // loading the list of web font `src`s we may be running in the context of the router thread, which
940        // means we won't be able to seend IPC messages to the FontCacheThread.
941        //
942        // TODO: This is completely wrong. The specification says that `local()` font-family should match
943        // against full PostScript names, but this is matching against font family names. This works...
944        // sometimes.
945        let mut local_fonts = HashMap::new();
946        for source in sources.iter() {
947            if let Source::Local(family_name) = source {
948                local_fonts
949                    .entry(family_name.name.clone())
950                    .or_insert_with(|| {
951                        let family = SingleFontFamily::FamilyName(FamilyName {
952                            name: family_name.name.clone(),
953                            syntax: FontFamilyNameSyntax::Quoted,
954                        });
955                        self.system_font_service_proxy
956                            .find_matching_font_templates(None, &family)
957                            .first()
958                            .cloned()
959                    });
960            }
961        }
962
963        self.process_next_web_font_source(WebFontDownloadState::new(
964            webview_id,
965            self.clone(),
966            css_font_face_descriptors,
967            completion_handler,
968            sources,
969            local_fonts,
970            document_context.clone(),
971        ));
972    }
973
974    pub(crate) fn process_next_web_font_source(
975        self: &Arc<FontContext>,
976        mut state: WebFontDownloadState,
977    ) {
978        let Some(source) = state.remaining_sources.pop() else {
979            state.handle_web_font_load_failure();
980            return;
981        };
982
983        let this = self.clone();
984        let web_font_family_name = state.css_font_face_descriptors.family_name.clone();
985        match source {
986            Source::Url(url_source) => {
987                RemoteWebFontDownloader::download(url_source, this, web_font_family_name, state)
988            },
989            Source::Local(ref local_family_name) => {
990                if let Some(new_template) = state
991                    .local_fonts
992                    .get(&local_family_name.name)
993                    .cloned()
994                    .flatten()
995                    .and_then(|local_template| {
996                        let template = FontTemplate::new_for_local_web_font(
997                            local_template,
998                            &state.css_font_face_descriptors,
999                            state.initiator.font_face_rule().cloned(),
1000                        )
1001                        .ok()?;
1002                        Some(template)
1003                    })
1004                {
1005                    state.handle_web_font_load_success(new_template);
1006                } else {
1007                    this.process_next_web_font_source(state);
1008                }
1009            },
1010        }
1011    }
1012}
1013
1014pub(crate) type ScriptWebFontLoadFinishedCallback =
1015    Box<dyn FnOnce(LowercaseFontFamilyName, Option<FontTemplate>) + Send>;
1016
1017#[derive(MallocSizeOf)]
1018pub(crate) struct FontFaceRuleInitiator {
1019    font_face_rule: FontFaceRuleDescriptors,
1020    #[ignore_malloc_size_of = "dyn Fn"]
1021    callback: StylesheetWebFontLoadFinishedCallback,
1022}
1023
1024#[derive(MallocSizeOf)]
1025pub(crate) enum WebFontLoadInitiator {
1026    Stylesheet(Box<FontFaceRuleInitiator>),
1027    Script(#[ignore_malloc_size_of = "dyn Fn"] ScriptWebFontLoadFinishedCallback),
1028}
1029
1030impl WebFontLoadInitiator {
1031    pub(crate) fn font_face_rule(&self) -> Option<&FontFaceRuleDescriptors> {
1032        match self {
1033            Self::Stylesheet(initiator) => Some(&initiator.font_face_rule),
1034            Self::Script(_) => None,
1035        }
1036    }
1037}
1038
1039struct RemoteWebFontDownloader {
1040    /// The URL of the font currently being loaded.
1041    url: ServoArc<Url>,
1042    web_font_family_name: LowercaseFontFamilyName,
1043    response_valid: bool,
1044    /// The data that has been received from the network thread so far.
1045    response_data: Vec<u8>,
1046    document_context: WebFontDocumentContext,
1047    font_context: Arc<FontContext>,
1048}
1049
1050enum DownloaderResponseResult {
1051    InProcess,
1052    Finished,
1053    Failure,
1054}
1055
1056impl RemoteWebFontDownloader {
1057    fn download(
1058        url_source: UrlSource,
1059        font_context: Arc<FontContext>,
1060        web_font_family_name: LowercaseFontFamilyName,
1061        state: WebFontDownloadState,
1062    ) {
1063        // https://drafts.csswg.org/css-fonts/#font-fetching-requirements
1064        let url = match url_source.url.url() {
1065            Some(url) => url.clone(),
1066            None => return,
1067        };
1068
1069        let document_context = &state.document_context;
1070
1071        let request = RequestBuilder::new(
1072            state.webview_id,
1073            UrlWithBlobClaim::from_url_without_having_claimed_blob(url.clone().into()),
1074            Referrer::ReferrerUrl(document_context.document_url.clone()),
1075        )
1076        .destination(Destination::Font)
1077        .mode(RequestMode::CorsMode)
1078        .credentials_mode(CredentialsMode::CredentialsSameOrigin)
1079        .service_workers_mode(ServiceWorkersMode::All)
1080        .policy_container(document_context.policy_container.clone())
1081        .client(document_context.request_client.clone());
1082
1083        let core_resource_thread_clone = font_context.resource_threads.lock().clone();
1084
1085        debug!("Loading @font-face {} from {}", web_font_family_name, url);
1086        let mut downloader = Self {
1087            url: url.clone(),
1088            web_font_family_name,
1089            response_valid: false,
1090            response_data: Vec::new(),
1091            document_context: document_context.clone(),
1092            font_context: font_context.clone(),
1093        };
1094
1095        font_context.handle_web_font_request_started(url.into(), state);
1096        fetch_async(
1097            &core_resource_thread_clone,
1098            request,
1099            None,
1100            Box::new(move |response_message| {
1101                match downloader.handle_web_font_fetch_message(response_message) {
1102                    DownloaderResponseResult::InProcess => {},
1103                    DownloaderResponseResult::Finished => {
1104                        downloader.process_downloaded_font_and_signal_completion()
1105                    },
1106                    DownloaderResponseResult::Failure => {
1107                        font_context.handle_web_font_request_failed(downloader.url.clone().into());
1108                    },
1109                }
1110            }),
1111        )
1112    }
1113
1114    /// After a download finishes, try to process the downloaded data, returning true if
1115    /// the font is added successfully to the [`FontContext`] or false if it isn't.
1116    fn process_downloaded_font_and_signal_completion(&mut self) {
1117        // Check if we still need this web font. If the stylesheet has been removed in the meantime
1118        // then there is no need to process it any further.
1119        if !self
1120            .font_context
1121            .has_pending_font_requests_for_url(self.url.clone())
1122        {
1123            return;
1124        }
1125
1126        let font_data = std::mem::take(&mut self.response_data);
1127        trace!(
1128            "Downloaded @font-face {} ({} bytes)",
1129            self.web_font_family_name,
1130            font_data.len()
1131        );
1132
1133        let font_data = match fontsan::process(&font_data) {
1134            Ok(bytes) => FontData::from_bytes(&bytes),
1135            Err(error) => {
1136                debug!(
1137                    "Sanitiser rejected web font url={:?} with {error:?}",
1138                    self.url.as_str(),
1139                );
1140                return self
1141                    .font_context
1142                    .handle_web_font_request_failed(self.url.clone().into());
1143            },
1144        };
1145
1146        let url: ServoUrl = self.url.clone().into();
1147        self.font_context
1148            .handle_web_font_request_succeeded(font_data, url);
1149    }
1150
1151    fn handle_web_font_fetch_message(
1152        &mut self,
1153        response_message: FetchResponseMsg,
1154    ) -> DownloaderResponseResult {
1155        match response_message {
1156            FetchResponseMsg::ProcessRequestBody(..) => DownloaderResponseResult::InProcess,
1157            FetchResponseMsg::ProcessCspViolations(_request_id, violations) => {
1158                self.document_context
1159                    .csp_handler
1160                    .process_violations(violations);
1161                DownloaderResponseResult::InProcess
1162            },
1163            FetchResponseMsg::ProcessResponse(_, meta_result) => {
1164                trace!(
1165                    "@font-face {} metadata ok={:?}",
1166                    self.web_font_family_name,
1167                    meta_result.is_ok()
1168                );
1169                self.response_valid = meta_result.is_ok();
1170                DownloaderResponseResult::InProcess
1171            },
1172            FetchResponseMsg::ProcessResponseChunk(_, new_bytes) => {
1173                trace!(
1174                    "@font-face {} chunk={:?}",
1175                    self.web_font_family_name, new_bytes
1176                );
1177                if self.response_valid {
1178                    self.response_data.extend(new_bytes.0)
1179                }
1180                DownloaderResponseResult::InProcess
1181            },
1182            FetchResponseMsg::ProcessResponseEOF(_, response, timing) => {
1183                trace!(
1184                    "@font-face {} EOF={:?}",
1185                    self.web_font_family_name, response
1186                );
1187                if response.is_err() || !self.response_valid {
1188                    return DownloaderResponseResult::Failure;
1189                }
1190                self.document_context
1191                    .network_timing_handler
1192                    .submit_timing(ServoUrl::from_url(self.url.as_ref().clone()), timing);
1193                DownloaderResponseResult::Finished
1194            },
1195        }
1196    }
1197}
1198
1199#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)]
1200struct FontCacheKey {
1201    font_identifier: FontIdentifier,
1202    font_descriptor: FontDescriptor,
1203}
1204
1205#[derive(Debug, MallocSizeOf)]
1206struct FontGroupCacheKey {
1207    #[ignore_malloc_size_of = "This is also stored as part of styling."]
1208    style: ServoArc<FontStyleStruct>,
1209    size: Au,
1210}
1211
1212impl PartialEq for FontGroupCacheKey {
1213    fn eq(&self, other: &FontGroupCacheKey) -> bool {
1214        self.style == other.style && self.size == other.size
1215    }
1216}
1217
1218impl Eq for FontGroupCacheKey {}
1219
1220impl Hash for FontGroupCacheKey {
1221    fn hash<H>(&self, hasher: &mut H)
1222    where
1223        H: Hasher,
1224    {
1225        self.style.hash.hash(hasher)
1226    }
1227}