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

View file

@ -8,7 +8,7 @@
use crate::media_queries::MediaList;
use crate::parser::ParserContext;
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 cssparser::SourceLocation;
use servo_arc::Arc;
@ -25,6 +25,7 @@ pub trait StylesheetLoader {
context: &ParserContext,
lock: &SharedRwLock,
media: Arc<Locked<MediaList>>,
supports: Option<ImportSupportsCondition>,
layer: Option<ImportLayer>,
) -> 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::document_rule::DocumentCondition;
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::layer_rule::{LayerBlockRule, LayerName, LayerStatementRule};
use crate::stylesheets::stylesheet::Namespaces;
@ -204,7 +204,7 @@ pub enum AtRulePrelude {
/// A @document rule, with its conditional.
Document(DocumentCondition),
/// A @import rule prelude.
Import(CssUrl, Arc<Locked<MediaList>>, Option<ImportLayer>),
Import(CssUrl, Arc<Locked<MediaList>>, Option<ImportSupportsCondition>, Option<ImportLayer>),
/// A @namespace rule prelude.
Namespace(Option<Prefix>, Namespace),
/// 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 = 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")]
let layers_enabled = static_prefs::pref!("layout.css.cascade-layers.enabled");
#[cfg(feature = "servo")]
@ -266,7 +284,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
let media = MediaList::parse(&self.context, input);
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" => {
if !self.check_state(State::Namespaces) {
@ -335,7 +353,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
start: &ParserState,
) -> Result<Self::AtRule, ()> {
let rule = match prelude {
AtRulePrelude::Import(url, media, layer) => {
AtRulePrelude::Import(url, media, supports, layer) => {
let loader = self
.loader
.expect("Expected a stylesheet loader for @import");
@ -346,6 +364,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
&self.context,
&self.shared_lock,
media,
supports,
layer,
);

View file

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