115 lines
4.0 KiB
Rust
115 lines
4.0 KiB
Rust
#![cfg(all(feature = "servo-engine", target_os = "macos"))]
|
|
|
|
use std::{error::Error, thread, time::Duration};
|
|
|
|
use ely_domain::{ProfileId, TabId, UrlText};
|
|
use ely_servo_host::{
|
|
NavigationRequest, ServoHost, ServoSurfaceSize, SoftwareServoHost, WebViewState,
|
|
};
|
|
|
|
const EXPECTED_PASS_COLOR: [u8; 3] = [31, 143, 76];
|
|
|
|
#[test]
|
|
fn macos_fallback_preserves_css_family_style_and_language() -> Result<(), Box<dyn Error>> {
|
|
let mut host = SoftwareServoHost::new(ServoSurfaceSize::new(320, 240))?;
|
|
let tab_id = TabId::new();
|
|
let profile_id = ProfileId::new();
|
|
let webview_id = host.create_webview(tab_id.clone(), profile_id)?;
|
|
|
|
host.navigate(NavigationRequest {
|
|
webview_id: webview_id.clone(),
|
|
tab_id,
|
|
url: UrlText::parse(font_probe_url())?,
|
|
})?;
|
|
|
|
for _ in 0..5_000 {
|
|
host.tick();
|
|
let snapshot = host.snapshot(&webview_id)?;
|
|
if snapshot.has_pending_frame() {
|
|
host.paint(&webview_id)?;
|
|
}
|
|
|
|
let snapshot = host.snapshot(&webview_id)?;
|
|
if snapshot.state() == &WebViewState::Complete
|
|
&& host
|
|
.last_rendered_frame()
|
|
.is_ok_and(|frame| center_pixel_rgb(&frame) == EXPECTED_PASS_COLOR)
|
|
{
|
|
return Ok(());
|
|
}
|
|
thread::sleep(Duration::from_millis(2));
|
|
}
|
|
|
|
let actual = host.last_rendered_frame().map(|frame| center_pixel_rgb(&frame));
|
|
let title = host.snapshot(&webview_id)?.title().map(str::to_owned);
|
|
Err(format!(
|
|
"macOS font fallback probe failed with center pixel {actual:?} and title {title:?}"
|
|
)
|
|
.into())
|
|
}
|
|
|
|
fn font_probe_url() -> String {
|
|
let html = r#"<!doctype html>
|
|
<html><head><meta charset="utf-8"><title>starting</title><style>
|
|
html,body{margin:0;width:100%;height:100%;background:rgb(181,37,46)}
|
|
canvas{position:absolute;left:-10000px;top:-10000px}
|
|
</style></head><body><script>
|
|
try {
|
|
const cases = [
|
|
['zh-Hans', '搜索中文', '48px Arial, sans-serif', "48px 'PingFang SC'"],
|
|
['ja', '日本語検索', '48px Arial, sans-serif', "48px 'Hiragino Sans'"],
|
|
['ko', '한국어검색', '48px Arial, sans-serif', "48px 'Apple SD Gothic Neo'"],
|
|
['hi', 'हिन्दी', '48px Arial, sans-serif', "48px 'Kohinoor Devanagari'"],
|
|
['zh-Hans', '搜索中文', '48px Times New Roman, serif', "48px 'Songti SC'"]
|
|
];
|
|
function pixels(lang, text, font) {
|
|
const canvas = document.createElement('canvas');
|
|
canvas.lang = lang;
|
|
canvas.width = 512;
|
|
canvas.height = 96;
|
|
document.body.appendChild(canvas);
|
|
const context = canvas.getContext('2d');
|
|
context.fillStyle = 'black';
|
|
context.font = font;
|
|
context.fillText(text, 8, 64);
|
|
return context.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
}
|
|
function equal(left, right) {
|
|
if (left.length !== right.length) return false;
|
|
for (let index = 0; index < left.length; index++) {
|
|
if (left[index] !== right[index]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
const results = cases.map(([lang, text, requested, expected]) =>
|
|
equal(pixels(lang, text, requested), pixels(lang, text, expected))
|
|
);
|
|
const passed = results.every(Boolean);
|
|
document.title = results.map((result, index) => `${index}:${result}`).join(',');
|
|
document.body.style.background = passed ? 'rgb(31,143,76)' : 'rgb(181,37,46)';
|
|
} catch (error) {
|
|
document.title = `${error.name}:${error.message}`;
|
|
document.body.style.background = 'rgb(181,37,46)';
|
|
}
|
|
</script></body></html>"#;
|
|
format!("data:text/html,{}", percent_encode(html))
|
|
}
|
|
|
|
fn percent_encode(value: &str) -> String {
|
|
value
|
|
.bytes()
|
|
.map(|byte| match byte {
|
|
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
|
|
(byte as char).to_string()
|
|
}
|
|
_ => format!("%{byte:02X}"),
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn center_pixel_rgb(frame: &ely_servo_host::RenderedFrame) -> [u8; 3] {
|
|
let index = (((frame.height() / 2) * frame.width() + frame.width() / 2) * 4) as usize;
|
|
let rgba = &frame.rgba_bytes()[index..index + 4];
|
|
[rgba[0], rgba[1], rgba[2]]
|
|
}
|