mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Instead of exposing many different kinds of messages to the compositor that are routed through the constellation, expose a single message type which can be sent across IPC channels. In addition, this IPC channel and the route to the crossbeam channel with the compositor is created along with the `CompositorProxy`, simplifying what needs to be passed around during pipeline initialization. Previously, some image updates (from video) were sent over IPC with a special serialization routine and some were sent via crossbeam channels (canvas). Now all updates go over the IPC channel `IpcSharedMemory` is used to avoid serialization penalties. This should improve performance and reduce copies for video, but add a memory copy overhead for canvas. This will improve in the future when canvas renders directly into a texture. All-in-all this is a simplification which opens the path toward having a standard compositor API and reduces the number of duplicate messages and proxying that had to happen in libservo. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
408 lines
13 KiB
Rust
408 lines
13 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use std::collections::HashMap;
|
|
use std::fs::File;
|
|
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, FontTemplateRequestResult,
|
|
FontTemplates, PlatformFontMethods, SystemFontServiceMessage, SystemFontServiceProxy,
|
|
SystemFontServiceProxySender,
|
|
};
|
|
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 style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
|
|
use style::properties::style_structs::Font as FontStyleStruct;
|
|
use style::values::computed::font::{
|
|
FamilyName, FontFamily, FontFamilyList, FontFamilyNameSyntax, FontSize, FontStretch, FontStyle,
|
|
FontWeight, SingleFontFamily,
|
|
};
|
|
use style::values::computed::{FontLanguageOverride, XLang};
|
|
use style::values::generics::font::LineHeight;
|
|
use style::ArcSlice;
|
|
use webrender_api::{FontInstanceKey, FontKey, IdNamespace};
|
|
use webrender_traits::CrossProcessCompositorApi;
|
|
|
|
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_compositor_api = CrossProcessCompositorApi::dummy();
|
|
|
|
let proxy_clone = Arc::new(system_font_service_proxy.to_sender().to_proxy());
|
|
Self {
|
|
context: FontContext::new(proxy_clone, mock_compositor_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 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::GetFontInstanceKey(result_sender) |
|
|
SystemFontServiceMessage::GetFontInstance(_, _, _, result_sender) => {
|
|
let _ = result_sender.send(FontInstanceKey(IdNamespace(0), 0));
|
|
},
|
|
SystemFontServiceMessage::GetFontKey(result_sender) => {
|
|
let _ = result_sender.send(FontKey(IdNamespace(0), 0));
|
|
},
|
|
SystemFontServiceMessage::Exit(result_sender) => {
|
|
let _ = result_sender.send(());
|
|
break;
|
|
},
|
|
SystemFontServiceMessage::Ping => {},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn new() -> Self {
|
|
let proxy = Self {
|
|
families: Default::default(),
|
|
data: Default::default(),
|
|
find_font_count: AtomicI32::new(0),
|
|
};
|
|
|
|
let mut csstest_ascii = FontTemplates::default();
|
|
proxy.add_face(&mut csstest_ascii, "csstest-ascii");
|
|
|
|
let mut csstest_basic = FontTemplates::default();
|
|
proxy.add_face(&mut csstest_basic, "csstest-basic-regular");
|
|
|
|
let mut fallback = FontTemplates::default();
|
|
proxy.add_face(&mut fallback, "csstest-basic-regular");
|
|
|
|
{
|
|
let mut families = proxy.families.lock();
|
|
families.insert("CSSTest ASCII".to_owned(), csstest_ascii);
|
|
families.insert("CSSTest Basic".to_owned(), csstest_basic);
|
|
families.insert(
|
|
fallback_font_families(FallbackFontSelectionOptions::default())[0].to_owned(),
|
|
fallback,
|
|
);
|
|
}
|
|
|
|
proxy
|
|
}
|
|
|
|
fn add_face(&self, family: &mut FontTemplates, name: &str) {
|
|
let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests", "support", "CSSTest"]
|
|
.iter()
|
|
.collect();
|
|
path.push(format!("{}.ttf", name));
|
|
|
|
let file = File::open(path).unwrap();
|
|
let data = Arc::new(FontData::from_bytes(
|
|
file.bytes().map(|b| b.unwrap()).collect(),
|
|
));
|
|
|
|
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");
|
|
family.add_template(FontTemplate::new(
|
|
identifier.clone(),
|
|
handle.descriptor(),
|
|
None,
|
|
));
|
|
|
|
self.data.lock().insert(identifier, data);
|
|
}
|
|
}
|
|
|
|
fn style() -> FontStyleStruct {
|
|
let mut style = FontStyleStruct {
|
|
font_family: FontFamily::serif(),
|
|
font_style: FontStyle::NORMAL,
|
|
font_variant_caps: FontVariantCaps::Normal,
|
|
font_weight: FontWeight::normal(),
|
|
font_size: FontSize::medium(),
|
|
font_stretch: FontStretch::hundred(),
|
|
hash: 0,
|
|
font_language_override: FontLanguageOverride::normal(),
|
|
line_height: LineHeight::Normal,
|
|
_x_lang: XLang::get_initial_value(),
|
|
};
|
|
style.compute_font_hash();
|
|
style
|
|
}
|
|
|
|
fn font_family(names: Vec<&str>) -> FontFamily {
|
|
let names = names.into_iter().map(|name| {
|
|
SingleFontFamily::FamilyName(FamilyName {
|
|
name: Atom::from(name),
|
|
syntax: FontFamilyNameSyntax::Quoted,
|
|
})
|
|
});
|
|
|
|
FontFamily {
|
|
families: FontFamilyList {
|
|
list: ArcSlice::from_iter(names),
|
|
},
|
|
is_system_font: false,
|
|
is_initial: false,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_font_group_is_cached_by_style() {
|
|
let context = TestContext::new();
|
|
|
|
let style1 = style();
|
|
|
|
let mut style2 = style();
|
|
style2.set_font_style(FontStyle::ITALIC);
|
|
|
|
assert!(
|
|
std::ptr::eq(
|
|
&*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
|
|
.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"
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_font_group_find_by_codepoint() {
|
|
let mut context = TestContext::new();
|
|
|
|
let mut style = style();
|
|
style.set_font_family(font_family(vec!["CSSTest ASCII", "CSSTest Basic"]));
|
|
|
|
let group = context.context.font_group(ServoArc::new(style));
|
|
|
|
let font = group
|
|
.write()
|
|
.find_by_codepoint(&mut context.context, 'a', None)
|
|
.unwrap();
|
|
assert_eq!(
|
|
font.identifier(),
|
|
FontIdentifier::Mock("csstest-ascii".into())
|
|
);
|
|
assert_eq!(
|
|
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.context, 'a', None)
|
|
.unwrap();
|
|
assert_eq!(
|
|
font.identifier(),
|
|
FontIdentifier::Mock("csstest-ascii".into())
|
|
);
|
|
assert_eq!(
|
|
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.context, 'á', None)
|
|
.unwrap();
|
|
assert_eq!(
|
|
font.identifier(),
|
|
FontIdentifier::Mock("csstest-basic-regular".into())
|
|
);
|
|
assert_eq!(
|
|
context
|
|
.system_font_service
|
|
.find_font_count
|
|
.fetch_add(0, Ordering::Relaxed),
|
|
2,
|
|
"both fonts should now have been loaded"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_font_fallback() {
|
|
let mut context = TestContext::new();
|
|
|
|
let mut style = style();
|
|
style.set_font_family(font_family(vec!["CSSTest ASCII"]));
|
|
|
|
let group = context.context.font_group(ServoArc::new(style));
|
|
|
|
let font = group
|
|
.write()
|
|
.find_by_codepoint(&mut context.context, 'a', None)
|
|
.unwrap();
|
|
assert_eq!(
|
|
font.identifier(),
|
|
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.context, 'á', None)
|
|
.unwrap();
|
|
assert_eq!(
|
|
font.identifier(),
|
|
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 context = TestContext::new();
|
|
|
|
let mut font_descriptor = FontDescriptor {
|
|
weight: FontWeight::normal(),
|
|
stretch: FontStretch::hundred(),
|
|
style: FontStyle::normal(),
|
|
variant: FontVariantCaps::Normal,
|
|
pt_size: Au(10),
|
|
};
|
|
|
|
let family = SingleFontFamily::FamilyName(FamilyName {
|
|
name: "CSSTest Basic".into(),
|
|
syntax: FontFamilyNameSyntax::Quoted,
|
|
});
|
|
let family_descriptor = FontFamilyDescriptor::new(family, FontSearchScope::Any);
|
|
|
|
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();
|
|
|
|
assert_ne!(
|
|
font1.descriptor.pt_size, font2.descriptor.pt_size,
|
|
"the same font should not have been returned"
|
|
);
|
|
|
|
assert_eq!(
|
|
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"
|
|
);
|
|
}
|