style: Enable lookup and computation of container size queries

Differential Revision: https://phabricator.services.mozilla.com/D158057
This commit is contained in:
David Shin 2022-10-17 17:26:13 +00:00 committed by Martin Robinson
parent 5cbb81a0df
commit 27958b191a
5 changed files with 326 additions and 45 deletions

View file

@ -21,6 +21,7 @@ use crate::selector_parser::PseudoElement;
use crate::shared_lock::StylesheetGuards; use crate::shared_lock::StylesheetGuards;
use crate::style_adjuster::StyleAdjuster; use crate::style_adjuster::StyleAdjuster;
use crate::stylesheets::{Origin, layer_rule::LayerOrder}; use crate::stylesheets::{Origin, layer_rule::LayerOrder};
use crate::stylesheets::container_rule::ContainerSizeQuery;
use crate::values::{computed, specified}; use crate::values::{computed, specified};
use fxhash::FxHashMap; use fxhash::FxHashMap;
use servo_arc::Arc; use servo_arc::Arc;
@ -275,6 +276,7 @@ where
}; };
let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root()); let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root());
let container_size_query = ContainerSizeQuery::for_option_element(element);
let mut context = computed::Context::new( let mut context = computed::Context::new(
// We'd really like to own the rules here to avoid refcount traffic, but // We'd really like to own the rules here to avoid refcount traffic, but
@ -291,6 +293,7 @@ where
), ),
quirks_mode, quirks_mode,
rule_cache_conditions, rule_cache_conditions,
container_size_query,
); );
let using_cached_reset_properties; let using_cached_reset_properties;

View file

