mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 16:07:24 +00:00
fix(renderer): load and retire web fonts by child Document owner
This commit is contained in:
@@ -128,13 +128,16 @@ fn reconcile_document_font_face_set_ready<'s>(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if unsafe { &*host_ptr }.document_handle() != handle {
|
||||
if unsafe { &*host_ptr }
|
||||
.owner_dispatch_scope_for_node(handle)
|
||||
.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
crate::document_runtime::DocumentRuntime::reconcile_document_web_fonts_in_current_scope(
|
||||
scope, host_ptr,
|
||||
crate::document_runtime::DocumentRuntime::reconcile_web_fonts_for_document_in_current_scope(
|
||||
scope, host_ptr, handle,
|
||||
);
|
||||
if unsafe { &*host_ptr }.document_web_fonts_have_pending_requests() {
|
||||
if unsafe { &*host_ptr }.document_web_fonts_have_pending_requests(handle) {
|
||||
if get_private_value(scope, fonts, DOCUMENT_FONT_FACE_SET_READY_RESOLVER)
|
||||
.is_some_and(|value| value.is_object())
|
||||
{
|
||||
@@ -152,20 +155,20 @@ fn reconcile_document_font_face_set_ready<'s>(
|
||||
set_font_face_set_status(scope, fonts, "loading");
|
||||
}
|
||||
} else {
|
||||
settle_document_font_face_set_ready(scope, host_ptr);
|
||||
settle_document_font_face_set_ready(scope, host_ptr, handle);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn settle_document_font_face_set_ready(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
host_ptr: *mut crate::native_bridge::JsContextHost,
|
||||
document: crate::document_runtime::DomHandle,
|
||||
) {
|
||||
let host = unsafe { &*host_ptr };
|
||||
if host.document_web_fonts_have_pending_requests() {
|
||||
if host.document_web_fonts_have_pending_requests(document) {
|
||||
return;
|
||||
}
|
||||
let Some(document) = crate::util::node_wrapper_from_handle(scope, host.document_handle())
|
||||
else {
|
||||
let Some(document) = crate::util::node_wrapper_from_handle(scope, document) else {
|
||||
return;
|
||||
};
|
||||
let Some(fonts) = document
|
||||
|
||||
@@ -704,6 +704,7 @@ impl JsContextHost {
|
||||
let current_owner = transition
|
||||
.current_owner()
|
||||
.expect("committed child document-open replacement must install an owner");
|
||||
host.retire_embedded_document_fonts(document_handle);
|
||||
host.replace_document_resource_loader_for_document_open(
|
||||
crate::native_bridge::WindowDocumentOwner::Frame(retired_owner),
|
||||
crate::network::context::DocumentFetchContext::new(
|
||||
|
||||
@@ -105,6 +105,7 @@ impl JsContextHost {
|
||||
retired_owner: crate::frame_owner_model::FrameDocumentTaskOwner,
|
||||
document_handle: DomHandle,
|
||||
) {
|
||||
self.retire_embedded_document_fonts(document_handle);
|
||||
self.retire_web_mcp_document(crate::native_bridge::WindowDocumentOwner::Frame(
|
||||
retired_owner,
|
||||
));
|
||||
|
||||
@@ -285,45 +285,11 @@ impl JsContextHost {
|
||||
);
|
||||
return;
|
||||
}
|
||||
for (binding, resource) in stylesheet_subresources {
|
||||
let request_url = resource.request_url().clone();
|
||||
let kind = resource.kind();
|
||||
let css_image = if kind
|
||||
== crate::css_resource_urls::StylesheetLoadBlockingResourceKind::Image
|
||||
&& self.layout_policy().uses_real_layout()
|
||||
{
|
||||
match self.admit_stylesheet_css_image(binding, request_url.as_str().to_owned()) {
|
||||
crate::native_bridge::CssImageResourceAdmission::Fetch(identity) => {
|
||||
Some(identity)
|
||||
}
|
||||
crate::native_bridge::CssImageResourceAdmission::Reused => {
|
||||
self.settle_stylesheet_subresource_load_delay(binding);
|
||||
continue;
|
||||
}
|
||||
crate::native_bridge::CssImageResourceAdmission::Untracked => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let failed_css_image = css_image.clone();
|
||||
if let Err(error) = crate::network_host::start_stylesheet_subresource_fetch(
|
||||
scope, self, binding, resource, css_image,
|
||||
) {
|
||||
if let Some(identity) = failed_css_image.as_ref() {
|
||||
let _ = self.fail_stylesheet_css_image(identity);
|
||||
}
|
||||
let settlement = self.settle_stylesheet_subresource_load_delay(binding);
|
||||
tracing::warn!(
|
||||
child_handle = ?completion.child_handle,
|
||||
owner = ?completion.owner,
|
||||
url = %request_url,
|
||||
?kind,
|
||||
settled = settlement.settled(),
|
||||
%error,
|
||||
"child stylesheet subresource failed before network scheduling"
|
||||
);
|
||||
}
|
||||
}
|
||||
crate::document_runtime::DocumentRuntime::start_stylesheet_subresource_fetches_in_current_scope(
|
||||
scope,
|
||||
self,
|
||||
stylesheet_subresources,
|
||||
);
|
||||
tracing::debug!(
|
||||
child_handle = ?completion.child_handle,
|
||||
owner = ?completion.owner,
|
||||
|
||||
@@ -568,49 +568,62 @@ impl JsContextHost {
|
||||
|
||||
pub(crate) fn document_web_font_resources_are_current(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
generation: crate::style_engine::StylesheetResourceGeneration,
|
||||
) -> bool {
|
||||
self.document_layout_state
|
||||
.borrow()
|
||||
.web_font_resources_are_current(generation)
|
||||
.web_font_resources_are_current(document, self.document_handle(), generation)
|
||||
}
|
||||
|
||||
pub(crate) fn document_web_font_sidecar_is_pristine(&self) -> bool {
|
||||
pub(crate) fn document_web_font_sidecar_is_pristine(&self, document: DomHandle) -> bool {
|
||||
self.document_layout_state
|
||||
.borrow()
|
||||
.web_font_sidecar_is_pristine()
|
||||
.web_font_sidecar_is_pristine(document, self.document_handle())
|
||||
}
|
||||
|
||||
pub(crate) fn publish_document_web_font_resource_generation(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
generation: crate::style_engine::StylesheetResourceGeneration,
|
||||
) {
|
||||
self.document_layout_state
|
||||
.borrow_mut()
|
||||
.publish_web_font_resource_generation(generation);
|
||||
.publish_web_font_resource_generation(document, self.document_handle(), generation);
|
||||
}
|
||||
|
||||
pub(crate) fn retain_document_web_font_slots<'a>(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
resources: impl IntoIterator<Item = &'a StylesheetLoadBlockingResource>,
|
||||
) {
|
||||
self.document_layout_state
|
||||
.borrow_mut()
|
||||
.retain_active_slots(resources);
|
||||
self.document_layout_state.borrow_mut().retain_active_slots(
|
||||
document,
|
||||
self.document_handle(),
|
||||
resources,
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn admit_document_web_font(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
resource: StylesheetLoadBlockingResource,
|
||||
) -> Option<StylesheetLoadBlockingResource> {
|
||||
self.document_layout_state.borrow_mut().admit(resource)
|
||||
self.document_layout_state
|
||||
.borrow_mut()
|
||||
.admit(document, self.document_handle(), resource)
|
||||
}
|
||||
|
||||
pub(crate) fn complete_document_web_font(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
terminal: CompletedStylesheetWebFont,
|
||||
) -> DocumentWebFontCompletion {
|
||||
let completion = self.document_layout_state.borrow_mut().complete(terminal);
|
||||
let completion = self.document_layout_state.borrow_mut().complete(
|
||||
document,
|
||||
self.document_handle(),
|
||||
terminal,
|
||||
);
|
||||
if matches!(&completion, DocumentWebFontCompletion::Registered(_)) {
|
||||
// Geometry sampled while the request was pending contains fallback
|
||||
// glyph advances. Retire that tree before fonts.ready observers run.
|
||||
@@ -621,10 +634,29 @@ impl JsContextHost {
|
||||
completion
|
||||
}
|
||||
|
||||
pub(crate) fn document_web_fonts_have_pending_requests(&self) -> bool {
|
||||
pub(crate) fn document_web_fonts_have_pending_requests(&self, document: DomHandle) -> bool {
|
||||
self.document_layout_state
|
||||
.borrow()
|
||||
.web_fonts_have_pending_requests()
|
||||
.web_fonts_have_pending_requests(document, self.document_handle())
|
||||
}
|
||||
|
||||
pub(crate) fn stylesheet_web_font_document(
|
||||
&self,
|
||||
binding: crate::frame_owner_model::StylesheetSubresourceLoadDelayBinding,
|
||||
) -> Option<DomHandle> {
|
||||
if !self.stylesheet_subresource_load_delay_is_current(binding) {
|
||||
return None;
|
||||
}
|
||||
match binding.child_handle() {
|
||||
Some(child) => self.child_browsing_context_document_handle(child),
|
||||
None => Some(self.document_handle()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn retire_embedded_document_fonts(&self, document: DomHandle) {
|
||||
self.document_layout_state
|
||||
.borrow_mut()
|
||||
.retire_embedded_document_fonts(document);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -40,7 +40,13 @@ pub(super) struct DocumentLayoutState {
|
||||
services: DocumentLayoutServices,
|
||||
embedded_document_services: HashMap<DomHandle, DocumentLayoutServices>,
|
||||
web_fonts: DocumentWebFontState,
|
||||
web_font_resource_generation: Option<StylesheetResourceGeneration>,
|
||||
// Allocated only when a child has enabled font resource work. The existing
|
||||
// child Parley service remains the sole owner of its registered fonts.
|
||||
#[expect(
|
||||
clippy::box_collection,
|
||||
reason = "Keep the per-Page field pointer-sized when child font loading is unused"
|
||||
)]
|
||||
embedded_web_fonts: Option<Box<HashMap<DomHandle, DocumentWebFontState>>>,
|
||||
visual_state_generation: u64,
|
||||
latest_layout: LatestLayoutTreeCache,
|
||||
/// Last used content viewport published by each live iframe owner's
|
||||
@@ -62,22 +68,82 @@ impl DocumentLayoutState {
|
||||
/// Both conditions are required: a request can own a slot before its
|
||||
/// stylesheet generation is published, while an empty published manifest
|
||||
/// still records reconciliation state that later mutations must advance.
|
||||
pub(super) fn web_font_sidecar_is_pristine(&self) -> bool {
|
||||
self.web_font_resource_generation.is_none() && self.web_fonts.is_empty()
|
||||
pub(super) fn web_font_sidecar_is_pristine(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
main_document: DomHandle,
|
||||
) -> bool {
|
||||
self.web_fonts_for_document(document, main_document)
|
||||
.is_none_or(DocumentWebFontState::is_pristine)
|
||||
}
|
||||
|
||||
pub(super) fn web_font_resources_are_current(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
main_document: DomHandle,
|
||||
generation: StylesheetResourceGeneration,
|
||||
) -> bool {
|
||||
self.web_font_resource_generation == Some(generation)
|
||||
self.web_fonts_for_document(document, main_document)
|
||||
.is_some_and(|fonts| fonts.resources_are_current(generation))
|
||||
}
|
||||
|
||||
pub(super) fn publish_web_font_resource_generation(
|
||||
&mut self,
|
||||
document: DomHandle,
|
||||
main_document: DomHandle,
|
||||
generation: StylesheetResourceGeneration,
|
||||
) {
|
||||
self.web_font_resource_generation = Some(generation);
|
||||
let fonts = if document == main_document {
|
||||
&mut self.web_fonts
|
||||
} else {
|
||||
self.embedded_web_fonts
|
||||
.get_or_insert_with(Default::default)
|
||||
.entry(document)
|
||||
.or_default()
|
||||
};
|
||||
// An empty manifest needs a revision, but no text/layout service.
|
||||
fonts.publish_resource_generation(generation);
|
||||
}
|
||||
|
||||
fn web_fonts_for_document(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
main_document: DomHandle,
|
||||
) -> Option<&DocumentWebFontState> {
|
||||
if document == main_document {
|
||||
Some(&self.web_fonts)
|
||||
} else {
|
||||
self.embedded_web_fonts.as_ref()?.get(&document)
|
||||
}
|
||||
}
|
||||
|
||||
fn with_web_fonts_for_document<T>(
|
||||
&mut self,
|
||||
document: DomHandle,
|
||||
main_document: DomHandle,
|
||||
consume: impl FnOnce(&mut DocumentWebFontState, &mut DocumentLayoutServices) -> T,
|
||||
) -> T {
|
||||
if document == main_document {
|
||||
return consume(&mut self.web_fonts, &mut self.services);
|
||||
}
|
||||
let fonts = self
|
||||
.embedded_web_fonts
|
||||
.get_or_insert_with(Default::default)
|
||||
.entry(document)
|
||||
.or_default();
|
||||
let services = self.embedded_document_services.entry(document).or_default();
|
||||
consume(fonts, services)
|
||||
}
|
||||
|
||||
pub(super) fn retire_embedded_document_fonts(&mut self, document: DomHandle) {
|
||||
if let Some(fonts) = &mut self.embedded_web_fonts {
|
||||
if fonts.remove(&document).is_some() {
|
||||
self.embedded_document_services.remove(&document);
|
||||
}
|
||||
if fonts.is_empty() {
|
||||
self.embedded_web_fonts = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) const fn visual_state_generation(&self) -> u64 {
|
||||
@@ -119,6 +185,12 @@ impl DocumentLayoutState {
|
||||
) {
|
||||
self.embedded_document_services
|
||||
.retain(|document, _| is_live(*document));
|
||||
if let Some(fonts) = &mut self.embedded_web_fonts {
|
||||
fonts.retain(|document, _| is_live(*document));
|
||||
if fonts.is_empty() {
|
||||
self.embedded_web_fonts = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn latest_layout(
|
||||
@@ -233,32 +305,66 @@ impl DocumentLayoutState {
|
||||
|
||||
pub(super) fn retain_active_slots<'a>(
|
||||
&mut self,
|
||||
document: DomHandle,
|
||||
main_document: DomHandle,
|
||||
resources: impl IntoIterator<Item = &'a StylesheetLoadBlockingResource>,
|
||||
) {
|
||||
self.web_fonts
|
||||
.retain_active_slots(resources, &mut self.services);
|
||||
if document == main_document {
|
||||
return self
|
||||
.web_fonts
|
||||
.retain_active_slots(resources, &mut self.services);
|
||||
}
|
||||
if let Some(fonts) = self
|
||||
.embedded_web_fonts
|
||||
.as_mut()
|
||||
.and_then(|fonts| fonts.get_mut(&document))
|
||||
&& let Some(services) = self.embedded_document_services.get_mut(&document)
|
||||
{
|
||||
fonts.retain_active_slots(resources, services);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn admit(
|
||||
&mut self,
|
||||
document: DomHandle,
|
||||
main_document: DomHandle,
|
||||
resource: StylesheetLoadBlockingResource,
|
||||
) -> Option<StylesheetLoadBlockingResource> {
|
||||
self.web_fonts.admit(resource, &mut self.services)
|
||||
self.with_web_fonts_for_document(document, main_document, |fonts, services| {
|
||||
fonts.admit(resource, services)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn complete(
|
||||
&mut self,
|
||||
document: DomHandle,
|
||||
main_document: DomHandle,
|
||||
terminal: CompletedStylesheetWebFont,
|
||||
) -> DocumentWebFontCompletion {
|
||||
let completion = self.web_fonts.complete(terminal, &mut self.services);
|
||||
// A stale completion must not recreate an already-retired sidecar.
|
||||
if self
|
||||
.web_fonts_for_document(document, main_document)
|
||||
.is_none()
|
||||
{
|
||||
return DocumentWebFontCompletion::Stale;
|
||||
}
|
||||
let completion =
|
||||
self.with_web_fonts_for_document(document, main_document, |fonts, services| {
|
||||
fonts.complete(terminal, services)
|
||||
});
|
||||
if !matches!(&completion, DocumentWebFontCompletion::Stale) {
|
||||
self.mark_visual_state_dirty();
|
||||
}
|
||||
completion
|
||||
}
|
||||
|
||||
pub(super) fn web_fonts_have_pending_requests(&self) -> bool {
|
||||
self.web_fonts.has_pending_requests()
|
||||
pub(super) fn web_fonts_have_pending_requests(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
main_document: DomHandle,
|
||||
) -> bool {
|
||||
self.web_fonts_for_document(document, main_document)
|
||||
.is_some_and(DocumentWebFontState::has_pending_requests)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -23,10 +23,10 @@ use moli_fetch::{
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum StylesheetSubresourceFetchStart {
|
||||
Pending,
|
||||
/// The request reached a synchronous terminal and has no main-Document
|
||||
/// The request reached a synchronous terminal and has no Document
|
||||
/// font state to commit.
|
||||
Settled,
|
||||
/// A main-Document font reached a synchronous terminal. Network failure
|
||||
/// A Document font reached a synchronous terminal. Network failure
|
||||
/// is represented by `CompletedStylesheetWebFont` with no body bytes.
|
||||
WebFontSettled(CompletedStylesheetWebFont),
|
||||
}
|
||||
@@ -60,15 +60,14 @@ pub(crate) fn start_stylesheet_subresource_fetch(
|
||||
if css_image.is_some() && resource_kind != StylesheetLoadBlockingResourceKind::Image {
|
||||
return Err("a stylesheet CSS image identity was bound to a non-image resource".to_owned());
|
||||
}
|
||||
let is_main_web_font = binding.child_handle().is_none()
|
||||
&& resource_kind == StylesheetLoadBlockingResourceKind::Font;
|
||||
if is_main_web_font {
|
||||
let is_web_font = resource_kind == StylesheetLoadBlockingResourceKind::Font;
|
||||
if is_web_font {
|
||||
let web_font = resource.web_font().ok_or_else(|| {
|
||||
"main Document stylesheet font is missing parsed @font-face metadata".to_owned()
|
||||
"Document stylesheet font is missing parsed @font-face metadata".to_owned()
|
||||
})?;
|
||||
if web_font.request_id().is_none() {
|
||||
return Err(
|
||||
"main Document stylesheet font is missing its admitted request identity".to_owned(),
|
||||
"Document stylesheet font is missing its admitted request identity".to_owned(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -129,8 +128,8 @@ pub(crate) fn start_stylesheet_subresource_fetch(
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
let terminal = if is_main_web_font {
|
||||
let font = web_font.expect("main web-font metadata was validated before consumption");
|
||||
let terminal = if is_web_font {
|
||||
let font = web_font.expect("web-font metadata was validated before consumption");
|
||||
StylesheetSubresourceFetchStart::WebFontSettled(
|
||||
if (200..=299).contains(&response.status) {
|
||||
CompletedStylesheetWebFont::response(font, response.clone_body_bytes())
|
||||
@@ -172,7 +171,7 @@ pub(crate) fn start_stylesheet_subresource_fetch(
|
||||
let _ = host.fail_stylesheet_css_image(identity);
|
||||
}
|
||||
host.settle_stylesheet_subresource_load_delay(binding);
|
||||
return Ok(synchronous_failure_terminal(is_main_web_font, web_font));
|
||||
return Ok(synchronous_failure_terminal(is_web_font, web_font));
|
||||
}
|
||||
let network_partition_key = active_subresource_network_partition_key(host, owner);
|
||||
let policy_context = effective_subresource_policy_context(scope, host, owner);
|
||||
@@ -306,12 +305,12 @@ pub(crate) fn start_stylesheet_subresource_fetch(
|
||||
}
|
||||
|
||||
fn synchronous_failure_terminal(
|
||||
is_main_web_font: bool,
|
||||
is_web_font: bool,
|
||||
web_font: Option<crate::css_resource_urls::StylesheetWebFont>,
|
||||
) -> StylesheetSubresourceFetchStart {
|
||||
if is_main_web_font {
|
||||
if is_web_font {
|
||||
StylesheetSubresourceFetchStart::WebFontSettled(CompletedStylesheetWebFont::failure(
|
||||
web_font.expect("main web-font metadata was validated before consumption"),
|
||||
web_font.expect("web-font metadata was validated before consumption"),
|
||||
))
|
||||
} else {
|
||||
StylesheetSubresourceFetchStart::Settled
|
||||
|
||||
@@ -426,12 +426,20 @@ impl ScriptVm {
|
||||
|
||||
pub(super) fn complete_document_web_font(
|
||||
&mut self,
|
||||
binding: crate::frame_owner_model::StylesheetSubresourceLoadDelayBinding,
|
||||
terminal: crate::css_resource_urls::CompletedStylesheetWebFont,
|
||||
) {
|
||||
let Some(document) = self
|
||||
._context_host
|
||||
.borrow()
|
||||
.stylesheet_web_font_document(binding)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
match self
|
||||
._context_host
|
||||
.borrow()
|
||||
.complete_document_web_font(terminal)
|
||||
.complete_document_web_font(document, terminal)
|
||||
{
|
||||
web_fonts::DocumentWebFontCompletion::Registered(outcome) => tracing::debug!(
|
||||
?outcome,
|
||||
@@ -449,7 +457,9 @@ impl ScriptVm {
|
||||
}
|
||||
}
|
||||
if let Err(error) = self.with_default_context_scope(|scope, host_ptr| {
|
||||
crate::context_bootstrap::settle_document_font_face_set_ready(scope, host_ptr);
|
||||
crate::context_bootstrap::settle_document_font_face_set_ready(
|
||||
scope, host_ptr, document,
|
||||
);
|
||||
Ok(())
|
||||
}) {
|
||||
tracing::warn!(%error, "failed to settle current document font readiness");
|
||||
|
||||
@@ -783,22 +783,19 @@ impl ScriptVm {
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
if binding.child_handle().is_none() {
|
||||
completed_web_font = web_font.map(|font| {
|
||||
if (200..=299).contains(&response_status)
|
||||
&& !opaque_response_blocked
|
||||
{
|
||||
crate::css_resource_urls::CompletedStylesheetWebFont::response(
|
||||
font,
|
||||
observable_response.clone_body_bytes(),
|
||||
)
|
||||
} else {
|
||||
crate::css_resource_urls::CompletedStylesheetWebFont::failure(
|
||||
font,
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
completed_web_font = web_font.map(|font| {
|
||||
let terminal = if (200..=299).contains(&response_status)
|
||||
&& !opaque_response_blocked
|
||||
{
|
||||
crate::css_resource_urls::CompletedStylesheetWebFont::response(
|
||||
font,
|
||||
observable_response.clone_body_bytes(),
|
||||
)
|
||||
} else {
|
||||
crate::css_resource_urls::CompletedStylesheetWebFont::failure(font)
|
||||
};
|
||||
(binding, terminal)
|
||||
});
|
||||
apply_stylesheet_subresource_terminal(&context_host, binding);
|
||||
}
|
||||
PendingSubresourceContinuation::Beacon
|
||||
@@ -912,11 +909,9 @@ impl ScriptVm {
|
||||
.borrow_mut()
|
||||
.fail_stylesheet_css_image(identity);
|
||||
}
|
||||
if binding.child_handle().is_none() {
|
||||
completed_web_font = web_font.map(
|
||||
crate::css_resource_urls::CompletedStylesheetWebFont::failure,
|
||||
);
|
||||
}
|
||||
completed_web_font = web_font.map(|font| {
|
||||
(binding, crate::css_resource_urls::CompletedStylesheetWebFont::failure(font))
|
||||
});
|
||||
apply_stylesheet_subresource_terminal(&context_host, binding);
|
||||
}
|
||||
PendingSubresourceContinuation::Beacon
|
||||
@@ -946,8 +941,8 @@ impl ScriptVm {
|
||||
);
|
||||
Ok(AsyncSubresourceFetchBodyActivity::WindowRealmEntered)
|
||||
});
|
||||
if let Some(web_font) = completed_web_font {
|
||||
self.complete_document_web_font(web_font);
|
||||
if let Some((binding, web_font)) = completed_web_font {
|
||||
self.complete_document_web_font(binding, web_font);
|
||||
}
|
||||
trace_async_subresource_stage(
|
||||
"async_subresource_resolve_pending_done",
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
use super::*;
|
||||
use crate::{
|
||||
css_resource_urls::{
|
||||
CompletedStylesheetWebFont, StylesheetLoadBlockingResource,
|
||||
stylesheet_load_blocking_font_resources,
|
||||
},
|
||||
protocol_types::OptionalResourceFetchMask,
|
||||
};
|
||||
use moli_page_types::LayoutPolicy;
|
||||
|
||||
fn font_resource() -> StylesheetLoadBlockingResource {
|
||||
stylesheet_load_blocking_font_resources(
|
||||
"@font-face{font-family:ChildDocumentSourceFont;src:url(font.woff2)}",
|
||||
&Url::parse("https://child-document-font.test/style.css").unwrap(),
|
||||
OptionalResourceFetchMask::FONT,
|
||||
)
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn font_response(resource: &StylesheetLoadBlockingResource) -> CompletedStylesheetWebFont {
|
||||
let (_, request) = resource.clone().into_parts();
|
||||
CompletedStylesheetWebFont::response(
|
||||
request.unwrap(),
|
||||
include_bytes!("../../../../moli-layout/tests/fixtures/moli-ahem.woff2").to_vec(),
|
||||
)
|
||||
}
|
||||
|
||||
// These regressions exercise explicit OnDemand layout, rather than changing
|
||||
// the legacy snapshot freshness contract to satisfy a Realtime fixture.
|
||||
fn refresh_child_geometry_without_paint(vm: &StandaloneScriptVmHarness) {
|
||||
let host = vm._context_host.borrow();
|
||||
let document = host.document_handle();
|
||||
let pass = host
|
||||
.build_layout_pass_for_document(
|
||||
document,
|
||||
moli_layout::LayoutPassRequest::new(
|
||||
host.layout_viewport_for_document(document),
|
||||
moli_layout::LayoutFlushReason::Explicit,
|
||||
),
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(pass.metrics.paint_operation_count, 0);
|
||||
host.publish_layout_pass_for_document(document, pass);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_web_font_document_open_retires_registrations_and_rejects_old_owner() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://child-document-font.test/",
|
||||
"<!doctype html><span id=parentTarget style='display:inline-block;font:20px ChildDocumentSourceFont,serif'>iiii</span><iframe id=frame></iframe>",
|
||||
);
|
||||
vm.set_layout_policy(LayoutPolicy::OnDemand);
|
||||
let rewrite = "globalThis.child=frame.contentWindow;child.document.open();\
|
||||
child.document.write('<!doctype html><style>@font-face{font-family:ChildDocumentSourceFont;src:url(font.woff2)}</style><span id=target style=\"display:inline-block;font:20px ChildDocumentSourceFont,serif\">iiii</span>');\
|
||||
child.document.close();globalThis.childTarget=child.document.getElementById('target')";
|
||||
vm.eval(rewrite).unwrap();
|
||||
let (document, binding, resource) = {
|
||||
let mut host = vm._context_host.borrow_mut();
|
||||
let frame = host.dom_host().element_handle_by_id("frame").unwrap();
|
||||
let document = host.child_browsing_context_document_handle(frame).unwrap();
|
||||
let binding = host
|
||||
.accept_current_stylesheet_subresource_load_delay_for_document(document)
|
||||
.unwrap();
|
||||
let resource = host
|
||||
.admit_document_web_font(document, font_resource())
|
||||
.unwrap();
|
||||
(document, binding, resource)
|
||||
};
|
||||
refresh_child_geometry_without_paint(&vm);
|
||||
let fallback = vm.eval("String(childTarget.offsetWidth)").unwrap();
|
||||
assert_ne!(fallback, "48");
|
||||
let parent = vm.eval("String(parentTarget.offsetWidth)").unwrap();
|
||||
vm.complete_document_web_font(binding, font_response(&resource));
|
||||
refresh_child_geometry_without_paint(&vm);
|
||||
assert_eq!(vm.eval("String(childTarget.offsetWidth)").unwrap(), "48");
|
||||
assert_eq!(vm.eval("String(parentTarget.offsetWidth)").unwrap(), parent);
|
||||
|
||||
vm.eval(rewrite).unwrap();
|
||||
refresh_child_geometry_without_paint(&vm);
|
||||
assert_eq!(
|
||||
vm.eval("String(childTarget.offsetWidth)").unwrap(),
|
||||
fallback
|
||||
);
|
||||
let (replacement_binding, replacement_resource) = {
|
||||
let mut host = vm._context_host.borrow_mut();
|
||||
let frame = host.dom_host().element_handle_by_id("frame").unwrap();
|
||||
// document.open reuses the DOM handle; the exact task owner differs.
|
||||
assert_eq!(
|
||||
host.child_browsing_context_document_handle(frame),
|
||||
Some(document)
|
||||
);
|
||||
assert!(host.stylesheet_web_font_document(binding).is_none());
|
||||
let binding = host
|
||||
.accept_current_stylesheet_subresource_load_delay_for_document(document)
|
||||
.unwrap();
|
||||
let resource = host
|
||||
.admit_document_web_font(document, font_resource())
|
||||
.unwrap();
|
||||
(binding, resource)
|
||||
};
|
||||
assert_eq!(
|
||||
resource.web_font().unwrap().request_id(),
|
||||
replacement_resource.web_font().unwrap().request_id()
|
||||
);
|
||||
let before = vm.layout_pass_observability_for_test().1;
|
||||
vm.complete_document_web_font(binding, font_response(&resource));
|
||||
assert_eq!(
|
||||
vm.eval("String(childTarget.offsetWidth)").unwrap(),
|
||||
fallback
|
||||
);
|
||||
assert_eq!(vm.layout_pass_observability_for_test().1, before);
|
||||
vm.complete_document_web_font(replacement_binding, font_response(&replacement_resource));
|
||||
refresh_child_geometry_without_paint(&vm);
|
||||
assert_eq!(vm.eval("String(childTarget.offsetWidth)").unwrap(), "48");
|
||||
vm.eval("frame.remove()").unwrap();
|
||||
assert!(
|
||||
vm._context_host
|
||||
.borrow()
|
||||
.stylesheet_web_font_document(replacement_binding)
|
||||
.is_none()
|
||||
);
|
||||
vm.complete_document_web_font(replacement_binding, font_response(&replacement_resource));
|
||||
refresh_child_geometry_without_paint(&vm);
|
||||
assert_eq!(vm.eval("String(parentTarget.offsetWidth)").unwrap(), parent);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn child_document_fonts_ready_waits_for_its_own_resource() {
|
||||
let (font_url, request_rx, release_tx, server) = spawn_gated_font_resource_server().await;
|
||||
let document_url = font_url.replace("/print-only.woff2", "/page");
|
||||
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).unwrap();
|
||||
loader.set_optional_resource_fetch_mask(OptionalResourceFetchMask::FONT);
|
||||
let (mut vm, mut completions) = new_parsed_test_vm_with_loader_and_resource_completion_queue(
|
||||
&document_url,
|
||||
"<!doctype html><span id=parentTarget style='display:inline-block;font:20px ChildReady,serif'>iiii</span><iframe id=frame></iframe>",
|
||||
&loader,
|
||||
);
|
||||
vm.set_layout_policy(LayoutPolicy::OnDemand);
|
||||
vm.eval(&format!(r#"
|
||||
globalThis.child=frame.contentWindow;
|
||||
child.document.open();
|
||||
child.document.write('<!doctype html><style>@font-face{{font-family:ChildReady;src:url({font_url})}}</style><span id=target style="display:inline-block;font:20px ChildReady,serif">iiii</span>');
|
||||
child.document.close();
|
||||
globalThis.childTarget=child.document.getElementById('target');
|
||||
globalThis.rootReady=false;globalThis.childReady=false;
|
||||
document.fonts.ready.then(fonts=>rootReady=fonts===document.fonts);
|
||||
globalThis.childPromise=child.document.fonts.ready;
|
||||
childPromise.then(fonts=>childReady=fonts===child.document.fonts);
|
||||
'installed'
|
||||
"#)).unwrap();
|
||||
tokio::time::timeout(std::time::Duration::from_secs(2), request_rx)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(vm.eval("JSON.stringify([rootReady,childReady,document.fonts.status,child.document.fonts.status,childPromise===child.document.fonts.ready])").unwrap(), r#"[true,false,"loaded","loading",true]"#);
|
||||
refresh_child_geometry_without_paint(&vm);
|
||||
let fallback = vm.eval("String(childTarget.offsetWidth)").unwrap();
|
||||
assert_ne!(fallback, "48");
|
||||
let parent = vm.eval("String(parentTarget.offsetWidth)").unwrap();
|
||||
release_tx.send(()).unwrap();
|
||||
server.await.unwrap();
|
||||
assert!(
|
||||
tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
completions.wait_for_arrival_without_timeout()
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
);
|
||||
let completion = completions.pop_next_async_subresource_event().unwrap();
|
||||
let _ = vm
|
||||
.complete_async_subresource_fetch_event_body(completion)
|
||||
.unwrap();
|
||||
vm.perform_owner_lane_task_microtask_checkpoints().unwrap();
|
||||
refresh_child_geometry_without_paint(&vm);
|
||||
assert_eq!(vm.eval("JSON.stringify([rootReady,childReady,child.document.fonts.status,childTarget.offsetWidth])").unwrap(), r#"[true,true,"loaded",48]"#);
|
||||
assert_eq!(vm.eval("String(parentTarget.offsetWidth)").unwrap(), parent);
|
||||
assert_eq!(vm.document_web_font_counts_for_test(), (0, 0, 0));
|
||||
assert_eq!(
|
||||
vm.layout_pass_observability_for_test()
|
||||
.3
|
||||
.unwrap()
|
||||
.paint_operation_count,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn child_web_fonts_share_the_resource_policy_and_preserve_document_isolation() {
|
||||
use base64::Engine;
|
||||
|
||||
let font = base64::engine::general_purpose::STANDARD.encode(include_bytes!(
|
||||
"../../../../moli-layout/tests/fixtures/moli-ahem.woff2"
|
||||
));
|
||||
for policy in [LayoutPolicy::Mock, LayoutPolicy::OnDemand] {
|
||||
for mask in [
|
||||
OptionalResourceFetchMask::NONE,
|
||||
OptionalResourceFetchMask::FONT,
|
||||
] {
|
||||
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).unwrap();
|
||||
loader.set_optional_resource_fetch_mask(mask);
|
||||
let (mut vm, _completions) =
|
||||
new_parsed_test_vm_with_loader_and_resource_completion_queue(
|
||||
"https://child-font-policy.test/",
|
||||
"<!doctype html><span id=parentTarget style='display:inline-block;font:20px ChildPolicy,serif'>iiii</span><iframe id=frame></iframe>",
|
||||
&loader,
|
||||
);
|
||||
vm.set_layout_policy(policy);
|
||||
vm.eval(&format!(r#"
|
||||
globalThis.child=frame.contentWindow;
|
||||
child.document.open();
|
||||
child.document.write('<!doctype html><style>@font-face{{font-family:ChildPolicy;src:url(data:font/woff2;base64,{font})}}</style><span id=target style="display:inline-block;font:20px ChildPolicy,serif">iiii</span>');
|
||||
child.document.close();
|
||||
globalThis.childTarget=child.document.getElementById('target');
|
||||
"#)).unwrap();
|
||||
vm.reconcile_document_web_fonts_for_layout();
|
||||
let fonts_enabled = mask == OptionalResourceFetchMask::FONT;
|
||||
{
|
||||
let host = vm._context_host.borrow();
|
||||
let frame = host.dom_host().element_handle_by_id("frame").unwrap();
|
||||
let document = host.child_browsing_context_document_handle(frame).unwrap();
|
||||
assert_eq!(
|
||||
host.document_web_font_sidecar_is_pristine(document),
|
||||
!fonts_enabled,
|
||||
"{policy:?}, {mask:?}"
|
||||
);
|
||||
assert!(!host.document_web_fonts_have_pending_requests(document));
|
||||
}
|
||||
assert_eq!(vm.document_web_font_counts_for_test(), (0, 0, 0));
|
||||
if policy == LayoutPolicy::OnDemand {
|
||||
refresh_child_geometry_without_paint(&vm);
|
||||
let child = vm.eval("String(childTarget.offsetWidth)").unwrap();
|
||||
assert_eq!(
|
||||
child == "48",
|
||||
fonts_enabled,
|
||||
"{policy:?}, {mask:?}: {child}"
|
||||
);
|
||||
let parent = vm.eval("String(parentTarget.offsetWidth)").unwrap();
|
||||
assert_ne!(
|
||||
parent, "48",
|
||||
"a child font must not become a parent registration"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod beforeinput_target_validation;
|
||||
mod child_document_web_fonts;
|
||||
mod credential_container;
|
||||
mod credential_json_options;
|
||||
mod cross_origin_navigation;
|
||||
|
||||
@@ -22,7 +22,7 @@ struct WebFontSlot {
|
||||
status: WebFontRequestStatus,
|
||||
}
|
||||
|
||||
/// Resource state for the current main Document's downloadable fonts.
|
||||
/// Resource state for one Document's downloadable fonts.
|
||||
///
|
||||
/// A request id is an identity for one concrete fetch, not a generation or a
|
||||
/// layout consistency fence. It prevents an old response from committing after
|
||||
@@ -33,6 +33,7 @@ struct WebFontSlot {
|
||||
pub(crate) struct DocumentWebFontState {
|
||||
next_request_id: u64,
|
||||
slots: BTreeMap<String, WebFontSlot>,
|
||||
resource_generation: Option<crate::style_engine::StylesheetResourceGeneration>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -44,8 +45,22 @@ pub(crate) enum DocumentWebFontCompletion {
|
||||
}
|
||||
|
||||
impl DocumentWebFontState {
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.slots.is_empty()
|
||||
pub(crate) fn is_pristine(&self) -> bool {
|
||||
self.resource_generation.is_none() && self.slots.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn resources_are_current(
|
||||
&self,
|
||||
generation: crate::style_engine::StylesheetResourceGeneration,
|
||||
) -> bool {
|
||||
self.resource_generation == Some(generation)
|
||||
}
|
||||
|
||||
pub(crate) fn publish_resource_generation(
|
||||
&mut self,
|
||||
generation: crate::style_engine::StylesheetResourceGeneration,
|
||||
) {
|
||||
self.resource_generation = Some(generation);
|
||||
}
|
||||
|
||||
pub(crate) fn has_pending_requests(&self) -> bool {
|
||||
|
||||
@@ -13,27 +13,53 @@ impl DocumentRuntime {
|
||||
) {
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
host.apply_pending_stylesheet_source_css_projections(scope, host_ptr);
|
||||
let font_fetch_enabled =
|
||||
host.current_main_document_resource_loader()
|
||||
.is_some_and(|loader| {
|
||||
loader.request_client().optional_resource_fetch_enabled(
|
||||
crate::types::SubresourceResourceType::Font,
|
||||
)
|
||||
});
|
||||
if !font_fetch_enabled {
|
||||
// The shared resource mask is checked before allocating a document
|
||||
// list. Default resource-free pages keep their existing cheap path.
|
||||
if !host
|
||||
.current_main_document_resource_loader()
|
||||
.is_some_and(|loader| {
|
||||
loader
|
||||
.request_client()
|
||||
.optional_resource_fetch_enabled(crate::types::SubresourceResourceType::Font)
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
let root = host.dom_host().document_element_handle();
|
||||
let Some(root) = root else {
|
||||
for document in host.active_layout_document_handles() {
|
||||
Self::reconcile_web_fonts_for_document_in_current_scope(scope, host_ptr, document);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn reconcile_web_fonts_for_document_in_current_scope(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
host_ptr: *mut JsContextHost,
|
||||
document: crate::document_runtime::DomHandle,
|
||||
) {
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
let Some(dispatch_scope) = host.owner_dispatch_scope_for_node(document) else {
|
||||
return;
|
||||
};
|
||||
let skip_pristine_document = {
|
||||
let document = host.document_handle();
|
||||
host.document_web_font_sidecar_is_pristine()
|
||||
&& !host.document_has_style_state(document)
|
||||
&& !host.document_has_active_author_stylesheet_sources(document)
|
||||
if !host
|
||||
.document_resource_loader_for_dispatch_scope(dispatch_scope)
|
||||
.is_some_and(|loader| {
|
||||
loader
|
||||
.request_client()
|
||||
.optional_resource_fetch_enabled(crate::types::SubresourceResourceType::Font)
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(root) = host
|
||||
.dom_host()
|
||||
.dom()
|
||||
.document_element_handle_for_document(document)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if skip_pristine_document {
|
||||
if host.document_web_font_sidecar_is_pristine(document)
|
||||
&& !host.document_has_style_state(document)
|
||||
&& !host.document_has_active_author_stylesheet_sources(document)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(resources) =
|
||||
@@ -42,36 +68,35 @@ impl DocumentRuntime {
|
||||
return;
|
||||
};
|
||||
tracing::trace!(
|
||||
?document,
|
||||
stylesheet_import_dependency_count = resources.imports().len(),
|
||||
stylesheet_web_font_dependency_count = resources.web_fonts().len(),
|
||||
"observed typed stylesheet resource manifest"
|
||||
);
|
||||
let generation = resources.generation();
|
||||
if host.document_web_font_resources_are_current(generation) {
|
||||
if host.document_web_font_resources_are_current(document, generation) {
|
||||
return;
|
||||
}
|
||||
let resources = resources.web_fonts().to_vec();
|
||||
host.retain_document_web_font_slots(resources.iter());
|
||||
host.retain_document_web_font_slots(document, resources.iter());
|
||||
if resources.is_empty() {
|
||||
host.publish_document_web_font_resource_generation(generation);
|
||||
host.publish_document_web_font_resource_generation(document, generation);
|
||||
return;
|
||||
}
|
||||
let resource_count = resources.len();
|
||||
let bound = {
|
||||
let bound = resources
|
||||
.into_iter()
|
||||
.filter_map(|resource| {
|
||||
host.accept_current_main_stylesheet_subresource_load_delay()
|
||||
.map(|binding| (binding, resource))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if bound.len() == resource_count {
|
||||
host.publish_document_web_font_resource_generation(generation);
|
||||
}
|
||||
bound
|
||||
};
|
||||
let bound = resources
|
||||
.into_iter()
|
||||
.filter_map(|resource| {
|
||||
host.accept_current_stylesheet_subresource_load_delay_for_document(document)
|
||||
.map(|binding| (binding, resource))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if bound.len() == resource_count {
|
||||
host.publish_document_web_font_resource_generation(document, generation);
|
||||
}
|
||||
Self::start_stylesheet_subresource_fetches_in_current_scope(scope, host_ptr, bound);
|
||||
}
|
||||
|
||||
pub(crate) fn settle_stylesheet_link_clients_in_current_scope(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
@@ -275,10 +300,11 @@ impl DocumentRuntime {
|
||||
let mut admitted = Vec::with_capacity(resources.len());
|
||||
for (binding, resource) in resources {
|
||||
match resource.kind() {
|
||||
crate::css_resource_urls::StylesheetLoadBlockingResourceKind::Font
|
||||
if binding.child_handle().is_none() =>
|
||||
{
|
||||
match host.admit_document_web_font(resource) {
|
||||
crate::css_resource_urls::StylesheetLoadBlockingResourceKind::Font => {
|
||||
let Some(document) = host.stylesheet_web_font_document(binding) else {
|
||||
continue;
|
||||
};
|
||||
match host.admit_document_web_font(document, resource) {
|
||||
Some(resource) => admitted.push((binding, resource, None)),
|
||||
None => {
|
||||
host.settle_stylesheet_subresource_load_delay(binding);
|
||||
@@ -308,8 +334,8 @@ impl DocumentRuntime {
|
||||
let request_url = resource.request_url().clone();
|
||||
let kind = resource.kind();
|
||||
let failed_css_image = css_image.clone();
|
||||
let failed_web_font = (binding.child_handle().is_none()
|
||||
&& kind == crate::css_resource_urls::StylesheetLoadBlockingResourceKind::Font)
|
||||
let failed_web_font = (kind
|
||||
== crate::css_resource_urls::StylesheetLoadBlockingResourceKind::Font)
|
||||
.then(|| {
|
||||
resource
|
||||
.web_font()
|
||||
@@ -323,7 +349,7 @@ impl DocumentRuntime {
|
||||
Ok(crate::network_host::StylesheetSubresourceFetchStart::WebFontSettled(
|
||||
web_font,
|
||||
)) => {
|
||||
Self::complete_document_web_font(host, web_font);
|
||||
Self::complete_document_web_font(host, binding, web_font);
|
||||
}
|
||||
Ok(
|
||||
crate::network_host::StylesheetSubresourceFetchStart::Pending
|
||||
@@ -335,7 +361,7 @@ impl DocumentRuntime {
|
||||
}
|
||||
let settlement = host.settle_stylesheet_subresource_load_delay(binding);
|
||||
if let Some(web_font) = failed_web_font {
|
||||
Self::complete_document_web_font(host, web_font);
|
||||
Self::complete_document_web_font(host, binding, web_font);
|
||||
}
|
||||
tracing::warn!(
|
||||
url = %request_url,
|
||||
@@ -357,9 +383,13 @@ impl DocumentRuntime {
|
||||
|
||||
fn complete_document_web_font(
|
||||
host: &JsContextHost,
|
||||
binding: crate::frame_owner_model::StylesheetSubresourceLoadDelayBinding,
|
||||
terminal: crate::css_resource_urls::CompletedStylesheetWebFont,
|
||||
) {
|
||||
match host.complete_document_web_font(terminal) {
|
||||
let Some(document) = host.stylesheet_web_font_document(binding) else {
|
||||
return;
|
||||
};
|
||||
match host.complete_document_web_font(document, terminal) {
|
||||
crate::script_vm::web_fonts::DocumentWebFontCompletion::Registered(outcome) => {
|
||||
tracing::debug!(
|
||||
?outcome,
|
||||
|
||||
Reference in New Issue
Block a user