From c806af3bc77bc22a8712a18de586f73dba711bd2 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Tue, 13 Oct 2026 03:55:08 +0800 Subject: [PATCH] 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). --- .../realtime_resize_observer.rs | 81 +++++++++++++++++++ .../script_vm/rendering_update/realtime.rs | 10 +++ 2 files changed, 91 insertions(+) diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/rendering_update/realtime_resize_observer.rs b/moli-renderer-v8/src/runtime/page_vm/tests/rendering_update/realtime_resize_observer.rs index 6fdf6bdb6e..9c3ea48ec6 100644 --- a/moli-renderer-v8/src/runtime/page_vm/tests/rendering_update/realtime_resize_observer.rs +++ b/moli-renderer-v8/src/runtime/page_vm/tests/rendering_update/realtime_resize_observer.rs @@ -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 = "iiii"; + page.vm_mut().eval(&format!(r#" + document.head.innerHTML=''; + document.body.innerHTML="iiii"; + 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> = + 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 + ); + }); +} diff --git a/moli-renderer-v8/src/script_vm/rendering_update/realtime.rs b/moli-renderer-v8/src/script_vm/rendering_update/realtime.rs index 4a047b10e9..7968dd0948 100644 --- a/moli-renderer-v8/src/script_vm/rendering_update/realtime.rs +++ b/moli-renderer-v8/src/script_vm/rendering_update/realtime.rs @@ -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) } }