fonts/
font_context.rs

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