@ -6,6 +6,7 @@
//! //!
//! [container]: https://drafts.csswg.org/css-contain-3/#container-rule //! [container]: https://drafts.csswg.org/css-contain-3/#container-rule
use crate::computed_value_flags::ComputedValueFlags;
use crate::dom::TElement; use crate::dom::TElement;
use crate::logical_geometry::{LogicalSize, WritingMode}; use crate::logical_geometry::{LogicalSize, WritingMode};
use crate::media_queries::Device; use crate::media_queries::Device;
@ -19,7 +20,7 @@ use crate::shared_lock::{
}; };
use crate::str::CssStringWriter; use crate::str::CssStringWriter;
use crate::stylesheets::CssRules; use crate::stylesheets::CssRules;
use crate::values::computed::{CSSPixelLength, Context, Ratio}; use crate::values::computed::{ContainerType, CSSPixelLength, Context, Ratio};
use crate::values::specified::ContainerName; use crate::values::specified::ContainerName;
use app_units::Au; use app_units::Au;
use cssparser::{Parser, SourceLocation}; use cssparser::{Parser, SourceLocation};
@ -112,6 +113,44 @@ pub struct ContainerLookupResult<E> {
pub style: Arc<ComputedValues>, pub style: Arc<ComputedValues>,
} }
fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags {
if ty_.contains(ContainerType::SIZE) {
return FeatureFlags::all_container_axes();
}
if ty_.contains(ContainerType::INLINE_SIZE) {
let physical_axis = if wm.is_vertical() {
FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS
} else {
FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS
};
return FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis;
}
FeatureFlags::empty()
}
enum TraversalResult<T> {
InProgress,
StopTraversal,
Done(T),
}
fn traverse_container<E, F, R>(mut e: E, evaluator: F) -> Option<(E, R)>
where
E: TElement,
F: Fn(E) -> TraversalResult<R>
{
while let Some(element) = e.traversal_parent() {
match evaluator(element) {
TraversalResult::InProgress => {},
TraversalResult::StopTraversal => break,
TraversalResult::Done(result) => return Some((element, result)),
}
e = element;
}
None
}
impl ContainerCondition { impl ContainerCondition {
/// Parse a container condition. /// Parse a container condition.
pub fn parse<'a>( pub fn parse<'a>(
@ -135,30 +174,16 @@ impl ContainerCondition {
}) })
} }
fn valid_container_info<E>(&self, potential_container: E) -> Option<ContainerLookupResult<E>> fn valid_container_info<E>(
&self,
potential_container: E
) -> TraversalResult<ContainerLookupResult<E>>
where where
E: TElement, E: TElement,
{ {
use crate::values::computed::ContainerType;
fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags {
if ty_.contains(ContainerType::SIZE) {
return FeatureFlags::all_container_axes();
}
if ty_.contains(ContainerType::INLINE_SIZE) {
let physical_axis = if wm.is_vertical() {
FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS
} else {
FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS
};
return FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis;
}
FeatureFlags::empty()
}
let data = match potential_container.borrow_data() { let data = match potential_container.borrow_data() {
Some(data) => data, Some(data) => data,
None => return None, None => return TraversalResult::InProgress,
}; };
let style = data.styles.primary(); let style = data.styles.primary();
let wm = style.writing_mode; let wm = style.writing_mode;
@ -168,20 +193,20 @@ impl ContainerCondition {
let container_type = box_style.clone_container_type(); let container_type = box_style.clone_container_type();
let available_axes = container_type_axes(container_type, wm); let available_axes = container_type_axes(container_type, wm);
if !available_axes.contains(self.flags.container_axes()) { if !available_axes.contains(self.flags.container_axes()) {
return None; return TraversalResult::InProgress;
} }
// Filter by container-name. // Filter by container-name.
let container_name = box_style.clone_container_name(); let container_name = box_style.clone_container_name();
for filter_name in self.name.0.iter() { for filter_name in self.name.0.iter() {
if !container_name.0.contains(filter_name) { if !container_name.0.contains(filter_name) {
return None; return TraversalResult::InProgress;
} }
} }
let size = potential_container.primary_box_size(); let size = potential_container.primary_box_size();
let style = style.clone(); let style = style.clone();
Some(ContainerLookupResult { TraversalResult::Done(ContainerLookupResult {
element: potential_container, element: potential_container,
info: ContainerInfo { size, wm }, info: ContainerInfo { size, wm },
style, style,
@ -189,18 +214,14 @@ impl ContainerCondition {
} }
/// Performs container lookup for a given element. /// Performs container lookup for a given element.
pub fn find_container<E>(&self, mut e: E) -> Option<ContainerLookupResult<E>> pub fn find_container<E>(&self, e: E) -> Option<ContainerLookupResult<E>>
where where
E: TElement, E: TElement,
{ {
while let Some(element) = e.traversal_parent() { match traverse_container(e, |element| self.valid_container_info(element)) {
if let Some(result) = self.valid_container_info(element) { Some((_, result)) => Some(result),
return Some(result); None => None,
}
e = element;
} }
None
} }
/// Tries to match a container query condition for a given element. /// Tries to match a container query condition for a given element.
@ -209,10 +230,18 @@ impl ContainerCondition {
E: TElement, E: TElement,
{ {
let result = self.find_container(element); let result = self.find_container(element);
let info = result.map(|r| (r.info, r.style)); let (container, info) = match result {
Context::for_container_query_evaluation(device, info, |context| { Some(r) => (Some(r.element), Some((r.info, r.style))),
self.condition.matches(context) None => (None, None),
}) };
// Set up the lookup for the container in question, as the condition may be using container query lengths.
let size_query_container_lookup = ContainerSizeQuery::for_option_element(container);
Context::for_container_query_evaluation(
device,
info,
size_query_container_lookup,
|context| self.condition.matches(context),
)
} }
} }
@ -320,3 +349,220 @@ pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [
), ),
), ),
]; ];
/// Result of a container size query, signifying the hypothetical containment boundary in terms of physical axes.
/// Defined by up to two size containers. Queries on logical axes are resolved with respect to the querying
/// element's writing mode.
#[derive(Copy, Clone, Default)]
pub struct ContainerSizeQueryResult {
width: Option<Au>,
height: Option<Au>,
}
impl ContainerSizeQueryResult {
fn get_viewport_size(context: &Context) -> Size2D<Au> {
use crate::values::specified::ViewportVariant;
context
.device()
.au_viewport_size_for_viewport_unit_resolution(ViewportVariant::Small)
}
fn get_logical_viewport_size(context: &Context) -> LogicalSize<Au> {
LogicalSize::from_physical(
context.builder.writing_mode,
Self::get_viewport_size(context),
)
}
/// Get the inline-size of the query container.
pub fn get_container_inline_size(&self, context: &Context) -> Au {
if context.builder.writing_mode.is_horizontal() {
if let Some(w) = self.width {
return w;
}
} else {
if let Some(h) = self.height {
return h;
}
}
Self::get_logical_viewport_size(context).inline
}
/// Get the block-size of the query container.
pub fn get_container_block_size(&self, context: &Context) -> Au {
if context.builder.writing_mode.is_horizontal() {
self.get_container_height(context)
} else {
self.get_container_width(context)
}
}
/// Get the width of the query container.
pub fn get_container_width(&self, context: &Context) -> Au {
if let Some(w) = self.width {
return w;
}
Self::get_viewport_size(context).width
}
/// Get the height of the query container.
pub fn get_container_height(&self, context: &Context) -> Au {
if let Some(h) = self.height {
return h;
}
Self::get_viewport_size(context).height
}
// Merge the result of a subsequent lookup, preferring the initial result.
fn merge(self, new_result: Self) -> Self {
let mut result = self;
if let Some(width) = new_result.width {
result.width.get_or_insert(width);
}
if let Some(height) = new_result.height {
result.height.get_or_insert(height);
}
result
}
fn is_complete(&self) -> bool {
self.width.is_some() && self.height.is_some()
}
}
/// Unevaluated lazy container size query.
pub enum ContainerSizeQuery<'a> {
/// Query prior to evaluation.
NotEvaluated(Box<dyn Fn() -> ContainerSizeQueryResult + 'a>),
/// Cached evaluated result.
Evaluated(ContainerSizeQueryResult),
}
impl<'a> ContainerSizeQuery<'a> {
fn evaluate_potential_size_container<E>(
e: E
) -> TraversalResult<ContainerSizeQueryResult>
where
E: TElement
{
let data = match e.borrow_data() {
Some(data) => data,
None => return TraversalResult::InProgress,
};
let style = data.styles.primary();
if !style
.flags
.contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
{
// We know we won't find a size container.
return TraversalResult::StopTraversal;
}
let wm = style.writing_mode;
let box_style = style.get_box();
let container_type = box_style.clone_container_type();
let size = e.primary_box_size();
match container_type {
ContainerType::SIZE => {
TraversalResult::Done(
ContainerSizeQueryResult {
width: Some(size.width),
height: Some(size.height)
}
)
},
ContainerType::INLINE_SIZE => {
if wm.is_horizontal() {
TraversalResult::Done(
ContainerSizeQueryResult {
width: Some(size.width),
height: None,
}
)
} else {
TraversalResult::Done(
ContainerSizeQueryResult {
width: None,
height: Some(size.height),
}
)
}
},
_ => TraversalResult::InProgress,
}
}
/// Find the query container size for a given element. Meant to be used as a callback for new().
fn lookup<E>(element: E) -> ContainerSizeQueryResult
where
E: TElement + 'a,
{
match traverse_container(element, |e| { Self::evaluate_potential_size_container(e) }) {
Some((container, result)) => if result.is_complete() {
result
} else {
// Traverse up from the found size container to see if we can get a complete containment.
result.merge(Self::lookup(container))
},
None => ContainerSizeQueryResult::default(),
}
}
/// Create a new instance of the container size query for given element, with a deferred lookup callback.
pub fn for_element<E>(element: E) -> Self
where
E: TElement + 'a,
{
// No need to bother if we're the top element.
if let Some(parent) = element.traversal_parent() {
let should_traverse = match parent.borrow_data() {
Some(data) => {
let style = data.styles.primary();
style
.flags
.contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
}
None => true, // `display: none`, still want to show a correct computed value, so give it a try.
};
if should_traverse {
return Self::NotEvaluated(Box::new(move || {
Self::lookup(element)
}));
}
}
Self::none()
}
/// Create a new instance, but with optional element.
pub fn for_option_element<E>(element: Option<E>) -> Self
where
E: TElement + 'a,
{
if let Some(e) = element {
Self::for_element(e)
} else {
Self::none()
}
}
/// Create a query that evaluates to empty, for cases where container size query is not required.
pub fn none() -> Self {
ContainerSizeQuery::Evaluated(ContainerSizeQueryResult::default())
}
/// Get the result of the container size query, doing the lookup if called for the first time.
pub fn get(&mut self) -> ContainerSizeQueryResult {
match self {
Self::NotEvaluated(lookup) => {
*self = Self::Evaluated((lookup)());
match self {
Self::Evaluated(info) => *info,
_ => unreachable!("Just evaluated but not set?"),
}
},
Self::Evaluated(info) => *info,
}
}
}

