Bug 1945342 - Prototype style() container queries. r=dshin
Not intended to be a full implementation, but more of an off-by-default prototype with the right pieces in place. This parses and evaluates the queries, but: * They don't evaluate against the right style (I didn't change the container query lookup yet). * They're not invalidated properly (this changes selector matching as a result of style changes which is not currently dealt with). Still I want to get this in since it's kinda straight-forward and needed for the eventual full implementation. There are some tests that start running now that I'd need to update, try in progress. Differential Revision: https://phabricator.services.mozilla.com/D236468
This commit is contained in:
@@ -9319,6 +9319,13 @@
|
||||
mirror: always
|
||||
rust: true
|
||||
|
||||
# Whether style() container queries are enabled
|
||||
- name: layout.css.style-queries.enabled
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
mirror: always
|
||||
rust: true
|
||||
|
||||
# Should we look for counter ancestor scopes first?
|
||||
- name: layout.css.counter-ancestor-scope.enabled
|
||||
type: bool
|
||||
|
||||
@@ -238,6 +238,23 @@ pub struct VariableValue {
|
||||
|
||||
trivial_to_computed_value!(VariableValue);
|
||||
|
||||
/// Given a potentially registered variable value turn it into a computed custom property value.
|
||||
pub fn compute_variable_value(
|
||||
value: &Arc<VariableValue>,
|
||||
registration: &PropertyRegistrationData,
|
||||
computed_context: &computed::Context,
|
||||
) -> Option<ComputedRegisteredValue> {
|
||||
if registration.syntax.is_universal() {
|
||||
return Some(ComputedRegisteredValue::universal(Arc::clone(value)));
|
||||
}
|
||||
compute_value(
|
||||
&value.css,
|
||||
&value.url_data,
|
||||
registration,
|
||||
computed_context,
|
||||
).ok()
|
||||
}
|
||||
|
||||
// For all purposes, we want values to be considered equal if their css text is equal.
|
||||
impl PartialEq for VariableValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
//! https://drafts.csswg.org/css-contain-3/#typedef-container-condition
|
||||
|
||||
use super::{FeatureFlags, FeatureType, QueryFeatureExpression};
|
||||
use crate::custom_properties;
|
||||
use crate::values::computed;
|
||||
use crate::{error_reporting::ContextualParseError, parser::ParserContext};
|
||||
use cssparser::{Parser, Token};
|
||||
use cssparser::{Parser, SourcePosition, Token};
|
||||
use selectors::kleene_value::KleeneValue;
|
||||
use servo_arc::Arc;
|
||||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||
|
||||
@@ -30,6 +32,81 @@ enum AllowOr {
|
||||
No,
|
||||
}
|
||||
|
||||
/// A style query feature:
|
||||
/// https://drafts.csswg.org/css-conditional-5/#typedef-style-feature
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub struct StyleFeature {
|
||||
name: custom_properties::Name,
|
||||
// TODO: This is a "primary" reference, probably should be unconditionally measured.
|
||||
#[ignore_malloc_size_of = "Arc"]
|
||||
value: Option<Arc<custom_properties::SpecifiedValue>>,
|
||||
}
|
||||
|
||||
impl ToCss for StyleFeature {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
dest.write_str("--")?;
|
||||
crate::values::serialize_atom_identifier(&self.name, dest)?;
|
||||
if let Some(ref v) = self.value {
|
||||
dest.write_str(": ")?;
|
||||
v.to_css(dest)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleFeature {
|
||||
fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
feature_type: FeatureType,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
if !static_prefs::pref!("layout.css.style-queries.enabled") ||
|
||||
feature_type != FeatureType::Container
|
||||
{
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
// TODO: Allow parsing nested style feature queries.
|
||||
let ident = input.expect_ident()?;
|
||||
// TODO(emilio): Maybe support non-custom properties?
|
||||
let name = match custom_properties::parse_name(ident.as_ref()) {
|
||||
Ok(name) => custom_properties::Name::from(name),
|
||||
Err(()) => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
|
||||
};
|
||||
let value = if input.try_parse(|i| i.expect_colon()).is_ok() {
|
||||
input.skip_whitespace();
|
||||
Some(Arc::new(custom_properties::SpecifiedValue::parse(
|
||||
input,
|
||||
&context.url_data,
|
||||
)?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Self { name, value })
|
||||
}
|
||||
|
||||
fn matches(&self, ctx: &computed::Context) -> KleeneValue {
|
||||
// FIXME(emilio): Confirm this is the right style to query.
|
||||
let registration = ctx
|
||||
.builder
|
||||
.stylist
|
||||
.expect("container queries should have a stylist around")
|
||||
.get_custom_property_registration(&self.name);
|
||||
let current_value = ctx
|
||||
.inherited_custom_properties()
|
||||
.get(registration, &self.name);
|
||||
KleeneValue::from(match self.value {
|
||||
Some(ref v) => current_value.is_some_and(|cur| {
|
||||
custom_properties::compute_variable_value(v, registration, ctx)
|
||||
.is_some_and(|v| v == *cur)
|
||||
}),
|
||||
None => current_value.is_some(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a condition.
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum QueryCondition {
|
||||
@@ -41,6 +118,8 @@ pub enum QueryCondition {
|
||||
Operation(Box<[QueryCondition]>, Operator),
|
||||
/// A condition wrapped in parenthesis.
|
||||
InParens(Box<QueryCondition>),
|
||||
/// A <style> query.
|
||||
Style(StyleFeature),
|
||||
/// [ <function-token> <any-value>? ) ] | [ ( <any-value>? ) ]
|
||||
GeneralEnclosed(String),
|
||||
}
|
||||
@@ -63,6 +142,11 @@ impl ToCss for QueryCondition {
|
||||
c.to_css(dest)?;
|
||||
dest.write_char(')')
|
||||
},
|
||||
QueryCondition::Style(ref c) => {
|
||||
dest.write_str("style(")?;
|
||||
c.to_css(dest)?;
|
||||
dest.write_char(')')
|
||||
},
|
||||
QueryCondition::Operation(ref list, op) => {
|
||||
let mut iter = list.iter();
|
||||
iter.next().unwrap().to_css(dest)?;
|
||||
@@ -100,8 +184,7 @@ impl QueryCondition {
|
||||
{
|
||||
visitor(self);
|
||||
match *self {
|
||||
Self::Feature(..) => {},
|
||||
Self::GeneralEnclosed(..) => {},
|
||||
Self::Feature(..) | Self::GeneralEnclosed(..) | Self::Style(..) => {},
|
||||
Self::Not(ref cond) => cond.visit(visitor),
|
||||
Self::Operation(ref conds, _op) => {
|
||||
for cond in conds.iter() {
|
||||
@@ -117,6 +200,9 @@ impl QueryCondition {
|
||||
pub fn cumulative_flags(&self) -> FeatureFlags {
|
||||
let mut result = FeatureFlags::empty();
|
||||
self.visit(&mut |condition| {
|
||||
if let Self::Style(..) = condition {
|
||||
result.insert(FeatureFlags::STYLE);
|
||||
}
|
||||
if let Self::Feature(ref f) = condition {
|
||||
result.insert(f.feature_flags())
|
||||
}
|
||||
@@ -200,6 +286,29 @@ impl QueryCondition {
|
||||
Err(feature_error)
|
||||
}
|
||||
|
||||
fn try_parse_block<'i, T, F>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, '_>,
|
||||
start: SourcePosition,
|
||||
parse: F,
|
||||
) -> Option<T>
|
||||
where
|
||||
F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i>>,
|
||||
{
|
||||
let nested = input.try_parse(|input| input.parse_nested_block(parse));
|
||||
match nested {
|
||||
Ok(nested) => Some(nested),
|
||||
Err(e) => {
|
||||
// We're about to swallow the error in a `<general-enclosed>`
|
||||
// condition, so report it while we can.
|
||||
let loc = e.location;
|
||||
let error = ContextualParseError::InvalidMediaRule(input.slice_from(start), e);
|
||||
context.log_css_error(loc, error);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a condition in parentheses, or `<general-enclosed>`.
|
||||
///
|
||||
/// https://drafts.csswg.org/mediaqueries/#typedef-media-in-parens
|
||||
@@ -213,25 +322,22 @@ impl QueryCondition {
|
||||
let start_location = input.current_source_location();
|
||||
match *input.next()? {
|
||||
Token::ParenthesisBlock => {
|
||||
let nested = input.try_parse(|input| {
|
||||
input.parse_nested_block(|input| {
|
||||
Self::parse_in_parenthesis_block(context, input, feature_type)
|
||||
})
|
||||
let nested = Self::try_parse_block(context, input, start, |input| {
|
||||
Self::parse_in_parenthesis_block(context, input, feature_type)
|
||||
});
|
||||
match nested {
|
||||
Ok(nested) => return Ok(nested),
|
||||
Err(e) => {
|
||||
// We're about to swallow the error in a `<general-enclosed>`
|
||||
// condition, so report it while we can.
|
||||
let loc = e.location;
|
||||
let error =
|
||||
ContextualParseError::InvalidMediaRule(input.slice_from(start), e);
|
||||
context.log_css_error(loc, error);
|
||||
},
|
||||
if let Some(nested) = nested {
|
||||
return Ok(nested);
|
||||
}
|
||||
},
|
||||
Token::Function(..) => {
|
||||
// TODO: handle `style()` queries, etc.
|
||||
Token::Function(ref name) => {
|
||||
if name.eq_ignore_ascii_case("style") {
|
||||
let feature = Self::try_parse_block(context, input, start, |input| {
|
||||
StyleFeature::parse(context, input, feature_type)
|
||||
});
|
||||
if let Some(feature) = feature {
|
||||
return Ok(Self::Style(feature));
|
||||
}
|
||||
}
|
||||
},
|
||||
ref t => return Err(start_location.new_unexpected_token_error(t.clone())),
|
||||
}
|
||||
@@ -250,6 +356,7 @@ impl QueryCondition {
|
||||
QueryCondition::GeneralEnclosed(_) => KleeneValue::Unknown,
|
||||
QueryCondition::InParens(ref c) => c.matches(context),
|
||||
QueryCondition::Not(ref c) => !c.matches(context),
|
||||
QueryCondition::Style(ref c) => c.matches(context),
|
||||
QueryCondition::Operation(ref conditions, op) => {
|
||||
debug_assert!(!conditions.is_empty(), "We never create an empty op");
|
||||
match op {
|
||||
|
||||
@@ -123,6 +123,8 @@ bitflags! {
|
||||
const CONTAINER_REQUIRES_HEIGHT_AXIS = 1 << 5;
|
||||
/// The feature evaluation depends on the viewport size.
|
||||
const VIEWPORT_DEPENDENT = 1 << 6;
|
||||
/// The feature evaluation depends on style queries.
|
||||
const STYLE = 1 << 7;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
prefs: [dom.customHighlightAPI.enabled:true,layout.css.transition-behavior.enabled:true,layout.css.basic-shape-shape.enabled:true]
|
||||
prefs: [dom.customHighlightAPI.enabled:true,layout.css.transition-behavior.enabled:true,layout.css.basic-shape-shape.enabled:true,layout.css.style-queries.enabled:true]
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
[at-container-style-parsing.html]
|
||||
expected: ERROR
|
||||
[Query condition should be valid: style(not ((--foo: calc(10px + 2em)) and ((--foo: url(x)))))]
|
||||
expected: FAIL
|
||||
|
||||
[Query condition should be valid: style((--foo: bar) or (--bar: 10px))]
|
||||
expected: FAIL
|
||||
|
||||
[Query condition should be valid: style(--foo: bar !important)]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
[at-container-style-serialization.html]
|
||||
expected: ERROR
|
||||
[Unknown CSS property after 'or']
|
||||
expected: FAIL
|
||||
|
||||
[Original string number in custom property value]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
[container-selection-unknown-features.html]
|
||||
expected: ERROR
|
||||
[width query with (foo: bar)]
|
||||
expected: FAIL
|
||||
|
||||
[width query with foo(bar)]
|
||||
expected: FAIL
|
||||
|
||||
[style query with (foo: bar)]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,2 +1,102 @@
|
||||
[custom-property-style-queries.html]
|
||||
expected: ERROR
|
||||
[ style(--inner: true)]
|
||||
expected: FAIL
|
||||
|
||||
[ style(--inner:true)]
|
||||
expected: FAIL
|
||||
|
||||
[ style(--inner:true )]
|
||||
expected: FAIL
|
||||
|
||||
[ style(--inner: true )]
|
||||
expected: FAIL
|
||||
|
||||
[ style(--inner-no-space: true)]
|
||||
expected: FAIL
|
||||
|
||||
[ style(--inner-no-space:true)]
|
||||
expected: FAIL
|
||||
|
||||
[ style(--inner-no-space:true )]
|
||||
expected: FAIL
|
||||
|
||||
[ style(--inner-no-space: true )]
|
||||
expected: FAIL
|
||||
|
||||
[ style(--inner-space-after: true)]
|
||||
expected: FAIL
|
||||
|
||||
[ style(--inner-space-after:true)]
|
||||
expected: FAIL
|
||||
|
||||
[ style(--inner-space-after:true )]
|
||||
expected: FAIL
|
||||
|
||||
[ style(--inner-space-after: true )]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer: true)]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer:true)]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer:true )]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer: true )]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer-no-space: true)]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer-no-space:true)]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer-no-space:true )]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer-no-space: true )]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer-space-after: true)]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer-space-after:true)]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer-space-after:true )]
|
||||
expected: FAIL
|
||||
|
||||
[outer style(--outer-space-after: true )]
|
||||
expected: FAIL
|
||||
|
||||
[Query custom property with !important declaration]
|
||||
expected: FAIL
|
||||
|
||||
[Query custom property using var()]
|
||||
expected: FAIL
|
||||
|
||||
[Query custom property including unknown var() reference with matching fallback]
|
||||
expected: FAIL
|
||||
|
||||
[Query custom property matching guaranteed-invalid values]
|
||||
expected: FAIL
|
||||
|
||||
[Style query 'initial' matching]
|
||||
expected: FAIL
|
||||
|
||||
[Style query 'initial' matching (with explicit 'initial' value)]
|
||||
expected: FAIL
|
||||
|
||||
[Style query 'inherit' matching]
|
||||
expected: FAIL
|
||||
|
||||
[Style query 'unset' matching]
|
||||
expected: FAIL
|
||||
|
||||
[Match registered <length> custom property with px via initial keyword.]
|
||||
expected: FAIL
|
||||
|
||||
[Match registered <length> custom property with initial value via initial keyword.]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,2 +1,12 @@
|
||||
[custom-property-style-query-change.html]
|
||||
expected: ERROR
|
||||
[Target child]
|
||||
expected: FAIL
|
||||
|
||||
[Target grandchild]
|
||||
expected: FAIL
|
||||
|
||||
[Registered property query child]
|
||||
expected: FAIL
|
||||
|
||||
[Registered property query grandchild]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[display-contents-dynamic-style-queries.html]
|
||||
expected: ERROR
|
||||
[After display and --foo changes, style() query causes the color to be green]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
[multiple-style-containers-comma-separated-queries.html]
|
||||
expected: ERROR
|
||||
[Should match the named outer container for --foo:bar]
|
||||
expected: FAIL
|
||||
|
||||
[Match the #combined container for --foo:qux which is also a size container]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[nested-size-style-container-invalidation.html]
|
||||
expected: ERROR
|
||||
[Green after reducing width]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[pseudo-elements-005.html]
|
||||
expected: ERROR
|
||||
@@ -1,2 +0,0 @@
|
||||
[registered-color-style-queries.html]
|
||||
expected: ERROR
|
||||
@@ -1,2 +1,36 @@
|
||||
[style-container-for-shadow-dom.html]
|
||||
expected: ERROR
|
||||
[Match container in outer tree]
|
||||
expected: FAIL
|
||||
|
||||
[Match container in the shadow tree for a host child in the host child's tree scope]
|
||||
expected: FAIL
|
||||
|
||||
[Match <slot> as a container for ::slotted element]
|
||||
expected: FAIL
|
||||
|
||||
[Match container in outer tree for :host]
|
||||
expected: FAIL
|
||||
|
||||
[Match ::part's parent in the shadow tree as the container for ::part]
|
||||
expected: FAIL
|
||||
|
||||
[Match ::slotted as a container for its ::before]
|
||||
expected: FAIL
|
||||
|
||||
[Match container in outer tree for :host::before]
|
||||
expected: FAIL
|
||||
|
||||
[Match the ::part as a container for ::before on ::part elements]
|
||||
expected: FAIL
|
||||
|
||||
[Match container for ::part selector in inner shadow tree for exportparts]
|
||||
expected: FAIL
|
||||
|
||||
[Match container for slot light tree child fallback]
|
||||
expected: FAIL
|
||||
|
||||
[Should match parent container inside shadow tree for ::part()]
|
||||
expected: FAIL
|
||||
|
||||
[A :host::part rule matching a container in the shadow tree]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[style-container-invalidation-inheritance.html]
|
||||
expected: ERROR
|
||||
[Changed --match inherits down descendants and affects container query]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[style-query-document-element.html]
|
||||
[style query should evaluate to true]
|
||||
expected: FAIL
|
||||
@@ -1,2 +0,0 @@
|
||||
[style-query-guaranteed-invalid.tentative.html]
|
||||
expected: ERROR
|
||||
@@ -1,2 +1,3 @@
|
||||
[style-query-no-cycle.html]
|
||||
expected: ERROR
|
||||
[style(--foo: var(--foo)) should match]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[style-query-registered-custom-invalid.tentative.html]
|
||||
expected: ERROR
|
||||
@@ -1,2 +1,3 @@
|
||||
[style-query-unset-on-root.html]
|
||||
expected: ERROR
|
||||
[Match style(--foo: unset) on :root element]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[style-query-with-unknown-width.html]
|
||||
expected: ERROR
|
||||
Reference in New Issue
Block a user