diff --git a/Cargo.lock b/Cargo.lock index 15e0a29c73fd7..ee9ac25d9964d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8215,6 +8215,7 @@ dependencies = [ "servo-malloc-size-of", "servo-profile-traits", "servo-url", + "servo_arc", "stylo", "uuid", "webrender_api", diff --git a/components/fonts/font_context.rs b/components/fonts/font_context.rs index 8b53bcbb04e0f..eb7ebde86aef0 100644 --- a/components/fonts/font_context.rs +++ b/components/fonts/font_context.rs @@ -12,8 +12,9 @@ use std::sync::atomic::{AtomicBool, Ordering}; use app_units::Au; use content_security_policy::Violation; use fonts_traits::{ - CSSFontFaceDescriptors, FontDescriptor, FontIdentifier, FontTemplate, FontTemplateRef, - FontTemplateRefMethods, StylesheetWebFontLoadFinishedCallback, + CSSFontFaceDescriptors, FontDescriptor, FontFaceRuleWithOrigin, FontIdentifier, FontTemplate, + FontTemplateRef, FontTemplateRefMethods, StylesheetWebFontLoadFinishedCallback, + WebFontSetDifference, }; use log::{debug, trace}; use malloc_size_of::MallocSizeOf; @@ -41,8 +42,8 @@ use style::font_face::{ }; use style::properties::generated::font_face::Descriptors as FontFaceRuleDescriptors; use style::properties::style_structs::Font as FontStyleStruct; -use style::shared_lock::{Locked, StylesheetGuards}; -use style::stylesheets::{FontFaceRule, Origin}; +use style::shared_lock::StylesheetGuards; +use style::stylesheets::FontFaceRule; use style::stylist::Stylist; use style::values::computed::font::{FamilyName, FontFamilyNameSyntax, SingleFontFamily}; use url::Url; @@ -644,7 +645,7 @@ pub trait FontContextWebFontMethods { guards: &StylesheetGuards<'_>, callback: StylesheetWebFontLoadFinishedCallback, document_context: &WebFontDocumentContext, - ); + ) -> WebFontSetDifference; fn load_single_font_face_rule( &self, font_face_rule: &FontFaceRule, @@ -697,38 +698,38 @@ impl FontContextWebFontMethods for Arc { guards: &StylesheetGuards<'_>, callback: StylesheetWebFontLoadFinishedCallback, document_context: &WebFontDocumentContext, - ) { - let mut removed_any = false; - - self.known_font_face_rules + ) -> WebFontSetDifference { + let difference = self + .known_font_face_rules .lock() - .diff_old_and_new_font_face_rules( - stylist, - guards, - |new_rule| { - self.load_single_font_face_rule( - new_rule, - webview_id, - callback.clone(), - document_context, - ); - }, - |stale_rule| { - self.remove_single_font_face_rule( - &stale_rule.descriptors, - &mut self.web_fonts.write(), - ); - removed_any = true; - }, + .diff_old_and_new_font_face_rules(stylist, guards); + + for added_rule in &difference.added_font_faces { + let added_rule = added_rule.read_with(guards); + self.load_single_font_face_rule( + added_rule, + webview_id, + callback.clone(), + document_context, + ); + } + for removed_rule in &difference.removed_font_faces { + let removed_rule = removed_rule.read_with(guards); + self.remove_single_font_face_rule( + &removed_rule.descriptors, + &mut self.web_fonts.write(), ); + } - if removed_any { + if !difference.removed_font_faces.is_empty() { // We modified the list of available fonts, so invalidate resolved font groups. self.resolved_font_groups.write().clear(); // Ensure that we clean up any WebRender resources on the next display list update. self.have_removed_web_fonts.store(true, Ordering::Relaxed); } + + difference } fn load_web_font_for_script( @@ -1237,36 +1238,16 @@ struct KnownFontFaceRule { generation: bool, } -#[derive(MallocSizeOf)] -struct FontFaceRuleWithOrigin { - #[conditional_malloc_size_of] - rule: ServoArc>, - origin: Origin, -} - -impl FontFaceRuleWithOrigin { - fn read_with<'a>(&'a self, guards: &'a StylesheetGuards) -> &'a FontFaceRule { - match self.origin { - Origin::Author => self.rule.read_with(guards.author), - Origin::UserAgent | Origin::User => self.rule.read_with(guards.ua_or_user), - } - } -} - impl KnownFontFaceRules { /// Computes the difference between the `@font-face `rules that are currently in effect /// and the ones that the `Stylist` knows about. The caller is notified about new or removed rules /// with callbacks. - fn diff_old_and_new_font_face_rules( + fn diff_old_and_new_font_face_rules( &mut self, stylist: &Stylist, guards: &StylesheetGuards<'_>, - mut new_rule_callback: NewRuleCallback, - mut stale_rule_callback: StaleRuleCallback, - ) where - NewRuleCallback: FnMut(&FontFaceRule), - StaleRuleCallback: FnMut(&FontFaceRule), - { + ) -> WebFontSetDifference { + let mut difference = WebFontSetDifference::default(); self.generation = !self.generation; let font_face_rules_in_cascade_order = stylist @@ -1274,10 +1255,7 @@ impl KnownFontFaceRules { .flat_map(|(extra_data, origin)| { extra_data.font_faces.iter().rev().zip(iter::repeat(origin)) }) - .map(|((rule, _layer), origin)| FontFaceRuleWithOrigin { - rule: rule.clone(), - origin, - }); + .map(|((rule, _layer), origin)| FontFaceRuleWithOrigin::new(rule.clone(), origin)); // First, find any *new* font families that were not defined previously let mut number_of_unchanged_rules = 0; @@ -1308,9 +1286,9 @@ impl KnownFontFaceRules { let mut index_of_existing_entry_for_this_rule = None; for (index, known_font_face) in known_font_faces_for_family.iter().enumerate() { // See if this is a entry for this @font-face that existed prior to the current update - if ServoArc::ptr_eq( - &known_font_face.rule_with_origin.rule, - &rule_with_origin.rule, + if FontFaceRuleWithOrigin::ptr_eq( + &known_font_face.rule_with_origin, + &rule_with_origin, ) { index_of_existing_entry_for_this_rule = Some(index); } @@ -1345,7 +1323,9 @@ impl KnownFontFaceRules { if conflicting_declaration_with_higher_priority_exists { let stale_rule = known_font_faces_for_family.remove(index_of_existing_entry_for_this_rule); - stale_rule_callback(stale_rule.rule_with_origin.read_with(guards)); + difference + .removed_font_faces + .push(stale_rule.rule_with_origin); } else { number_of_unchanged_rules += 1; known_font_faces_for_family[index_of_existing_entry_for_this_rule].generation = @@ -1357,7 +1337,7 @@ impl KnownFontFaceRules { continue; } else { // This is a new rule that does not conflict with anything that previously existed, so insert it. - new_rule_callback(borrowed_rule); + difference.added_font_faces.push(rule_with_origin.clone()); known_font_faces_for_family.push(KnownFontFaceRule { rule_with_origin, generation: self.generation, @@ -1369,7 +1349,7 @@ impl KnownFontFaceRules { // This is the common case, where the new set of known @font-face rules is a superset of // the old one after applying the cascade. In this case there is nothing more to do, // because all old @font-face rules are still present. - return; + return difference; } // Remove all `@font-face` rules that were not updated - those no longer exist on the stylist. @@ -1377,11 +1357,15 @@ impl KnownFontFaceRules { known_font_faces_for_family .extract_if(.., |rule| rule.generation != self.generation) .for_each(|removed_rule| { - stale_rule_callback(removed_rule.rule_with_origin.read_with(guards)) + difference + .removed_font_faces + .push(removed_rule.rule_with_origin); }); !known_font_faces_for_family.is_empty() }); + + difference } } diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index 6b806441536b8..ac7982cc7c357 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -17,7 +17,7 @@ use embedder_traits::{ }; use euclid::{Point2D, Rect, Scale, Size2D}; use fonts::{FontContext, FontContextWebFontMethods}; -use fonts_traits::StylesheetWebFontLoadFinishedCallback; +use fonts_traits::{StylesheetWebFontLoadFinishedCallback, WebFontSetDifference}; use icu_locid::subtags::Language; use layout_api::{ AxesOverflow, BoxAreaType, CSSPixelRectVec, DangerousStyleNode, IFrameSizes, Layout, @@ -1003,12 +1003,8 @@ impl LayoutThread { }); let mut reflow_statistics = Default::default(); - let (mut reflow_phases_run, iframe_sizes) = self.restyle_and_build_trees( - &mut reflow_request, - document, - root_element, - &image_resolver, - ); + let (mut reflow_phases_run, iframe_sizes, changed_web_fonts) = self + .restyle_and_build_trees(&mut reflow_request, document, root_element, &image_resolver); if self.build_stacking_context_tree_for_reflow(&reflow_request) { reflow_phases_run.insert(ReflowPhasesRun::BuiltStackingContextTree); } @@ -1042,6 +1038,7 @@ impl LayoutThread { pending_svg_elements_for_serialization, iframe_sizes: Some(iframe_sizes), reflow_statistics, + changed_web_fonts, }) } @@ -1052,7 +1049,7 @@ impl LayoutThread { document: ServoDangerousStyleDocument<'dom>, guards: &StylesheetGuards, ua_stylesheets: &UserAgentStylesheets, - ) -> StylesheetInvalidationSet { + ) -> StylistStylesheetUpdate { let need_user_agent_stylesheet_addition = !self.have_added_user_agent_stylesheets; if need_user_agent_stylesheet_addition { for stylesheet in &ua_stylesheets.user_agent_stylesheets { @@ -1092,17 +1089,23 @@ impl LayoutThread { // Load new @font-face rules and remove old ones if necessary. // TODO: Can we make the invalidation set tell us whether any @font-face rules changed? - if need_user_agent_stylesheet_addition || reflow_request.stylesheets_changed() { - self.font_context.rebuild_font_face_set( - self.webview_id, - &self.stylist, - guards, - self.web_font_finished_loading_callback.clone(), - &reflow_request.document_context, - ); - } + let changed_web_fonts = + if need_user_agent_stylesheet_addition || reflow_request.stylesheets_changed() { + self.font_context.rebuild_font_face_set( + self.webview_id, + &self.stylist, + guards, + self.web_font_finished_loading_callback.clone(), + &reflow_request.document_context, + ) + } else { + WebFontSetDifference::default() + }; - invalidation_set + StylistStylesheetUpdate { + invalidation_set, + changed_web_fonts, + } } #[servo_tracing::instrument(skip_all)] @@ -1112,7 +1115,7 @@ impl LayoutThread { document: ServoDangerousStyleDocument<'_>, root_element: ServoLayoutElement<'_>, image_resolver: &Arc, - ) -> (ReflowPhasesRun, IFrameSizes) { + ) -> (ReflowPhasesRun, IFrameSizes, WebFontSetDifference) { let mut snapshot_map = SnapshotMap::new(); let _snapshot_setter = match reflow_request.restyle.as_mut() { Some(restyle) => SnapshotSetter::new(restyle, &mut snapshot_map), @@ -1144,7 +1147,14 @@ impl LayoutThread { } } - self.prepare_stylist_for_reflow(reflow_request, document, &guards, &user_agent_stylesheets) + let stylist_update = self.prepare_stylist_for_reflow( + reflow_request, + document, + &guards, + &user_agent_stylesheets, + ); + stylist_update + .invalidation_set .process_style(dangerous_root_element, Some(&snapshot_map)); if self.previously_highlighted_dom_node.get() != reflow_request.highlighted_dom_node { @@ -1256,7 +1266,11 @@ impl LayoutThread { if !damage.contains(LayoutDamage::DescendantCollectedAsLayoutRoot) { layout_context.style_context.stylist.rule_tree().maybe_gc(); - return (ReflowPhasesRun::empty(), IFrameSizes::default()); + return ( + ReflowPhasesRun::empty(), + IFrameSizes::default(), + stylist_update.changed_web_fonts, + ); } debug_assert!(!layout_roots.is_empty()); @@ -1267,6 +1281,7 @@ impl LayoutThread { return ( ReflowPhasesRun::RanLayout, std::mem::take(&mut *layout_context.iframe_sizes.lock()), + stylist_update.changed_web_fonts, ); } @@ -1317,6 +1332,7 @@ impl LayoutThread { ( ReflowPhasesRun::RanLayout, std::mem::take(&mut *iframe_sizes), + stylist_update.changed_web_fonts, ) } @@ -1891,3 +1907,11 @@ impl ReflowPhases { } } } + +/// Summarizes changes after flushing stylesheets on the `Stylist`. +struct StylistStylesheetUpdate { + /// Information about what kind of selectors changed. + invalidation_set: StylesheetInvalidationSet, + /// A list of changes to the set of web fonts. + changed_web_fonts: WebFontSetDifference, +} diff --git a/components/script/dom/css/fontface.rs b/components/script/dom/css/fontface.rs index 47c266a7434f3..f6f534702ccb1 100644 --- a/components/script/dom/css/fontface.rs +++ b/components/script/dom/css/fontface.rs @@ -7,7 +7,10 @@ use std::rc::Rc; use cssparser::{Parser, ParserInput}; use dom_struct::dom_struct; -use fonts::{FontContext, FontContextWebFontMethods, FontTemplate, LowercaseFontFamilyName}; +use fonts::{ + FontContext, FontContextWebFontMethods, FontFaceRuleWithOrigin, FontTemplate, + LowercaseFontFamilyName, +}; use js::context::JSContext; use js::rust::HandleObject; use script_bindings::cell::DomRefCell; @@ -15,6 +18,7 @@ use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx use style::error_reporting::ParseErrorReporter; use style::font_face::SourceList; use style::properties::font_face::Descriptors; +use style::shared_lock::StylesheetGuards; use style::stylesheets::{CssRuleType, FontFaceRule, UrlExtraData}; use style_traits::{ParsingMode, ToCss}; @@ -65,6 +69,12 @@ pub struct FontFace { /// #[conditional_malloc_size_of] font_status_promise: Rc, + + /// The `@font-face` rule that this `FontFace` object is [css-connected] to, if any. + /// + /// [css-connected]: https://drafts.csswg.org/css-font-loading/#css-connected + #[no_trace] + css_font_face_rule: DomRefCell>, } /// Given the various font face descriptors, construct the equivalent `@font-face` css rule as a @@ -213,6 +223,7 @@ impl FontFace { }), status: Cell::new(FontFaceLoadStatus::Error), template: RefCell::default(), + css_font_face_rule: Default::default(), } } @@ -263,6 +274,7 @@ impl FontFace { urls: DomRefCell::new(sources), template: RefCell::default(), font_status_promise, + css_font_face_rule: Default::default(), } } @@ -344,6 +356,91 @@ impl FontFace { font_face } + /// Constructs a unrooted `FontFace` object for a font that is backed by a `@font-face` rule. + pub(crate) fn new_inherited_for_web_font( + cx: &mut JSContext, + global: &GlobalScope, + family_name: DOMString, + descriptors: FontFaceDescriptors, + src: Option, + font_face_rule: FontFaceRuleWithOrigin, + ) -> Self { + Self { + reflector: Reflector::new(), + status: Cell::new(FontFaceLoadStatus::Loading), + descriptors: DomRefCell::new(descriptors), + font_face_set: MutNullableDom::default(), + family_name: DomRefCell::new(family_name), + urls: DomRefCell::new(src), + template: RefCell::default(), + font_status_promise: Promise::new(cx, global), + css_font_face_rule: DomRefCell::new(Some(font_face_rule)), + } + } + + /// Constructs a `FontFace` object for a font that is backed by a `@font-face` rule. + pub(crate) fn new_for_web_font( + cx: &mut JSContext, + global: &GlobalScope, + font_face_rule: FontFaceRuleWithOrigin, + guards: &StylesheetGuards, + ) -> Option> { + let new_web_font_ref = font_face_rule.read_with(guards); + let Some(family_name) = new_web_font_ref + .descriptors + .font_family + .as_ref() + .map(|name| DOMString::from(&*name.name)) + else { + // Web fonts without a family name are not loaded, and they should not appear in document.fonts either. + return None; + }; + + // https://drafts.csswg.org/css-font-loading/#font-face-css-connection + // > The FontFace object corresponding to a @font-face rule has its family, style, weight, stretch, + // > unicodeRange, variant, and featureSettings attributes set to the same value as the corresponding + // > descriptors in the @font-face rule. + // FIXME: Serializing these attributes is not trivial, so we don't do it for now. + let descriptors = FontFaceDescriptors::default(); + + Some(reflect_dom_object_with_proto_and_cx( + Box::new(Self::new_inherited_for_web_font( + cx, + global, + family_name, + descriptors, + new_web_font_ref.descriptors.src.clone(), + font_face_rule, + )), + global, + None, + cx, + )) + } + + /// Mark this font face as *not* [css-connected]. + /// + /// [css-connected]: https://drafts.csswg.org/css-font-loading/#css-connected + pub(crate) fn disconnect_from_css(&self) { + *self.css_font_face_rule.borrow_mut() = None; + } + + /// Return true if the `FontFace` is [css-connected] *and* was created by the provided + /// `@font-face` rule. + /// + /// [css-connected]: https://drafts.csswg.org/css-font-loading/#css-connected + pub(crate) fn is_connected_to_font_face_rule( + &self, + target_rule: &FontFaceRuleWithOrigin, + ) -> bool { + self.css_font_face_rule + .borrow() + .as_ref() + .is_some_and(|connected_rule| { + FontFaceRuleWithOrigin::ptr_eq(connected_rule, target_rule) + }) + } + /// Step 3 of fn load_from_data(&self, cx: &mut JSContext, global: &GlobalScope, data: Vec) { // Step 3.1 Set font face’s status attribute to "loading". @@ -585,17 +682,15 @@ impl FontFaceMethods for FontFace { /// loaded, it does nothing. /// fn Load(&self, cx: &mut JSContext) -> Rc { + // Step 2. If font face’s [[Urls]] slot is null, or its status attribute is anything + // other than "unloaded", return font face’s [[FontStatusPromise]] and abort these + // steps. let Some(sources) = self.urls.borrow_mut().take() else { - // Step 2. If font face’s [[Urls]] slot is null, or its status attribute is anything - // other than "unloaded", return font face’s [[FontStatusPromise]] and abort these - // steps. return self.font_status_promise.clone(); }; - - // FontFace must not be loaded at this point as `self.urls` is not None, implying `Load` - // wasn't called already. In our implementation, `urls` is set after parsing, so it - // cannot be `Some` if the status is `Error`. - debug_assert_eq!(self.status.get(), FontFaceLoadStatus::Unloaded); + if self.status.get() != FontFaceLoadStatus::Unloaded { + return self.font_status_promise.clone(); + } let global = self.global(); let trusted = Trusted::new(self); diff --git a/components/script/dom/css/fontfaceset.rs b/components/script/dom/css/fontfaceset.rs index 072615063bc77..71c9410e71ee8 100644 --- a/components/script/dom/css/fontfaceset.rs +++ b/components/script/dom/css/fontfaceset.rs @@ -6,6 +6,7 @@ use std::cell::RefCell; use std::rc::Rc; use dom_struct::dom_struct; +use fonts::FontFaceRuleWithOrigin; use js::context::JSContext; use js::gc::Handle; use js::jsapi::Value; @@ -153,6 +154,33 @@ impl FontFaceSet { } } } + + /// Marks the entries corresponding to removed `@font-face` rules as not [css-connected]. + /// + /// [css-connected]: https://drafts.csswg.org/css-font-loading/#css-connected + pub(crate) fn notify_font_face_rules_removed( + &self, + removed_font_face_rules: &[FontFaceRuleWithOrigin], + ) { + let entries = self.set_entries.borrow_mut(); + for removed_font_face_rule in removed_font_face_rules { + let Some(matching_font_face_object) = entries + .iter() + .find(|entry| entry.is_connected_to_font_face_rule(removed_font_face_rule)) + else { + if cfg!(debug_assertions) { + unreachable!("Removed @font-face that was not previously present"); + } + log::warn!("Removed @font-face that was not previously present"); + continue; + }; + + // https://drafts.csswg.org/css-font-loading/#font-face-css-connection: + // > If a @font-face rule is removed from the document, its corresponding FontFace object is no longer CSS-connected. + // > The connection is not restorable by any means. + matching_font_face_object.disconnect_from_css(); + } + } } impl FontFaceSetMethods for FontFaceSet { @@ -203,7 +231,9 @@ impl FontFaceSetMethods for FontFaceSet { } /// - fn Clear(&self) { + fn Clear(&self, cx: &mut JSContext) { + self.flush_author_font_set(cx); + // Step 1. Remove all non-CSS-connected items from the FontFaceSet’s set entries, // its [[LoadedFonts]] list, and its [[FailedFonts]] list. self.set_entries.borrow_mut().clear(); @@ -278,8 +308,8 @@ impl FontFaceSetMethods for FontFaceSet { } /// - fn Size(&self) -> u32 { - self.set_entries.borrow().len() as u32 + fn Size(&self, cx: &mut JSContext) -> u32 { + self.size(cx) } } diff --git a/components/script/dom/window/window.rs b/components/script/dom/window/window.rs index 76823b8b18d60..81f3c441bbab7 100644 --- a/components/script/dom/window/window.rs +++ b/components/script/dom/window/window.rs @@ -29,7 +29,10 @@ use embedder_traits::{ WebDriverJSResult, WebDriverLoadStatus, }; use euclid::{Point2D, Rect, Scale, Size2D, Vector2D}; -use fonts::{CspViolationHandler, FontContext, NetworkTimingHandler, WebFontDocumentContext}; +use fonts::{ + CspViolationHandler, FontContext, NetworkTimingHandler, WebFontDocumentContext, + WebFontSetDifference, +}; use js::context::{JSContext, NoGC}; use js::glue::DumpJSStack; use js::jsapi::{GCReason, Heap, JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE}; @@ -66,6 +69,7 @@ use script_bindings::codegen::GenericBindings::WindowBinding::ScrollToOptions; use script_bindings::conversions::SafeToJSValConvertible; use script_bindings::dom::UnrootedDom; use script_bindings::interfaces::{HasOrigin, WindowHelpers}; +use script_bindings::like::Setlike; use script_bindings::reflector::DomObject; use script_bindings::root::Root; use script_traits::{ConstellationInputEvent, ScriptThreadMessage}; @@ -90,6 +94,7 @@ use style::error_reporting::{ContextualParseError, ParseErrorReporter}; use style::properties::PropertyId; use style::properties::style_structs::Font; use style::selector_parser::PseudoElement; +use style::shared_lock::StylesheetGuards; use style::str::HTML_SPACE_CHARACTERS; use style::stylesheets::UrlExtraData; use style_traits::CSSPixel; @@ -176,7 +181,7 @@ use crate::dom::storage::Storage; #[cfg(feature = "bluetooth")] use crate::dom::testrunner::TestRunner; use crate::dom::trustedtypes::trustedtypepolicyfactory::TrustedTypePolicyFactory; -use crate::dom::types::{ImageBitmap, MouseEvent, SVGSVGElement, UIEvent}; +use crate::dom::types::{FontFace, ImageBitmap, MouseEvent, SVGSVGElement, UIEvent}; use crate::dom::useractivation::UserActivationTimestamp; use crate::dom::visualviewport::{VisualViewport, VisualViewportChanges}; #[cfg(feature = "webgpu")] @@ -2690,6 +2695,8 @@ impl Window { self.emit_timeline_marker(marker.end()); } + self.handle_new_or_removed_web_fonts_post_reflow(cx, reflow_result.changed_web_fonts); + self.handle_pending_images_post_reflow( cx, reflow_result.pending_images, @@ -3574,6 +3581,38 @@ impl Window { false } + /// Adds and removes entries from `document.fonts` as needed after a reflow. + fn handle_new_or_removed_web_fonts_post_reflow( + &self, + cx: &mut JSContext, + changed_web_fonts: WebFontSetDifference, + ) { + if changed_web_fonts.is_empty() { + return; + } + + let document = self.Document(); + let fonts = document.Fonts(cx); + if !changed_web_fonts.removed_font_faces.is_empty() { + fonts.notify_font_face_rules_removed(&changed_web_fonts.removed_font_faces); + } + + if !changed_web_fonts.added_font_faces.is_empty() { + let shared_locks = document.shared_style_locks(); + let guards = StylesheetGuards { + author: &shared_locks.author.read(), + ua_or_user: &shared_locks.ua_or_user.read(), + }; + for new_web_font in changed_web_fonts.added_font_faces { + if let Some(font_face) = + FontFace::new_for_web_font(cx, self.upcast(), new_web_font, &guards) + { + fonts.add(cx, font_face); + } + } + } + } + #[expect(unsafe_code)] fn handle_pending_images_post_reflow( &self, diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 1b54294068550..dbbc2308709af 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -447,7 +447,7 @@ DOMInterfaces = { }, 'FontFaceSet': { - 'cx': ['Add', 'Load', 'Ready'], + 'cx': ['Add', 'Load', 'Ready', 'Clear', 'Size'], }, 'FormData': { diff --git a/components/shared/fonts/Cargo.toml b/components/shared/fonts/Cargo.toml index 588df1560e31b..79191cac5824b 100644 --- a/components/shared/fonts/Cargo.toml +++ b/components/shared/fonts/Cargo.toml @@ -25,6 +25,7 @@ parking_lot = { workspace = true } profile_traits = { workspace = true } read-fonts = { workspace = true } serde = { workspace = true } +servo_arc = { workspace = true } servo-base = { workspace = true } servo-url = { workspace = true } stylo = { workspace = true } diff --git a/components/shared/fonts/lib.rs b/components/shared/fonts/lib.rs index 9e6b6bfd0952b..b677b0a70cd3d 100644 --- a/components/shared/fonts/lib.rs +++ b/components/shared/fonts/lib.rs @@ -18,7 +18,10 @@ pub use font_template::*; use malloc_size_of_derive::MallocSizeOf; use num_derive::{NumOps, One, Zero}; use serde::{Deserialize, Serialize}; +use servo_arc::Arc as ServoArc; use servo_base::generic_channel::GenericSharedMemory; +use style::shared_lock::StylesheetGuards; +use style::stylesheets::{FontFaceRule, LockedFontFaceRule, Origin}; pub use system_font_service_proxy::*; use webrender_api::euclid::num::One; @@ -158,3 +161,43 @@ pub struct FontDataAndIndex { pub enum FontDataError { FailedToLoad, } + +/// Describes how the set of active `@font-face` rules was changed after a call to `FontContext::rebuild_font_face_set`. +#[derive(Clone, Default)] +pub struct WebFontSetDifference { + /// A list of `@font-face` rules that were added in this update. + pub added_font_faces: Vec, + /// A list of `@font-face` rules that were removed in this update. + pub removed_font_faces: Vec, +} + +impl WebFontSetDifference { + /// Returns `true` iff the font face set remained unchanged by the update. + pub fn is_empty(&self) -> bool { + self.added_font_faces.is_empty() && self.removed_font_faces.is_empty() + } +} + +#[derive(Clone, MallocSizeOf)] +pub struct FontFaceRuleWithOrigin { + #[conditional_malloc_size_of] + rule: ServoArc, + origin: Origin, +} + +impl FontFaceRuleWithOrigin { + pub fn new(rule: ServoArc, origin: Origin) -> Self { + Self { rule, origin } + } + + pub fn ptr_eq(first: &Self, second: &Self) -> bool { + ServoArc::ptr_eq(&first.rule, &second.rule) + } + + pub fn read_with<'a>(&'a self, guards: &'a StylesheetGuards) -> &'a FontFaceRule { + match self.origin { + Origin::Author => self.rule.read_with(guards.author), + Origin::UserAgent | Origin::User => self.rule.read_with(guards.ua_or_user), + } + } +} diff --git a/components/shared/layout/lib.rs b/components/shared/layout/lib.rs index 81236b9f1383e..f287b41f9bab7 100644 --- a/components/shared/layout/lib.rs +++ b/components/shared/layout/lib.rs @@ -28,7 +28,7 @@ use background_hang_monitor_api::BackgroundHangMonitorRegister; use bitflags::bitflags; use embedder_traits::{Cursor, ScriptToEmbedderChan, Theme, UntrustedNodeAddress, ViewportDetails}; use euclid::{Point2D, Rect}; -use fonts::{FontContext, TextByteRange, WebFontDocumentContext}; +use fonts::{FontContext, TextByteRange, WebFontDocumentContext, WebFontSetDifference}; pub use layout_damage::{AccessibilityDamage, LayoutDamage}; pub use layout_dom::{ DangerousStyleElementOf, DangerousStyleNodeOf, LayoutDomTypeBundle, LayoutElementOf, @@ -606,7 +606,7 @@ impl RestyleReason { } /// Information derived from a layout pass that needs to be returned to the script thread. -#[derive(Debug, Default)] +#[derive(Default)] pub struct ReflowResult { /// The phases that were run during this reflow. pub reflow_phases_run: ReflowPhasesRun, @@ -625,6 +625,8 @@ pub struct ReflowResult { /// finished before reaching this stage of the layout. I.e., no update /// required. pub iframe_sizes: Option, + /// Enumerates web fonts that were added or removed as part of restyling. + pub changed_web_fonts: WebFontSetDifference, } bitflags! { diff --git a/tests/wpt/meta/css/css-font-loading/nonexistent-file-url.html.ini b/tests/wpt/meta/css/css-font-loading/nonexistent-file-url.html.ini deleted file mode 100644 index e2d64da17025b..0000000000000 --- a/tests/wpt/meta/css/css-font-loading/nonexistent-file-url.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[nonexistent-file-url.html] - [nonexistent-file-url] - expected: FAIL diff --git a/tests/wpt/meta/css/css-fonts/matching/font-unicode-PUA.html.ini b/tests/wpt/meta/css/css-fonts/matching/font-unicode-PUA.html.ini index e187b9e3e5628..d7f2f88d78152 100644 --- a/tests/wpt/meta/css/css-fonts/matching/font-unicode-PUA.html.ini +++ b/tests/wpt/meta/css/css-fonts/matching/font-unicode-PUA.html.ini @@ -1,9 +1,10 @@ [font-unicode-PUA.html] + expected: TIMEOUT [PUA character U+F000 is rendered with a non-generic font.] - expected: FAIL + expected: NOTRUN [PUA character U+F001 is rendered with a non-generic font.] - expected: FAIL + expected: NOTRUN [PUA character U+F002 is rendered with a non-generic font.] - expected: FAIL + expected: NOTRUN diff --git a/tests/wpt/meta/infrastructure/assumptions/document-fonts-ready.html.ini b/tests/wpt/meta/infrastructure/assumptions/document-fonts-ready.html.ini deleted file mode 100644 index 2b1d39397556f..0000000000000 --- a/tests/wpt/meta/infrastructure/assumptions/document-fonts-ready.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[document-fonts-ready.html] - [document.fonts.ready resolves after layout depending on loaded fonts] - expected: FAIL