View file

@ -16,6 +16,7 @@ use crate::rule_cache::RuleCacheConditions;
use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard}; use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard};
use crate::str::CssStringWriter; use crate::str::CssStringWriter;
use crate::stylesheets::cascading_at_rule::DescriptorDeclaration; use crate::stylesheets::cascading_at_rule::DescriptorDeclaration;
use crate::stylesheets::container_rule::ContainerSizeQuery;
use crate::stylesheets::{Origin, StylesheetInDocument}; use crate::stylesheets::{Origin, StylesheetInDocument};
use crate::values::computed::{Context, ToComputedValue}; use crate::values::computed::{Context, ToComputedValue};
use crate::values::generics::length::LengthPercentageOrAuto; use crate::values::generics::length::LengthPercentageOrAuto;
@ -670,6 +671,7 @@ impl MaybeNew for ViewportConstraints {
StyleBuilder::for_inheritance(device, None, None), StyleBuilder::for_inheritance(device, None, None),
quirks_mode, quirks_mode,
&mut conditions, &mut conditions,
ContainerSizeQuery::none(),
); );
// DEVICE-ADAPT § 9.3 Resolving 'extend-to-zoom' // DEVICE-ADAPT § 9.3 Resolving 'extend-to-zoom'

View file

@ -22,7 +22,9 @@ use crate::media_queries::Device;
use crate::properties; use crate::properties;
use crate::properties::{ComputedValues, LonghandId, StyleBuilder}; use crate::properties::{ComputedValues, LonghandId, StyleBuilder};
use crate::rule_cache::RuleCacheConditions; use crate::rule_cache::RuleCacheConditions;
use crate::stylesheets::container_rule::ContainerInfo; use crate::stylesheets::container_rule::{
ContainerInfo, ContainerSizeQuery, ContainerSizeQueryResult,
};
use crate::values::specified::length::FontBaseSize; use crate::values::specified::length::FontBaseSize;
use crate::{ArcSlice, Atom, One}; use crate::{ArcSlice, Atom, One};
use euclid::{default, Point2D, Rect, Size2D}; use euclid::{default, Point2D, Rect, Size2D};
@ -189,9 +191,18 @@ pub struct Context<'a> {
/// ///
/// FIXME(emilio): Drop the refcell. /// FIXME(emilio): Drop the refcell.
pub rule_cache_conditions: RefCell<&'a mut RuleCacheConditions>, pub rule_cache_conditions: RefCell<&'a mut RuleCacheConditions>,
/// Container size query for this context.
container_size_query: RefCell<ContainerSizeQuery<'a>>,
} }
impl<'a> Context<'a> { impl<'a> Context<'a> {
/// Lazily evaluate the container size query, returning the result.
pub fn get_container_size_query(&self) -> ContainerSizeQueryResult {
let mut resolved = self.container_size_query.borrow_mut();
resolved.get().clone()
}
/// Creates a suitable context for media query evaluation, in which /// Creates a suitable context for media query evaluation, in which
/// font-relative units compute against the system_font, and executes `f` /// font-relative units compute against the system_font, and executes `f`
/// with it. /// with it.
@ -209,6 +220,7 @@ impl<'a> Context<'a> {
container_info: None, container_info: None,
for_non_inherited_property: None, for_non_inherited_property: None,
rule_cache_conditions: RefCell::new(&mut conditions), rule_cache_conditions: RefCell::new(&mut conditions),
container_size_query: RefCell::new(ContainerSizeQuery::none()),
}; };
f(&context) f(&context)
} }
@ -218,6 +230,7 @@ impl<'a> Context<'a> {
pub fn for_container_query_evaluation<F, R>( pub fn for_container_query_evaluation<F, R>(
device: &Device, device: &Device,
container_info_and_style: Option<(ContainerInfo, Arc<ComputedValues>)>, container_info_and_style: Option<(ContainerInfo, Arc<ComputedValues>)>,
container_size_query: ContainerSizeQuery,
f: F, f: F,
) -> R ) -> R
where where
@ -241,6 +254,7 @@ impl<'a> Context<'a> {
container_info, container_info,
for_non_inherited_property: None, for_non_inherited_property: None,
rule_cache_conditions: RefCell::new(&mut conditions), rule_cache_conditions: RefCell::new(&mut conditions),
container_size_query: RefCell::new(container_size_query),
}; };
f(&context) f(&context)
@ -251,6 +265,7 @@ impl<'a> Context<'a> {
builder: StyleBuilder<'a>, builder: StyleBuilder<'a>,
quirks_mode: QuirksMode, quirks_mode: QuirksMode,
rule_cache_conditions: &'a mut RuleCacheConditions, rule_cache_conditions: &'a mut RuleCacheConditions,
container_size_query: ContainerSizeQuery<'a>,
) -> Self { ) -> Self {
Self { Self {
builder, builder,
@ -261,6 +276,7 @@ impl<'a> Context<'a> {
for_smil_animation: false, for_smil_animation: false,
for_non_inherited_property: None, for_non_inherited_property: None,
rule_cache_conditions: RefCell::new(rule_cache_conditions), rule_cache_conditions: RefCell::new(rule_cache_conditions),
container_size_query: RefCell::new(container_size_query),
} }
} }
@ -270,6 +286,7 @@ impl<'a> Context<'a> {
for_smil_animation: bool, for_smil_animation: bool,
quirks_mode: QuirksMode, quirks_mode: QuirksMode,
rule_cache_conditions: &'a mut RuleCacheConditions, rule_cache_conditions: &'a mut RuleCacheConditions,
container_size_query: ContainerSizeQuery<'a>,
) -> Self { ) -> Self {
Self { Self {
builder, builder,
@ -280,6 +297,7 @@ impl<'a> Context<'a> {
for_smil_animation, for_smil_animation,
for_non_inherited_property: None, for_non_inherited_property: None,
rule_cache_conditions: RefCell::new(rule_cache_conditions), rule_cache_conditions: RefCell::new(rule_cache_conditions),
container_size_query: RefCell::new(container_size_query),
} }
} }

