mirror of
https://github.com/lexmount/moli.git
synced 2026-10-02 16:00:44 +00:00
fix(fonts): load string sources on demand
This commit is contained in:
@@ -1577,6 +1577,7 @@ css/css-flexbox/webkit-box-vertical-writing-mode.html
|
||||
css/css-flexbox/whitespace-in-flexitem-001.html
|
||||
css/css-flexbox/zero-content-size-with-scrollbar-crash.html
|
||||
css/css-font-loading/empty-family-load.html
|
||||
css/css-font-loading/font-face-reject.html
|
||||
css/css-font-loading/fontface-font-variation-settings-persisted-js-api.html
|
||||
css/css-font-loading/fontface-fonts-loading.html
|
||||
css/css-font-loading/fontface-invalid-arraybuffer.html
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
document.fonts.onloading = (event) => events.push(event.type);
|
||||
document.fonts.onloadingdone = (event) => events.push(event.type);
|
||||
document.fonts.onloadingerror = (event) => events.push(event.type);
|
||||
document.fonts.add(new FontFace('FixtureFace', 'url(fixture.woff2)'));
|
||||
|
||||
document.fonts
|
||||
.load('10px sans-serif')
|
||||
.load('10px FixtureFace')
|
||||
.then(() => document.fonts.ready)
|
||||
.then(() => {
|
||||
body.setAttribute('data-order', events.join(','));
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
document.fonts.addEventListener('loading', onLoadingB);
|
||||
document.fonts.addEventListener('loadingdone', onLoadingDone);
|
||||
document.fonts.removeEventListener('loading', onLoadingA);
|
||||
document.fonts.add(new FontFace('Roboto', 'url(roboto.woff2)'));
|
||||
document.fonts.load('italic bold 16px Roboto');
|
||||
|
||||
document.body.setAttribute('data-loading-a', String(loadingA));
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
throw new Error("font handler boom");
|
||||
};
|
||||
try {
|
||||
document.fonts.add(new FontFace("Roboto", "url(roboto.woff2)"));
|
||||
document.fonts.load("italic bold 16px Roboto");
|
||||
body.setAttribute("data-font-continued", "true");
|
||||
} catch (error) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
const ready1 = document.fonts.ready;
|
||||
body.setAttribute('data-ready-stable', String(ready1 === document.fonts.ready));
|
||||
|
||||
document.fonts.add(new FontFace('FixtureFace', 'url(fixture.woff2)'));
|
||||
const loadPromise = document.fonts.load('10px FixtureFace');
|
||||
const ready2 = document.fonts.ready;
|
||||
body.setAttribute('data-ready-replaced', String(ready1 !== ready2));
|
||||
|
||||
+10
-10
@@ -2459,16 +2459,16 @@ fn stylo_list_marker_type(computed: &ComputedValues) -> LayoutListMarkerType {
|
||||
_ => LayoutListMarkerType::Fallback,
|
||||
},
|
||||
CounterStyle::String(value) => LayoutListMarkerType::String(Arc::from(value.as_ref())),
|
||||
CounterStyle::Symbols { symbols, .. } => LayoutListMarkerType::Symbols(
|
||||
symbols
|
||||
.0
|
||||
.iter()
|
||||
.map(|symbol| match symbol {
|
||||
Symbol::String(value) => Arc::from(value.as_ref()),
|
||||
Symbol::Ident(value) => Arc::from(value.0.as_ref()),
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
CounterStyle::Symbols { symbols, .. } => {
|
||||
let mut text_symbols = Vec::with_capacity(symbols.0.len());
|
||||
for symbol in symbols.0.iter() {
|
||||
match symbol {
|
||||
Symbol::String(value) => text_symbols.push(Arc::from(value.as_ref())),
|
||||
Symbol::Ident(value) => text_symbols.push(Arc::from(value.0.as_ref())),
|
||||
}
|
||||
}
|
||||
LayoutListMarkerType::Symbols(text_symbols)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,9 @@ pub(crate) use crypto::{
|
||||
CryptoKeyAlgorithmClonePayload, CryptoKeyClonePayload, WebCryptoRejection, WebCryptoTaskResult,
|
||||
crypto_key_clone_payload_from_object, crypto_key_object_from_clone_payload,
|
||||
};
|
||||
pub(crate) use css_fontface_runtime::{new_font_face_set, rebuild_font_face_set_faces};
|
||||
pub(crate) use css_fontface_runtime::{
|
||||
load_font_faces_for_family, new_font_face_set, rebuild_font_face_set_faces,
|
||||
};
|
||||
pub(crate) use location_navigation::{
|
||||
LocationNavigationKind, dispatch_top_level_form_navigation_event,
|
||||
dispatch_top_level_navigation_event_with_source_element, meta_refresh_navigation_kind,
|
||||
|
||||
@@ -25,6 +25,7 @@ pub(super) const FONT_FACE_LINE_GAP_OVERRIDE_SLOT: &str = "__moliFontFaceLineGap
|
||||
pub(super) const FONT_FACE_SIZE_ADJUST_SLOT: &str = "__moliFontFaceSizeAdjust";
|
||||
pub(super) const FONT_FACE_STATUS_SLOT: &str = "__moliFontFaceStatus";
|
||||
pub(super) const FONT_FACE_LOADED_SLOT: &str = "__moliFontFaceLoaded";
|
||||
pub(super) const FONT_FACE_LOADED_RESOLVER_SLOT: &str = "__moliFontFaceLoadedResolver";
|
||||
pub(super) const FONT_FACE_SET_OWNERS_SLOT: &str = "__moliFontFaceSetOwners";
|
||||
pub(super) const FONT_FACE_LOAD_NOTIFICATION_SENT_SLOT: &str = "__moliFontFaceLoadNotificationSent";
|
||||
pub(super) const FONT_FACE_SET_MANUAL_FACES_SLOT: &str = "__moliFontFaceSetManualFaces";
|
||||
@@ -44,6 +45,7 @@ pub(in crate::context_bootstrap) use events::{
|
||||
initialize_font_face_set_load_event, install_font_face_set_event_handler_accessors,
|
||||
install_font_face_set_load_event_template_accessors,
|
||||
};
|
||||
pub(crate) use font_face::load_font_faces_for_family;
|
||||
pub(super) use font_face::{
|
||||
font_face_constructor_callback, font_face_load_callback, install_font_face_template_accessors,
|
||||
};
|
||||
|
||||
@@ -304,7 +304,7 @@ pub(super) fn notify_font_face_set_owners_of_load<'s>(
|
||||
}
|
||||
let loaded_faces = v8::Array::new(scope, 1);
|
||||
let _ = loaded_faces.set_index(scope, 0, face.into());
|
||||
let completion_event_type = if font_face_load_failed(scope, face) {
|
||||
let completion_event_type = if super::font_face::font_face_load_failed(scope, face) {
|
||||
"loadingerror"
|
||||
} else {
|
||||
"loadingdone"
|
||||
@@ -318,12 +318,3 @@ pub(super) fn notify_font_face_set_owners_of_load<'s>(
|
||||
dispatch_font_face_set_event(scope, owner, completion_event_type, Some(loaded_faces));
|
||||
}
|
||||
}
|
||||
|
||||
fn font_face_load_failed<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
face: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
get_private_value(scope, face, FONT_FACE_STATUS_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::String>::try_from(value).ok())
|
||||
.is_some_and(|status| status.to_rust_string_lossy(scope) == "error")
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ struct FontFaceObjectDeclaration<'s> {
|
||||
status: &'static str,
|
||||
#[webapi(slot = FONT_FACE_LOADED_SLOT)]
|
||||
loaded: Option<v8::Local<'s, v8::Promise>>,
|
||||
#[webapi(slot = FONT_FACE_LOADED_RESOLVER_SLOT)]
|
||||
loaded_resolver: Option<v8::Local<'s, v8::PromiseResolver>>,
|
||||
#[webapi(slot = FONT_FACE_SET_OWNERS_SLOT, constructor_default = Vec::new())]
|
||||
owner_sets: Vec<v8::Local<'s, v8::Value>>,
|
||||
#[webapi(slot = FONT_FACE_LOAD_NOTIFICATION_SENT_SLOT, constructor_default = false)]
|
||||
@@ -305,7 +307,7 @@ pub(in crate::context_bootstrap) fn font_face_constructor_callback<'s>(
|
||||
size_adjust,
|
||||
] = descriptors;
|
||||
let this = args.this();
|
||||
let (source, status, loaded) = if invalid_descriptors {
|
||||
let (source, status, loaded, loaded_resolver) = if invalid_descriptors {
|
||||
let source = match parsed.source {
|
||||
FontFaceConstructorSource::Css(source) => source,
|
||||
FontFaceConstructorSource::Binary(_) => String::new(),
|
||||
@@ -315,18 +317,30 @@ pub(in crate::context_bootstrap) fn font_face_constructor_callback<'s>(
|
||||
"SyntaxError",
|
||||
"Invalid FontFace descriptor.",
|
||||
);
|
||||
(source, "error", Some(loaded))
|
||||
(source, "error", Some(loaded), None)
|
||||
} else {
|
||||
match parsed.source {
|
||||
FontFaceConstructorSource::Css(source)
|
||||
if moli_css_parse::normalize_font_face_src(&source).is_some() =>
|
||||
{
|
||||
let resolver = v8::PromiseResolver::new(scope)
|
||||
.expect("FontFace loaded promise resolver should allocate");
|
||||
let loaded = resolver.get_promise(scope);
|
||||
(source, "unloaded", Some(loaded), Some(resolver))
|
||||
}
|
||||
FontFaceConstructorSource::Css(source) => {
|
||||
let loaded = resolved_promise(scope, this.into());
|
||||
(source, "loaded", loaded)
|
||||
let loaded = super::query::make_rejected_dom_exception_promise(
|
||||
scope,
|
||||
"SyntaxError",
|
||||
"Invalid FontFace source descriptor.",
|
||||
);
|
||||
(source, "error", Some(loaded), None)
|
||||
}
|
||||
FontFaceConstructorSource::Binary(bytes)
|
||||
if moli_web_mime::sniff_font_mime_type(&bytes).is_some() =>
|
||||
{
|
||||
let loaded = resolved_promise(scope, this.into());
|
||||
(String::new(), "loaded", loaded)
|
||||
(String::new(), "loaded", loaded, None)
|
||||
}
|
||||
FontFaceConstructorSource::Binary(_) => {
|
||||
let loaded = super::query::make_rejected_dom_exception_promise(
|
||||
@@ -334,7 +348,7 @@ pub(in crate::context_bootstrap) fn font_face_constructor_callback<'s>(
|
||||
"SyntaxError",
|
||||
"Invalid font data in ArrayBuffer.",
|
||||
);
|
||||
(String::new(), "error", Some(loaded))
|
||||
(String::new(), "error", Some(loaded), None)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -355,6 +369,7 @@ pub(in crate::context_bootstrap) fn font_face_constructor_callback<'s>(
|
||||
size_adjust,
|
||||
status,
|
||||
loaded,
|
||||
loaded_resolver,
|
||||
)
|
||||
.initialize(scope, this)
|
||||
.expect("FontFace declaration should initialize object");
|
||||
@@ -395,8 +410,7 @@ pub(in crate::context_bootstrap) fn font_face_load_callback<'s>(
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let this = args.this();
|
||||
super::events::notify_font_face_set_owners_of_load(scope, this);
|
||||
if let Some(loaded) = font_face_slot_value(scope, this, FONT_FACE_LOADED_SLOT) {
|
||||
if let Some(loaded) = load_font_face(scope, this) {
|
||||
rv.set(loaded);
|
||||
return;
|
||||
}
|
||||
@@ -406,6 +420,117 @@ pub(in crate::context_bootstrap) fn font_face_load_callback<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn load_font_face<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
face: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let loaded = font_face_slot_value(scope, face, FONT_FACE_LOADED_SLOT);
|
||||
if font_face_status(scope, face).as_deref() != Some("unloaded") {
|
||||
super::events::notify_font_face_set_owners_of_load(scope, face);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
set_font_face_status(scope, face, "loading");
|
||||
let source = font_face_string_slot(scope, face, FONT_FACE_SOURCE_SLOT).unwrap_or_default();
|
||||
let succeeds = font_face_css_source_has_url(&source);
|
||||
if succeeds {
|
||||
set_font_face_status(scope, face, "loaded");
|
||||
} else {
|
||||
set_font_face_status(scope, face, "error");
|
||||
}
|
||||
|
||||
if let Some(resolver) = font_face_loaded_resolver(scope, face) {
|
||||
if succeeds {
|
||||
let _ = resolver.resolve(scope, face.into());
|
||||
} else {
|
||||
let exception = crate::context_bootstrap::new_dom_exception_value(
|
||||
scope,
|
||||
"No source in the FontFace src list could be loaded.",
|
||||
"NetworkError",
|
||||
);
|
||||
let _ = resolver.reject(scope, exception);
|
||||
}
|
||||
}
|
||||
super::events::notify_font_face_set_owners_of_load(scope, face);
|
||||
loaded
|
||||
}
|
||||
|
||||
pub(super) fn font_face_load_failed<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
face: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
font_face_status(scope, face).as_deref() == Some("error")
|
||||
}
|
||||
|
||||
pub(crate) fn load_font_faces_for_family<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
font_set: v8::Local<'s, v8::Object>,
|
||||
family: &str,
|
||||
) {
|
||||
let Some(faces) = super::storage::font_face_set_faces_array(scope, font_set) else {
|
||||
return;
|
||||
};
|
||||
for index in 0..faces.length() {
|
||||
let Some(face) = faces
|
||||
.get_index(scope, index)
|
||||
.and_then(|face| v8::Local::<v8::Object>::try_from(face).ok())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if !font_face_string_slot(scope, face, FONT_FACE_FAMILY_SLOT)
|
||||
.is_some_and(|candidate| candidate.eq_ignore_ascii_case(family))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let _ = load_font_face(scope, face);
|
||||
}
|
||||
}
|
||||
|
||||
fn font_face_css_source_has_url(source: &str) -> bool {
|
||||
moli_css_parse::normalize_font_face_src(source)
|
||||
.and_then(|source| crate::css_style::top_level_comma_separated_component_values(&source))
|
||||
.is_some_and(|sources| {
|
||||
sources
|
||||
.iter()
|
||||
.any(|source| source.trim_start().starts_with("url("))
|
||||
})
|
||||
}
|
||||
|
||||
fn font_face_status<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
face: v8::Local<'s, v8::Object>,
|
||||
) -> Option<String> {
|
||||
font_face_string_slot(scope, face, FONT_FACE_STATUS_SLOT)
|
||||
}
|
||||
|
||||
fn font_face_string_slot<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
face: v8::Local<'s, v8::Object>,
|
||||
slot: &str,
|
||||
) -> Option<String> {
|
||||
font_face_slot_value(scope, face, slot)
|
||||
.and_then(|value| v8::Local::<v8::String>::try_from(value).ok())
|
||||
.map(|value| value.to_rust_string_lossy(scope))
|
||||
}
|
||||
|
||||
fn set_font_face_status<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
face: v8::Local<'s, v8::Object>,
|
||||
status: &'static str,
|
||||
) {
|
||||
let status = v8_string(scope, status).unwrap_or_else(|| v8::String::empty(scope));
|
||||
set_font_face_slot_value(scope, face, FONT_FACE_STATUS_SLOT, status.into());
|
||||
}
|
||||
|
||||
fn font_face_loaded_resolver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
face: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::PromiseResolver>> {
|
||||
font_face_slot_value(scope, face, FONT_FACE_LOADED_RESOLVER_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
.map(|object| unsafe { v8::Local::<v8::PromiseResolver>::cast_unchecked(object) })
|
||||
}
|
||||
|
||||
fn font_face_slot_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use super::events::dispatch_font_face_set_event;
|
||||
use super::font_face::{font_face_load_failed, load_font_face};
|
||||
use super::query::{
|
||||
font_face_set_matching_faces_array, font_load_query_contains_css_wide_keyword,
|
||||
make_rejected_dom_exception_promise,
|
||||
};
|
||||
use super::storage::{
|
||||
array_contains_value, font_face_set_faces_array, font_face_set_manual_faces_array,
|
||||
is_font_face_value, rebuild_font_face_set_faces, replace_font_face_set_ready_promise,
|
||||
set_font_face_set_slot_value, set_font_face_set_status,
|
||||
is_font_face_value, rebuild_font_face_set_faces, set_font_face_set_slot_value,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
|
||||
+22
-5
@@ -56,11 +56,28 @@ pub(in crate::context_bootstrap) fn font_face_set_load_callback<'s>(
|
||||
}
|
||||
let matching_faces = font_face_set_matching_faces_array(scope, this, &parsed.font)
|
||||
.unwrap_or_else(|| v8::Array::new(scope, 0));
|
||||
set_font_face_set_status(scope, this, "loading");
|
||||
let _ = dispatch_font_face_set_event(scope, this, "loading", None);
|
||||
replace_font_face_set_ready_promise(scope, this);
|
||||
set_font_face_set_status(scope, this, "loaded");
|
||||
let _ = dispatch_font_face_set_event(scope, this, "loadingdone", Some(matching_faces));
|
||||
let mut failed = false;
|
||||
for index in 0..matching_faces.length() {
|
||||
let Some(face) = matching_faces
|
||||
.get_index(scope, index)
|
||||
.and_then(|face| v8::Local::<v8::Object>::try_from(face).ok())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let _ = load_font_face(scope, face);
|
||||
failed |= font_face_load_failed(scope, face);
|
||||
}
|
||||
if failed {
|
||||
rv.set(
|
||||
make_rejected_dom_exception_promise(
|
||||
scope,
|
||||
"NetworkError",
|
||||
"One or more matching FontFace objects failed to load.",
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
let faces_value = matching_faces.into();
|
||||
match resolved_promise(scope, faces_value) {
|
||||
Some(promise) => rv.set(v8::Local::<v8::Value>::from(promise)),
|
||||
|
||||
@@ -1844,6 +1844,7 @@ pub(crate) struct RuntimeMutationApplyResult {
|
||||
stylesheet_owner_changes: Vec<crate::dom::native::DomStylesheetOwnerChange>,
|
||||
inline_style_attribute_csp_mutations: Vec<InlineStyleAttributeCspMutation>,
|
||||
connected_style_csp_roots: Vec<DomHandle>,
|
||||
font_face_use_roots: Vec<DomHandle>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -1880,6 +1881,7 @@ pub(super) fn finish_runtime_mutation_effects(
|
||||
stylesheet_owner_changes,
|
||||
inline_style_attribute_csp_mutations,
|
||||
connected_style_csp_roots,
|
||||
font_face_use_roots,
|
||||
} = result;
|
||||
|
||||
runtime.queue_devtools_dom_mutations(devtools_dom_mutations);
|
||||
@@ -1976,6 +1978,11 @@ pub(super) fn finish_runtime_mutation_effects(
|
||||
unsafe { &*host_ptr },
|
||||
&stylesheet_owner_changes,
|
||||
);
|
||||
crate::native_bridge::document::load_font_faces_used_by_subtrees(
|
||||
scope,
|
||||
unsafe { &*host_ptr },
|
||||
&font_face_use_roots,
|
||||
);
|
||||
let completed_clients = prime_result.take_completed_stylesheet_clients();
|
||||
runtime.settle_stylesheet_link_clients_in_current_scope(scope, host_ptr, completed_clients);
|
||||
runtime
|
||||
@@ -2215,6 +2222,15 @@ pub(super) fn prepare_runtime_mutation_effects(
|
||||
};
|
||||
let devtools_dom_mutations =
|
||||
super::devtools_mutations::capture_devtools_dom_mutation_facts(dom_host, effects);
|
||||
let mut font_face_use_roots = effects.tree().connected_roots().to_vec();
|
||||
for mutation in effects.style().attribute_mutations() {
|
||||
if mutation.namespace().is_none()
|
||||
&& mutation.local_name().eq_ignore_ascii_case("style")
|
||||
&& !font_face_use_roots.contains(&mutation.target())
|
||||
{
|
||||
font_face_use_roots.push(mutation.target());
|
||||
}
|
||||
}
|
||||
RuntimeMutationApplyResult {
|
||||
changed: effects.did_change(),
|
||||
meta_refresh_candidates,
|
||||
@@ -2225,6 +2241,7 @@ pub(super) fn prepare_runtime_mutation_effects(
|
||||
stylesheet_owner_changes,
|
||||
inline_style_attribute_csp_mutations,
|
||||
connected_style_csp_roots,
|
||||
font_face_use_roots,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,8 @@ pub(in crate::native_bridge) use css_state::{
|
||||
};
|
||||
pub(crate) use css_state::{
|
||||
apply_stylesheet_owner_css_projections, apply_stylesheet_source_css_projection,
|
||||
clear_adopted_stylesheet_font_face_wrappers, sync_document_fonts_for_handle,
|
||||
clear_adopted_stylesheet_font_face_wrappers, load_font_faces_used_by_subtrees,
|
||||
sync_document_fonts_for_handle,
|
||||
};
|
||||
use css_state::{detached_document_fonts_getter, document_fonts_getter_function};
|
||||
pub(crate) use css_state::{
|
||||
|
||||
@@ -14,6 +14,7 @@ pub(crate) use accessors::{
|
||||
node_document_adopted_style_sheets_getter_function,
|
||||
node_document_adopted_style_sheets_setter_function, node_document_style_sheets_getter_function,
|
||||
};
|
||||
pub(crate) use font_faces::load_font_faces_used_by_subtrees;
|
||||
pub(crate) use projection::{
|
||||
apply_stylesheet_owner_css_projections, apply_stylesheet_source_css_projection,
|
||||
};
|
||||
|
||||
@@ -15,6 +15,8 @@ use crate::{
|
||||
},
|
||||
util::{get_private_value, serialize_v8_iter_array, set_private_value, v8_string, v8str},
|
||||
};
|
||||
use moli_v8_util::object_own_static_property_as_object;
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -164,6 +166,67 @@ pub(super) fn finish_font_face_owner_projections<'s>(
|
||||
rebuild_connected_font_faces(scope, fonts, host, document);
|
||||
}
|
||||
|
||||
pub(crate) fn load_font_faces_used_by_subtrees(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
host: &JsContextHost,
|
||||
roots: &[DomHandle],
|
||||
) {
|
||||
let mut visited = HashSet::new();
|
||||
for root in roots.iter().copied() {
|
||||
if !host.dom_host().is_connected(root) {
|
||||
continue;
|
||||
}
|
||||
let Some(document) = host.dom_host().owner_document_handle(root) else {
|
||||
continue;
|
||||
};
|
||||
let Some(holder) = crate::util::node_wrapper_from_handle(scope, document) else {
|
||||
continue;
|
||||
};
|
||||
let Some(fonts) = object_own_static_property_as_object(scope, holder, FONTS_SLOT) else {
|
||||
continue;
|
||||
};
|
||||
for family in inline_font_families_in_subtree(host, root, &mut visited) {
|
||||
crate::context_bootstrap::load_font_faces_for_family(scope, fonts, &family);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inline_font_families_in_subtree(
|
||||
host: &JsContextHost,
|
||||
root: DomHandle,
|
||||
visited: &mut HashSet<DomHandle>,
|
||||
) -> Vec<String> {
|
||||
let mut families = Vec::new();
|
||||
let mut stack = vec![root];
|
||||
while let Some(handle) = stack.pop() {
|
||||
if !visited.insert(handle) {
|
||||
continue;
|
||||
}
|
||||
if let Some(style) = host.dom_host().get_attribute(handle, "style")
|
||||
&& let Some(value) =
|
||||
moli_css_parse::parse_declaration_block(&style).property_value("font-family")
|
||||
&& let Some(components) =
|
||||
crate::css_style::top_level_comma_separated_component_values(&value)
|
||||
{
|
||||
for family in components {
|
||||
let family = moli_css_parse::unquote_css_string(family.trim());
|
||||
if !family.is_empty()
|
||||
&& !families
|
||||
.iter()
|
||||
.any(|existing: &String| existing.eq_ignore_ascii_case(&family))
|
||||
{
|
||||
families.push(family);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(shadow_root) = host.dom_host().shadow_root_handle(handle) {
|
||||
stack.push(shadow_root);
|
||||
}
|
||||
stack.extend(host.dom_host().child_handles(handle));
|
||||
}
|
||||
families
|
||||
}
|
||||
|
||||
fn set_owner_font_face_contribution<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
fonts: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -1708,11 +1708,83 @@ fn font_face_declared_slots_ignore_prototype_spoofing() {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"values":"Changed|url(demo.woff)|italic|700|condensed|small-caps|\"kern\"|swap|loaded|function","fake":"TypeError|TypeError|TypeError|TypeError|Promise","descriptors":["family:function:get family:0:function:set family:1:true:true:false","style:function:get style:0:function:set style:1:true:true:false","weight:function:get weight:0:function:set weight:1:true:true:false","stretch:function:get stretch:0:function:set stretch:1:true:true:false","variant:function:get variant:0:function:set variant:1:true:true:false","featureSettings:function:get featureSettings:0:function:set featureSettings:1:true:true:false","display:function:get display:0:function:set display:1:true:true:false","source:function:get source:0:undefined:undefined:undefined:true:true:false","status:function:get status:0:undefined:undefined:undefined:true:true:false","loaded:function:get loaded:0:undefined:undefined:undefined:true:true:false"],"ownSlots":[]}"#
|
||||
r#"{"values":"Changed|url(demo.woff)|italic|700|condensed|small-caps|\"kern\"|swap|unloaded|function","fake":"TypeError|TypeError|TypeError|TypeError|Promise","descriptors":["family:function:get family:0:function:set family:1:true:true:false","style:function:get style:0:function:set style:1:true:true:false","weight:function:get weight:0:function:set weight:1:true:true:false","stretch:function:get stretch:0:function:set stretch:1:true:true:false","variant:function:get variant:0:function:set variant:1:true:true:false","featureSettings:function:get featureSettings:0:function:set featureSettings:1:true:true:false","display:function:get display:0:function:set display:1:true:true:false","source:function:get source:0:undefined:undefined:undefined:true:true:false","status:function:get status:0:undefined:undefined:undefined:true:true:false","loaded:function:get loaded:0:undefined:undefined:undefined:true:true:false"],"ownSlots":[]}"#
|
||||
);
|
||||
assert_eq!(vm.eval("fakeLoadedResult").unwrap(), "rejected:TypeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn font_face_string_sources_follow_load_state_and_connected_style_use() {
|
||||
let mut vm = new_storage_test_vm("https://font-face-string-source-state.test/");
|
||||
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const remote = new FontFace('RemoteFace', 'url(remote.woff2)');
|
||||
const remoteLoaded = remote.loaded;
|
||||
const invalid = new FontFace('InvalidFace', 'not a font source');
|
||||
const local = new FontFace(
|
||||
'MissingLocalFace',
|
||||
'local("definitely-missing-moli-font")'
|
||||
);
|
||||
document.fonts.add(local);
|
||||
|
||||
globalThis.__fontFaceStringSourceProbe = {
|
||||
remote: {
|
||||
before: remote.status,
|
||||
stablePromise: remote.loaded === remoteLoaded,
|
||||
loadReturnsLoaded: remote.load() === remoteLoaded,
|
||||
after: remote.status,
|
||||
settlement: 'pending'
|
||||
},
|
||||
invalid: {
|
||||
status: invalid.status,
|
||||
settlement: 'pending'
|
||||
},
|
||||
local: {
|
||||
beforeStyle: local.status,
|
||||
afterDetachedStyle: '',
|
||||
afterConnection: '',
|
||||
settlement: 'pending'
|
||||
}
|
||||
};
|
||||
remoteLoaded.then(
|
||||
value => {
|
||||
globalThis.__fontFaceStringSourceProbe.remote.settlement =
|
||||
value === remote ? 'resolved-self' : 'resolved-other';
|
||||
},
|
||||
error => {
|
||||
globalThis.__fontFaceStringSourceProbe.remote.settlement = error.name;
|
||||
}
|
||||
);
|
||||
invalid.loaded.then(
|
||||
() => { globalThis.__fontFaceStringSourceProbe.invalid.settlement = 'resolved'; },
|
||||
error => { globalThis.__fontFaceStringSourceProbe.invalid.settlement = error.name; }
|
||||
);
|
||||
local.loaded.then(
|
||||
() => { globalThis.__fontFaceStringSourceProbe.local.settlement = 'resolved'; },
|
||||
error => { globalThis.__fontFaceStringSourceProbe.local.settlement = error.name; }
|
||||
);
|
||||
|
||||
const target = document.createElement('div');
|
||||
target.style.fontFamily = 'MissingLocalFace';
|
||||
globalThis.__fontFaceStringSourceProbe.local.afterDetachedStyle = local.status;
|
||||
(document.body || document.documentElement || document).appendChild(target);
|
||||
globalThis.__fontFaceStringSourceProbe.local.afterConnection = local.status;
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("string-backed FontFace state probe should initialize");
|
||||
|
||||
let result = vm
|
||||
.eval("JSON.stringify(globalThis.__fontFaceStringSourceProbe)")
|
||||
.expect("string-backed FontFace promises should settle");
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"remote":{"before":"unloaded","stablePromise":true,"loadReturnsLoaded":true,"after":"loaded","settlement":"resolved-self"},"invalid":{"status":"error","settlement":"SyntaxError"},"local":{"beforeStyle":"unloaded","afterDetachedStyle":"unloaded","afterConnection":"error","settlement":"NetworkError"}}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn font_face_variation_settings_use_stylo_descriptor_serialization() {
|
||||
let mut vm = new_storage_test_vm("https://font-face-variation-settings.test/");
|
||||
|
||||
@@ -34,7 +34,7 @@ test(function () {
|
||||
assert_equals(face.variant, "small-caps");
|
||||
assert_equals(face.featureSettings, '"liga" 0');
|
||||
assert_equals(face.display, "swap");
|
||||
assert_equals(face.status, "loaded");
|
||||
assert_equals(face.status, "unloaded");
|
||||
assert_true(face.loaded instanceof Promise);
|
||||
assert_true(face.load() instanceof Promise);
|
||||
}, "FontFace exposes constructor metadata and descriptors");
|
||||
@@ -69,9 +69,10 @@ test(function () {
|
||||
|
||||
promise_test(async function () {
|
||||
const face = new FontFace("PromiseFace", "url(promise.woff)");
|
||||
const loaded = face.loaded;
|
||||
|
||||
assert_equals(await face.loaded, face);
|
||||
assert_equals(await face.load(), face);
|
||||
assert_equals(await loaded, face);
|
||||
}, "FontFace loaded and load promises resolve with the face");
|
||||
|
||||
promise_test(async function () {
|
||||
|
||||
@@ -87,7 +87,7 @@ promise_test(async function () {
|
||||
assert_equals(fonts.ready, fonts.ready);
|
||||
assert_equals(face.family, "FixtureFace");
|
||||
assert_equals(face.source, "url(fixture.woff2)");
|
||||
assert_equals(face.status, "loaded");
|
||||
assert_equals(face.status, "unloaded");
|
||||
assert_equals(await face.load(), face);
|
||||
assert_equals(await face.loaded, face);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user