style: Implement supports() syntax for @import rules

Implemented supports conditions using supports() in @import rules as per
CSS Cascading and Inheritance Level 4.

Locked behind new pref, layout.css.import-supports.enabled,
only enabled in nightlies in this patch.

Also added new WPT tests for @import supports() generally.

Spec: https://drafts.csswg.org/css-cascade-4/#conditional-import
WPT tests: https://wpt.fyi/results/css/css-cascade/import-conditions.html

Differential Revision: https://phabricator.services.mozilla.com/D172622
This commit is contained in:
CanadaHonk 2023-04-13 09:02:30 +00:00 committed by Martin Robinson
parent b844a4e992
commit 1ac62a4ce8
4 changed files with 61 additions and 11 deletions

View file

@ -10,6 +10,7 @@ use crate::media_queries::MediaList;
use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock}; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock};
use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter; use crate::str::CssStringWriter;
use crate::stylesheets::supports_rule::SupportsCondition;
use crate::stylesheets::layer_rule::LayerName; use crate::stylesheets::layer_rule::LayerName;
use crate::stylesheets::{CssRule, StylesheetInDocument}; use crate::stylesheets::{CssRule, StylesheetInDocument};
use crate::values::CssUrl; use crate::values::CssUrl;
@ -24,9 +25,13 @@ use to_shmem::{self, SharedMemoryBuilder, ToShmem};
pub enum ImportSheet { pub enum ImportSheet {
/// A bonafide stylesheet. /// A bonafide stylesheet.
Sheet(crate::gecko::data::GeckoStyleSheet), Sheet(crate::gecko::data::GeckoStyleSheet),
/// An @import created while parsing off-main-thread, whose Gecko sheet has /// An @import created while parsing off-main-thread, whose Gecko sheet has
/// yet to be created and attached. /// yet to be created and attached.
Pending, Pending,
/// An @import created with a false <supports-condition>, so will never be fetched.
Refused,
} }
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
@ -41,6 +46,11 @@ impl ImportSheet {
ImportSheet::Pending ImportSheet::Pending
} }
/// Creates a refused ImportSheet for a load that will not happen.
pub fn new_refused() -> Self {
ImportSheet::Refused
}
/// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it /// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it
/// exists. /// exists.
pub fn as_sheet(&self) -> Option<&crate::gecko::data::GeckoStyleSheet> { pub fn as_sheet(&self) -> Option<&crate::gecko::data::GeckoStyleSheet> {
@ -52,6 +62,7 @@ impl ImportSheet {
} }
Some(s) Some(s)
}, },
ImportSheet::Refused |
ImportSheet::Pending => None, ImportSheet::Pending => None,
} }
} }
@ -88,6 +99,7 @@ impl DeepCloneWithLock for ImportSheet {
ImportSheet::Sheet(unsafe { GeckoStyleSheet::from_addrefed(clone) }) ImportSheet::Sheet(unsafe { GeckoStyleSheet::from_addrefed(clone) })
}, },
ImportSheet::Pending => ImportSheet::Pending, ImportSheet::Pending => ImportSheet::Pending,
ImportSheet::Refused => ImportSheet::Refused,
} }
} }
} }
@ -131,6 +143,16 @@ pub struct ImportLayer {
pub name: Option<LayerName>, pub name: Option<LayerName>,
} }
/// The supports condition in an import rule.
#[derive(Debug, Clone)]
pub struct ImportSupportsCondition {
/// The supports condition.
pub condition: SupportsCondition,
/// If the import is enabled, from the result of the import condition.
pub enabled: bool
}
impl ToCss for ImportLayer { impl ToCss for ImportLayer {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where where
@ -160,6 +182,9 @@ pub struct ImportRule {
/// ImportSheet just has stub behavior until it appears. /// ImportSheet just has stub behavior until it appears.
pub stylesheet: ImportSheet, pub stylesheet: ImportSheet,
/// A <supports-condition> for the rule.
pub supports: Option<ImportSupportsCondition>,
/// A `layer()` function name. /// A `layer()` function name.
pub layer: Option<ImportLayer>, pub layer: Option<ImportLayer>,
@ -185,6 +210,7 @@ impl DeepCloneWithLock for ImportRule {
ImportRule { ImportRule {
url: self.url.clone(), url: self.url.clone(),
stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params), stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params),
supports: self.supports.clone(),
layer: self.layer.clone(), layer: self.layer.clone(),
source_location: self.source_location.clone(), source_location: self.source_location.clone(),
} }
@ -196,6 +222,12 @@ impl ToCssWithGuard for ImportRule {
dest.write_str("@import ")?; dest.write_str("@import ")?;
self.url.to_css(&mut CssWriter::new(dest))?; self.url.to_css(&mut CssWriter::new(dest))?;
if let Some(ref supports) = self.supports {
dest.write_str(" supports(")?;
supports.condition.to_css(&mut CssWriter::new(dest))?;
dest.write_char(')')?;
}
if let Some(media) = self.stylesheet.media(guard) { if let Some(media) = self.stylesheet.media(guard) {
if !media.is_empty() { if !media.is_empty() {
dest.write_char(' ')?; dest.write_char(' ')?;

View file

@ -8,7 +8,7 @@
use crate::media_queries::MediaList; use crate::media_queries::MediaList;
use crate::parser::ParserContext; use crate::parser::ParserContext;
use crate::shared_lock::{Locked, SharedRwLock}; use crate::shared_lock::{Locked, SharedRwLock};
use crate::stylesheets::import_rule::{ImportLayer, ImportRule}; use crate::stylesheets::import_rule::{ImportLayer, ImportSupportsCondition, ImportRule};
use crate::values::CssUrl; use crate::values::CssUrl;
use cssparser::SourceLocation; use cssparser::SourceLocation;
use servo_arc::Arc; use servo_arc::Arc;
@ -25,6 +25,7 @@ pub trait StylesheetLoader {
context: &ParserContext, context: &ParserContext,
lock: &SharedRwLock, lock: &SharedRwLock,
media: Arc<Locked<MediaList>>, media: Arc<Locked<MediaList>>,
supports: Option<ImportSupportsCondition>,
layer: Option<ImportLayer>, layer: Option<ImportLayer>,
) -> Arc<Locked<ImportRule>>; ) -> Arc<Locked<ImportRule>>;
} }

View file

@ -16,7 +16,7 @@ use crate::str::starts_with_ignore_ascii_case;
use crate::stylesheets::container_rule::{ContainerCondition, ContainerRule}; use crate::stylesheets::container_rule::{ContainerCondition, ContainerRule};
use crate::stylesheets::document_rule::DocumentCondition; use crate::stylesheets::document_rule::DocumentCondition;
use crate::stylesheets::font_feature_values_rule::parse_family_name_list; use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
use crate::stylesheets::import_rule::ImportLayer; use crate::stylesheets::import_rule::{ImportLayer, ImportSupportsCondition};
use crate::stylesheets::keyframes_rule::parse_keyframe_list; use crate::stylesheets::keyframes_rule::parse_keyframe_list;
use crate::stylesheets::layer_rule::{LayerBlockRule, LayerName, LayerStatementRule}; use crate::stylesheets::layer_rule::{LayerBlockRule, LayerName, LayerStatementRule};
use crate::stylesheets::stylesheet::Namespaces; use crate::stylesheets::stylesheet::Namespaces;
@ -204,7 +204,7 @@ pub enum AtRulePrelude {
/// A @document rule, with its conditional. /// A @document rule, with its conditional.
Document(DocumentCondition), Document(DocumentCondition),
/// A @import rule prelude. /// A @import rule prelude.
Import(CssUrl, Arc<Locked<MediaList>>, Option<ImportLayer>), Import(CssUrl, Arc<Locked<MediaList>>, Option<ImportSupportsCondition>, Option<ImportLayer>),
/// A @namespace rule prelude. /// A @namespace rule prelude.
Namespace(Option<Prefix>, Namespace), Namespace(Option<Prefix>, Namespace),
/// A @layer rule prelude. /// A @layer rule prelude.
@ -241,6 +241,24 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
let url_string = input.expect_url_or_string()?.as_ref().to_owned(); let url_string = input.expect_url_or_string()?.as_ref().to_owned();
let url = CssUrl::parse_from_string(url_string, &self.context, CorsMode::None); let url = CssUrl::parse_from_string(url_string, &self.context, CorsMode::None);
let supports = if !static_prefs::pref!("layout.css.import-supports.enabled") {
None
} else {
input.try_parse(SupportsCondition::parse_for_import).map(|condition| {
let eval_context = ParserContext::new_with_rule_type(
&self.context,
CssRuleType::Style,
self.namespaces,
);
let enabled = condition.eval(&eval_context, self.namespaces);
ImportSupportsCondition {
condition,
enabled
}
}).ok()
};
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
let layers_enabled = static_prefs::pref!("layout.css.cascade-layers.enabled"); let layers_enabled = static_prefs::pref!("layout.css.cascade-layers.enabled");
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
@ -266,7 +284,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
let media = MediaList::parse(&self.context, input); let media = MediaList::parse(&self.context, input);
let media = Arc::new(self.shared_lock.wrap(media)); let media = Arc::new(self.shared_lock.wrap(media));
return Ok(AtRulePrelude::Import(url, media, layer)); return Ok(AtRulePrelude::Import(url, media, supports, layer));
}, },
"namespace" => { "namespace" => {
if !self.check_state(State::Namespaces) { if !self.check_state(State::Namespaces) {
@ -335,7 +353,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
start: &ParserState, start: &ParserState,
) -> Result<Self::AtRule, ()> { ) -> Result<Self::AtRule, ()> {
let rule = match prelude { let rule = match prelude {
AtRulePrelude::Import(url, media, layer) => { AtRulePrelude::Import(url, media, supports, layer) => {
let loader = self let loader = self
.loader .loader
.expect("Expected a stylesheet loader for @import"); .expect("Expected a stylesheet loader for @import");
@ -346,6 +364,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
&self.context, &self.context,
&self.shared_lock, &self.shared_lock,
media, media,
supports,
layer, layer,
); );

View file

@ -204,8 +204,8 @@ impl SupportsCondition {
Token::ParenthesisBlock => { Token::ParenthesisBlock => {
let nested = input let nested = input
.try_parse(|input| input.parse_nested_block(parse_condition_or_declaration)); .try_parse(|input| input.parse_nested_block(parse_condition_or_declaration));
if nested.is_ok() { if let Ok(nested) = nested {
return nested; return Ok(Self::Parenthesized(Box::new(nested)));
} }
}, },
Token::Function(ref ident) => { Token::Function(ref ident) => {
@ -286,7 +286,7 @@ pub fn parse_condition_or_declaration<'i, 't>(
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<SupportsCondition, ParseError<'i>> { ) -> Result<SupportsCondition, ParseError<'i>> {
if let Ok(condition) = input.try_parse(SupportsCondition::parse) { if let Ok(condition) = input.try_parse(SupportsCondition::parse) {
Ok(SupportsCondition::Parenthesized(Box::new(condition))) Ok(condition)
} else { } else {
Declaration::parse(input).map(SupportsCondition::Declaration) Declaration::parse(input).map(SupportsCondition::Declaration)
} }
@ -330,9 +330,7 @@ impl ToCss for SupportsCondition {
Ok(()) Ok(())
}, },
SupportsCondition::Declaration(ref decl) => { SupportsCondition::Declaration(ref decl) => {
dest.write_char('(')?; decl.to_css(dest)
decl.to_css(dest)?;
dest.write_char(')')
}, },
SupportsCondition::Selector(ref selector) => { SupportsCondition::Selector(ref selector) => {
dest.write_str("selector(")?; dest.write_str("selector(")?;