mirror of
https://github.com/lexmount/moli.git
synced 2026-11-13 16:10:01 +00:00
fix(layout): project ResizeObserver sizes from frozen box axes
This commit is contained in:
@@ -33,7 +33,7 @@ pub struct LayoutDocumentMetrics {
|
||||
pub content_size: LayoutSize,
|
||||
}
|
||||
|
||||
/// CSSOM View and observer metrics for one source element.
|
||||
/// CSSOM View metrics for one source element.
|
||||
///
|
||||
/// Transformed quads use viewport CSS pixels and therefore include CSS
|
||||
/// `zoom`. Offset, size, and scroll fields are untransformed and have the
|
||||
@@ -65,6 +65,25 @@ pub struct LayoutElementMetrics<N> {
|
||||
pub pointer_events: bool,
|
||||
}
|
||||
|
||||
/// One ResizeObserver box size in logical CSS pixels.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct LayoutResizeObserverSize {
|
||||
pub inline_size: f32,
|
||||
pub block_size: f32,
|
||||
}
|
||||
|
||||
/// CSS-box ResizeObserver sizes projected wholly from one frozen principal box.
|
||||
/// The physical content size is used for contentRect width/height. The logical
|
||||
/// content and border pairs use the writing axes and effective zoom captured
|
||||
/// with the geometry, independently of authored box-sizing. This owned answer
|
||||
/// does not add state to the frozen tree or provide SVG bounding-box semantics.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct LayoutResizeObserverMetrics {
|
||||
pub content_size: LayoutSize,
|
||||
pub content_box_size: LayoutResizeObserverSize,
|
||||
pub border_box_size: LayoutResizeObserverSize,
|
||||
}
|
||||
|
||||
/// One scroll container on a target's layout ancestor chain.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct LayoutScrollContainerMetrics<N> {
|
||||
@@ -113,6 +132,10 @@ pub enum LayoutQuery<N> {
|
||||
ElementMetrics {
|
||||
source: N,
|
||||
},
|
||||
/// Physical content dimensions and logical CSS content/border sizes for RO.
|
||||
ResizeObserverMetrics {
|
||||
source: N,
|
||||
},
|
||||
/// Used Grid row and column tracks for a principal Grid container.
|
||||
UsedGridTracks {
|
||||
source: N,
|
||||
@@ -166,6 +189,7 @@ pub enum LayoutQueryAnswer<N> {
|
||||
ContentQuads(Vec<LayoutQuad>),
|
||||
TextRangeRects(Vec<LayoutQuad>),
|
||||
ElementMetrics(Option<LayoutElementMetrics<N>>),
|
||||
ResizeObserverMetrics(Option<LayoutResizeObserverMetrics>),
|
||||
UsedGridTracks(Option<LayoutResolvedGridTracks>),
|
||||
ScrollIntoViewGeometry(Option<LayoutScrollIntoViewGeometry<N>>),
|
||||
IntersectionGeometry(Option<LayoutIntersectionGeometry>),
|
||||
@@ -309,6 +333,11 @@ where
|
||||
LayoutQuery::ElementMetrics { source } => {
|
||||
LayoutQueryAnswer::ElementMetrics(self.element_metrics_for_source(*source))
|
||||
}
|
||||
LayoutQuery::ResizeObserverMetrics { source } => {
|
||||
LayoutQueryAnswer::ResizeObserverMetrics(
|
||||
self.resize_observer_metrics_for_source(*source),
|
||||
)
|
||||
}
|
||||
LayoutQuery::UsedGridTracks { source } => {
|
||||
LayoutQueryAnswer::UsedGridTracks(self.used_grid_tracks_for_source(*source))
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@ use super::{
|
||||
LayoutOutputBoxId, LayoutPoint, LayoutQuad, LayoutRect, LayoutResolvedGridTracks,
|
||||
LayoutSize, LayoutTransform2D, LayoutUsedSize,
|
||||
},
|
||||
query::{LayoutElementMetrics, LayoutNodeOutput},
|
||||
query::{
|
||||
LayoutElementMetrics, LayoutNodeOutput, LayoutResizeObserverMetrics,
|
||||
LayoutResizeObserverSize,
|
||||
},
|
||||
tree::{CssSizingBox, FrozenLayoutBox, FrozenLayoutTree},
|
||||
};
|
||||
|
||||
@@ -104,6 +107,19 @@ where
|
||||
self.element_metrics_for_source_with_offset_parent_filter(source, |_| true)
|
||||
}
|
||||
|
||||
/// ResizeObserver's CSS-box sizes, interpreted using only metadata and
|
||||
/// geometry from this snapshot. Non-replaced inline and suppressed sources
|
||||
/// have no applicable CSS sizing box and produce no measurement here.
|
||||
pub fn resize_observer_metrics_for_source(
|
||||
&self,
|
||||
source: N,
|
||||
) -> Option<LayoutResizeObserverMetrics> {
|
||||
self.boxes
|
||||
.iter()
|
||||
.find(|layout_box| layout_box.principal_source == Some(source))?
|
||||
.resize_observer_metrics()
|
||||
}
|
||||
|
||||
/// Physical and logical CSSOM sizes from this snapshot's sizing box,
|
||||
/// without transforms, integer rounding, or absolute CSS zoom. A source
|
||||
/// without an applicable principal box keeps its computed value.
|
||||
@@ -549,6 +565,35 @@ where
|
||||
}
|
||||
|
||||
impl<N> FrozenLayoutBox<N> {
|
||||
fn resize_observer_metrics(&self) -> Option<LayoutResizeObserverMetrics> {
|
||||
let sizing = self.css_sizing?;
|
||||
let unzoom = CssomAbsoluteZoom::new(self.effective_zoom);
|
||||
let content_size = unzoom.size(LayoutSize::new(
|
||||
self.content_box.width,
|
||||
self.content_box.height,
|
||||
));
|
||||
let border_size = unzoom.size(LayoutSize::new(
|
||||
self.border_box.width,
|
||||
self.border_box.height,
|
||||
));
|
||||
let logical = |size: LayoutSize| {
|
||||
let (inline_size, block_size) = if sizing.horizontal {
|
||||
(size.width, size.height)
|
||||
} else {
|
||||
(size.height, size.width)
|
||||
};
|
||||
LayoutResizeObserverSize {
|
||||
inline_size,
|
||||
block_size,
|
||||
}
|
||||
};
|
||||
Some(LayoutResizeObserverMetrics {
|
||||
content_size,
|
||||
content_box_size: logical(content_size),
|
||||
border_box_size: logical(border_size),
|
||||
})
|
||||
}
|
||||
|
||||
fn used_size(&self) -> Option<LayoutUsedSize> {
|
||||
let sizing = self.css_sizing?;
|
||||
let rect = match sizing.box_kind {
|
||||
|
||||
@@ -59,10 +59,11 @@ pub use layout_tree::{
|
||||
LayoutFragmentBoxModel, LayoutFragmentId, LayoutFragmentKind, LayoutHit,
|
||||
LayoutIntersectionGeometry, LayoutNodeOutput, LayoutOutputBoxId, LayoutPaintedSurfaceHit,
|
||||
LayoutPassMetrics, LayoutPassResult, LayoutPoint, LayoutQuad, LayoutQuery, LayoutQueryAnswer,
|
||||
LayoutQueryBatch, LayoutRect, LayoutResolvedGridTrackList, LayoutResolvedGridTracks,
|
||||
LayoutScrollContainerMetrics, LayoutScrollExtent, LayoutScrollIntoViewGeometry, LayoutSize,
|
||||
LayoutTransform2D, LayoutTreeRetentionMetrics, LayoutUsedSize, LayoutViewport,
|
||||
MAX_RETAINED_LAYOUT_BOXES, MAX_RETAINED_LAYOUT_FRAGMENTS, MAX_RETAINED_LAYOUT_TREE_BYTES,
|
||||
LayoutQueryBatch, LayoutRect, LayoutResizeObserverMetrics, LayoutResizeObserverSize,
|
||||
LayoutResolvedGridTrackList, LayoutResolvedGridTracks, LayoutScrollContainerMetrics,
|
||||
LayoutScrollExtent, LayoutScrollIntoViewGeometry, LayoutSize, LayoutTransform2D,
|
||||
LayoutTreeRetentionMetrics, LayoutUsedSize, LayoutViewport, MAX_RETAINED_LAYOUT_BOXES,
|
||||
MAX_RETAINED_LAYOUT_FRAGMENTS, MAX_RETAINED_LAYOUT_TREE_BYTES,
|
||||
};
|
||||
pub use normalize::{NormalizedBoxNode, NormalizedBoxTree, NormalizedFormattingContext};
|
||||
pub use normalize_source::{
|
||||
|
||||
@@ -454,7 +454,7 @@ fn for_each_resize_observation<'s, 'i>(
|
||||
handles
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|source| moli_layout::LayoutQuery::ElementMetrics { source }),
|
||||
.map(|source| moli_layout::LayoutQuery::ResizeObserverMetrics { source }),
|
||||
);
|
||||
let answers = match crate::native_bridge::element::published_geometry_batch(
|
||||
runtime,
|
||||
@@ -480,10 +480,10 @@ fn for_each_resize_observation<'s, 'i>(
|
||||
}
|
||||
};
|
||||
for (handle, answer) in handles.into_iter().zip(answers) {
|
||||
let moli_layout::LayoutQueryAnswer::ElementMetrics(metrics) = answer else {
|
||||
let moli_layout::LayoutQueryAnswer::ResizeObserverMetrics(metrics) = answer else {
|
||||
return Err(moli_layout::LayoutError::source_contract(
|
||||
"ResizeObserver geometry",
|
||||
"provider returned a mismatched element-metrics answer",
|
||||
"provider returned a mismatched ResizeObserver-metrics answer",
|
||||
));
|
||||
};
|
||||
geometry.insert(handle, (metrics, dpr));
|
||||
@@ -493,43 +493,36 @@ fn for_each_resize_observation<'s, 'i>(
|
||||
for entry in pending {
|
||||
let (metrics, dpr) = entry
|
||||
.handle
|
||||
.and_then(|handle| geometry.get(&handle).cloned())
|
||||
.and_then(|handle| geometry.get(&handle).copied())
|
||||
.and_then(|(metrics, dpr)| metrics.map(|metrics| (metrics, dpr)))
|
||||
.map(|(metrics, dpr)| (Some(metrics), dpr))
|
||||
.unwrap_or((None, 1.0));
|
||||
let content_width = metrics
|
||||
.as_ref()
|
||||
.map(|metrics| f64::from(metrics.content_size.width))
|
||||
.unwrap_or(0.0);
|
||||
let content_height = metrics
|
||||
.as_ref()
|
||||
.map(|metrics| f64::from(metrics.content_size.height))
|
||||
.unwrap_or(0.0);
|
||||
let border_width = metrics
|
||||
.as_ref()
|
||||
.map(|metrics| f64::from(metrics.offset_size.width))
|
||||
.unwrap_or(0.0);
|
||||
let border_height = metrics
|
||||
.as_ref()
|
||||
.map(|metrics| f64::from(metrics.offset_size.height))
|
||||
.unwrap_or(0.0);
|
||||
let rect = (0.0, 0.0, content_width, content_height);
|
||||
let content_box_size = (content_width, content_height);
|
||||
let border_box_size = (border_width, border_height);
|
||||
.unwrap_or((moli_layout::LayoutResizeObserverMetrics::default(), 1.0));
|
||||
let rect = (
|
||||
0.0,
|
||||
0.0,
|
||||
f64::from(metrics.content_size.width),
|
||||
f64::from(metrics.content_size.height),
|
||||
);
|
||||
let content_box_size = (
|
||||
f64::from(metrics.content_box_size.inline_size),
|
||||
f64::from(metrics.content_box_size.block_size),
|
||||
);
|
||||
let border_box_size = (
|
||||
f64::from(metrics.border_box_size.inline_size),
|
||||
f64::from(metrics.border_box_size.block_size),
|
||||
);
|
||||
// Preserve the existing device-pixel approximation, now on the same
|
||||
// logical axes. Effective zoom and positional snapping are separate.
|
||||
let device_pixel_content_box_size = (
|
||||
(content_width * f64::from(dpr)).round(),
|
||||
(content_height * f64::from(dpr)).round(),
|
||||
(content_box_size.0 * f64::from(dpr)).round(),
|
||||
(content_box_size.1 * f64::from(dpr)).round(),
|
||||
);
|
||||
let observed_size = match entry
|
||||
.record
|
||||
.and_then(|record| observed_record_box(scope, record))
|
||||
{
|
||||
Some(ResizeObserverBoxOptions::BorderBox) => (border_width, border_height),
|
||||
Some(ResizeObserverBoxOptions::DevicePixelContentBox) => (
|
||||
(content_width * f64::from(dpr)).round(),
|
||||
(content_height * f64::from(dpr)).round(),
|
||||
),
|
||||
Some(ResizeObserverBoxOptions::ContentBox) | None => (content_width, content_height),
|
||||
Some(ResizeObserverBoxOptions::BorderBox) => border_box_size,
|
||||
Some(ResizeObserverBoxOptions::DevicePixelContentBox) => device_pixel_content_box_size,
|
||||
Some(ResizeObserverBoxOptions::ContentBox) | None => content_box_size,
|
||||
};
|
||||
if compare_reported_sizes && let Some(record) = entry.record {
|
||||
let previous = resize_observer_last_reported_size(scope, record);
|
||||
|
||||
@@ -4,7 +4,8 @@ use moli_layout::{
|
||||
LayoutAnswers, LayoutBoxModel, LayoutCaretPosition, LayoutDocumentMetrics,
|
||||
LayoutElementMetrics, LayoutFlushReason, LayoutHit, LayoutIntersectionGeometry,
|
||||
LayoutPassMetrics, LayoutPoint, LayoutQuery, LayoutQueryAnswer, LayoutQueryBatch,
|
||||
LayoutScrollContainerMetrics, LayoutScrollIntoViewGeometry, LayoutSize,
|
||||
LayoutResizeObserverMetrics, LayoutResizeObserverSize, LayoutScrollContainerMetrics,
|
||||
LayoutScrollIntoViewGeometry, LayoutSize,
|
||||
};
|
||||
|
||||
use super::super::client_rect::{quad_from_client_rect, zero_client_rect};
|
||||
@@ -107,6 +108,22 @@ pub(crate) fn answer_query(
|
||||
LayoutQuery::ElementMetrics { source } => {
|
||||
LayoutQueryAnswer::ElementMetrics(mock_element_metrics(runtime, *source))
|
||||
}
|
||||
LayoutQuery::ResizeObserverMetrics { source } => {
|
||||
// Keep Mock's existing approximate physical-as-logical contract.
|
||||
LayoutQueryAnswer::ResizeObserverMetrics(mock_element_metrics(runtime, *source).map(
|
||||
|metrics| LayoutResizeObserverMetrics {
|
||||
content_size: metrics.content_size,
|
||||
content_box_size: LayoutResizeObserverSize {
|
||||
inline_size: metrics.content_size.width,
|
||||
block_size: metrics.content_size.height,
|
||||
},
|
||||
border_box_size: LayoutResizeObserverSize {
|
||||
inline_size: metrics.offset_size.width,
|
||||
block_size: metrics.offset_size.height,
|
||||
},
|
||||
},
|
||||
))
|
||||
}
|
||||
LayoutQuery::UsedGridTracks { .. } => LayoutQueryAnswer::UsedGridTracks(None),
|
||||
LayoutQuery::ScrollIntoViewGeometry { source } => {
|
||||
LayoutQueryAnswer::ScrollIntoViewGeometry(mock_scroll_into_view_geometry(
|
||||
|
||||
+1
@@ -2,6 +2,7 @@ use super::realtime_opportunity::run_opportunity;
|
||||
use super::*;
|
||||
use moli_page_types::LayoutPolicy;
|
||||
|
||||
mod geometry;
|
||||
mod lifecycle;
|
||||
mod ownership;
|
||||
mod rewrites;
|
||||
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
use super::*;
|
||||
|
||||
fn observe_axis_boxes(page: &mut PageVm, size: &str) {
|
||||
page.vm_mut().eval(&format!(r#"
|
||||
document.body.innerHTML='<div id=target style="display:block;{size};padding:0;border:2px solid;box-sizing:content-box;writing-mode:vertical-lr"></div>';
|
||||
window.samples={{content:[],border:[]}};
|
||||
window.record=e=>[
|
||||
e.contentRect.width,e.contentRect.height,
|
||||
e.contentBoxSize[0].inlineSize,e.contentBoxSize[0].blockSize,
|
||||
e.borderBoxSize[0].inlineSize,e.borderBoxSize[0].blockSize
|
||||
];
|
||||
window.contentObserver=new ResizeObserver(entries=>samples.content.push(...entries.map(record)));
|
||||
window.borderObserver=new ResizeObserver(entries=>samples.border.push(...entries.map(record)));
|
||||
contentObserver.observe(target);
|
||||
borderObserver.observe(target,{{box:'border-box'}});
|
||||
"#)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn realtime_ro_physical_dimensions_notify_when_only_logical_axes_change() {
|
||||
run_page_vm_large_stack_async_test("realtime-ro-physical-axis-change", || async move {
|
||||
let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default()).unwrap();
|
||||
let mut page = realtime_page(&loader);
|
||||
observe_axis_boxes(&mut page, "width:50px;height:100px");
|
||||
run_opportunity(&mut page, &loader).await.unwrap();
|
||||
assert_eq!(
|
||||
page.vm_mut().eval("JSON.stringify([samples.content.map(x=>x.slice(0,2)),samples.border.map(x=>x.slice(0,2))])").unwrap(),
|
||||
"[[[50,100]],[[50,100]]]"
|
||||
);
|
||||
|
||||
page.vm_mut()
|
||||
.eval("target.style.writingMode='horizontal-tb'")
|
||||
.unwrap();
|
||||
run_opportunity(&mut page, &loader).await.unwrap();
|
||||
assert_eq!(
|
||||
page.vm_mut()
|
||||
.eval("JSON.stringify([samples.content.length,samples.border.length])")
|
||||
.unwrap(),
|
||||
"[2,2]",
|
||||
"changing only logical axes is a size change for both observed boxes"
|
||||
);
|
||||
let expected = r#"{"content":[[50,100,100,50,104,54],[50,100,50,100,54,104]],"border":[[50,100,100,50,104,54],[50,100,50,100,54,104]]}"#;
|
||||
assert_eq!(
|
||||
page.vm_mut().eval("JSON.stringify(samples)").unwrap(),
|
||||
expected
|
||||
);
|
||||
let builds = page.vm().layout_pass_observability_for_test().1;
|
||||
page.vm_mut().eval("requestAnimationFrame(()=>{})").unwrap();
|
||||
run_opportunity(&mut page, &loader).await.unwrap();
|
||||
assert_eq!(
|
||||
page.vm_mut().eval("JSON.stringify(samples)").unwrap(),
|
||||
expected
|
||||
);
|
||||
assert_eq!(page.vm().layout_pass_observability_for_test().1, builds);
|
||||
assert_eq!(
|
||||
page.vm()
|
||||
.layout_pass_observability_for_test()
|
||||
.3
|
||||
.unwrap()
|
||||
.paint_operation_count,
|
||||
0
|
||||
);
|
||||
assert!(
|
||||
page.next_rendering_opportunity_deadline_for_test()
|
||||
.is_none()
|
||||
);
|
||||
assert!(page.vm().next_timeout_deadline().is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn realtime_ro_logical_dimensions_do_not_notify_when_only_physical_axes_change() {
|
||||
run_page_vm_large_stack_async_test("realtime-ro-logical-axis-change", || async move {
|
||||
let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default()).unwrap();
|
||||
let mut page = realtime_page(&loader);
|
||||
observe_axis_boxes(&mut page, "inline-size:100px;block-size:50px");
|
||||
run_opportunity(&mut page, &loader).await.unwrap();
|
||||
let expected = r#"{"content":[[50,100,100,50,104,54]],"border":[[50,100,100,50,104,54]]}"#;
|
||||
assert_eq!(
|
||||
page.vm_mut().eval("JSON.stringify([samples.content.map(x=>x.slice(0,2)),samples.border.map(x=>x.slice(0,2))])").unwrap(),
|
||||
"[[[50,100]],[[50,100]]]",
|
||||
"the ordinary backend must first resolve these logical CSS dimensions"
|
||||
);
|
||||
|
||||
page.vm_mut()
|
||||
.eval("target.style.writingMode='horizontal-tb'")
|
||||
.unwrap();
|
||||
run_opportunity(&mut page, &loader).await.unwrap();
|
||||
assert_eq!(
|
||||
page.vm_mut()
|
||||
.eval("JSON.stringify([samples.content.length,samples.border.length])")
|
||||
.unwrap(),
|
||||
"[1,1]",
|
||||
"unchanged logical sizes must not produce a second notification"
|
||||
);
|
||||
assert_eq!(
|
||||
page.vm_mut().eval("JSON.stringify(samples)").unwrap(),
|
||||
expected
|
||||
);
|
||||
let builds = page.vm().layout_pass_observability_for_test().1;
|
||||
// The observer decision above must use the frame's already-current
|
||||
// layout. This getter proves the physical box swapped without fixing it.
|
||||
assert_eq!(
|
||||
page.vm_mut()
|
||||
.eval("JSON.stringify([target.offsetWidth,target.offsetHeight])")
|
||||
.unwrap(),
|
||||
"[104,54]"
|
||||
);
|
||||
assert_eq!(page.vm().layout_pass_observability_for_test().1, builds);
|
||||
page.vm_mut().eval("requestAnimationFrame(()=>{})").unwrap();
|
||||
run_opportunity(&mut page, &loader).await.unwrap();
|
||||
assert_eq!(
|
||||
page.vm_mut().eval("JSON.stringify(samples)").unwrap(),
|
||||
expected
|
||||
);
|
||||
assert_eq!(page.vm().layout_pass_observability_for_test().1, builds);
|
||||
assert_eq!(
|
||||
page.vm()
|
||||
.layout_pass_observability_for_test()
|
||||
.3
|
||||
.unwrap()
|
||||
.paint_operation_count,
|
||||
0
|
||||
);
|
||||
assert!(
|
||||
page.next_rendering_opportunity_deadline_for_test()
|
||||
.is_none()
|
||||
);
|
||||
assert!(page.vm().next_timeout_deadline().is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn realtime_ro_uses_the_axes_frozen_with_each_published_sample() {
|
||||
run_page_vm_large_stack_async_test("realtime-ro-published-axes", || async move {
|
||||
for force in [false, true] {
|
||||
let loader =
|
||||
crate::network::ResourceRequestClient::new(&FetchConfig::default()).unwrap();
|
||||
let mut page = realtime_page(&loader);
|
||||
page.vm_mut().eval(&format!(r#"
|
||||
document.body.innerHTML='<div id=a style="width:100px;height:20px;padding:0;border:0"></div><div id=b style="width:50px;height:100px;padding:0;border:0;writing-mode:vertical-lr"></div>';
|
||||
window.order=[];window.errors=[];
|
||||
window.onerror=message=>{{errors.push(message);return true}};
|
||||
window.first=new ResizeObserver(()=>{{order.push('first');b.style.writingMode='horizontal-tb';if({force})void b.offsetWidth}});
|
||||
window.second=new ResizeObserver(entries=>order.push(entries.map(e=>[
|
||||
e.contentRect.width,e.contentRect.height,
|
||||
e.contentBoxSize[0].inlineSize,e.contentBoxSize[0].blockSize,
|
||||
e.borderBoxSize[0].inlineSize,e.borderBoxSize[0].blockSize
|
||||
])));
|
||||
first.observe(a);second.observe(b);
|
||||
"#)).unwrap();
|
||||
run_opportunity(&mut page, &loader).await.unwrap();
|
||||
assert_eq!(
|
||||
page.vm_mut()
|
||||
.eval("JSON.stringify([order,errors])")
|
||||
.unwrap(),
|
||||
if force {
|
||||
r#"[["first",[[50,100,50,100,50,100]]],[]]"#
|
||||
} else {
|
||||
r#"[["first",[[50,100,100,50,100,50]]],["ResizeObserver loop completed with undelivered notifications."]]"#
|
||||
}
|
||||
);
|
||||
if !force {
|
||||
run_opportunity(&mut page, &loader).await.unwrap();
|
||||
assert_eq!(
|
||||
page.vm_mut().eval("JSON.stringify(order)").unwrap(),
|
||||
r#"["first",[[50,100,100,50,100,50]],[[50,100,50,100,50,100]]]"#
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
page.next_rendering_opportunity_deadline_for_test()
|
||||
.is_none()
|
||||
);
|
||||
assert_eq!(
|
||||
page.vm()
|
||||
.layout_pass_observability_for_test()
|
||||
.3
|
||||
.unwrap()
|
||||
.paint_operation_count,
|
||||
0
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -176,3 +176,87 @@ async fn resize_observer_entries_preserve_webidl_snapshots_and_callback_realms()
|
||||
assert_eq!(result["failures"], serde_json::json!([]), "{name}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ondemand_ro_uses_published_css_box_axes_and_unzoomed_dimensions() {
|
||||
let mut vm = new_storage_html_test_vm("https://resize-published-axis.test/");
|
||||
vm.set_layout_policy(moli_page_types::LayoutPolicy::OnDemand);
|
||||
vm.eval(r#"
|
||||
document.body.innerHTML='<div id=target style="display:block;width:50px;height:100px;padding:0;border:2px solid;box-sizing:content-box;writing-mode:vertical-lr;zoom:2"></div>';
|
||||
window.samples=[];
|
||||
"#).unwrap();
|
||||
vm.publish_layout().unwrap();
|
||||
let builds = vm.layout_pass_observability_for_test().1;
|
||||
vm.eval(
|
||||
r#"
|
||||
window.observer=new ResizeObserver(entries=>samples.push(...entries.map(e=>[
|
||||
e.contentRect.width,e.contentRect.height,
|
||||
e.contentBoxSize[0].inlineSize,e.contentBoxSize[0].blockSize,
|
||||
e.borderBoxSize[0].inlineSize,e.borderBoxSize[0].blockSize
|
||||
])));
|
||||
observer.observe(target);
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vm.eval("JSON.stringify(samples)").unwrap(),
|
||||
"[[50,100,100,50,104,54]]"
|
||||
);
|
||||
assert_eq!(vm.layout_pass_observability_for_test().1, builds);
|
||||
|
||||
vm.eval("target.style.writingMode='horizontal-tb'").unwrap();
|
||||
assert_eq!(
|
||||
vm.eval("JSON.stringify(samples)").unwrap(),
|
||||
"[[50,100,100,50,104,54]]",
|
||||
"OnDemand must continue sampling the axes frozen with its previous layout"
|
||||
);
|
||||
assert_eq!(vm.layout_pass_observability_for_test().1, builds);
|
||||
vm.publish_layout().unwrap();
|
||||
let builds = vm.layout_pass_observability_for_test().1;
|
||||
// Publishing a snapshot is not itself a legacy RO scheduling operation.
|
||||
// This mutation schedules its existing check without requesting layout.
|
||||
vm.eval("target.setAttribute('data-resample','yes')")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vm.eval("JSON.stringify(samples)").unwrap(),
|
||||
"[[50,100,100,50,104,54],[50,100,50,100,54,104]]"
|
||||
);
|
||||
assert_eq!(vm.layout_pass_observability_for_test().1, builds);
|
||||
assert_eq!(
|
||||
vm.layout_pass_observability_for_test()
|
||||
.3
|
||||
.unwrap()
|
||||
.paint_operation_count,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_ro_css_box_projection_excludes_non_replaced_inline_fragments() {
|
||||
let mut vm = new_storage_html_test_vm("https://resize-css-box-kind.test/");
|
||||
vm.set_layout_policy(moli_page_types::LayoutPolicy::OnDemand);
|
||||
vm.eval(r#"
|
||||
document.body.innerHTML='<span id=inline style="padding:0;border:2px solid">text</span><span id=atomic style="display:inline-block;width:50px;height:100px;padding:0;border:2px solid;box-sizing:content-box;writing-mode:vertical-lr"></span>';
|
||||
window.samples=[];
|
||||
"#).unwrap();
|
||||
vm.publish_layout().unwrap();
|
||||
let builds = vm.layout_pass_observability_for_test().1;
|
||||
vm.eval(
|
||||
r#"
|
||||
window.observer=new ResizeObserver(entries=>samples.push(...entries.map(e=>[
|
||||
e.target.id,e.contentRect.width,e.contentRect.height,
|
||||
e.contentBoxSize[0].inlineSize,e.contentBoxSize[0].blockSize,
|
||||
e.borderBoxSize[0].inlineSize,e.borderBoxSize[0].blockSize
|
||||
])));
|
||||
observer.observe(document.getElementById('inline'));
|
||||
observer.observe(document.getElementById('atomic'));
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vm.eval("JSON.stringify(samples)").unwrap(),
|
||||
r#"[["inline",0,0,0,0,0,0],["atomic",50,100,100,50,104,54]]"#,
|
||||
"a non-replaced inline's CSSOM fragment union is not an RO CSS box"
|
||||
);
|
||||
assert_eq!(vm.layout_pass_observability_for_test().1, builds);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user