fix(renderer): reconcile font resources after realtime frames

Admit CSS font sources after current layout and observer sampling so changes from script and observer callbacks can load without screenshots. Reuse the existing resource mask and document ownership checks; font registration schedules the next required geometry update.

Add root/child observer-driven CSSOM and disabled-font regressions. Validated with cargo fmt --all, workspace all-target all-feature Clippy, and 20,121 passing nextest tests (17 skipped).
This commit is contained in:
ldm0
2026-11-13 06:00:48 +08:00
parent 78d666d85f
commit c806af3bc7
2 changed files with 91 additions and 0 deletions
@@ -67,3 +67,84 @@ fn realtime_ro_initial_delivery_follows_raf_and_becomes_idle_without_capture() {
);
});
}
#[test]
fn realtime_frames_admit_cssom_fonts_after_observer_callbacks_without_capture() {
assert_frame_cssom_font_admission(true);
}
#[test]
fn realtime_frame_cssom_fonts_preserve_the_disabled_resource_policy() {
assert_frame_cssom_font_admission(false);
}
fn assert_frame_cssom_font_admission(fonts_enabled: bool) {
run_page_vm_large_stack_async_test("realtime-cssom-font-admission", move || async move {
use base64::Engine;
let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default()).unwrap();
loader.set_optional_resource_fetch_mask(if fonts_enabled {
crate::protocol_types::OptionalResourceFetchMask::FONT
} else {
crate::protocol_types::OptionalResourceFetchMask::NONE
});
let mut page = realtime_page(&loader);
let font = base64::engine::general_purpose::STANDARD.encode(include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../moli-layout/tests/fixtures/moli-ahem.woff2"
)));
let markup = "<!doctype html><style>@font-face{font-family:ObserverFont;src:local(MissingObserverFont)}</style><span id=target style='display:inline-block;font:20px ObserverFont,serif'>iiii</span>";
page.vm_mut().eval(&format!(r#"
document.head.innerHTML='<style>@font-face{{font-family:ObserverFont;src:local(MissingObserverFont)}}</style>';
document.body.innerHTML="<span id=target style='display:inline-block;font:20px ObserverFont,serif'>iiii</span><iframe id=frame></iframe>";
const child=frame.contentDocument;
child.open();child.write({markup});child.close();
window.samples=[[],[]];window.observers=[];
[document,child].forEach((doc,index)=>{{
const ro=new doc.defaultView.ResizeObserver(entries=>{{
samples[index].push(entries[0].contentRect.width);
if(samples[index].length===1){{
doc.styleSheets[0].cssRules[0].style.src='url(data:font/woff2;base64,{font})';
}}
}});
observers.push(ro);ro.observe(doc.getElementById('target'));
}});
"#,markup=serde_json::to_string(markup).unwrap())).unwrap();
// Advance bounded native frame opportunities, without screenshots,
// geometry queries or a manual font-reconciliation helper.
for _ in 0..4 {
if page
.next_rendering_opportunity_deadline_for_test()
.is_none()
{
break;
}
run_opportunity(&mut page, &loader).await.unwrap();
}
let samples: Vec<Vec<f64>> =
serde_json::from_str(&page.vm_mut().eval("JSON.stringify(samples)").unwrap()).unwrap();
for sample in &samples {
assert_eq!(
sample.len(),
if fonts_enabled { 2 } else { 1 },
"font policy {fonts_enabled}: {samples:?}"
);
assert_ne!(sample[0], 48.0);
if fonts_enabled {
assert_eq!(sample[1], 48.0);
}
}
assert!(
page.next_rendering_opportunity_deadline_for_test()
.is_none()
);
assert_eq!(
page.vm()
.layout_pass_observability_for_test()
.3
.unwrap()
.paint_operation_count,
0
);
});
}
@@ -127,6 +127,16 @@ impl ScriptVm {
if unsafe { &*host_ptr }.realtime_rendering_opportunity_is_current(id, root) {
crate::observer_runtime::sample_realtime_intersections(scope, host_ptr)?;
}
if unsafe { &*host_ptr }.realtime_rendering_opportunity_is_current(id, root) {
// Layout has published the actual child viewports, and rAF/RO may
// have changed CSS font sources. Admit those resources through the
// existing owner-checked loader after sampling this frame. A ready
// font invalidates geometry and requests its own next opportunity;
// synchronous getters never start resource or callback work.
crate::document_runtime::DocumentRuntime::reconcile_document_web_fonts_in_current_scope(
scope, host_ptr,
);
}
Ok(invoked)
}
}