View file

@ -716,16 +716,28 @@ impl ContainerRelativeLength {
/// Computes the given container-relative length. /// Computes the given container-relative length.
pub fn to_computed_value(&self, context: &Context) -> CSSPixelLength { pub fn to_computed_value(&self, context: &Context) -> CSSPixelLength {
// TODO(dshin): For now, use the small viewport size. let size = context.get_container_size_query();
let small_viewport_size = match *self { let (factor, container_length) = match *self {
ContainerRelativeLength::Cqw(v) => ViewportPercentageLength::Svw(v), ContainerRelativeLength::Cqw(v) => (v, size.get_container_width(context)),
ContainerRelativeLength::Cqh(v) => ViewportPercentageLength::Svh(v), ContainerRelativeLength::Cqh(v) => (v, size.get_container_height(context)),
ContainerRelativeLength::Cqi(v) => ViewportPercentageLength::Svi(v), ContainerRelativeLength::Cqi(v) => (v, size.get_container_inline_size(context)),
ContainerRelativeLength::Cqb(v) => ViewportPercentageLength::Svb(v), ContainerRelativeLength::Cqb(v) => (v, size.get_container_block_size(context)),
ContainerRelativeLength::Cqmin(v) => ViewportPercentageLength::Svmin(v), ContainerRelativeLength::Cqmin(v) => (
ContainerRelativeLength::Cqmax(v) => ViewportPercentageLength::Svmax(v), v,
cmp::min(
size.get_container_inline_size(context),
size.get_container_block_size(context),
),
),
ContainerRelativeLength::Cqmax(v) => (
v,
cmp::max(
size.get_container_inline_size(context),
size.get_container_block_size(context),
),
),
}; };
small_viewport_size.to_computed_value(context) CSSPixelLength::new(((container_length.to_f64_px()) * factor as f64 / 100.0) as f32)
} }
} }