style: Implement @import layer|layer(<name>)

This works modulo the existing nested layer order bug. Will be covered
by WPT /css/css-cascade/layer-import.html once the feature is enabled (I
can probably enable it right away for those tests, but I'd rather fix
the obvious bugs first).

Differential Revision: https://phabricator.services.mozilla.com/D124538
This commit is contained in:
Emilio Cobos Álvarez 2023-05-27 06:09:24 +02:00 committed by Oriol Brufau
parent 3dc3fb9412
commit 6bc198b757
4 changed files with 98 additions and 25 deletions

View file

@ -11,6 +11,7 @@ use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock};
use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter;
use crate::stylesheets::{CssRule, StylesheetInDocument};
use crate::stylesheets::layer_rule::LayerName;
use crate::values::CssUrl;
use cssparser::SourceLocation;
use std::fmt::{self, Write};
@ -135,6 +136,31 @@ impl DeepCloneWithLock for ImportSheet {
}
}
/// The layer keyword or function in an import rule.
#[derive(Debug)]
pub struct ImportLayer {
/// Whether the layer is anonymous.
pub is_anonymous: bool,
/// The layer name.
pub name: LayerName,
}
impl ToCss for ImportLayer {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.is_anonymous {
dest.write_str("layer")
} else {
dest.write_str("layer(")?;
self.name.to_css(dest)?;
dest.write_char(')')
}
}
}
/// The [`@import`][import] at-rule.
///
/// [import]: https://drafts.csswg.org/css-cascade-3/#at-import
@ -148,6 +174,9 @@ pub struct ImportRule {
/// ImportSheet just has stub behavior until it appears.
pub stylesheet: ImportSheet,
/// A `layer()` function name.
pub layer: Option<ImportLayer>,
/// The line and column of the rule's source code.
pub source_location: SourceLocation,
}
@ -170,6 +199,16 @@ impl DeepCloneWithLock for ImportRule {
ImportRule {
url: self.url.clone(),
stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params),
layer: self.layer.as_ref().map(|layer| {
ImportLayer {
is_anonymous: layer.is_anonymous,
name: if layer.is_anonymous {
LayerName::new_anonymous()
} else {
layer.name.clone()
},
}
}),
source_location: self.source_location.clone(),
}
}
@ -180,14 +219,18 @@ impl ToCssWithGuard for ImportRule {
dest.write_str("@import ")?;
self.url.to_css(&mut CssWriter::new(dest))?;
match self.stylesheet.media(guard) {
Some(media) if !media.is_empty() => {
dest.write_str(" ")?;
if let Some(media) = self.stylesheet.media(guard) {
if !media.is_empty() {
dest.write_char(' ')?;
media.to_css(&mut CssWriter::new(dest))?;
},
_ => {},
};
}
}
dest.write_str(";")
if let Some(ref layer) = self.layer {
dest.write_char(' ')?;
layer.to_css(&mut CssWriter::new(dest))?;
}
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::ImportRule;
use crate::stylesheets::import_rule::{ImportRule, ImportLayer};
use crate::values::CssUrl;
use cssparser::SourceLocation;
use servo_arc::Arc;
@ -25,5 +25,6 @@ pub trait StylesheetLoader {
context: &ParserContext,
lock: &SharedRwLock,
media: Arc<Locked<MediaList>>,
layer: Option<ImportLayer>,
) -> Arc<Locked<ImportRule>>;
}

View file

@ -18,6 +18,7 @@ use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
use crate::stylesheets::keyframes_rule::parse_keyframe_list;
use crate::stylesheets::stylesheet::Namespaces;
use crate::stylesheets::supports_rule::SupportsCondition;
use crate::stylesheets::import_rule::ImportLayer;
use crate::stylesheets::layer_rule::{LayerName, LayerRuleKind};
use crate::stylesheets::viewport_rule;
use crate::stylesheets::AllowImportRules;
@ -169,7 +170,7 @@ pub enum AtRulePrelude {
/// A @document rule, with its conditional.
Document(DocumentCondition),
/// A @import rule prelude.
Import(CssUrl, Arc<Locked<MediaList>>),
Import(CssUrl, Arc<Locked<MediaList>>, Option<ImportLayer>),
/// A @namespace rule prelude.
Namespace(Option<Prefix>, Namespace),
/// A @layer rule prelude.
@ -206,10 +207,29 @@ 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 layer = if !static_prefs::pref!("layout.css.cascade-layers.enabled") {
None
} else if input.try_parse(|input| input.expect_ident_matching("layer")).is_ok() {
Some(ImportLayer {
is_anonymous: true,
name: LayerName::new_anonymous(),
})
} else {
input.try_parse(|input| {
input.expect_function_matching("layer")?;
input.parse_nested_block(|input| {
LayerName::parse(&self.context, input)
}).map(|name| ImportLayer {
is_anonymous: false,
name,
})
}).ok()
};
let media = MediaList::parse(&self.context, input);
let media = Arc::new(self.shared_lock.wrap(media));
return Ok(AtRulePrelude::Import(url, media));
return Ok(AtRulePrelude::Import(url, media, layer));
},
"namespace" => {
if !self.check_state(State::Namespaces) {
@ -263,7 +283,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
start: &ParserState,
) -> Result<Self::AtRule, ()> {
let rule = match prelude {
AtRulePrelude::Import(url, media) => {
AtRulePrelude::Import(url, media, layer) => {
let loader = self
.loader
.expect("Expected a stylesheet loader for @import");
@ -274,6 +294,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
&self.context,
&self.shared_lock,
media,
layer,
);
self.state = State::Imports;

View file

@ -2329,15 +2329,36 @@ impl CascadeData {
continue;
}
fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> u32 {
// TODO: Measure what's more common / expensive, if
// layer.clone() or the double hash lookup in the insert
// case.
if let Some(order) = data.layer_order.get(layer) {
return *order;
}
let order = data.next_layer_order;
data.layer_order.insert(layer.clone(), order);
data.next_layer_order += 1;
order
}
let mut layer_names_to_pop = 0;
let mut children_layer_order = current_layer_order;
match *rule {
CssRule::Import(ref lock) => {
let import_rule = lock.read_with(guard);
if rebuild_kind.should_rebuild_invalidation() {
let import_rule = lock.read_with(guard);
self.effective_media_query_results
.saw_effective(import_rule);
}
if let Some(ref layer) = import_rule.layer {
for name in layer.name.layer_names() {
current_layer.0.push(name.clone());
children_layer_order = maybe_register_layer(self, &current_layer);
layer_names_to_pop += 1;
}
}
},
CssRule::Media(ref lock) => {
@ -2349,19 +2370,6 @@ impl CascadeData {
CssRule::Layer(ref lock) => {
use crate::stylesheets::layer_rule::LayerRuleKind;
fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> u32 {
// TODO: Measure what's more common / expensive, if
// layer.clone() or the double hash lookup in the insert
// case.
if let Some(order) = data.layer_order.get(layer) {
return *order;
}
let order = data.next_layer_order;
data.layer_order.insert(layer.clone(), order);
data.next_layer_order += 1;
order
}
let layer_rule = lock.read_with(guard);
match layer_rule.kind {
LayerRuleKind::Block { ref name, .. } => {