fonts: Simplify FontContext in two ways that affect the unit test (#33541)

This is done by no longer forwarding compositor-bound messages through
SystemFontService and making `FontContext` non-generic:

- Messages from the `FontContext` to the `Compositor` no longer need to be
  forwarded through the `SystemFontService`. Instead send these messages
  directly through the script IPC channel to the `Compositor`.

- Instead of adding a mock `SystemFontServiceProxy`, simply implement a
  mock `SystemFontService` on the other side of an IPC channel in the
  `font_context` unit test. This allows making `FontContext`
  non-generic, greatly simplifying the code. The extra complexity moves
  into the unit test.

These changes necessitate adding a new kind of `FontIdentifier`,
`FontIdentifier::Mock` due to the fact that local fonts have
platform-specific identifiers. This avoids having to pretend like the
system font service can have web fonts -- which was always a bit of a
hack.

These two changes are combined into one PR because they both require
extensive and similar chages in the font_context unit test which
dependended on the details of both of them.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2024-09-25 22:15:47 +02:00 committed by GitHub
parent 1daa0b4fc7
commit ac567645a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 482 additions and 425 deletions

View file

@ -31,7 +31,7 @@ use crate::font_context::FontContext;
use crate::font_template::{FontTemplateDescriptor, FontTemplateRef, FontTemplateRefMethods};
use crate::platform::font::{FontTable, PlatformFont};
pub use crate::platform::font_list::fallback_font_families;
use crate::system_font_service::{FontIdentifier, SystemFontServiceProxyTrait};
use crate::system_font_service::FontIdentifier;
use crate::{
ByteIndex, EmojiPresentationPreference, FallbackFontSelectionOptions, FontData, GlyphData,
GlyphId, GlyphStore, Shaper,
@ -550,9 +550,9 @@ impl FontGroup {
/// `codepoint`. If no such font is found, returns the first available font or fallback font
/// (which will cause a "glyph not found" character to be rendered). If no font at all can be
/// found, returns None.
pub fn find_by_codepoint<S: SystemFontServiceProxyTrait>(
pub fn find_by_codepoint(
&mut self,
font_context: &FontContext<S>,
font_context: &FontContext,
codepoint: char,
next_codepoint: Option<char>,
) -> Option<FontRef> {
@ -622,10 +622,7 @@ impl FontGroup {
}
/// Find the first available font in the group, or the first available fallback font.
pub fn first<S: SystemFontServiceProxyTrait>(
&mut self,
font_context: &FontContext<S>,
) -> Option<FontRef> {
pub fn first(&mut self, font_context: &FontContext) -> Option<FontRef> {
// From https://drafts.csswg.org/css-fonts/#first-available-font:
// > The first available font, used for example in the definition of font-relative lengths
// > such as ex or in the definition of the line-height property, is defined to be the first
@ -649,14 +646,13 @@ impl FontGroup {
/// Attempts to find a font which matches the given `template_predicate` and `font_predicate`.
/// This method mutates because we may need to load new font data in the process of finding
/// a suitable font.
fn find<S, TemplatePredicate, FontPredicate>(
fn find<TemplatePredicate, FontPredicate>(
&mut self,
font_context: &FontContext<S>,
font_context: &FontContext,
template_predicate: TemplatePredicate,
font_predicate: FontPredicate,
) -> Option<FontRef>
where
S: SystemFontServiceProxyTrait,
TemplatePredicate: Fn(FontTemplateRef) -> bool,
FontPredicate: Fn(&FontRef) -> bool,
{
@ -678,15 +674,14 @@ impl FontGroup {
/// `font_predicate`. The default family (i.e. "serif") will be tried first, followed by
/// platform-specific family names. If a `codepoint` is provided, then its Unicode block may be
/// used to refine the list of family names which will be tried.
fn find_fallback<S, TemplatePredicate, FontPredicate>(
fn find_fallback<TemplatePredicate, FontPredicate>(
&mut self,
font_context: &FontContext<S>,
font_context: &FontContext,
options: FallbackFontSelectionOptions,
template_predicate: TemplatePredicate,
font_predicate: FontPredicate,
) -> Option<FontRef>
where
S: SystemFontServiceProxyTrait,
TemplatePredicate: Fn(FontTemplateRef) -> bool,
FontPredicate: Fn(&FontRef) -> bool,
{
@ -750,15 +745,14 @@ impl FontGroupFamily {
}
}
fn find<S, TemplatePredicate, FontPredicate>(
fn find<TemplatePredicate, FontPredicate>(
&mut self,
font_descriptor: &FontDescriptor,
font_context: &FontContext<S>,
font_context: &FontContext,
template_predicate: &TemplatePredicate,
font_predicate: &FontPredicate,
) -> Option<FontRef>
where
S: SystemFontServiceProxyTrait,
TemplatePredicate: Fn(FontTemplateRef) -> bool,
FontPredicate: Fn(&FontRef) -> bool,
{
@ -781,10 +775,10 @@ impl FontGroupFamily {
.next()
}
fn members<S: SystemFontServiceProxyTrait>(
fn members(
&mut self,
font_descriptor: &FontDescriptor,
font_context: &FontContext<S>,
font_context: &FontContext,
) -> impl Iterator<Item = &mut FontGroupFamilyMember> {
let family_descriptor = &self.family_descriptor;
let members = self.members.get_or_insert_with(|| {

View file

@ -12,6 +12,7 @@ use app_units::Au;
use crossbeam_channel::unbounded;
use fnv::FnvHasher;
use fonts_traits::WebFontLoadFinishedCallback;
use ipc_channel::ipc;
use log::{debug, trace};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use malloc_size_of_derive::MallocSizeOf;
@ -29,7 +30,8 @@ use style::stylesheets::{CssRule, DocumentStyleSheet, FontFaceRule, StylesheetIn
use style::values::computed::font::{FamilyName, FontFamilyNameSyntax, SingleFontFamily};
use style::Atom;
use url::Url;
use webrender_api::{FontInstanceKey, FontKey};
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey};
use webrender_traits::{ScriptToCompositorMsg, WebRenderScriptApi};
use crate::font::{
Font, FontDescriptor, FontFamilyDescriptor, FontGroup, FontRef, FontSearchScope,
@ -37,10 +39,8 @@ use crate::font::{
use crate::font_store::{CrossThreadFontStore, CrossThreadWebRenderFontStore};
use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods};
use crate::platform::font::PlatformFont;
use crate::system_font_service::{
CSSFontFaceDescriptors, FontIdentifier, SystemFontServiceProxyTrait,
};
use crate::{FontData, LowercaseFontFamilyName, PlatformFontMethods};
use crate::system_font_service::{CSSFontFaceDescriptors, FontIdentifier};
use crate::{FontData, LowercaseFontFamilyName, PlatformFontMethods, SystemFontServiceProxy};
static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h)
@ -48,10 +48,13 @@ static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h)
/// working with fonts. It is the public API used by the layout and
/// paint code. It talks directly to the system font service where
/// required.
pub struct FontContext<Proxy: SystemFontServiceProxyTrait> {
pub(crate) system_font_service_proxy: Arc<Proxy>,
pub struct FontContext {
pub(crate) system_font_service_proxy: Arc<SystemFontServiceProxy>,
resource_threads: ReentrantMutex<CoreResourceThread>,
/// A sender that can send messages and receive replies from the compositor.
webrender_api: ReentrantMutex<WebRenderScriptApi>,
/// The actual instances of fonts ie a [`FontTemplate`] combined with a size and
/// other font properties, along with the font data and a platform font instance.
fonts: RwLock<HashMap<FontCacheKey, Option<FontRef>>>,
@ -67,7 +70,7 @@ pub struct FontContext<Proxy: SystemFontServiceProxyTrait> {
have_removed_web_fonts: AtomicBool,
}
impl<S: SystemFontServiceProxyTrait> MallocSizeOf for FontContext<S> {
impl MallocSizeOf for FontContext {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
let font_cache_size = self
.fonts
@ -87,12 +90,17 @@ impl<S: SystemFontServiceProxyTrait> MallocSizeOf for FontContext<S> {
}
}
impl<Proxy: SystemFontServiceProxyTrait> FontContext<Proxy> {
pub fn new(system_font_service_proxy: Arc<Proxy>, resource_threads: ResourceThreads) -> Self {
impl FontContext {
pub fn new(
system_font_service_proxy: Arc<SystemFontServiceProxy>,
webrender_api: WebRenderScriptApi,
resource_threads: ResourceThreads,
) -> Self {
#[allow(clippy::default_constructed_unit_structs)]
Self {
system_font_service_proxy,
resource_threads: ReentrantMutex::new(resource_threads.core_thread),
webrender_api: ReentrantMutex::new(webrender_api),
fonts: Default::default(),
resolved_font_groups: Default::default(),
web_fonts: Arc::new(RwLock::default()),
@ -106,11 +114,13 @@ impl<Proxy: SystemFontServiceProxyTrait> FontContext<Proxy> {
}
pub(crate) fn get_font_data(&self, identifier: &FontIdentifier) -> Arc<FontData> {
self.web_fonts
.read()
.get_font_data(identifier)
.or_else(|| self.system_font_service_proxy.get_font_data(identifier))
.expect("Could not find font data")
match identifier {
FontIdentifier::Web(_) => self.web_fonts.read().get_font_data(identifier),
FontIdentifier::Local(_) | FontIdentifier::Mock(_) => {
self.system_font_service_proxy.get_font_data(identifier)
},
}
.expect("Could not find font data")
}
/// Handle the situation where a web font finishes loading, specifying if the load suceeded or failed.
@ -272,11 +282,13 @@ impl<Proxy: SystemFontServiceProxyTrait> FontContext<Proxy> {
)?;
font.font_key = match font_template.identifier() {
FontIdentifier::Local(_) => self.system_font_service_proxy.get_system_font_instance(
font_template.identifier(),
font_descriptor.pt_size,
font.webrender_font_instance_flags(),
),
FontIdentifier::Local(_) | FontIdentifier::Mock(_) => {
self.system_font_service_proxy.get_system_font_instance(
font_template.identifier(),
font_descriptor.pt_size,
font.webrender_font_instance_flags(),
)
},
FontIdentifier::Web(_) => self.webrender_font_store.write().get_font_instance(
self,
font_template.clone(),
@ -291,6 +303,42 @@ impl<Proxy: SystemFontServiceProxyTrait> FontContext<Proxy> {
fn invalidate_font_groups_after_web_font_load(&self) {
self.resolved_font_groups.write().clear();
}
pub(crate) fn get_web_font(&self, data: Arc<FontData>, index: u32) -> FontKey {
let (result_sender, result_receiver) =
ipc::channel().expect("failed to create IPC channel");
let _ = self
.webrender_api
.lock()
.sender()
.send(ScriptToCompositorMsg::AddFont(
data.as_ipc_shared_memory(),
index,
result_sender,
));
result_receiver.recv().unwrap()
}
pub(crate) fn get_web_font_instance(
&self,
font_key: FontKey,
font_size: f32,
font_flags: FontInstanceFlags,
) -> FontInstanceKey {
let (result_sender, result_receiver) =
ipc::channel().expect("failed to create IPC channel");
let _ = self
.webrender_api
.lock()
.sender()
.send(ScriptToCompositorMsg::AddFontInstance(
font_key,
font_size,
font_flags,
result_sender,
));
result_receiver.recv().unwrap()
}
}
#[derive(Clone)]
@ -318,7 +366,7 @@ pub trait FontContextWebFontMethods {
-> (Vec<FontKey>, Vec<FontInstanceKey>);
}
impl<S: SystemFontServiceProxyTrait + 'static> FontContextWebFontMethods for Arc<FontContext<S>> {
impl FontContextWebFontMethods for Arc<FontContext> {
fn add_all_web_fonts_from_stylesheet(
&self,
stylesheet: &DocumentStyleSheet,
@ -523,8 +571,8 @@ impl<S: SystemFontServiceProxyTrait + 'static> FontContextWebFontMethods for Arc
}
}
struct RemoteWebFontDownloader<Proxy: SystemFontServiceProxyTrait> {
font_context: Arc<FontContext<Proxy>>,
struct RemoteWebFontDownloader {
font_context: Arc<FontContext>,
url: ServoArc<Url>,
web_font_family_name: LowercaseFontFamilyName,
response_valid: Mutex<bool>,
@ -537,10 +585,10 @@ enum DownloaderResponseResult {
Failure,
}
impl<Proxy: SystemFontServiceProxyTrait + 'static> RemoteWebFontDownloader<Proxy> {
impl RemoteWebFontDownloader {
fn download(
url_source: UrlSource,
font_context: Arc<FontContext<Proxy>>,
font_context: Arc<FontContext>,
web_font_family_name: LowercaseFontFamilyName,
state: WebFontDownloadState,
) {
@ -628,12 +676,11 @@ impl<Proxy: SystemFontServiceProxyTrait + 'static> RemoteWebFontDownloader<Proxy
descriptor
.override_values_with_css_font_template_descriptors(&state.css_font_face_descriptors);
let Ok(new_template) =
FontTemplate::new_for_remote_web_font(url, descriptor, Some(state.stylesheet.clone()))
else {
return false;
};
let new_template = FontTemplate::new(
FontIdentifier::Web(url),
descriptor,
Some(state.stylesheet.clone()),
);
let not_cancelled = self.font_context.web_fonts.write().handle_web_font_loaded(
state,
new_template,

View file

@ -18,9 +18,7 @@ use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey};
use crate::font::FontDescriptor;
use crate::font_context::WebFontDownloadState;
use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods, IsOblique};
use crate::system_font_service::{
FontIdentifier, LowercaseFontFamilyName, SystemFontServiceProxyTrait,
};
use crate::system_font_service::{FontIdentifier, LowercaseFontFamilyName};
use crate::FontContext;
/// A data structure to store data for fonts. If sent across IPC channels and only a
@ -171,7 +169,7 @@ impl FontStore {
FontIdentifier::Local(local_identifier) => {
Arc::new(FontData::from_bytes(local_identifier.read_data_from_file()))
},
FontIdentifier::Web(_) => unreachable!("Web fonts should always have data."),
_ => unreachable!("Web and mock fonts should always have data."),
})
}
@ -196,9 +194,9 @@ pub struct WebRenderFontStore {
pub(crate) type CrossThreadWebRenderFontStore = Arc<RwLock<WebRenderFontStore>>;
impl WebRenderFontStore {
pub(crate) fn get_font_instance<Proxy: SystemFontServiceProxyTrait>(
pub(crate) fn get_font_instance(
&mut self,
font_context: &FontContext<Proxy>,
font_context: &FontContext,
font_template: FontTemplateRef,
pt_size: Au,
flags: FontInstanceFlags,
@ -210,18 +208,14 @@ impl WebRenderFontStore {
.entry(identifier.clone())
.or_insert_with(|| {
let data = font_context.get_font_data(&identifier);
font_context
.system_font_service_proxy
.get_web_font(data, identifier.index())
font_context.get_web_font(data, identifier.index())
});
*self
.webrender_font_instance_map
.entry((font_key, pt_size))
.or_insert_with(|| {
font_context
.system_font_service_proxy
.get_web_font_instance(font_key, pt_size.to_f32_px(), flags)
font_context.get_web_font_instance(font_key, pt_size.to_f32_px(), flags)
})
}

View file

@ -9,14 +9,12 @@ use std::sync::Arc;
use atomic_refcell::AtomicRefCell;
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
use servo_url::ServoUrl;
use style::computed_values::font_stretch::T as FontStretch;
use style::computed_values::font_style::T as FontStyle;
use style::stylesheets::DocumentStyleSheet;
use style::values::computed::font::FontWeight;
use crate::font::FontDescriptor;
use crate::platform::font_list::LocalFontIdentifier;
use crate::system_font_service::{
CSSFontFaceDescriptors, ComputedFontStyleDescriptor, FontIdentifier,
};
@ -160,29 +158,17 @@ impl Debug for FontTemplate {
/// is common, regardless of the number of instances of
/// this font handle per thread.
impl FontTemplate {
/// Create a new [`FontTemplate`] for a system font installed locally.
pub fn new_for_local_font(
identifier: LocalFontIdentifier,
descriptor: FontTemplateDescriptor,
) -> FontTemplate {
FontTemplate {
identifier: FontIdentifier::Local(identifier),
descriptor,
stylesheet: None,
}
}
/// Create a new [`FontTemplate`] for a `@font-family` with a `url(...)` `src` font.
pub fn new_for_remote_web_font(
url: ServoUrl,
/// Create a new [`FontTemplate`].
pub fn new(
identifier: FontIdentifier,
descriptor: FontTemplateDescriptor,
stylesheet: Option<DocumentStyleSheet>,
) -> Result<FontTemplate, &'static str> {
Ok(FontTemplate {
identifier: FontIdentifier::Web(url),
) -> FontTemplate {
FontTemplate {
identifier,
descriptor,
stylesheet,
})
}
}
/// Create a new [`FontTemplate`] for a `@font-family` with a `local(...)` `src`. This takes in

View file

@ -19,7 +19,8 @@ use style::Atom;
use super::xml::{Attribute, Node};
use crate::{
FallbackFontSelectionOptions, FontTemplate, FontTemplateDescriptor, LowercaseFontFamilyName,
FallbackFontSelectionOptions, FontIdentifier, FontTemplate, FontTemplateDescriptor,
LowercaseFontFamilyName,
};
static FONT_LIST: LazyLock<FontList> = LazyLock::new(|| FontList::new());
@ -491,9 +492,10 @@ where
None => StyleFontStyle::NORMAL,
};
let descriptor = FontTemplateDescriptor::new(weight, stretch, style);
callback(FontTemplate::new_for_local_font(
local_font_identifier,
callback(FontTemplate::new(
FontIdentifier::Local(local_font_identifier),
descriptor,
None,
));
};

View file

@ -36,7 +36,10 @@ use super::c_str_to_string;
use crate::font::map_platform_values_to_style_values;
use crate::font_template::{FontTemplate, FontTemplateDescriptor};
use crate::platform::add_noto_fallback_families;
use crate::{EmojiPresentationPreference, FallbackFontSelectionOptions, LowercaseFontFamilyName};
use crate::{
EmojiPresentationPreference, FallbackFontSelectionOptions, FontIdentifier,
LowercaseFontFamilyName,
};
/// An identifier for a local font on systems using Freetype.
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
@ -155,9 +158,10 @@ where
};
let descriptor = FontTemplateDescriptor::new(weight, stretch, style);
callback(FontTemplate::new_for_local_font(
local_font_identifier,
callback(FontTemplate::new(
FontIdentifier::Local(local_font_identifier),
descriptor,
None,
))
}

View file

@ -22,7 +22,7 @@ use style::Atom;
use unicode_script::Script;
use crate::{
EmojiPresentationPreference, FallbackFontSelectionOptions, FontTemplate,
EmojiPresentationPreference, FallbackFontSelectionOptions, FontIdentifier, FontTemplate,
FontTemplateDescriptor, LowercaseFontFamilyName,
};
@ -492,9 +492,10 @@ where
None => StyleFontStyle::NORMAL,
};
let descriptor = FontTemplateDescriptor::new(weight, stretch, style);
callback(FontTemplate::new_for_local_font(
local_font_identifier,
callback(FontTemplate::new(
FontIdentifier::Local(local_font_identifier),
descriptor,
None,
));
};

View file

@ -86,7 +86,7 @@ impl CoreTextFontCache {
core_text::font::new_from_descriptor(&descriptor, clamped_pt_size)
},
FontIdentifier::Web(_) => {
FontIdentifier::Web(_) | FontIdentifier::Mock(_) => {
let provider = CGDataProvider::from_buffer(data);
let cgfont = CGFont::from_data_provider(provider).ok()?;
core_text::font::new_from_CGFont(&cgfont, clamped_pt_size)

View file

@ -18,7 +18,7 @@ use webrender_api::NativeFontHandle;
use crate::platform::add_noto_fallback_families;
use crate::platform::font::CoreTextFontTraitsMapping;
use crate::{
EmojiPresentationPreference, FallbackFontSelectionOptions, FontTemplate,
EmojiPresentationPreference, FallbackFontSelectionOptions, FontIdentifier, FontTemplate,
FontTemplateDescriptor, LowercaseFontFamilyName,
};
@ -85,7 +85,11 @@ where
postscript_name: Atom::from(family_descriptor.font_name()),
path: Atom::from(path),
};
callback(FontTemplate::new_for_local_font(identifier, descriptor));
callback(FontTemplate::new(
FontIdentifier::Local(identifier),
descriptor,
None,
));
}
}
}

View file

@ -14,7 +14,7 @@ use style::values::computed::{FontStyle as StyleFontStyle, FontWeight as StyleFo
use style::values::specified::font::FontStretchKeyword;
use crate::{
EmojiPresentationPreference, FallbackFontSelectionOptions, FontTemplate,
EmojiPresentationPreference, FallbackFontSelectionOptions, FontIdentifier, FontTemplate,
FontTemplateDescriptor, LowercaseFontFamilyName,
};
@ -78,9 +78,10 @@ where
let local_font_identifier = LocalFontIdentifier {
font_descriptor: Arc::new(font.to_descriptor()),
};
callback(FontTemplate::new_for_local_font(
local_font_identifier,
callback(FontTemplate::new(
FontIdentifier::Local(local_font_identifier),
template_descriptor,
None,
))
}
}

View file

@ -24,6 +24,7 @@ use style::values::computed::font::{
};
use style::values::computed::{FontStretch, FontWeight};
use style::values::specified::FontStretch as SpecifiedFontStretch;
use style::Atom;
use tracing::{span, Level};
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey};
use webrender_traits::WebRenderFontApi;
@ -41,26 +42,27 @@ use crate::FontData;
pub enum FontIdentifier {
Local(LocalFontIdentifier),
Web(ServoUrl),
Mock(Atom),
}
impl FontIdentifier {
pub fn index(&self) -> u32 {
match *self {
Self::Local(ref local_font_identifier) => local_font_identifier.index(),
Self::Web(_) => 0,
Self::Web(_) | Self::Mock(_) => 0,
}
}
}
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct FontTemplateRequestResult {
templates: Vec<FontTemplate>,
template_data: Vec<(FontIdentifier, Arc<FontData>)>,
pub templates: Vec<FontTemplate>,
pub template_data: Vec<(FontIdentifier, Arc<FontData>)>,
}
/// Commands that the `FontContext` sends to the `SystemFontService`.
#[derive(Debug, Deserialize, Serialize)]
pub enum Command {
pub enum SystemFontServiceMessage {
GetFontTemplates(
Option<FontDescriptor>,
SingleFontFamily,
@ -72,8 +74,6 @@ pub enum Command {
FontInstanceFlags,
IpcSender<FontInstanceKey>,
),
GetWebFont(Arc<FontData>, u32, IpcSender<FontKey>),
GetWebFontInstance(FontKey, f32, FontInstanceFlags, IpcSender<FontInstanceKey>),
Exit(IpcSender<()>),
Ping,
}
@ -93,7 +93,7 @@ struct ResolvedGenericFontFamilies {
/// responsible for reading the list of system fonts, handling requests to match against
/// them, and ensuring that only one copy of system font data is loaded at a time.
pub struct SystemFontService {
port: IpcReceiver<Command>,
port: IpcReceiver<SystemFontServiceMessage>,
local_families: FontStore,
webrender_api: Box<dyn WebRenderFontApi>,
webrender_fonts: HashMap<FontIdentifier, FontKey>,
@ -102,7 +102,7 @@ pub struct SystemFontService {
}
#[derive(Clone, Deserialize, Serialize)]
pub struct SystemFontServiceProxySender(IpcSender<Command>);
pub struct SystemFontServiceProxySender(pub IpcSender<SystemFontServiceMessage>);
impl SystemFontServiceProxySender {
pub fn to_proxy(&self) -> SystemFontServiceProxy {
@ -145,37 +145,21 @@ impl SystemFontService {
let msg = self.port.recv().unwrap();
match msg {
Command::GetFontTemplates(font_descriptor, font_family, result_sender) => {
SystemFontServiceMessage::GetFontTemplates(
font_descriptor,
font_family,
result_sender,
) => {
let span = span!(Level::TRACE, "GetFontTemplates", servo_profiling = true);
let _span = span.enter();
let _ =
result_sender.send(self.get_font_templates(font_descriptor, font_family));
},
Command::GetFontInstance(identifier, pt_size, flags, result) => {
SystemFontServiceMessage::GetFontInstance(identifier, pt_size, flags, result) => {
let _ = result.send(self.get_font_instance(identifier, pt_size, flags));
},
Command::GetWebFont(data, font_index, result_sender) => {
self.webrender_api.forward_add_font_message(
data.as_ipc_shared_memory(),
font_index,
result_sender,
);
},
Command::GetWebFontInstance(
font_key,
font_size,
font_instance_flags,
result_sender,
) => {
self.webrender_api.forward_add_font_instance_message(
font_key,
font_size,
font_instance_flags,
result_sender,
);
},
Command::Ping => (),
Command::Exit(result) => {
SystemFontServiceMessage::Ping => (),
SystemFontServiceMessage::Exit(result) => {
let _ = result.send(());
break;
},
@ -325,40 +309,17 @@ impl SystemFontService {
}
}
/// A trait for accessing the [`SystemFontServiceProxy`] necessary for unit testing.
pub trait SystemFontServiceProxyTrait: Send + Sync {
fn find_matching_font_templates(
&self,
descriptor_to_match: Option<&FontDescriptor>,
font_family_name: &SingleFontFamily,
) -> Vec<FontTemplateRef>;
fn get_system_font_instance(
&self,
font_identifier: FontIdentifier,
size: Au,
flags: FontInstanceFlags,
) -> FontInstanceKey;
fn get_web_font(&self, data: Arc<FontData>, index: u32) -> FontKey;
fn get_web_font_instance(
&self,
font_key: FontKey,
size: f32,
flags: FontInstanceFlags,
) -> FontInstanceKey;
fn get_font_data(&self, identifier: &FontIdentifier) -> Option<Arc<FontData>>;
}
#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)]
struct FontTemplateCacheKey {
font_descriptor: Option<FontDescriptor>,
family_descriptor: SingleFontFamily,
}
/// The public interface to the [`SystemFontService`], used by per-Document `FontContext`
/// instances (via [`SystemFontServiceProxyTrait`]).
/// The public interface to the [`SystemFontService`], used by per-Document
/// `FontContext` instances.
#[derive(Debug)]
pub struct SystemFontServiceProxy {
sender: ReentrantMutex<IpcSender<Command>>,
sender: ReentrantMutex<IpcSender<SystemFontServiceMessage>>,
templates: RwLock<HashMap<FontTemplateCacheKey, Vec<FontTemplateRef>>>,
data_cache: RwLock<HashMap<FontIdentifier, Arc<FontData>>>,
}
@ -453,7 +414,7 @@ impl SystemFontServiceProxy {
let (response_chan, response_port) = ipc::channel().unwrap();
self.sender
.lock()
.send(Command::Exit(response_chan))
.send(SystemFontServiceMessage::Exit(response_chan))
.expect("Couldn't send SystemFontService exit message");
response_port
.recv()
@ -463,10 +424,8 @@ impl SystemFontServiceProxy {
pub fn to_sender(&self) -> SystemFontServiceProxySender {
SystemFontServiceProxySender(self.sender.lock().clone())
}
}
impl SystemFontServiceProxyTrait for SystemFontServiceProxy {
fn get_system_font_instance(
pub(crate) fn get_system_font_instance(
&self,
identifier: FontIdentifier,
size: Au,
@ -475,7 +434,7 @@ impl SystemFontServiceProxyTrait for SystemFontServiceProxy {
let (response_chan, response_port) = ipc::channel().expect("failed to create IPC channel");
self.sender
.lock()
.send(Command::GetFontInstance(
.send(SystemFontServiceMessage::GetFontInstance(
identifier,
size,
flags,
@ -485,7 +444,11 @@ impl SystemFontServiceProxyTrait for SystemFontServiceProxy {
let instance_key = response_port.recv();
if instance_key.is_err() {
let font_thread_has_closed = self.sender.lock().send(Command::Ping).is_err();
let font_thread_has_closed = self
.sender
.lock()
.send(SystemFontServiceMessage::Ping)
.is_err();
assert!(
font_thread_has_closed,
"Failed to receive a response from live font cache"
@ -495,7 +458,7 @@ impl SystemFontServiceProxyTrait for SystemFontServiceProxy {
instance_key.unwrap()
}
fn find_matching_font_templates(
pub(crate) fn find_matching_font_templates(
&self,
descriptor_to_match: Option<&FontDescriptor>,
family_descriptor: &SingleFontFamily,
@ -516,7 +479,7 @@ impl SystemFontServiceProxyTrait for SystemFontServiceProxy {
let (response_chan, response_port) = ipc::channel().expect("failed to create IPC channel");
self.sender
.lock()
.send(Command::GetFontTemplates(
.send(SystemFontServiceMessage::GetFontTemplates(
descriptor_to_match.cloned(),
family_descriptor.clone(),
response_chan,
@ -525,7 +488,11 @@ impl SystemFontServiceProxyTrait for SystemFontServiceProxy {
let reply = response_port.recv();
let Ok(reply) = reply else {
let font_thread_has_closed = self.sender.lock().send(Command::Ping).is_err();
let font_thread_has_closed = self
.sender
.lock()
.send(SystemFontServiceMessage::Ping)
.is_err();
assert!(
font_thread_has_closed,
"Failed to receive a response from live font cache"
@ -544,34 +511,7 @@ impl SystemFontServiceProxyTrait for SystemFontServiceProxy {
templates
}
fn get_web_font(&self, data: Arc<FontData>, index: u32) -> FontKey {
let (result_sender, result_receiver) =
ipc::channel().expect("failed to create IPC channel");
let _ = self
.sender
.lock()
.send(Command::GetWebFont(data, index, result_sender));
result_receiver.recv().unwrap()
}
fn get_web_font_instance(
&self,
font_key: FontKey,
font_size: f32,
font_flags: FontInstanceFlags,
) -> FontInstanceKey {
let (result_sender, result_receiver) =
ipc::channel().expect("failed to create IPC channel");
let _ = self.sender.lock().send(Command::GetWebFontInstance(
font_key,
font_size,
font_flags,
result_sender,
));
result_receiver.recv().unwrap()
}
fn get_font_data(&self, identifier: &FontIdentifier) -> Option<Arc<FontData>> {
pub(crate) fn get_font_data(&self, identifier: &FontIdentifier) -> Option<Arc<FontData>> {
self.data_cache.read().get(identifier).cloned()
}
}

View file

@ -8,20 +8,21 @@ use std::io::prelude::*;
use std::path::PathBuf;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Arc;
use std::thread;
use app_units::Au;
use fonts::platform::font::PlatformFont;
use fonts::{
fallback_font_families, FallbackFontSelectionOptions, FontContext, FontData, FontDescriptor,
FontFamilyDescriptor, FontIdentifier, FontSearchScope, FontTemplate, FontTemplateRef,
FontTemplates, PlatformFontMethods, SystemFontServiceProxyTrait,
FontFamilyDescriptor, FontIdentifier, FontSearchScope, FontTemplate, FontTemplateRequestResult,
FontTemplates, PlatformFontMethods, SystemFontServiceMessage, SystemFontServiceProxy,
SystemFontServiceProxySender,
};
use ipc_channel::ipc;
use ipc_channel::ipc::{self, IpcReceiver};
use net_traits::ResourceThreads;
use parking_lot::Mutex;
use servo_arc::Arc as ServoArc;
use servo_atoms::Atom;
use servo_url::ServoUrl;
use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::{
@ -31,15 +32,111 @@ use style::values::computed::font::{
use style::values::computed::{FontLanguageOverride, XLang};
use style::values::generics::font::LineHeight;
use style::ArcSlice;
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey, IdNamespace};
use webrender_api::{FontInstanceKey, IdNamespace};
use webrender_traits::WebRenderScriptApi;
struct MockFontCacheThread {
struct TestContext {
context: FontContext,
system_font_service: Arc<MockSystemFontService>,
system_font_service_proxy: SystemFontServiceProxy,
}
impl TestContext {
fn new() -> TestContext {
let (system_font_service, system_font_service_proxy) = MockSystemFontService::spawn();
let (core_sender, _) = ipc::channel().unwrap();
let (storage_sender, _) = ipc::channel().unwrap();
let mock_resource_threads = ResourceThreads::new(core_sender, storage_sender);
let mock_webrender_api = WebRenderScriptApi::dummy();
let proxy_clone = Arc::new(system_font_service_proxy.to_sender().to_proxy());
Self {
context: FontContext::new(proxy_clone, mock_webrender_api, mock_resource_threads),
system_font_service,
system_font_service_proxy,
}
}
}
impl Drop for TestContext {
fn drop(&mut self) {
self.system_font_service_proxy.exit();
}
}
struct MockSystemFontService {
families: Mutex<HashMap<String, FontTemplates>>,
data: Mutex<HashMap<FontIdentifier, Arc<FontData>>>,
find_font_count: AtomicI32,
}
impl MockFontCacheThread {
impl MockSystemFontService {
pub fn spawn() -> (Arc<MockSystemFontService>, SystemFontServiceProxy) {
let (sender, receiver) = ipc::channel().unwrap();
let system_font_service = Arc::new(Self::new());
let system_font_service_clone = system_font_service.clone();
thread::Builder::new()
.name("MockSystemFontService".to_owned())
.spawn(move || system_font_service_clone.run(receiver))
.expect("Thread spawning failed");
(
system_font_service,
SystemFontServiceProxySender(sender).to_proxy(),
)
}
fn run(&self, receiver: IpcReceiver<SystemFontServiceMessage>) {
loop {
match receiver.recv().unwrap() {
SystemFontServiceMessage::GetFontTemplates(
descriptor_to_match,
font_family,
result_sender,
) => {
self.find_font_count.fetch_add(1, Ordering::Relaxed);
let SingleFontFamily::FamilyName(family_name) = font_family else {
let _ = result_sender.send(FontTemplateRequestResult::default());
continue;
};
let templates: Vec<_> = self
.families
.lock()
.get_mut(&*family_name.name)
.map(|family| family.find_for_descriptor(descriptor_to_match.as_ref()))
.unwrap()
.into_iter()
.map(|template| template.borrow().clone())
.collect();
let template_data = templates
.iter()
.map(|template| {
let identifier = template.identifier().clone();
let data = self.data.lock().get(&identifier).unwrap().clone();
(identifier, data)
})
.collect();
let _ = result_sender.send(FontTemplateRequestResult {
templates,
template_data,
});
},
SystemFontServiceMessage::GetFontInstance(_, _, _, result_sender) => {
let _ = result_sender.send(FontInstanceKey(IdNamespace(0), 0));
},
SystemFontServiceMessage::Exit(result_sender) => {
let _ = result_sender.send(());
break;
},
SystemFontServiceMessage::Ping => {},
}
}
}
fn new() -> Self {
let proxy = Self {
families: Default::default(),
@ -69,18 +166,6 @@ impl MockFontCacheThread {
proxy
}
fn identifier_for_font_name(name: &str) -> FontIdentifier {
FontIdentifier::Web(Self::url_for_font_name(name))
}
fn url_for_font_name(name: &str) -> ServoUrl {
let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests", "support", "CSSTest"]
.iter()
.collect();
path.push(format!("{}.ttf", name));
ServoUrl::from_file_path(path).unwrap()
}
fn add_face(&self, family: &mut FontTemplates, name: &str) {
let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests", "support", "CSSTest"]
.iter()
@ -92,65 +177,20 @@ impl MockFontCacheThread {
file.bytes().map(|b| b.unwrap()).collect(),
));
let url = Self::url_for_font_name(name);
let identifier = FontIdentifier::Web(url.clone());
let identifier = FontIdentifier::Mock(name.into());
let handle =
PlatformFont::new_from_data(identifier.clone(), data.as_arc().clone(), 0, None)
.expect("Could not load test font");
let template =
FontTemplate::new_for_remote_web_font(url, handle.descriptor(), None).unwrap();
family.add_template(template);
family.add_template(FontTemplate::new(
identifier.clone(),
handle.descriptor(),
None,
));
self.data.lock().insert(identifier, data);
}
}
impl SystemFontServiceProxyTrait for MockFontCacheThread {
fn find_matching_font_templates(
&self,
descriptor_to_match: Option<&FontDescriptor>,
font_family: &SingleFontFamily,
) -> Vec<FontTemplateRef> {
self.find_font_count.fetch_add(1, Ordering::Relaxed);
let SingleFontFamily::FamilyName(family_name) = font_family else {
return Vec::new();
};
self.families
.lock()
.get_mut(&*family_name.name)
.map(|family| family.find_for_descriptor(descriptor_to_match))
.unwrap_or_default()
}
fn get_system_font_instance(
&self,
_font_identifier: FontIdentifier,
_size: Au,
_flags: FontInstanceFlags,
) -> FontInstanceKey {
FontInstanceKey(IdNamespace(0), 0)
}
fn get_web_font(&self, _data: Arc<fonts::FontData>, _index: u32) -> FontKey {
FontKey(IdNamespace(0), 0)
}
fn get_web_font_instance(
&self,
_font_key: FontKey,
_size: f32,
_flags: FontInstanceFlags,
) -> FontInstanceKey {
FontInstanceKey(IdNamespace(0), 0)
}
fn get_font_data(&self, identifier: &FontIdentifier) -> Option<Arc<fonts::FontData>> {
self.data.lock().get(identifier).cloned()
}
}
fn style() -> FontStyleStruct {
let mut style = FontStyleStruct {
font_family: FontFamily::serif(),
@ -185,16 +225,9 @@ fn font_family(names: Vec<&str>) -> FontFamily {
}
}
fn mock_resource_threads() -> ResourceThreads {
let (core_sender, _) = ipc::channel().unwrap();
let (storage_sender, _) = ipc::channel().unwrap();
ResourceThreads::new(core_sender, storage_sender)
}
#[test]
fn test_font_group_is_cached_by_style() {
let source = Arc::new(MockFontCacheThread::new());
let context = FontContext::new(source, mock_resource_threads());
let context = TestContext::new();
let style1 = style();
@ -203,16 +236,28 @@ fn test_font_group_is_cached_by_style() {
assert!(
std::ptr::eq(
&*context.font_group(ServoArc::new(style1.clone())).read(),
&*context.font_group(ServoArc::new(style1.clone())).read()
&*context
.context
.font_group(ServoArc::new(style1.clone()))
.read(),
&*context
.context
.font_group(ServoArc::new(style1.clone()))
.read()
),
"the same font group should be returned for two styles with the same hash"
);
assert!(
!std::ptr::eq(
&*context.font_group(ServoArc::new(style1.clone())).read(),
&*context.font_group(ServoArc::new(style2.clone())).read()
&*context
.context
.font_group(ServoArc::new(style1.clone()))
.read(),
&*context
.context
.font_group(ServoArc::new(style2.clone()))
.read()
),
"different font groups should be returned for two styles with different hashes"
)
@ -220,52 +265,60 @@ fn test_font_group_is_cached_by_style() {
#[test]
fn test_font_group_find_by_codepoint() {
let source = Arc::new(MockFontCacheThread::new());
let mut context = FontContext::new(source.clone(), mock_resource_threads());
let mut context = TestContext::new();
let mut style = style();
style.set_font_family(font_family(vec!["CSSTest ASCII", "CSSTest Basic"]));
let group = context.font_group(ServoArc::new(style));
let group = context.context.font_group(ServoArc::new(style));
let font = group
.write()
.find_by_codepoint(&mut context, 'a', None)
.find_by_codepoint(&mut context.context, 'a', None)
.unwrap();
assert_eq!(
font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-ascii")
FontIdentifier::Mock("csstest-ascii".into())
);
assert_eq!(
source.find_font_count.fetch_add(0, Ordering::Relaxed),
context
.system_font_service
.find_font_count
.fetch_add(0, Ordering::Relaxed),
1,
"only the first font in the list should have been loaded"
);
let font = group
.write()
.find_by_codepoint(&mut context, 'a', None)
.find_by_codepoint(&mut context.context, 'a', None)
.unwrap();
assert_eq!(
font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-ascii")
FontIdentifier::Mock("csstest-ascii".into())
);
assert_eq!(
source.find_font_count.fetch_add(0, Ordering::Relaxed),
context
.system_font_service
.find_font_count
.fetch_add(0, Ordering::Relaxed),
1,
"we shouldn't load the same font a second time"
);
let font = group
.write()
.find_by_codepoint(&mut context, 'á', None)
.find_by_codepoint(&mut context.context, 'á', None)
.unwrap();
assert_eq!(
font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-basic-regular")
FontIdentifier::Mock("csstest-basic-regular".into())
);
assert_eq!(
source.find_font_count.fetch_add(0, Ordering::Relaxed),
context
.system_font_service
.find_font_count
.fetch_add(0, Ordering::Relaxed),
2,
"both fonts should now have been loaded"
);
@ -273,39 +326,37 @@ fn test_font_group_find_by_codepoint() {
#[test]
fn test_font_fallback() {
let source = Arc::new(MockFontCacheThread::new());
let mut context = FontContext::new(source, mock_resource_threads());
let mut context = TestContext::new();
let mut style = style();
style.set_font_family(font_family(vec!["CSSTest ASCII"]));
let group = context.font_group(ServoArc::new(style));
let group = context.context.font_group(ServoArc::new(style));
let font = group
.write()
.find_by_codepoint(&mut context, 'a', None)
.find_by_codepoint(&mut context.context, 'a', None)
.unwrap();
assert_eq!(
font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-ascii"),
FontIdentifier::Mock("csstest-ascii".into()),
"a family in the group should be used if there is a matching glyph"
);
let font = group
.write()
.find_by_codepoint(&mut context, 'á', None)
.find_by_codepoint(&mut context.context, 'á', None)
.unwrap();
assert_eq!(
font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-basic-regular"),
FontIdentifier::Mock("csstest-basic-regular".into()),
"a fallback font should be used if there is no matching glyph in the group"
);
}
#[test]
fn test_font_template_is_cached() {
let source = Arc::new(MockFontCacheThread::new());
let context = FontContext::new(source.clone(), mock_resource_threads());
let context = TestContext::new();
let mut font_descriptor = FontDescriptor {
weight: FontWeight::normal(),
@ -321,14 +372,19 @@ fn test_font_template_is_cached() {
});
let family_descriptor = FontFamilyDescriptor::new(family, FontSearchScope::Any);
let font_template = context.matching_templates(&font_descriptor, &family_descriptor)[0].clone();
let font_template = context
.context
.matching_templates(&font_descriptor, &family_descriptor)[0]
.clone();
let font1 = context
.context
.font(font_template.clone(), &font_descriptor)
.unwrap();
font_descriptor.pt_size = Au(20);
let font2 = context
.context
.font(font_template.clone(), &font_descriptor)
.unwrap();
@ -338,7 +394,10 @@ fn test_font_template_is_cached() {
);
assert_eq!(
source.find_font_count.fetch_add(0, Ordering::Relaxed),
context
.system_font_service
.find_font_count
.fetch_add(0, Ordering::Relaxed),
1,
"we should only have fetched the template data from the cache thread once"
);