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