style: Accept a comma-separated list of language codes in the :lang() pseudo

This is the other extension to the :lang() pseudo-class in  Selectors-4.
(Also supported in Safari.)

Depends on D174999

Differential Revision: https://phabricator.services.mozilla.com/D175000
This commit is contained in:
Jonathan Kew 2023-04-12 21:46:47 +00:00 committed by Martin Robinson
parent 13e2d10474
commit a2df8f7ea5
2 changed files with 48 additions and 15 deletions

View file

@ -19,7 +19,7 @@ use dom::{DocumentState, ElementState};
use selectors::parser::SelectorParseErrorKind; use selectors::parser::SelectorParseErrorKind;
use selectors::SelectorList; use selectors::SelectorList;
use std::fmt; use std::fmt;
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss as ToCss_}; use style_traits::{Comma, CssWriter, OneOrMoreSeparated, ParseError, StyleParseErrorKind, ToCss as ToCss_};
pub use crate::gecko::pseudo_element::{ pub use crate::gecko::pseudo_element::{
PseudoElement, EAGER_PSEUDOS, EAGER_PSEUDO_COUNT, PSEUDO_COUNT, PseudoElement, EAGER_PSEUDOS, EAGER_PSEUDO_COUNT, PSEUDO_COUNT,
@ -38,7 +38,13 @@ bitflags! {
} }
/// The type used to store the language argument to the `:lang` pseudo-class. /// The type used to store the language argument to the `:lang` pseudo-class.
pub type Lang = AtomIdent; #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
pub enum Lang {
/// A single language code.
Single(AtomIdent),
/// A list of language codes.
List(Box<Vec<AtomIdent>>),
}
macro_rules! pseudo_class_name { macro_rules! pseudo_class_name {
([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => {
@ -60,6 +66,10 @@ macro_rules! pseudo_class_name {
} }
apply_non_ts_list!(pseudo_class_name); apply_non_ts_list!(pseudo_class_name);
impl OneOrMoreSeparated for AtomIdent {
type S = Comma;
}
impl ToCss for NonTSPseudoClass { impl ToCss for NonTSPseudoClass {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where where
@ -71,7 +81,10 @@ impl ToCss for NonTSPseudoClass {
$(NonTSPseudoClass::$name => concat!(":", $css),)* $(NonTSPseudoClass::$name => concat!(":", $css),)*
NonTSPseudoClass::Lang(ref s) => { NonTSPseudoClass::Lang(ref s) => {
dest.write_str(":lang(")?; dest.write_str(":lang(")?;
cssparser::ToCss::to_css(s, dest)?; match &s {
Lang::Single(lang) => cssparser::ToCss::to_css(lang, dest)?,
Lang::List(list) => list.to_css(&mut CssWriter::new(dest))?,
}
return dest.write_char(')'); return dest.write_char(')');
}, },
NonTSPseudoClass::MozLocaleDir(ref dir) => { NonTSPseudoClass::MozLocaleDir(ref dir) => {
@ -375,8 +388,17 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
) -> Result<NonTSPseudoClass, ParseError<'i>> { ) -> Result<NonTSPseudoClass, ParseError<'i>> {
let pseudo_class = match_ignore_ascii_case! { &name, let pseudo_class = match_ignore_ascii_case! { &name,
"lang" => { "lang" => {
let name = parser.expect_ident_or_string()?; let result = parser.parse_comma_separated(|input| {
NonTSPseudoClass::Lang(Lang::from(name.as_ref())) Ok(AtomIdent::from(input.expect_ident_or_string()?.as_ref()))
})?;
if result.is_empty() {
return Err(parser.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
if result.len() == 1 {
NonTSPseudoClass::Lang(Lang::Single(result[0].clone()))
} else {
NonTSPseudoClass::Lang(Lang::List(Box::new(result)))
}
}, },
"-moz-locale-dir" => { "-moz-locale-dir" => {
NonTSPseudoClass::MozLocaleDir(Direction::parse(parser)?) NonTSPseudoClass::MozLocaleDir(Direction::parse(parser)?)

View file

@ -1560,20 +1560,31 @@ impl<'le> TElement for GeckoElement<'le> {
} }
fn match_element_lang(&self, override_lang: Option<Option<AttrValue>>, value: &Lang) -> bool { fn match_element_lang(&self, override_lang: Option<Option<AttrValue>>, value: &Lang) -> bool {
// Gecko supports :lang() from CSS Selectors 3, which only accepts a // Gecko supports :lang() from CSS Selectors 4, which accepts a list
// single language tag, and which performs simple dash-prefix matching // of language tags, and does BCP47-style range matching.
// on it.
let override_lang_ptr = match override_lang { let override_lang_ptr = match override_lang {
Some(Some(ref atom)) => atom.as_ptr(), Some(Some(ref atom)) => atom.as_ptr(),
_ => ptr::null_mut(), _ => ptr::null_mut(),
}; };
unsafe { match value {
Gecko_MatchLang( Lang::Single(lang) => unsafe {
self.0, Gecko_MatchLang(
override_lang_ptr, self.0,
override_lang.is_some(), override_lang_ptr,
value.as_slice().as_ptr(), override_lang.is_some(),
) lang.as_slice().as_ptr(),
)
},
Lang::List(list) => {
list.iter().any(|lang| unsafe {
Gecko_MatchLang(
self.0,
override_lang_ptr,
override_lang.is_some(),
lang.as_slice().as_ptr(),
)
})
},
} }
} }