perf(layout): reuse prepared context for anonymous styles

This commit is contained in:
ldm0
2026-10-16 20:03:07 +08:00
committed by Donough Liu
parent 9279ef478a
commit 07368cea9a
16 changed files with 470 additions and 64 deletions
+33
View File
@@ -0,0 +1,33 @@
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 }
}
}
/// 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.
#[derive(Clone, Copy, Debug)]
pub struct LayoutAnonymousStyleContext<'a, N> {
pub owner: N,
pub parent_style: &'a ResolvedLayoutStyle,
}
impl<'a, N> LayoutAnonymousStyleContext<'a, N> {
pub const fn new(owner: N, parent_style: &'a ResolvedLayoutStyle) -> Self {
Self {
owner,
parent_style,
}
}
}
+12 -8
View File
@@ -13,10 +13,10 @@ use std::{
};
use crate::{
LayoutAnonymousReason, LayoutBoxId, LayoutBoxKind, LayoutCapabilityDiagnostic, LayoutDisplay,
LayoutElementCategory, LayoutElementSemantics, LayoutError, LayoutPseudo, LayoutSource,
LayoutSourceKind, LayoutStyleResolver, LayoutWorld, ResolvedLayoutPseudoStyle,
ResolvedLayoutStyle,
LayoutAnonymousReason, LayoutAnonymousStyleContext, LayoutBoxId, LayoutBoxKind,
LayoutCapabilityDiagnostic, LayoutDisplay, LayoutElementCategory, LayoutElementSemantics,
LayoutError, LayoutPseudo, LayoutSource, LayoutSourceKind, LayoutStyleResolver, LayoutWorld,
ResolvedLayoutPseudoStyle, ResolvedLayoutStyle,
form::form_control_context,
replaced::ReplacedContext,
style::{InlineWhiteSpaceCollapse, LayoutOverflowMode},
@@ -885,9 +885,10 @@ where
run.clear();
return Ok(());
}
let style = self
.styles
.anonymous_style(owner, parent_style, LayoutDisplay::Block)?;
let style = self.styles.anonymous_style(
LayoutAnonymousStyleContext::new(owner, parent_style),
LayoutDisplay::Block,
)?;
let mut anonymous = LayoutWorld::new_box(
None,
Some(owner),
@@ -1146,7 +1147,10 @@ where
return Ok(last);
}
let style = self.styles.anonymous_style(owner, parent_style, display)?;
let style = self.styles.anonymous_style(
LayoutAnonymousStyleContext::new(owner, parent_style),
display,
)?;
let id = world.allocate(LayoutWorld::new_box(
None,
Some(owner),
+5 -5
View File
@@ -5,9 +5,10 @@ use std::{fmt::Debug, hash::Hash, sync::Arc};
use taffy::Size;
use crate::{
LayoutAnonymousReason, LayoutBoxId, LayoutBoxKind, LayoutDisplay, LayoutElementCategory,
LayoutElementSemantics, LayoutError, LayoutFormControlKind, LayoutInputControlKind,
LayoutStyleResolver, LayoutWorld, ResolvedLayoutStyle, replaced::ReplacedContext,
LayoutAnonymousReason, LayoutAnonymousStyleContext, LayoutBoxId, LayoutBoxKind, LayoutDisplay,
LayoutElementCategory, LayoutElementSemantics, LayoutError, LayoutFormControlKind,
LayoutInputControlKind, LayoutStyleResolver, LayoutWorld, ResolvedLayoutStyle,
replaced::ReplacedContext,
};
pub(crate) fn prepare_form_controls<N, R>(
@@ -52,8 +53,7 @@ where
None
};
let editor_style = styles.anonymous_style(
owner,
&world.boxes[control.index()].style,
LayoutAnonymousStyleContext::new(owner, &world.boxes[control.index()].style),
LayoutDisplay::Block,
)?;
let selection = world.boxes[control.index()].text_selection;
+2
View File
@@ -9,6 +9,7 @@
/// `LayoutUnit` precision.
pub(crate) const LAYOUT_SUBPIXELS_PER_CSS_PIXEL: f32 = 64.0;
mod anonymous_style;
mod builder;
mod capture;
mod containment;
@@ -40,6 +41,7 @@ mod taffy_tree;
mod text;
mod world;
pub use anonymous_style::{LayoutAnonymousStyleContext, LayoutStyleOrigin};
pub use builder::build_layout_world;
pub use capture::{
FULL_DOCUMENT_CAPTURE_CSS_DIMENSION_LIMIT, PaintCaptureRegion, PaintCaptureRequest,
+5 -3
View File
@@ -662,10 +662,12 @@ where
fn anonymous_style(
&mut self,
_owner: N,
parent: &ResolvedLayoutStyle,
context: crate::LayoutAnonymousStyleContext<'_, N>,
display: crate::LayoutDisplay,
) -> Result<ResolvedLayoutStyle, LayoutError> {
Ok(ResolvedLayoutStyle::anonymous_from(parent, display))
Ok(ResolvedLayoutStyle::anonymous_from(
context.parent_style,
display,
))
}
}
+16
View File
@@ -482,6 +482,7 @@ impl ResolvedLayoutTransform {
/// invalid.
#[derive(Clone)]
pub struct ResolvedLayoutStyle {
source_origin: Option<crate::LayoutStyleOrigin>,
pub(crate) computed: Option<ServoArc<ComputedValues>>,
pub(crate) taffy: Style<Atom>,
preferred_aspect_ratio: PreferredAspectRatio,
@@ -865,6 +866,7 @@ impl ResolvedLayoutStyle {
};
}
Self {
source_origin: None,
computed: Some(computed),
taffy,
preferred_aspect_ratio,
@@ -942,6 +944,7 @@ impl ResolvedLayoutStyle {
};
let sticky_inset = taffy.inset;
Self {
source_origin: None,
computed: None,
taffy,
preferred_aspect_ratio,
@@ -1167,6 +1170,17 @@ 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<crate::LayoutStyleOrigin> {
self.source_origin
}
pub fn background_color(&self) -> PaintColor {
self.background_color
}
@@ -1979,6 +1993,7 @@ 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,
@@ -2046,6 +2061,7 @@ 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),
+6 -4
View File
@@ -193,12 +193,14 @@ impl LayoutStyleResolver<usize> for TestStyles {
fn anonymous_style(
&mut self,
owner: usize,
parent: &ResolvedLayoutStyle,
context: moli_layout::LayoutAnonymousStyleContext<'_, usize>,
display: LayoutDisplay,
) -> Result<ResolvedLayoutStyle, LayoutError> {
self.anonymous_queries.push((owner, display));
Ok(ResolvedLayoutStyle::anonymous_from(parent, display))
self.anonymous_queries.push((context.owner, display));
Ok(ResolvedLayoutStyle::anonymous_from(
context.parent_style,
display,
))
}
}
+5 -3
View File
@@ -141,11 +141,13 @@ impl LayoutStyleResolver<usize> for FixtureStyles {
fn anonymous_style(
&mut self,
_owner: usize,
parent: &ResolvedLayoutStyle,
context: moli_layout::LayoutAnonymousStyleContext<'_, usize>,
display: LayoutDisplay,
) -> Result<ResolvedLayoutStyle, LayoutError> {
Ok(ResolvedLayoutStyle::anonymous_from(parent, display))
Ok(ResolvedLayoutStyle::anonymous_from(
context.parent_style,
display,
))
}
}
@@ -1,18 +1,29 @@
use moli_layout::{
LayoutDisplay, LayoutError, LayoutStyleResolver, LayoutViewport, ResolvedLayoutElementStyles,
ResolvedLayoutPseudoStyle, ResolvedLayoutStyle,
LayoutAnonymousStyleContext, LayoutDisplay, LayoutError, LayoutStyleOrigin,
LayoutStyleResolver, LayoutViewport, ResolvedLayoutElementStyles, ResolvedLayoutPseudoStyle,
ResolvedLayoutStyle,
};
use std::{
sync::atomic::{AtomicU64, Ordering},
time::Instant,
};
use std::time::Instant;
use crate::{
document_runtime::DomHandle,
native_bridge::{JsContextHost, element::StyleObservation},
native_bridge::{
JsContextHost,
element::{PreparedAnonymousStyleContext, StyleObservation},
},
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<PreparedAnonymousStyleContext<'a>>,
pass_id: u64,
scripting_enabled: bool,
profile: Option<NativeLayoutStyleResolverProfile>,
}
@@ -41,16 +52,28 @@ impl<'a> NativeLayoutStyleResolver<'a> {
viewport: LayoutViewport,
) -> Self {
let mut reads = layout_style_read_scope(runtime, document, viewport);
let _ = reads.read(root).computed_values();
// Drain pending invalidations and update the retained world before
// freezing the anonymous-style context for this synchronous pass.
let anonymous_context = reads.read(root).prepare_anonymous_style_context();
Self {
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;
@@ -129,7 +152,8 @@ impl LayoutStyleResolver<DomHandle> for NativeLayoutStyleResolver<'_> {
);
}
let phase_started = self.profile.as_ref().map(|_| Instant::now());
let mut resolved = ResolvedLayoutStyle::from_stylo(computed);
let origin = self.style_origin(node);
let mut resolved = ResolvedLayoutStyle::from_stylo(computed).with_source_origin(origin);
if let Some(profile) = self.profile.as_mut() {
profile.primary_projection_ns = profile.primary_projection_ns.saturating_add(
phase_started
@@ -138,8 +162,10 @@ impl LayoutStyleResolver<DomHandle> for NativeLayoutStyleResolver<'_> {
);
}
let phase_started = self.profile.as_ref().map(|_| Instant::now());
let before = before.map(ResolvedLayoutStyle::from_stylo);
let after = after.map(ResolvedLayoutStyle::from_stylo);
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));
if let Some(profile) = self.profile.as_mut() {
profile.eager_pseudo_count = profile
.eager_pseudo_count
@@ -191,8 +217,11 @@ impl LayoutStyleResolver<DomHandle> 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))
ResolvedLayoutPseudoStyle::new(
ResolvedLayoutStyle::from_stylo(computed).with_source_origin(origin),
)
});
if let Some(profile) = self.profile.as_mut() {
profile.marker_projection_ns = profile.marker_projection_ns.saturating_add(
@@ -208,21 +237,25 @@ impl LayoutStyleResolver<DomHandle> for NativeLayoutStyleResolver<'_> {
&mut self,
node: DomHandle,
) -> Result<Option<ResolvedLayoutPseudoStyle>, LayoutError> {
let origin = self.style_origin(node);
Ok(self
.reads
.read(node)
.pseudo_computed_values("placeholder")
.map(|computed| {
ResolvedLayoutPseudoStyle::new(ResolvedLayoutStyle::from_stylo(computed))
ResolvedLayoutPseudoStyle::new(
ResolvedLayoutStyle::from_stylo(computed).with_source_origin(origin),
)
}))
}
fn anonymous_style(
&mut self,
owner: DomHandle,
parent: &ResolvedLayoutStyle,
context: LayoutAnonymousStyleContext<'_, DomHandle>,
display: LayoutDisplay,
) -> Result<ResolvedLayoutStyle, LayoutError> {
let owner = context.owner;
let parent = context.parent_style;
let parent_computed = parent.stylo_computed_values().ok_or_else(|| {
LayoutError::style_resolution(
format!("{owner:?}"),
@@ -231,9 +264,27 @@ impl LayoutStyleResolver<DomHandle> for NativeLayoutStyleResolver<'_> {
})?;
let anonymous_kind = anonymous_box_kind(display);
let phase_started = self.profile.as_ref().map(|_| Instant::now());
let read = self.reads.read(owner);
let computed = read
.anonymous_computed_values(parent_computed.as_ref(), anonymous_kind)
let prepared = self.anonymous_context.as_ref().ok_or_else(|| {
LayoutError::style_resolution(
format!("{owner:?}"),
"anonymous style pass has no prepared document world",
)
})?;
if self.runtime.dom_host().owner_document_handle(owner) != Some(prepared.document()) {
return Err(LayoutError::style_resolution(
format!("{owner:?}"),
"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(|| {
LayoutError::style_resolution(
format!("{owner:?}"),
@@ -249,7 +300,7 @@ impl LayoutStyleResolver<DomHandle> for NativeLayoutStyleResolver<'_> {
);
}
let phase_started = self.profile.as_ref().map(|_| Instant::now());
let mut resolved = ResolvedLayoutStyle::from_stylo(computed);
let mut resolved = ResolvedLayoutStyle::from_stylo(computed).with_source_origin(origin);
resolved.force_layout_display(display);
if let Some(profile) = self.profile.as_mut() {
profile.anonymous_projection_ns = profile.anonymous_projection_ns.saturating_add(
@@ -290,10 +341,253 @@ const fn anonymous_box_kind(display: LayoutDisplay) -> StyloAnonymousBoxKind {
#[cfg(test)]
mod tests {
use super::anonymous_box_kind;
use super::*;
use crate::style_engine::StyloAnonymousBoxKind;
use moli_layout::LayoutDisplay;
fn fixture() -> crate::script_vm::StandaloneScriptVmHarness {
use crate::{
dom::native::DomHost, parser::HtmlParser, script_vm::ScriptVmDefaultWorldBootstrap,
};
let _runtime = crate::JsRuntime::initialize();
let document = HtmlParser::SCRIPTING_ENABLED.parse(
url::Url::parse("https://anonymous-style.test/").unwrap(),
"<!doctype html><style>html,body{margin:0}#owner{font-size:23px;color:red}#other{font-size:41px;color:blue}</style><div id=owner>one</div><div id=other>two</div><iframe id=frame></iframe>".into(),
);
let queue = crate::page_task_queue::PageTaskQueueTestHarness::new();
let mut vm = ScriptVmDefaultWorldBootstrap::standalone_from_dom_host_for_test(
DomHost::from_dom(document),
queue.owner_attached_runtime_page_task_sender_for_test(),
queue.parser_boundary_sender(),
)
.unwrap()
.finish()
.unwrap();
vm.install_page_task_residence_for_executor_test(queue.residence());
vm.set_layout_policy(crate::real_layout_test_policy());
vm
}
fn parent_style(
resolver: &mut NativeLayoutStyleResolver<'_>,
owner: DomHandle,
) -> ResolvedLayoutStyle {
ResolvedLayoutStyle::from_stylo(resolver.reads.read(owner).computed_values().unwrap())
.with_source_origin(resolver.style_origin(owner))
}
#[test]
fn anonymous_styles_reuse_one_prepared_observation() {
let vm = fixture();
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 viewport = host.layout_viewport_for_document(document);
let mut resolver = NativeLayoutStyleResolver::new(&host, root, document, viewport);
let parent = parent_style(&mut resolver, 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,
)
.unwrap();
assert_eq!(style.display(), LayoutDisplay::Block);
std::hint::black_box(style);
}
let profile = resolver.profile.as_ref().unwrap();
assert_eq!(profile.anonymous_count, 10_000);
assert_eq!(
resolver.reads.read_count_for_test(),
reads,
"anonymous boxes must not observe element styles again"
);
eprintln!(
"anonymous_count={} anonymous_read_ns={} anonymous_projection_ns={}",
profile.anonymous_count, profile.anonymous_read_ns, profile.anonymous_projection_ns
);
}
}
#[test]
fn anonymous_styles_reject_mismatched_owners_documents_and_passes() {
let mut vm = fixture();
vm.eval("document.getElementById('frame').contentDocument.body.textContent = 'child'")
.unwrap();
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 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 frame = host.dom_host().element_handle_by_id("frame").unwrap();
let foreign_document = host.child_browsing_context_document_handle(frame).unwrap();
let foreign_root = host
.dom_host()
.dom()
.document_element_handle_for_document(foreign_document)
.unwrap();
let mut foreign =
NativeLayoutStyleResolver::new(&host, foreign_root, foreign_document, viewport);
let foreign_parent = parent_style(&mut foreign, foreign_root);
assert!(
foreign
.anonymous_style(
LayoutAnonymousStyleContext::new(foreign_root, &foreign_parent),
LayoutDisplay::Block
)
.is_ok()
);
let error = resolver
.anonymous_style(
LayoutAnonymousStyleContext::new(foreign_root, &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
.as_ref()
.unwrap()
.computed_values(
foreign_root,
parent.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_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();
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 parent = parent_style(&mut resolver, owner);
assert_eq!(parent.font_size(), 23.0);
assert_eq!(
resolver
.anonymous_style(
LayoutAnonymousStyleContext::new(owner, &parent),
LayoutDisplay::Block
)
.unwrap()
.font_size(),
23.0
);
parent
};
for (mutation, expected) in [
(
"document.getElementById('owner').style.fontSize = '37px'",
37.0,
),
(
"document.querySelector('style').textContent += '#owner {font-size:51px!important}'",
51.0,
),
] {
vm.eval(mutation).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 parent = parent_style(&mut resolver, owner);
assert_eq!(parent.font_size(), expected);
let reads = resolver.reads.read_count_for_test();
let anonymous = resolver
.anonymous_style(
LayoutAnonymousStyleContext::new(owner, &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()
);
}
}
#[test]
fn anonymous_table_roles_use_the_matching_servo_precomputed_pseudo() {
assert_eq!(
@@ -1422,7 +1422,7 @@ impl JsContextHost {
)
}
fn style_source_document_context_for_read_document(
pub(crate) fn style_source_document_context_for_read_document(
&self,
read_document: DomHandle,
) -> OwnedStyleSourceDocumentContext {
@@ -1800,9 +1800,9 @@ impl JsContextHost {
owner: DomHandle,
parent_style: &style::properties::ComputedValues,
anonymous_kind: crate::style_engine::StyloAnonymousBoxKind,
document_context: &OwnedStyleSourceDocumentContext,
read_document: DomHandle,
) -> Option<crate::style_engine::StyloComputedStyleSnapshot> {
let document_context = self.style_source_document_context_for_read_document(read_document);
self.style_engine
.computed_anonymous_style_snapshot_from_current_observation(
self.dom_host(),
@@ -166,8 +166,8 @@ pub(crate) use shadow_dom::{
ensure_shadow_root_adopted_style_sheets_initialized,
};
pub(crate) use styles::{
ComputedStyleRead, StyleMode, active_css_animation_transform_value,
computed_style_properties_for_inspector_handle,
ComputedStyleRead, PreparedAnonymousStyleContext, StyleMode,
active_css_animation_transform_value, computed_style_properties_for_inspector_handle,
computed_style_property_values_for_document_snapshot, css_animation_start_applies,
cssom_style_entry_requires_structured_parser,
cssom_style_property_mutation_affected_names_with_pdb,
@@ -18,7 +18,7 @@ pub(crate) use accessors::{
style_css_text_getter_callback, style_css_text_setter_callback, style_length_getter_callback,
};
pub(in crate::native_bridge::element) use declaration::style_base_url;
pub(crate) use declaration::{ComputedStyleRead, StyleObservation};
pub(crate) use declaration::{ComputedStyleRead, PreparedAnonymousStyleContext, StyleObservation};
use declaration::{
StyleComputationContext, style_property_count_with_context, style_property_value_with_context,
style_runtime_and_handle_from_object,
@@ -9,7 +9,7 @@ mod values;
pub(crate) use identity::StyleMode;
pub(super) use identity::style_runtime_and_handle_from_object;
pub(super) use observation::StyleComputationContext;
pub(crate) use observation::{ComputedStyleRead, StyleObservation};
pub(crate) use observation::{ComputedStyleRead, PreparedAnonymousStyleContext, StyleObservation};
pub(crate) use parse::{
cssom_style_entry_requires_structured_parser,
cssom_style_property_uses_preferred_pdb_supplemental_entries,
@@ -3,8 +3,9 @@ use std::{cell::RefCell, rc::Rc};
use crate::{
document_runtime::DomHandle,
style_engine::{
FullStyleWorldSnapshot, PreparedStyleWorldUpdate, StyleObservationSnapshot, StyleViewport,
StyleWorldUpdatePlan, StyloComputedStyleSnapshot, StyloStyleEnvironment,
FullStyleWorldSnapshot, OwnedStyleSourceDocumentContext, PreparedStyleWorldUpdate,
StyleObservationSnapshot, StyleViewport, StyleWorldUpdatePlan, StyloComputedStyleSnapshot,
StyloStyleEnvironment,
},
};
@@ -67,6 +68,8 @@ impl StyleComputationContext {
/// query needs it, then shared by every read in this observation.
pub(crate) struct StyleObservation<'a> {
runtime: &'a JsContextHost,
#[cfg(test)]
read_count: usize,
/// `None` resolves one stable context per touched Document. Layout and
/// detached CSSOM snapshots use `Some` to pin an exact caller-owned
/// viewport instead.
@@ -269,6 +272,8 @@ impl<'a> StyleObservation<'a> {
) -> Self {
Self {
runtime,
#[cfg(test)]
read_count: 0,
fixed_context,
tracks_persistent_world,
sizing_target,
@@ -283,7 +288,16 @@ impl<'a> StyleObservation<'a> {
self.runtime
}
#[cfg(test)]
pub(crate) const fn read_count_for_test(&self) -> usize {
self.read_count
}
pub(crate) fn read(&mut self, handle: DomHandle) -> ComputedStyleRead<'a> {
#[cfg(test)]
{
self.read_count += 1;
}
let read_document = self
.fixed_context
.unwrap_or_default()
@@ -417,5 +431,44 @@ pub(crate) struct ComputedStyleRead<'a> {
pub(super) stylo_style: Option<StyloComputedStyleSnapshot>,
}
/// A document world already updated by the pass's initial style read. Layout
/// remains synchronous; subsequent anonymous boxes only inherit and cascade.
pub(crate) struct PreparedAnonymousStyleContext<'a> {
runtime: &'a JsContextHost,
document: DomHandle,
document_context: OwnedStyleSourceDocumentContext,
}
impl<'a> PreparedAnonymousStyleContext<'a> {
pub(super) fn new(runtime: &'a JsContextHost, document: DomHandle) -> Self {
Self {
runtime,
document,
document_context: runtime.style_source_document_context_for_read_document(document),
}
}
pub(crate) const fn document(&self) -> DomHandle {
self.document
}
pub(crate) fn computed_values(
&self,
owner: DomHandle,
parent_style: &style::properties::ComputedValues,
anonymous_kind: crate::style_engine::StyloAnonymousBoxKind,
) -> Option<style::servo_arc::Arc<style::properties::ComputedValues>> {
self.runtime
.computed_anonymous_style_snapshot_from_current_observation(
owner,
parent_style,
anonymous_kind,
&self.document_context,
self.document,
)
.map(|snapshot| snapshot.computed_values())
}
}
#[cfg(test)]
mod sizing_tests;
@@ -17,8 +17,8 @@ use crate::{
},
style_engine::{
ComputedRenderedStyleFacts, FullStyleWorldSnapshot, StyleViewport,
StylesheetResourceSnapshot, StyloAnonymousBoxKind, StyloComputedStyleSnapshot,
StyloStylesheetSource, computed_property_is_queryable,
StylesheetResourceSnapshot, StyloComputedStyleSnapshot, StyloStylesheetSource,
computed_property_is_queryable,
},
},
};
@@ -29,7 +29,8 @@ use style::{properties::ComputedValues, servo_arc::Arc as ServoArc, stylesheets:
use super::super::super::super::JsContextHost;
use super::observation::{
ComputedStyleRead, RetainedStyleObservation, StyleComputationContext, StyleObservation,
ComputedStyleRead, PreparedAnonymousStyleContext, RetainedStyleObservation,
StyleComputationContext, StyleObservation,
};
use super::sizing::used_size_from_layout_snapshot;
use super::style_world::{
@@ -271,23 +272,17 @@ impl<'a> ComputedStyleRead<'a> {
.map(|snapshot| snapshot.computed_values())
}
pub(crate) fn anonymous_computed_values(
pub(crate) fn prepare_anonymous_style_context(
&self,
parent_style: &ComputedValues,
anonymous_kind: StyloAnonymousBoxKind,
) -> Option<ServoArc<ComputedValues>> {
) -> Option<PreparedAnonymousStyleContext<'a>> {
self.stylo_style.as_ref()?;
let read_document = self
.context
.resolved_read_document(self.runtime, self.handle);
self.runtime
.computed_anonymous_style_snapshot_from_current_observation(
self.handle,
parent_style,
anonymous_kind,
read_document,
)
.map(|snapshot| snapshot.computed_values())
Some(PreparedAnonymousStyleContext::new(
self.runtime,
read_document,
))
}
fn property_in_prepared_scope(&self, property: &str) -> String {
@@ -637,6 +637,9 @@ pub(super) fn computed_anonymous_style_snapshot_from_current_observation(
read_document: DomHandle,
) -> Option<StyloComputedStyleSnapshot> {
let owner_document = owner_document_for_computed_style_read(host, owner)?;
if owner_document != read_document {
return None;
}
trace_computed_style_read(
document_url,
owner,
@@ -646,7 +649,7 @@ pub(super) fn computed_anonymous_style_snapshot_from_current_observation(
Some(anonymous_kind.trace_name()),
document_context,
);
let world = engine.world_for_document(owner_document);
let world = engine.world_for_document(read_document);
computed_anonymous_style_snapshot_in_current_world(
engine,
host,