From 0059a9b23829aeded76d0ee4c8a63506df6c69aa Mon Sep 17 00:00:00 2001 From: ldm0 Date: Fri, 16 Oct 2026 21:02:01 +0800 Subject: [PATCH] perf(layout): remove per-style anonymous provenance --- moli-layout/src/anonymous_style.rs | 52 ++-- moli-layout/src/form.rs | 3 +- moli-layout/src/lib.rs | 2 +- moli-layout/src/source.rs | 2 +- moli-layout/src/style.rs | 16 -- moli-layout/tests/box_builder_contract.rs | 4 +- moli-layout/tests/main_layout_contract.rs | 2 +- .../src/layout_renderer/style_resolver.rs | 225 ++++++++---------- 8 files changed, 133 insertions(+), 173 deletions(-) diff --git a/moli-layout/src/anonymous_style.rs b/moli-layout/src/anonymous_style.rs index 36ed97a1a8..029ba28193 100644 --- a/moli-layout/src/anonymous_style.rs +++ b/moli-layout/src/anonymous_style.rs @@ -1,33 +1,45 @@ -use crate::ResolvedLayoutStyle; - -/// Renderer-assigned provenance of a projected style. Pass identities must -/// remain unique while any of their styles can still be used as parents. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct LayoutStyleOrigin { - pass: u64, - owner: u64, -} - -impl LayoutStyleOrigin { - pub const fn new(pass: u64, owner: u64) -> Self { - Self { pass, owner } - } -} +use crate::{LayoutBox, ResolvedLayoutStyle}; /// Inheritance input for an anonymous box in the resolver's prepared pass. -/// The owner supplies provenance and diagnostics; it does not select a style -/// world or request another element-style observation. +/// Owner and style are borrowed from the same parent box. During construction, +/// layout may also pair a newly resolved style with its source before its box +/// exists. Renderers consume this input without selecting another style world +/// or observing the owner's element style again. #[derive(Clone, Copy, Debug)] pub struct LayoutAnonymousStyleContext<'a, N> { - pub owner: N, - pub parent_style: &'a ResolvedLayoutStyle, + owner: N, + parent_style: &'a ResolvedLayoutStyle, } impl<'a, N> LayoutAnonymousStyleContext<'a, N> { - pub const fn new(owner: N, parent_style: &'a ResolvedLayoutStyle) -> Self { + pub(crate) const fn new(owner: N, parent_style: &'a ResolvedLayoutStyle) -> Self { Self { owner, parent_style, } } + + /// Derives both inputs from one parent instead of accepting an unrelated + /// owner and style. A parent without a source or anonymous owner has no + /// native inheritance context. + pub fn from_parent(parent: &'a LayoutBox) -> Option + where + N: Copy, + { + Some(Self::new( + parent.source().or(parent.owner())?, + parent.style(), + )) + } + + pub const fn owner(&self) -> N + where + N: Copy, + { + self.owner + } + + pub const fn parent_style(&self) -> &'a ResolvedLayoutStyle { + self.parent_style + } } diff --git a/moli-layout/src/form.rs b/moli-layout/src/form.rs index 8ff749b1ed..75dc4b0280 100644 --- a/moli-layout/src/form.rs +++ b/moli-layout/src/form.rs @@ -53,7 +53,8 @@ where None }; let editor_style = styles.anonymous_style( - LayoutAnonymousStyleContext::new(owner, &world.boxes[control.index()].style), + LayoutAnonymousStyleContext::from_parent(&world.boxes[control.index()]) + .expect("form control has a source owner"), LayoutDisplay::Block, )?; let selection = world.boxes[control.index()].text_selection; diff --git a/moli-layout/src/lib.rs b/moli-layout/src/lib.rs index 15a878a8cb..0cca8bff57 100644 --- a/moli-layout/src/lib.rs +++ b/moli-layout/src/lib.rs @@ -41,7 +41,7 @@ mod taffy_tree; mod text; mod world; -pub use anonymous_style::{LayoutAnonymousStyleContext, LayoutStyleOrigin}; +pub use anonymous_style::LayoutAnonymousStyleContext; pub use builder::build_layout_world; pub use capture::{ FULL_DOCUMENT_CAPTURE_CSS_DIMENSION_LIMIT, PaintCaptureRegion, PaintCaptureRequest, diff --git a/moli-layout/src/source.rs b/moli-layout/src/source.rs index 4a93eb6b0e..9e79728f13 100644 --- a/moli-layout/src/source.rs +++ b/moli-layout/src/source.rs @@ -666,7 +666,7 @@ where display: crate::LayoutDisplay, ) -> Result { Ok(ResolvedLayoutStyle::anonymous_from( - context.parent_style, + context.parent_style(), display, )) } diff --git a/moli-layout/src/style.rs b/moli-layout/src/style.rs index 117f88a5b4..f0904f61ce 100644 --- a/moli-layout/src/style.rs +++ b/moli-layout/src/style.rs @@ -482,7 +482,6 @@ impl ResolvedLayoutTransform { /// invalid. #[derive(Clone)] pub struct ResolvedLayoutStyle { - source_origin: Option, pub(crate) computed: Option>, pub(crate) taffy: Style, preferred_aspect_ratio: PreferredAspectRatio, @@ -866,7 +865,6 @@ impl ResolvedLayoutStyle { }; } Self { - source_origin: None, computed: Some(computed), taffy, preferred_aspect_ratio, @@ -944,7 +942,6 @@ impl ResolvedLayoutStyle { }; let sticky_inset = taffy.inset; Self { - source_origin: None, computed: None, taffy, preferred_aspect_ratio, @@ -1170,17 +1167,6 @@ impl ResolvedLayoutStyle { self.computed.as_ref() } - /// Binds a renderer projection to its pass and source. Cloning and - /// deriving anonymous/text styles preserve this identity. - pub fn with_source_origin(mut self, origin: crate::LayoutStyleOrigin) -> Self { - self.source_origin = Some(origin); - self - } - - pub const fn source_origin(&self) -> Option { - self.source_origin - } - pub fn background_color(&self) -> PaintColor { self.background_color } @@ -1993,7 +1979,6 @@ impl ResolvedLayoutStyle { pub(crate) fn text_leaf_from(parent: &Self) -> Self { Self { - source_origin: parent.source_origin, computed: parent.computed.clone(), taffy: Style { display: TaffyDisplay::Block, @@ -2061,7 +2046,6 @@ impl ResolvedLayoutStyle { /// Stylo allocation (primarily DOM-free tests and conservative fallback). pub fn anonymous_from(parent: &Self, display: LayoutDisplay) -> Self { Self { - source_origin: parent.source_origin, computed: parent.computed.clone(), taffy: Style { display: taffy_display(display), diff --git a/moli-layout/tests/box_builder_contract.rs b/moli-layout/tests/box_builder_contract.rs index b9b4dcb3da..2c4ac7ef28 100644 --- a/moli-layout/tests/box_builder_contract.rs +++ b/moli-layout/tests/box_builder_contract.rs @@ -196,9 +196,9 @@ impl LayoutStyleResolver for TestStyles { context: moli_layout::LayoutAnonymousStyleContext<'_, usize>, display: LayoutDisplay, ) -> Result { - self.anonymous_queries.push((context.owner, display)); + self.anonymous_queries.push((context.owner(), display)); Ok(ResolvedLayoutStyle::anonymous_from( - context.parent_style, + context.parent_style(), display, )) } diff --git a/moli-layout/tests/main_layout_contract.rs b/moli-layout/tests/main_layout_contract.rs index 8beff80a0c..c23eb3988f 100644 --- a/moli-layout/tests/main_layout_contract.rs +++ b/moli-layout/tests/main_layout_contract.rs @@ -145,7 +145,7 @@ impl LayoutStyleResolver for FixtureStyles { display: LayoutDisplay, ) -> Result { Ok(ResolvedLayoutStyle::anonymous_from( - context.parent_style, + context.parent_style(), display, )) } diff --git a/moli-renderer-v8/src/layout_renderer/style_resolver.rs b/moli-renderer-v8/src/layout_renderer/style_resolver.rs index defccdfba2..729d50776a 100644 --- a/moli-renderer-v8/src/layout_renderer/style_resolver.rs +++ b/moli-renderer-v8/src/layout_renderer/style_resolver.rs @@ -1,12 +1,8 @@ use moli_layout::{ - LayoutAnonymousStyleContext, LayoutDisplay, LayoutError, LayoutStyleOrigin, - LayoutStyleResolver, LayoutViewport, ResolvedLayoutElementStyles, ResolvedLayoutPseudoStyle, - ResolvedLayoutStyle, -}; -use std::{ - sync::atomic::{AtomicU64, Ordering}, - time::Instant, + LayoutAnonymousStyleContext, LayoutDisplay, LayoutError, LayoutStyleResolver, LayoutViewport, + ResolvedLayoutElementStyles, ResolvedLayoutPseudoStyle, ResolvedLayoutStyle, }; +use std::time::Instant; use crate::{ document_runtime::DomHandle, @@ -17,13 +13,10 @@ use crate::{ style_engine::{StyleViewport, StyloAnonymousBoxKind}, }; -static NEXT_STYLE_PASS: AtomicU64 = AtomicU64::new(1); - pub(super) struct NativeLayoutStyleResolver<'a> { runtime: &'a JsContextHost, reads: StyleObservation<'a>, anonymous_context: Option>, - pass_id: u64, scripting_enabled: bool, profile: Option, } @@ -59,21 +52,12 @@ impl<'a> NativeLayoutStyleResolver<'a> { runtime, reads, anonymous_context, - pass_id: NEXT_STYLE_PASS - .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |next| { - next.checked_add(1) - }) - .expect("layout style pass identity exhausted"), scripting_enabled: runtime.document_scripting_enabled(document), profile: moli_trace::cpu_profile_enabled() .then(NativeLayoutStyleResolverProfile::default), } } - fn style_origin(&self, owner: DomHandle) -> LayoutStyleOrigin { - LayoutStyleOrigin::new(self.pass_id, u64::from(owner.index_u32())) - } - pub(super) fn trace_profile(&self, document: crate::document_runtime::DomHandle) { let Some(profile) = self.profile.as_ref() else { return; @@ -152,8 +136,7 @@ impl LayoutStyleResolver for NativeLayoutStyleResolver<'_> { ); } let phase_started = self.profile.as_ref().map(|_| Instant::now()); - let origin = self.style_origin(node); - let mut resolved = ResolvedLayoutStyle::from_stylo(computed).with_source_origin(origin); + let mut resolved = ResolvedLayoutStyle::from_stylo(computed); if let Some(profile) = self.profile.as_mut() { profile.primary_projection_ns = profile.primary_projection_ns.saturating_add( phase_started @@ -162,10 +145,8 @@ impl LayoutStyleResolver for NativeLayoutStyleResolver<'_> { ); } let phase_started = self.profile.as_ref().map(|_| Instant::now()); - let before = before - .map(|computed| ResolvedLayoutStyle::from_stylo(computed).with_source_origin(origin)); - let after = after - .map(|computed| ResolvedLayoutStyle::from_stylo(computed).with_source_origin(origin)); + let before = before.map(ResolvedLayoutStyle::from_stylo); + let after = after.map(ResolvedLayoutStyle::from_stylo); if let Some(profile) = self.profile.as_mut() { profile.eager_pseudo_count = profile .eager_pseudo_count @@ -217,11 +198,8 @@ impl LayoutStyleResolver for NativeLayoutStyleResolver<'_> { ); } let phase_started = self.profile.as_ref().map(|_| Instant::now()); - let origin = self.style_origin(node); let resolved = computed.map(|computed| { - ResolvedLayoutPseudoStyle::new( - ResolvedLayoutStyle::from_stylo(computed).with_source_origin(origin), - ) + ResolvedLayoutPseudoStyle::new(ResolvedLayoutStyle::from_stylo(computed)) }); if let Some(profile) = self.profile.as_mut() { profile.marker_projection_ns = profile.marker_projection_ns.saturating_add( @@ -237,15 +215,12 @@ impl LayoutStyleResolver for NativeLayoutStyleResolver<'_> { &mut self, node: DomHandle, ) -> Result, LayoutError> { - let origin = self.style_origin(node); Ok(self .reads .read(node) .pseudo_computed_values("placeholder") .map(|computed| { - ResolvedLayoutPseudoStyle::new( - ResolvedLayoutStyle::from_stylo(computed).with_source_origin(origin), - ) + ResolvedLayoutPseudoStyle::new(ResolvedLayoutStyle::from_stylo(computed)) })) } @@ -254,8 +229,8 @@ impl LayoutStyleResolver for NativeLayoutStyleResolver<'_> { context: LayoutAnonymousStyleContext<'_, DomHandle>, display: LayoutDisplay, ) -> Result { - let owner = context.owner; - let parent = context.parent_style; + let owner = context.owner(); + let parent = context.parent_style(); let parent_computed = parent.stylo_computed_values().ok_or_else(|| { LayoutError::style_resolution( format!("{owner:?}"), @@ -276,13 +251,6 @@ impl LayoutStyleResolver for NativeLayoutStyleResolver<'_> { "anonymous owner document does not match the prepared style context", )); } - let origin = self.style_origin(owner); - if parent.source_origin() != Some(origin) { - return Err(LayoutError::style_resolution( - format!("{owner:?}"), - "anonymous parent style does not match its owner and layout pass", - )); - } let computed = prepared .computed_values(owner, parent_computed.as_ref(), anonymous_kind) .ok_or_else(|| { @@ -300,7 +268,7 @@ impl LayoutStyleResolver for NativeLayoutStyleResolver<'_> { ); } let phase_started = self.profile.as_ref().map(|_| Instant::now()); - let mut resolved = ResolvedLayoutStyle::from_stylo(computed).with_source_origin(origin); + let mut resolved = ResolvedLayoutStyle::from_stylo(computed); resolved.force_layout_display(display); if let Some(profile) = self.profile.as_mut() { profile.anonymous_projection_ns = profile.anonymous_projection_ns.saturating_add( @@ -368,12 +336,20 @@ mod tests { vm } - fn parent_style( + fn layout_world( resolver: &mut NativeLayoutStyleResolver<'_>, + root: DomHandle, + ) -> moli_layout::LayoutWorld { + let source = super::super::source_view::NativeLayoutSourceView::new(resolver.runtime, root); + moli_layout::build_layout_world(&source, resolver).unwrap() + } + + fn parent_context( + world: &moli_layout::LayoutWorld, owner: DomHandle, - ) -> ResolvedLayoutStyle { - ResolvedLayoutStyle::from_stylo(resolver.reads.read(owner).computed_values().unwrap()) - .with_source_origin(resolver.style_origin(owner)) + ) -> LayoutAnonymousStyleContext<'_, DomHandle> { + let parent = world.box_by_id(world.source_box(owner).unwrap()).unwrap(); + LayoutAnonymousStyleContext::from_parent(parent).unwrap() } #[test] @@ -386,16 +362,14 @@ mod tests { let owner = host.dom_host().element_handle_by_id("owner").unwrap(); let viewport = host.layout_viewport_for_document(document); let mut resolver = NativeLayoutStyleResolver::new(&host, root, document, viewport); - let parent = parent_style(&mut resolver, owner); + let world = layout_world(&mut resolver, root); + let parent = parent_context(&world, owner); let reads = resolver.reads.read_count_for_test(); for _ in 0..3 { resolver.profile = Some(NativeLayoutStyleResolverProfile::default()); for _ in 0..10_000 { let style = resolver - .anonymous_style( - LayoutAnonymousStyleContext::new(owner, &parent), - LayoutDisplay::Block, - ) + .anonymous_style(parent, LayoutDisplay::Block) .unwrap(); assert_eq!(style.display(), LayoutDisplay::Block); std::hint::black_box(style); @@ -415,7 +389,7 @@ mod tests { } #[test] - fn anonymous_styles_reject_mismatched_owners_documents_and_passes() { + fn anonymous_styles_reject_foreign_documents() { let mut vm = fixture(); vm.eval("document.getElementById('frame').contentDocument.body.textContent = 'child'") .unwrap(); @@ -424,38 +398,10 @@ mod tests { let document = host.document_handle(); let root = host.dom_host().document_element_handle().unwrap(); let owner = host.dom_host().element_handle_by_id("owner").unwrap(); - let other = host.dom_host().element_handle_by_id("other").unwrap(); let viewport = host.layout_viewport_for_document(document); let mut resolver = NativeLayoutStyleResolver::new(&host, root, document, viewport); - let parent = parent_style(&mut resolver, owner); - let other_parent = parent_style(&mut resolver, other); - for (owner, parent) in [(owner, &other_parent), (other, &parent)] { - let error = resolver - .anonymous_style( - LayoutAnonymousStyleContext::new(owner, parent), - LayoutDisplay::Block, - ) - .unwrap_err(); - assert!( - error.to_string().contains("owner and layout pass"), - "{error}" - ); - } - - let mut inherited = parent.clone(); - let reads = resolver.reads.read_count_for_test(); - for display in [ - LayoutDisplay::Table, - LayoutDisplay::TableRow, - LayoutDisplay::TableCell, - ] { - inherited = resolver - .anonymous_style(LayoutAnonymousStyleContext::new(owner, &inherited), display) - .unwrap(); - assert_eq!(inherited.font_size(), parent.font_size()); - assert_eq!(inherited.source_origin(), parent.source_origin()); - } - assert_eq!(resolver.reads.read_count_for_test(), reads); + let world = layout_world(&mut resolver, root); + let parent = parent_context(&world, owner); let frame = host.dom_host().element_handle_by_id("frame").unwrap(); let foreign_document = host.child_browsing_context_document_handle(frame).unwrap(); @@ -466,30 +412,17 @@ mod tests { .unwrap(); let mut foreign = NativeLayoutStyleResolver::new(&host, foreign_root, foreign_document, viewport); - let foreign_parent = parent_style(&mut foreign, foreign_root); + let foreign_world = layout_world(&mut foreign, foreign_root); + let foreign_parent = parent_context(&foreign_world, foreign_root); assert!( foreign - .anonymous_style( - LayoutAnonymousStyleContext::new(foreign_root, &foreign_parent), - LayoutDisplay::Block - ) + .anonymous_style(foreign_parent, LayoutDisplay::Block) .is_ok() ); let error = resolver - .anonymous_style( - LayoutAnonymousStyleContext::new(foreign_root, &foreign_parent), - LayoutDisplay::Block, - ) + .anonymous_style(foreign_parent, LayoutDisplay::Block) .unwrap_err(); assert!(error.to_string().contains("owner document"), "{error}"); - assert!( - resolver - .anonymous_style( - LayoutAnonymousStyleContext::new(owner, &foreign_parent), - LayoutDisplay::Block - ) - .is_err() - ); assert!( resolver .anonymous_context @@ -497,27 +430,70 @@ mod tests { .unwrap() .computed_values( foreign_root, - parent.stylo_computed_values().unwrap(), + parent.parent_style().stylo_computed_values().unwrap(), StyloAnonymousBoxKind::Generic ) .is_none() ); + } - let mut next = NativeLayoutStyleResolver::new(&host, root, document, viewport); - assert!( - next.anonymous_style( - LayoutAnonymousStyleContext::new(owner, &parent), - LayoutDisplay::Block - ) - .is_err() + #[test] + fn anonymous_table_styles_inherit_from_their_parent_boxes() { + let mut vm = fixture(); + vm.eval( + r#"document.getElementById('owner').innerHTML = 'onetwo'"#, + ) + .unwrap(); + vm.sync_live_document_style_sources(); + let host = vm.context_host_weak_for_test().upgrade().unwrap(); + let host = host.borrow(); + let document = host.document_handle(); + let root = host.dom_host().document_element_handle().unwrap(); + let owner = host.dom_host().element_handle_by_id("owner").unwrap(); + let mut resolver = NativeLayoutStyleResolver::new( + &host, + root, + document, + host.layout_viewport_for_document(document), ); + let world = layout_world(&mut resolver, root); + let reads = resolver.reads.read_count_for_test(); + let mut pending = vec![world.source_box(owner).unwrap()]; + let mut displays = Vec::new(); + while let Some(id) = pending.pop() { + let parent = world.box_by_id(id).unwrap(); + pending.extend_from_slice(parent.children()); + if parent.anonymous_reason().is_none() { + continue; + } + let context = LayoutAnonymousStyleContext::from_parent(parent).unwrap(); + assert_eq!(context.owner(), parent.owner().unwrap()); + assert_eq!(context.parent_style().font_size(), 23.0); + let anonymous = resolver + .anonymous_style(context, LayoutDisplay::Block) + .unwrap(); + assert_eq!(anonymous.font_size(), 23.0); + displays.push(parent.style().display()); + } + for display in [ + LayoutDisplay::Table, + LayoutDisplay::TableRowGroup, + LayoutDisplay::TableRow, + LayoutDisplay::TableCell, + ] { + assert!( + displays.contains(&display), + "missing {display:?}: {displays:?}" + ); + } + assert_eq!(resolver.reads.read_count_for_test(), reads); } #[test] fn anonymous_styles_prepare_pending_invalidations_for_each_pass() { let mut vm = fixture(); vm.sync_live_document_style_sources(); - let initial = { + { let host = vm.context_host_weak_for_test().upgrade().unwrap(); let host = host.borrow(); let document = host.document_handle(); @@ -529,20 +505,17 @@ mod tests { document, host.layout_viewport_for_document(document), ); - let parent = parent_style(&mut resolver, owner); - assert_eq!(parent.font_size(), 23.0); + let world = layout_world(&mut resolver, root); + let parent = parent_context(&world, owner); + assert_eq!(parent.parent_style().font_size(), 23.0); assert_eq!( resolver - .anonymous_style( - LayoutAnonymousStyleContext::new(owner, &parent), - LayoutDisplay::Block - ) + .anonymous_style(parent, LayoutDisplay::Block) .unwrap() .font_size(), 23.0 ); - parent - }; + } for (mutation, expected) in [ ( "document.getElementById('owner').style.fontSize = '37px'", @@ -566,25 +539,15 @@ mod tests { document, host.layout_viewport_for_document(document), ); - let parent = parent_style(&mut resolver, owner); - assert_eq!(parent.font_size(), expected); + let world = layout_world(&mut resolver, root); + let parent = parent_context(&world, owner); + assert_eq!(parent.parent_style().font_size(), expected); let reads = resolver.reads.read_count_for_test(); let anonymous = resolver - .anonymous_style( - LayoutAnonymousStyleContext::new(owner, &parent), - LayoutDisplay::Block, - ) + .anonymous_style(parent, LayoutDisplay::Block) .unwrap(); assert_eq!(anonymous.font_size(), expected); assert_eq!(resolver.reads.read_count_for_test(), reads); - assert!( - resolver - .anonymous_style( - LayoutAnonymousStyleContext::new(owner, &initial), - LayoutDisplay::Block - ) - .is_err() - ); } }