mirror of
https://github.com/lexmount/moli.git
synced 2026-11-06 08:07:25 +00:00
perf(layout): reuse prepared context for anonymous styles
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user