mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
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:
parent
b844a4e992
commit
1ac62a4ce8
4 changed files with 61 additions and 11 deletions
|
@ -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(' ')?;
|
||||||
|
|
|
@ -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>>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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(")?;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue