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