mirror of
https://github.com/lexmount/moli.git
synced 2026-09-25 08:01:28 +00:00
feat(layout): expose resolved Grid tracks
This commit is contained in:
Generated
+1
-1
@@ -4823,7 +4823,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "taffy"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/ldm0/taffy?rev=b7735b0aba77dd0e98015f3cd1dd26a71f90a415#b7735b0aba77dd0e98015f3cd1dd26a71f90a415"
|
||||
source = "git+https://github.com/ldm0/taffy?rev=847d1ce20c03b0378eafa530fa43f1e92d9292ea#847d1ce20c03b0378eafa530fa43f1e92d9292ea"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"serde",
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ stylo_atoms = { git = "https://github.com/ldm0/stylo", rev = "32c3de3f2aab3340c5
|
||||
stylo_dom = { git = "https://github.com/ldm0/stylo", rev = "32c3de3f2aab3340c51f03f2a0878d1e4eed7fd6" }
|
||||
stylo_static_prefs = { git = "https://github.com/ldm0/stylo", rev = "32c3de3f2aab3340c51f03f2a0878d1e4eed7fd6" }
|
||||
stylo_traits = { git = "https://github.com/ldm0/stylo", rev = "32c3de3f2aab3340c51f03f2a0878d1e4eed7fd6" }
|
||||
taffy = { git = "https://github.com/ldm0/taffy", rev = "b7735b0aba77dd0e98015f3cd1dd26a71f90a415" }
|
||||
taffy = { git = "https://github.com/ldm0/taffy", rev = "847d1ce20c03b0378eafa530fa43f1e92d9292ea" }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
disallowed_methods = "deny"
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
//! Browser-owned resolved Grid tracks projected from the numeric backend.
|
||||
|
||||
use style::Atom;
|
||||
|
||||
use crate::{
|
||||
LAYOUT_SUBPIXELS_PER_CSS_PIXEL, LayoutResolvedGridTrackList, LayoutResolvedGridTracks,
|
||||
};
|
||||
|
||||
pub(crate) fn project_resolved_grid_tracks(
|
||||
style: &taffy::Style<Atom>,
|
||||
detailed: taffy::DetailedGridInfo,
|
||||
) -> Option<LayoutResolvedGridTracks> {
|
||||
let row_line_names = expanded_line_names(
|
||||
&style.grid_template_rows,
|
||||
&style.grid_template_row_names,
|
||||
usize::from(detailed.rows.explicit_tracks),
|
||||
usize::from(detailed.rows.auto_repetitions),
|
||||
)?;
|
||||
let column_line_names = expanded_line_names(
|
||||
&style.grid_template_columns,
|
||||
&style.grid_template_column_names,
|
||||
usize::from(detailed.columns.explicit_tracks),
|
||||
usize::from(detailed.columns.auto_repetitions),
|
||||
)?;
|
||||
Some(LayoutResolvedGridTracks {
|
||||
rows: project_tracks(detailed.rows, row_line_names)?,
|
||||
columns: project_tracks(detailed.columns, column_line_names)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn project_tracks(
|
||||
tracks: taffy::DetailedGridTracksInfo,
|
||||
explicit_line_names: Vec<Vec<Atom>>,
|
||||
) -> Option<LayoutResolvedGridTrackList> {
|
||||
let track_count = usize::from(tracks.negative_implicit_tracks)
|
||||
.checked_add(usize::from(tracks.explicit_tracks))?
|
||||
.checked_add(usize::from(tracks.positive_implicit_tracks))?;
|
||||
if tracks.sizes.len() != track_count
|
||||
|| tracks.gutters.len() != track_count.saturating_add(1)
|
||||
|| explicit_line_names.len() != usize::from(tracks.explicit_tracks).saturating_add(1)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let used_track_sizes = tracks
|
||||
.sizes
|
||||
.into_iter()
|
||||
.map(to_blink_layout_unit)
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
Some(LayoutResolvedGridTrackList {
|
||||
negative_implicit_track_count: usize::from(tracks.negative_implicit_tracks),
|
||||
explicit_track_count: usize::from(tracks.explicit_tracks),
|
||||
positive_implicit_track_count: usize::from(tracks.positive_implicit_tracks),
|
||||
used_track_sizes,
|
||||
explicit_line_names,
|
||||
})
|
||||
}
|
||||
|
||||
/// Blink performs Grid sizing in a 26.6 fixed-point `LayoutUnit`. Taffy uses
|
||||
/// floats, so truncate finite non-negative used track sizes at this ownership
|
||||
/// boundary before they become observable through CSSOM.
|
||||
fn to_blink_layout_unit(value: f32) -> Option<f32> {
|
||||
if !value.is_finite() || value < 0.0 {
|
||||
return None;
|
||||
}
|
||||
let raw = (f64::from(value) * f64::from(LAYOUT_SUBPIXELS_PER_CSS_PIXEL))
|
||||
.trunc()
|
||||
.min(f64::from(i32::MAX));
|
||||
Some(raw as f32 / LAYOUT_SUBPIXELS_PER_CSS_PIXEL)
|
||||
}
|
||||
|
||||
fn expanded_line_names(
|
||||
template: &[taffy::GridTemplateComponent<Atom>],
|
||||
template_line_names: &[Vec<Atom>],
|
||||
explicit_track_count: usize,
|
||||
auto_repetitions: usize,
|
||||
) -> Option<Vec<Vec<Atom>>> {
|
||||
if template.is_empty() {
|
||||
return template_line_names
|
||||
.is_empty()
|
||||
.then(|| vec![Vec::new(); explicit_track_count.saturating_add(1)]);
|
||||
}
|
||||
if template_line_names.len() != template.len().saturating_add(1) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let append_names = |line: &mut Vec<Atom>, names: &[Atom]| {
|
||||
line.extend(names.iter().cloned());
|
||||
};
|
||||
let mut lines = vec![Vec::new()];
|
||||
let mut expanded_track_count = 0usize;
|
||||
let mut saw_auto_repeat = false;
|
||||
for (index, component) in template.iter().enumerate() {
|
||||
append_names(lines.last_mut()?, template_line_names.get(index)?);
|
||||
match component {
|
||||
taffy::GridTemplateComponent::Single(_) => {
|
||||
expanded_track_count = expanded_track_count.checked_add(1)?;
|
||||
lines.push(Vec::new());
|
||||
}
|
||||
taffy::GridTemplateComponent::Repeat(repeat) => {
|
||||
if repeat.tracks.is_empty()
|
||||
|| repeat.line_names.len() != repeat.tracks.len().saturating_add(1)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let repeat_count = match repeat.count {
|
||||
taffy::RepetitionCount::Count(count) => usize::from(count),
|
||||
taffy::RepetitionCount::AutoFill | taffy::RepetitionCount::AutoFit => {
|
||||
if saw_auto_repeat {
|
||||
return None;
|
||||
}
|
||||
saw_auto_repeat = true;
|
||||
auto_repetitions
|
||||
}
|
||||
};
|
||||
for _ in 0..repeat_count {
|
||||
append_names(lines.last_mut()?, repeat.line_names.first()?);
|
||||
for track_index in 0..repeat.tracks.len() {
|
||||
expanded_track_count = expanded_track_count.checked_add(1)?;
|
||||
lines.push(Vec::new());
|
||||
append_names(lines.last_mut()?, repeat.line_names.get(track_index + 1)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !saw_auto_repeat && auto_repetitions != 0 {
|
||||
return None;
|
||||
}
|
||||
append_names(lines.last_mut()?, template_line_names.get(template.len())?);
|
||||
if expanded_track_count > explicit_track_count {
|
||||
return None;
|
||||
}
|
||||
|
||||
// `grid-template-areas` can extend the explicit grid beyond the authored
|
||||
// track list. Those extra tracks have no authored line names; generated
|
||||
// area names do not serialize into the resolved track listing.
|
||||
lines.resize_with(explicit_track_count.saturating_add(1), Vec::new);
|
||||
Some(lines)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use taffy::style_helpers::TaffyAuto;
|
||||
|
||||
fn names(values: &[&str]) -> Vec<Atom> {
|
||||
values.iter().map(|value| Atom::from(*value)).collect()
|
||||
}
|
||||
|
||||
fn track() -> taffy::TrackSizingFunction {
|
||||
taffy::TrackSizingFunction::AUTO
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixed_repeat_merges_names_at_each_expanded_boundary() {
|
||||
let template = vec![
|
||||
taffy::GridTemplateComponent::Single(track()),
|
||||
taffy::GridTemplateComponent::Repeat(taffy::GridTemplateRepetition {
|
||||
count: taffy::RepetitionCount::Count(2),
|
||||
tracks: vec![track(), track()],
|
||||
line_names: vec![names(&["c"]), names(&["d"]), names(&["e"])],
|
||||
}),
|
||||
taffy::GridTemplateComponent::Single(track()),
|
||||
];
|
||||
let line_names = vec![names(&["a"]), names(&["b"]), names(&["f"]), names(&["g"])];
|
||||
|
||||
assert_eq!(
|
||||
expanded_line_names(&template, &line_names, 6, 0),
|
||||
Some(vec![
|
||||
vec!["a".into()],
|
||||
vec!["b".into(), "c".into()],
|
||||
vec!["d".into()],
|
||||
vec!["e".into(), "c".into()],
|
||||
vec!["d".into()],
|
||||
vec!["e".into(), "f".into()],
|
||||
vec!["g".into()],
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_repeat_uses_the_layout_result_count() {
|
||||
let template = vec![
|
||||
taffy::GridTemplateComponent::Single(track()),
|
||||
taffy::GridTemplateComponent::Repeat(taffy::GridTemplateRepetition {
|
||||
count: taffy::RepetitionCount::AutoFill,
|
||||
tracks: vec![track(), track()],
|
||||
line_names: vec![names(&["c"]), names(&["d"]), names(&["e"])],
|
||||
}),
|
||||
taffy::GridTemplateComponent::Single(track()),
|
||||
];
|
||||
let line_names = vec![names(&["a"]), names(&["b"]), names(&["f"]), names(&["g"])];
|
||||
let expanded = expanded_line_names(&template, &line_names, 12, 5).expect("valid expansion");
|
||||
|
||||
assert_eq!(expanded.len(), 13);
|
||||
assert_eq!(expanded[1], names(&["b", "c"]));
|
||||
assert_eq!(expanded[3], names(&["e", "c"]));
|
||||
assert_eq!(expanded[11], names(&["e", "f"]));
|
||||
assert_eq!(expanded[12], names(&["g"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn area_expanded_explicit_grid_does_not_change_auto_repeat_count() {
|
||||
let template = vec![taffy::GridTemplateComponent::Repeat(
|
||||
taffy::GridTemplateRepetition {
|
||||
count: taffy::RepetitionCount::AutoFill,
|
||||
tracks: vec![track()],
|
||||
line_names: vec![names(&["a"]), names(&["b"])],
|
||||
},
|
||||
)];
|
||||
let line_names = vec![Vec::new(), Vec::new()];
|
||||
|
||||
let expanded = expanded_line_names(&template, &line_names, 8, 5)
|
||||
.expect("grid-template-areas may extend the explicit grid after auto-repeat");
|
||||
|
||||
assert_eq!(expanded.len(), 9);
|
||||
assert_eq!(expanded[0], names(&["a"]));
|
||||
assert_eq!(expanded[4], names(&["b", "a"]));
|
||||
assert_eq!(expanded[5], names(&["b"]));
|
||||
assert!(expanded[6..].iter().all(Vec::is_empty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn used_track_sizes_are_truncated_to_blink_layout_units() {
|
||||
assert_eq!(to_blink_layout_unit(100.0 / 3.0), Some(33.328_125));
|
||||
assert_eq!(to_blink_layout_unit(0.015_625), Some(0.015_625));
|
||||
assert_eq!(to_blink_layout_unit(-1.0), None);
|
||||
assert_eq!(to_blink_layout_unit(f32::NAN), None);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use style::Atom;
|
||||
|
||||
use crate::LayoutPosition;
|
||||
|
||||
/// Viewport inputs shared by layout, geometry queries, and paint projection.
|
||||
@@ -55,6 +57,38 @@ impl LayoutSize {
|
||||
}
|
||||
}
|
||||
|
||||
/// Frozen resolved track list for one axis of a CSS Grid formatting context.
|
||||
///
|
||||
/// Track sizes are retained in layout CSS pixels at Blink-compatible 1/64-pixel
|
||||
/// precision. The authored line-name snapshot is kept in the same layout epoch
|
||||
/// so a CSSOM read cannot combine old used sizes with names from a newer style
|
||||
/// mutation.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct LayoutResolvedGridTrackList {
|
||||
pub negative_implicit_track_count: usize,
|
||||
pub explicit_track_count: usize,
|
||||
pub positive_implicit_track_count: usize,
|
||||
pub used_track_sizes: Vec<f32>,
|
||||
/// Expanded names for each explicit grid line. The length is the explicit
|
||||
/// track count plus one; names at repeat boundaries are already merged.
|
||||
pub explicit_line_names: Vec<Vec<Atom>>,
|
||||
}
|
||||
|
||||
impl LayoutResolvedGridTrackList {
|
||||
pub fn track_count(&self) -> usize {
|
||||
self.negative_implicit_track_count
|
||||
.saturating_add(self.explicit_track_count)
|
||||
.saturating_add(self.positive_implicit_track_count)
|
||||
}
|
||||
}
|
||||
|
||||
/// Frozen resolved row and column track lists for a CSS Grid container.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct LayoutResolvedGridTracks {
|
||||
pub rows: LayoutResolvedGridTrackList,
|
||||
pub columns: LayoutResolvedGridTrackList,
|
||||
}
|
||||
|
||||
/// An axis-aligned rectangle in one explicit layout coordinate space.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct LayoutRect {
|
||||
|
||||
@@ -7,8 +7,8 @@ use crate::LayoutError;
|
||||
use super::{
|
||||
hit_test::{LayoutCaretPosition, LayoutHit},
|
||||
model::{
|
||||
LayoutBoxModel, LayoutFragmentId, LayoutOutputBoxId, LayoutPoint, LayoutQuad, LayoutSize,
|
||||
LayoutViewport,
|
||||
LayoutBoxModel, LayoutFragmentId, LayoutOutputBoxId, LayoutPoint, LayoutQuad,
|
||||
LayoutResolvedGridTracks, LayoutSize, LayoutViewport,
|
||||
},
|
||||
pass_result::{LayoutFlushReason, LayoutPassMetrics},
|
||||
tree::FrozenLayoutTree,
|
||||
@@ -113,6 +113,10 @@ pub enum LayoutQuery<N> {
|
||||
ElementMetrics {
|
||||
source: N,
|
||||
},
|
||||
/// Used Grid row and column tracks for a principal Grid container.
|
||||
UsedGridTracks {
|
||||
source: N,
|
||||
},
|
||||
ScrollIntoViewGeometry {
|
||||
source: N,
|
||||
},
|
||||
@@ -162,6 +166,7 @@ pub enum LayoutQueryAnswer<N> {
|
||||
ContentQuads(Vec<LayoutQuad>),
|
||||
TextRangeRects(Vec<LayoutQuad>),
|
||||
ElementMetrics(Option<LayoutElementMetrics<N>>),
|
||||
UsedGridTracks(Option<LayoutResolvedGridTracks>),
|
||||
ScrollIntoViewGeometry(Option<LayoutScrollIntoViewGeometry<N>>),
|
||||
IntersectionGeometry(Option<LayoutIntersectionGeometry>),
|
||||
HitTest(Option<LayoutHit<N>>),
|
||||
@@ -232,6 +237,9 @@ where
|
||||
LayoutQuery::ElementMetrics { source } => {
|
||||
LayoutQueryAnswer::ElementMetrics(self.element_metrics_for_source(*source))
|
||||
}
|
||||
LayoutQuery::UsedGridTracks { source } => {
|
||||
LayoutQueryAnswer::UsedGridTracks(self.used_grid_tracks_for_source(*source))
|
||||
}
|
||||
LayoutQuery::ScrollIntoViewGeometry { source } => {
|
||||
LayoutQueryAnswer::ScrollIntoViewGeometry(
|
||||
self.scroll_into_view_geometry_for_source(*source),
|
||||
|
||||
@@ -7,7 +7,8 @@ use crate::LayoutPosition;
|
||||
use super::{
|
||||
model::{
|
||||
LayoutBoxModel, LayoutCoordinateSpaceId, LayoutFragmentBoxModel, LayoutFragmentKind,
|
||||
LayoutOutputBoxId, LayoutPoint, LayoutQuad, LayoutRect, LayoutSize, LayoutTransform2D,
|
||||
LayoutOutputBoxId, LayoutPoint, LayoutQuad, LayoutRect, LayoutResolvedGridTracks,
|
||||
LayoutSize, LayoutTransform2D,
|
||||
},
|
||||
query::{LayoutElementMetrics, LayoutNodeOutput},
|
||||
tree::FrozenLayoutTree,
|
||||
@@ -207,6 +208,21 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns used Grid tracks from the same frozen epoch as other CSSOM
|
||||
/// geometry, normalized out of the container's effective CSS zoom.
|
||||
pub fn used_grid_tracks_for_source(&self, source: N) -> Option<LayoutResolvedGridTracks> {
|
||||
let output = self.source_output(source)?;
|
||||
let layout_box = self.boxes.get(output.principal_box?.index())?;
|
||||
let mut grid = layout_box.resolved_grid_tracks.clone()?;
|
||||
let unzoom = CssomAbsoluteZoom::new(layout_box.geometry.effective_zoom);
|
||||
for tracks in [&mut grid.rows, &mut grid.columns] {
|
||||
for size in &mut tracks.used_track_sizes {
|
||||
*size = unzoom.scalar(*size);
|
||||
}
|
||||
}
|
||||
Some(grid)
|
||||
}
|
||||
|
||||
/// Resolves a viewport point into the coordinate system Blink uses for
|
||||
/// `MouseEvent.offsetX/Y`: a box target's padding edge, or the shared IFC
|
||||
/// coordinate space for a flattened inline layout object.
|
||||
@@ -475,6 +491,10 @@ impl CssomAbsoluteZoom {
|
||||
LayoutPoint::new(point.x / self.0, point.y / self.0)
|
||||
}
|
||||
|
||||
fn scalar(self, value: f32) -> f32 {
|
||||
value / self.0
|
||||
}
|
||||
|
||||
fn size(self, size: LayoutSize) -> LayoutSize {
|
||||
LayoutSize::new(size.width / self.0, size.height / self.0)
|
||||
}
|
||||
@@ -763,6 +783,7 @@ mod tests {
|
||||
geometry_source: Some(1),
|
||||
principal_source: Some(1),
|
||||
hit_source: Some(1),
|
||||
resolved_grid_tracks: None,
|
||||
control_paint_order: None,
|
||||
}],
|
||||
vec![
|
||||
|
||||
@@ -25,6 +25,7 @@ pub struct FrozenLayoutBox<N> {
|
||||
pub geometry_source: Option<N>,
|
||||
pub principal_source: Option<N>,
|
||||
pub hit_source: Option<N>,
|
||||
pub resolved_grid_tracks: Option<super::model::LayoutResolvedGridTracks>,
|
||||
/// Paint ordinal of this box's scrollbar/corner surface, if any.
|
||||
pub(crate) control_paint_order: Option<u32>,
|
||||
}
|
||||
@@ -122,9 +123,38 @@ where
|
||||
}
|
||||
|
||||
let box_allocations = self.boxes.iter().fold(0usize, |bytes, layout_box| {
|
||||
bytes.saturating_add(allocation::<LayoutFragmentId>(
|
||||
layout_box.fragments.capacity(),
|
||||
))
|
||||
let fragment_bytes = allocation::<LayoutFragmentId>(layout_box.fragments.capacity());
|
||||
let grid_bytes = layout_box.resolved_grid_tracks.as_ref().map_or(0, |grid| {
|
||||
let numeric_bytes = [
|
||||
grid.rows.used_track_sizes.capacity(),
|
||||
grid.columns.used_track_sizes.capacity(),
|
||||
]
|
||||
.into_iter()
|
||||
.fold(0usize, |bytes, capacity| {
|
||||
bytes.saturating_add(allocation::<f32>(capacity))
|
||||
});
|
||||
let line_name_bytes =
|
||||
[&grid.rows, &grid.columns]
|
||||
.into_iter()
|
||||
.fold(0usize, |bytes, tracks| {
|
||||
let outer =
|
||||
allocation::<Vec<String>>(tracks.explicit_line_names.capacity());
|
||||
tracks.explicit_line_names.iter().fold(
|
||||
bytes.saturating_add(outer),
|
||||
|bytes, names| {
|
||||
let atoms = allocation::<style::Atom>(names.capacity());
|
||||
let retained_text = names.iter().fold(0usize, |bytes, name| {
|
||||
bytes.saturating_add(name.len())
|
||||
});
|
||||
bytes.saturating_add(atoms).saturating_add(retained_text)
|
||||
},
|
||||
)
|
||||
});
|
||||
numeric_bytes.saturating_add(line_name_bytes)
|
||||
});
|
||||
bytes
|
||||
.saturating_add(fragment_bytes)
|
||||
.saturating_add(grid_bytes)
|
||||
});
|
||||
let own_estimated_geometry_bytes = std::mem::size_of::<Self>()
|
||||
.saturating_add(allocation::<FrozenLayoutBox<N>>(self.boxes.capacity()))
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
/// Number of fixed layout subpixels in one CSS pixel, matching Blink's
|
||||
/// `LayoutUnit` precision.
|
||||
pub const LAYOUT_SUBPIXELS_PER_CSS_PIXEL: f32 = 64.0;
|
||||
pub(crate) const LAYOUT_SUBPIXELS_PER_CSS_PIXEL: f32 = 64.0;
|
||||
|
||||
mod builder;
|
||||
mod capture;
|
||||
@@ -15,6 +15,7 @@ mod containment;
|
||||
mod error;
|
||||
mod form;
|
||||
mod gradient;
|
||||
mod grid;
|
||||
mod inline;
|
||||
mod intrinsic;
|
||||
mod layout_tree;
|
||||
@@ -53,10 +54,10 @@ pub use layout_tree::{
|
||||
LayoutFragmentBoxModel, LayoutFragmentId, LayoutFragmentKind, LayoutHit,
|
||||
LayoutIntersectionGeometry, LayoutNodeOutput, LayoutOutputBoxId, LayoutPaintedSurfaceHit,
|
||||
LayoutPassMetrics, LayoutPassResult, LayoutPoint, LayoutQuad, LayoutQuery, LayoutQueryAnswer,
|
||||
LayoutQueryBatch, LayoutRect, LayoutScrollContainerMetrics, LayoutScrollExtent,
|
||||
LayoutScrollIntoViewGeometry, LayoutSize, LayoutTransform2D, LayoutTreeRetentionMetrics,
|
||||
LayoutViewport, MAX_RETAINED_LAYOUT_BOXES, MAX_RETAINED_LAYOUT_FRAGMENTS,
|
||||
MAX_RETAINED_LAYOUT_TREE_BYTES,
|
||||
LayoutQueryBatch, LayoutRect, LayoutResolvedGridTrackList, LayoutResolvedGridTracks,
|
||||
LayoutScrollContainerMetrics, LayoutScrollExtent, LayoutScrollIntoViewGeometry, LayoutSize,
|
||||
LayoutTransform2D, LayoutTreeRetentionMetrics, LayoutViewport, MAX_RETAINED_LAYOUT_BOXES,
|
||||
MAX_RETAINED_LAYOUT_FRAGMENTS, MAX_RETAINED_LAYOUT_TREE_BYTES,
|
||||
};
|
||||
pub use normalize::{NormalizedBoxNode, NormalizedBoxTree, NormalizedFormattingContext};
|
||||
pub use normalize_source::{
|
||||
|
||||
@@ -928,6 +928,7 @@ where
|
||||
.next()
|
||||
.expect("a frozen layout tree always owns the viewport coordinate space"),
|
||||
);
|
||||
let world = self.world;
|
||||
let boxes = self
|
||||
.boxes
|
||||
.into_iter()
|
||||
@@ -947,14 +948,20 @@ where
|
||||
control_paint_order,
|
||||
),
|
||||
coordinate_space,
|
||||
)| FrozenLayoutBox {
|
||||
geometry,
|
||||
scroll_extent,
|
||||
coordinate_space,
|
||||
geometry_source,
|
||||
principal_source,
|
||||
hit_source,
|
||||
control_paint_order,
|
||||
)| {
|
||||
let resolved_grid_tracks = world.boxes[geometry.id.index()]
|
||||
.resolved_grid_tracks
|
||||
.clone();
|
||||
FrozenLayoutBox {
|
||||
geometry,
|
||||
scroll_extent,
|
||||
coordinate_space,
|
||||
geometry_source,
|
||||
principal_source,
|
||||
hit_source,
|
||||
resolved_grid_tracks,
|
||||
control_paint_order,
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
@@ -4,17 +4,18 @@ use parley::{AlignmentOptions, PositionedLayoutItem, YieldData};
|
||||
use style::Atom;
|
||||
use taffy::{
|
||||
AlignContent, AlignContentKeyword, AlignmentSafety, AvailableSpace, BlockContext,
|
||||
BlockFormattingContext, BoxSizing, CacheTree, Clear, Dimension, Display, FloatDirection,
|
||||
Layout, LayoutBlockContainer, LayoutFlexboxContainer, LayoutGridContainer, LayoutInput,
|
||||
LayoutOutput, LayoutPartialTree, LeafLayoutContext, Line, MaybeMath, MaybeResolve, NodeId,
|
||||
Point, ResolveOrZero, RoundTree, RunMode, Size, SizingMode, SizingPurpose, Style,
|
||||
BlockFormattingContext, BoxSizing, CacheTree, Clear, DetailedGridInfo, Dimension, Display,
|
||||
FloatDirection, Layout, LayoutBlockContainer, LayoutFlexboxContainer, LayoutGridContainer,
|
||||
LayoutInput, LayoutOutput, LayoutPartialTree, LeafLayoutContext, Line, MaybeMath, MaybeResolve,
|
||||
NodeId, Point, ResolveOrZero, RoundTree, RunMode, Size, SizingMode, SizingPurpose, Style,
|
||||
TraversePartialTree, TraverseTree, compute_block_layout, compute_cached_layout,
|
||||
compute_flexbox_layout, compute_grid_layout, compute_hidden_layout,
|
||||
compute_leaf_layout_with_context, compute_root_layout, round_layout,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
LayoutBoxId, LayoutBoxKind, LayoutCapabilityDiagnostic, LayoutWorld, PaintRect, PaintViewport,
|
||||
LAYOUT_SUBPIXELS_PER_CSS_PIXEL, LayoutBoxId, LayoutBoxKind, LayoutCapabilityDiagnostic,
|
||||
LayoutWorld, PaintRect, PaintViewport,
|
||||
inline::{
|
||||
InlineFormattingContext, InlineFragments, InlineLinePlacement, InlineObjectRole,
|
||||
break_inline_lines, build_inline_fragments, build_inline_line_placements,
|
||||
@@ -27,9 +28,6 @@ use crate::{
|
||||
world::InlineStaticPosition,
|
||||
};
|
||||
|
||||
// Blink stores box geometry in 1/64 CSS-pixel LayoutUnits.
|
||||
const LAYOUT_SUBPIXELS_PER_CSS_PIXEL: f32 = 64.0;
|
||||
|
||||
pub(crate) struct PreparedWorldLayout {
|
||||
positioned_static_placeholders: Vec<PositionedStaticPlaceholder>,
|
||||
numeric_unrounded_layouts: Vec<Layout>,
|
||||
@@ -133,6 +131,7 @@ where
|
||||
layout_box.layout_children.clear();
|
||||
layout_box.positioned_containing_block = None;
|
||||
layout_box.inline_static_position = None;
|
||||
layout_box.resolved_grid_tracks = None;
|
||||
}
|
||||
|
||||
world.viewport_layout.children.clear();
|
||||
@@ -1612,6 +1611,28 @@ where
|
||||
fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
|
||||
self.get_core_container_style(child_node_id)
|
||||
}
|
||||
|
||||
fn set_detailed_grid_info(&mut self, node_id: NodeId, detailed_grid_info: DetailedGridInfo) {
|
||||
let layout_box = &self.boxes[LayoutBoxId::from_taffy(node_id).index()];
|
||||
if layout_box
|
||||
.capability_diagnostics
|
||||
.contains(&LayoutCapabilityDiagnostic::GridTemplateModeDeferred)
|
||||
// The current backend reports detailed Grid tracks in physical
|
||||
// axes. Publishing them for a vertical container would expose its
|
||||
// width as the used `grid-template-columns` inline size. Keep the
|
||||
// computed value until logical Grid constraints land in Taffy.
|
||||
|| !layout_box.style.uses_horizontal_writing_mode()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(resolved_grid_tracks) =
|
||||
crate::grid::project_resolved_grid_tracks(&layout_box.style.taffy, detailed_grid_info)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
self.boxes[LayoutBoxId::from_taffy(node_id).index()].resolved_grid_tracks =
|
||||
Some(resolved_grid_tracks);
|
||||
}
|
||||
}
|
||||
|
||||
impl<N> RoundTree for LayoutWorld<N>
|
||||
|
||||
@@ -5,9 +5,9 @@ use taffy::{Cache, Layout, Point, Style};
|
||||
|
||||
use crate::{
|
||||
LayoutCssImageReference, LayoutElementSemantics, LayoutError, LayoutPoint, LayoutPseudo,
|
||||
LayoutScrollbarAxis, LayoutScrollbarColors, LayoutScrollbarGutter, LayoutScrollbarWidth,
|
||||
ResolvedLayoutStyle, inline::InlineFormattingContext, replaced::ReplacedContext,
|
||||
style::LayoutOverflowMode,
|
||||
LayoutResolvedGridTracks, LayoutScrollbarAxis, LayoutScrollbarColors, LayoutScrollbarGutter,
|
||||
LayoutScrollbarWidth, ResolvedLayoutStyle, inline::InlineFormattingContext,
|
||||
replaced::ReplacedContext, style::LayoutOverflowMode,
|
||||
};
|
||||
|
||||
/// Dense identifier scoped to exactly one [`LayoutWorld`].
|
||||
@@ -252,6 +252,11 @@ pub struct LayoutBox<N> {
|
||||
/// owner's conflict-resolution grid.
|
||||
pub(crate) collapsed_table_border_part: bool,
|
||||
pub(crate) inline_formatting_context: bool,
|
||||
/// Used Grid tracks produced by the numeric layout pass.
|
||||
///
|
||||
/// This is browser-owned canonical geometry rather than a retained Taffy
|
||||
/// object. It is frozen with the rest of the layout tree for CSSOM reads.
|
||||
pub(crate) resolved_grid_tracks: Option<LayoutResolvedGridTracks>,
|
||||
pub(crate) cache: Cache,
|
||||
pub(crate) unrounded_layout: Layout,
|
||||
pub(crate) final_layout: Layout,
|
||||
@@ -1092,6 +1097,7 @@ where
|
||||
collapsed_table_borders: None,
|
||||
collapsed_table_border_part: false,
|
||||
inline_formatting_context: false,
|
||||
resolved_grid_tracks: None,
|
||||
cache: Cache::new(),
|
||||
unrounded_layout: Layout::with_order(0),
|
||||
final_layout: Layout::with_order(0),
|
||||
|
||||
@@ -33,7 +33,7 @@ pub(crate) use provider::{
|
||||
observable_bounding_client_rect, observable_bounding_client_rects, observable_caret_position,
|
||||
observable_client_rects, observable_element_metrics, observable_event_offset,
|
||||
observable_geometry_batch, observable_hit_test_all, observable_scroll_adjusted_client_rect,
|
||||
observable_sources_with_fragments,
|
||||
observable_sources_with_fragments, observable_used_grid_tracks,
|
||||
};
|
||||
pub(in crate::native_bridge) use rects::{
|
||||
node_get_bounding_client_rect_callback, node_get_client_rects_callback,
|
||||
|
||||
@@ -69,6 +69,7 @@ pub(crate) fn answer_queries(
|
||||
LayoutQuery::ElementMetrics { source } => {
|
||||
LayoutQueryAnswer::ElementMetrics(mock_element_metrics(runtime, *source))
|
||||
}
|
||||
LayoutQuery::UsedGridTracks { .. } => LayoutQueryAnswer::UsedGridTracks(None),
|
||||
LayoutQuery::ScrollIntoViewGeometry { source } => {
|
||||
LayoutQueryAnswer::ScrollIntoViewGeometry(mock_scroll_into_view_geometry(
|
||||
runtime, document, *source,
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::collections::HashSet;
|
||||
use moli_layout::{
|
||||
LayoutAnswers, LayoutBoxModel, LayoutCaretPosition, LayoutDocumentMetrics,
|
||||
LayoutElementMetrics, LayoutError, LayoutFlushReason, LayoutHit, LayoutPoint, LayoutQuery,
|
||||
LayoutQueryAnswer, LayoutQueryBatch, LayoutScrollIntoViewGeometry,
|
||||
LayoutQueryAnswer, LayoutQueryBatch, LayoutResolvedGridTracks, LayoutScrollIntoViewGeometry,
|
||||
};
|
||||
|
||||
use super::client_rect::{ClientRect, client_rect_from_quad, union_client_rect, zero_client_rect};
|
||||
@@ -234,6 +234,29 @@ pub(crate) fn observable_element_metrics(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn observable_used_grid_tracks(
|
||||
runtime: &JsContextHost,
|
||||
source: DomHandle,
|
||||
reason: LayoutFlushReason,
|
||||
) -> Result<Option<LayoutResolvedGridTracks>, LayoutError> {
|
||||
if !runtime.dom_host().is_connected(source) {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(document) = runtime.layout_document_for_source(source) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let answers = observable_geometry_batch(
|
||||
runtime,
|
||||
document,
|
||||
reason,
|
||||
&LayoutQueryBatch::new(vec![LayoutQuery::UsedGridTracks { source }]),
|
||||
)?;
|
||||
match answers.answers.into_iter().next() {
|
||||
Some(LayoutQueryAnswer::UsedGridTracks(tracks)) => Ok(tracks),
|
||||
_ => Err(provider_contract_error("used Grid tracks")),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn observable_scroll_into_view_geometry(
|
||||
runtime: &JsContextHost,
|
||||
source: DomHandle,
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::{
|
||||
document_runtime::DomHandle,
|
||||
native_bridge::element::geometry::{
|
||||
ClientRect, observable_bounding_client_rect, observable_bounding_client_rects,
|
||||
observable_used_grid_tracks,
|
||||
},
|
||||
style_engine::{
|
||||
ComputedDisplayKind, ComputedRenderedStyleFacts, FullStyleWorldSnapshot, StyleViewport,
|
||||
@@ -5075,6 +5076,11 @@ fn resolve_moli_computed_style_value(
|
||||
if property == "font-family" {
|
||||
return normalize_cssom_font_family_value(value).unwrap_or_else(|| value.to_owned());
|
||||
}
|
||||
if matches!(property, "grid-template-columns" | "grid-template-rows")
|
||||
&& let Some(tracks) = resolved_grid_template_tracks(runtime, handle, property)
|
||||
{
|
||||
return tracks;
|
||||
}
|
||||
if property == "width"
|
||||
&& let Some(width) =
|
||||
resolve_computed_width_with_inline_fallback(runtime, handle, value, context, resolution)
|
||||
@@ -5121,6 +5127,86 @@ fn resolve_moli_computed_style_value(
|
||||
value.to_owned()
|
||||
}
|
||||
|
||||
fn resolved_grid_template_tracks(
|
||||
runtime: &JsContextHost,
|
||||
handle: DomHandle,
|
||||
property: &str,
|
||||
) -> Option<String> {
|
||||
if !runtime.layout_policy().uses_real_layout() {
|
||||
return None;
|
||||
}
|
||||
let grid = observable_used_grid_tracks(
|
||||
runtime,
|
||||
handle,
|
||||
moli_layout::LayoutFlushReason::SynchronousGeometry,
|
||||
)
|
||||
.ok()??;
|
||||
let tracks = match property {
|
||||
"grid-template-columns" => &grid.columns,
|
||||
"grid-template-rows" => &grid.rows,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
serialize_used_grid_track_list(tracks)
|
||||
}
|
||||
|
||||
fn serialize_used_grid_track_list(
|
||||
tracks: &moli_layout::LayoutResolvedGridTrackList,
|
||||
) -> Option<String> {
|
||||
if tracks.track_count() != tracks.used_track_sizes.len() {
|
||||
return None;
|
||||
}
|
||||
if tracks.used_track_sizes.is_empty() {
|
||||
return Some("none".to_owned());
|
||||
}
|
||||
if tracks.explicit_line_names.len() != tracks.explicit_track_count.saturating_add(1) {
|
||||
return None;
|
||||
}
|
||||
let mut components = Vec::with_capacity(
|
||||
tracks.used_track_sizes.len().saturating_add(
|
||||
tracks
|
||||
.explicit_line_names
|
||||
.iter()
|
||||
.filter(|names| !names.is_empty())
|
||||
.count(),
|
||||
),
|
||||
);
|
||||
let mut size_index = 0usize;
|
||||
for _ in 0..tracks.negative_implicit_track_count {
|
||||
components.push(format_non_negative_used_css_px(f64::from(
|
||||
*tracks.used_track_sizes.get(size_index)?,
|
||||
)));
|
||||
size_index += 1;
|
||||
}
|
||||
for (track_index, names) in tracks.explicit_line_names.iter().enumerate() {
|
||||
if !names.is_empty() {
|
||||
let mut serialized = String::from("[");
|
||||
for (index, name) in names.iter().enumerate() {
|
||||
if index > 0 {
|
||||
serialized.push(' ');
|
||||
}
|
||||
serialize_identifier(name.as_ref(), &mut serialized)
|
||||
.expect("serializing an identifier into String cannot fail");
|
||||
}
|
||||
serialized.push(']');
|
||||
components.push(serialized);
|
||||
}
|
||||
if track_index < tracks.explicit_track_count {
|
||||
components.push(format_non_negative_used_css_px(f64::from(
|
||||
*tracks.used_track_sizes.get(size_index)?,
|
||||
)));
|
||||
size_index += 1;
|
||||
}
|
||||
}
|
||||
for _ in 0..tracks.positive_implicit_track_count {
|
||||
components.push(format_non_negative_used_css_px(f64::from(
|
||||
*tracks.used_track_sizes.get(size_index)?,
|
||||
)));
|
||||
size_index += 1;
|
||||
}
|
||||
(size_index == tracks.used_track_sizes.len()).then(|| components.join(" "))
|
||||
}
|
||||
|
||||
fn computed_axis_position_shorthand_value(
|
||||
runtime: &JsContextHost,
|
||||
handle: DomHandle,
|
||||
@@ -6538,17 +6624,51 @@ fn parse_css_percent(value: &str) -> Option<f64> {
|
||||
}
|
||||
|
||||
fn format_css_px(value: f64) -> String {
|
||||
if (value.round() - value).abs() < 0.000_001 {
|
||||
return format!("{}px", value.round() as i64);
|
||||
format!("{}px", format_css_numeric_literal(value))
|
||||
}
|
||||
|
||||
/// Matches Blink's `CSSNumericLiteralValue` serialization, which uses `%g`
|
||||
/// with six significant digits for finite non-integer dimensions.
|
||||
fn format_css_numeric_literal(value: f64) -> String {
|
||||
if value == 0.0 {
|
||||
return "0".to_owned();
|
||||
}
|
||||
let mut serialized = format!("{value:.6}");
|
||||
while serialized.contains('.') && serialized.ends_with('0') {
|
||||
if !value.is_finite() {
|
||||
return value.to_string();
|
||||
}
|
||||
|
||||
// Formatting in scientific notation first gives us both the six-digit
|
||||
// rounding and the post-rounding exponent without reimplementing floating
|
||||
// point decimal conversion.
|
||||
let scientific = format!("{value:.5e}");
|
||||
let (mantissa, exponent) = scientific
|
||||
.rsplit_once('e')
|
||||
.expect("finite f64 scientific formatting must contain an exponent");
|
||||
let exponent = exponent
|
||||
.parse::<i32>()
|
||||
.expect("f64 scientific formatting must contain a decimal exponent");
|
||||
if (-4..6).contains(&exponent) {
|
||||
let fractional_digits = usize::try_from((5 - exponent).max(0)).unwrap_or(0);
|
||||
let mut serialized = format!("{value:.fractional_digits$}");
|
||||
trim_decimal_zeros(&mut serialized);
|
||||
return serialized;
|
||||
}
|
||||
|
||||
let mut mantissa = mantissa.to_owned();
|
||||
trim_decimal_zeros(&mut mantissa);
|
||||
format!("{mantissa}e{exponent:+03}")
|
||||
}
|
||||
|
||||
fn trim_decimal_zeros(serialized: &mut String) {
|
||||
if !serialized.contains('.') {
|
||||
return;
|
||||
}
|
||||
while serialized.ends_with('0') {
|
||||
serialized.pop();
|
||||
}
|
||||
if serialized.ends_with('.') {
|
||||
serialized.pop();
|
||||
}
|
||||
format!("{serialized}px")
|
||||
}
|
||||
|
||||
fn format_non_negative_used_css_px(value: f64) -> String {
|
||||
@@ -7030,7 +7150,7 @@ mod tests {
|
||||
use super::{
|
||||
KEYFRAME_NESTING_DEPTH_LIMIT, animation_shorthand_names, box_shorthand_component,
|
||||
collect_custom_functions_from_css, compress_box_shorthand_value,
|
||||
custom_function_container_rule_texts, format_css_number,
|
||||
custom_function_container_rule_texts, format_css_number, format_css_px,
|
||||
keyframe_has_supported_animation_values, keyframe_property_values,
|
||||
normalize_computed_color_functions, normalize_css_integer_token, normalize_style_value,
|
||||
simple_var_function_parts,
|
||||
@@ -7064,6 +7184,14 @@ mod tests {
|
||||
assert_eq!(format_css_number(120.00005), "120.00005");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn css_pixel_serialization_matches_blink_six_significant_digits() {
|
||||
assert_eq!(format_css_px(33.328_125), "33.3281px");
|
||||
assert_eq!(format_css_px(0.015_625), "0.015625px");
|
||||
assert_eq!(format_css_px(999_999.0), "999999px");
|
||||
assert_eq!(format_css_px(1_000_000.0), "1e+06px");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connected_shadow_roots_for_document_excludes_child_document_roots() {
|
||||
let mut host = test_host();
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
use super::*;
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn computed_style_serializes_used_grid_tracks_from_the_frozen_layout_tree() {
|
||||
run_page_vm_async_test(async move {
|
||||
let loader =
|
||||
crate::network::ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
|
||||
let mut page_vm = test_page_vm_with_loader_and_document_url(
|
||||
&loader,
|
||||
Vec::new(),
|
||||
Url::parse("https://example.com/grid-used-track-cssom.html")?,
|
||||
);
|
||||
page_vm.vm_mut().eval(
|
||||
r#"
|
||||
document.head.innerHTML = `<style>
|
||||
html,body{margin:0}
|
||||
.grid{display:grid;width:300px}
|
||||
#intrinsic{grid-template-columns:fit-content(75%)}
|
||||
#intrinsic>div{width:75px}
|
||||
#rows{height:100px;grid-template-rows:30px 1fr}
|
||||
#named{grid-template-columns:[a] 21px [b] repeat(2,[c] 22px [d] 23px [e]) [f] 1fr [g]}
|
||||
#automatic{grid-template-columns:[a] 21px [b] repeat(auto-fill,[c] 22px [d] 23px [e]) [f] 24px [g]}
|
||||
#auto-fit{width:44px;grid-template-columns:1px [a] repeat(auto-fit,[b] 20px [c]) [d] 3px}
|
||||
#implicit{grid-template-columns:none;grid-auto-columns:35px}
|
||||
#implicit>div{grid-column:1}
|
||||
#leading{grid-template-columns:[a] 40px [b];grid-auto-columns:15px}
|
||||
#leading>div{grid-column:-3}
|
||||
#areas{width:100px;grid-template-areas:'a a';grid-template-columns:none}
|
||||
#area-repeat{width:100px;grid-template-areas:'a a a a a a a a';grid-template-columns:repeat(auto-fill,20px)}
|
||||
#fractional{width:100px;grid-template-columns:repeat(3,1fr)}
|
||||
#zoomed{zoom:2;width:100px;grid-template-columns:1fr 3fr}
|
||||
#vertical{writing-mode:vertical-rl;width:100px;height:300px;grid-template-columns:1fr 3fr}
|
||||
</style>`;
|
||||
document.body.innerHTML = `
|
||||
<div class=grid id=intrinsic><div></div></div>
|
||||
<div class=grid id=rows></div>
|
||||
<div class=grid id=named></div>
|
||||
<div class=grid id=automatic></div>
|
||||
<div class=grid id=auto-fit></div>
|
||||
<div class=grid id=implicit><div></div></div>
|
||||
<div class=grid id=leading><div></div></div>
|
||||
<div class=grid id=areas></div>
|
||||
<div class=grid id=area-repeat></div>
|
||||
<div class=grid id=fractional></div>
|
||||
<div class=grid id=zoomed></div>
|
||||
<div class=grid id=vertical></div>`;
|
||||
'installed'
|
||||
"#,
|
||||
)?;
|
||||
page_vm.vm_mut().sync_live_document_style_sources();
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.screenshot_layout_snapshot(moli_layout::PaintViewport::new(400, 300, 1.0))?
|
||||
.expect("used Grid track CSSOM screenshot layout");
|
||||
|
||||
let values = page_vm.vm_mut().eval(
|
||||
r#"JSON.stringify({
|
||||
intrinsic:getComputedStyle(document.getElementById('intrinsic')).gridTemplateColumns,
|
||||
rows:getComputedStyle(document.getElementById('rows')).gridTemplateRows,
|
||||
named:getComputedStyle(document.getElementById('named')).gridTemplateColumns,
|
||||
automatic:getComputedStyle(document.getElementById('automatic')).gridTemplateColumns,
|
||||
autoFit:getComputedStyle(document.getElementById('auto-fit')).gridTemplateColumns,
|
||||
implicit:getComputedStyle(document.getElementById('implicit')).gridTemplateColumns,
|
||||
leading:getComputedStyle(document.getElementById('leading')).gridTemplateColumns,
|
||||
areas:getComputedStyle(document.getElementById('areas')).gridTemplateColumns,
|
||||
areaRepeat:getComputedStyle(document.getElementById('area-repeat')).gridTemplateColumns,
|
||||
fractional:getComputedStyle(document.getElementById('fractional')).gridTemplateColumns,
|
||||
zoomed:getComputedStyle(document.getElementById('zoomed')).gridTemplateColumns,
|
||||
vertical:getComputedStyle(document.getElementById('vertical')).gridTemplateColumns
|
||||
})"#,
|
||||
)?;
|
||||
let values: serde_json::Value = serde_json::from_str(&values)?;
|
||||
assert_eq!(
|
||||
values,
|
||||
serde_json::json!({
|
||||
"intrinsic": "75px",
|
||||
"rows": "30px 70px",
|
||||
"named": "[a] 21px [b c] 22px [d] 23px [e c] 22px [d] 23px [e f] 189px [g]",
|
||||
"automatic": "[a] 21px [b c] 22px [d] 23px [e c] 22px [d] 23px [e c] 22px [d] 23px [e c] 22px [d] 23px [e c] 22px [d] 23px [e f] 24px [g]",
|
||||
"autoFit": "1px [a b] 0px [c b] 0px [c d] 3px",
|
||||
"implicit": "35px",
|
||||
"leading": "15px [a] 40px [b]",
|
||||
"areas": "50px 50px",
|
||||
"areaRepeat": "20px 20px 20px 20px 20px 0px 0px 0px",
|
||||
"fractional": "33.3281px 33.3281px 33.3281px",
|
||||
"zoomed": "25px 75px",
|
||||
"vertical": "1fr 3fr",
|
||||
}),
|
||||
"resolved horizontal Grid longhands must expose used tracks while preserving expanded line names, without publishing physical-axis values for vertical Grid",
|
||||
);
|
||||
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.eval("document.getElementById('named').style.cssText='width:400px;grid-template-columns:[new] 1fr 1fr';'mutated'")?;
|
||||
assert_eq!(
|
||||
page_vm.vm_mut().eval(
|
||||
"getComputedStyle(document.getElementById('named')).gridTemplateColumns",
|
||||
)?,
|
||||
"[a] 21px [b c] 22px [d] 23px [e c] 22px [d] 23px [e f] 189px [g]",
|
||||
"a synchronous style read must stay on the last published layout epoch",
|
||||
);
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.screenshot_layout_snapshot(moli_layout::PaintViewport::new(400, 300, 1.0))?
|
||||
.expect("updated used Grid track CSSOM screenshot layout");
|
||||
assert_eq!(
|
||||
page_vm.vm_mut().eval(
|
||||
"getComputedStyle(document.getElementById('named')).gridTemplateColumns",
|
||||
)?,
|
||||
"[new] 200px 200px",
|
||||
"a screenshot must publish the new Grid track sizes",
|
||||
);
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
.await
|
||||
.expect("used Grid track CSSOM fixture should run");
|
||||
}
|
||||
@@ -123,6 +123,7 @@ mod element_toggle_event;
|
||||
mod fetch_xhr;
|
||||
mod file_entry_file_callback;
|
||||
mod file_system_directory_reader;
|
||||
mod grid_resolved_track_values;
|
||||
mod hash_change_delivery;
|
||||
mod history_traversal;
|
||||
mod image_load_event;
|
||||
|
||||
Reference in New Issue
Block a user