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