diff --git a/README.md b/README.md index 86b1535a..fdb75149 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,11 @@ A CSS parser, transformer, and minifier written in Rust. - `hwb()` color syntax - Percent syntax for opacity - `#rgba` and `#rrggbbaa` hex colors + - Selectors + - `:not` with multiple arguments + - `:lang` with multiple arguments + - `:dir` + - `:is` - Double position gradient stops (e.g. `red 40% 80%`) - `clamp()` function - Alignment shorthands (e.g. `place-items`) diff --git a/scripts/build-prefixes.js b/scripts/build-prefixes.js index 33a80c0f..e48f4e3a 100644 --- a/scripts/build-prefixes.js +++ b/scripts/build-prefixes.js @@ -37,6 +37,27 @@ prefixes['clip-path'].browsers = prefixes['clip-path'].browsers.filter(b => { ); }); +prefixes['any-pseudo'] = { + browsers: Object.entries(mdn.css.selectors.is.__compat.support) + .flatMap(([key, value]) => { + if (Array.isArray(value)) { + key = MDN_BROWSER_MAPPING[key] || key; + let any = value.find(v => v.alternative_name?.includes('-any'))?.version_added; + let supported = value.find(x => x.version_added && !x.alternative_name)?.version_added; + if (any && supported) { + let parts = supported.split('.'); + parts[0]--; + supported = parts.join('.'); + return [`${key} ${any}}`, `${key} ${supported}`]; + } + } + + return []; + }) +} + +console.log(prefixes['any-pseudo']) + let flexSpec = {}; let oldGradient = {}; let p = new Map(); @@ -199,7 +220,23 @@ let mdnFeatures = { logicalTextAlign: mdn.css.properties['text-align']['flow_relative_values_start_and_end'].__compat.support, labColors: mdn.css.types.color.lab.__compat.support, oklabColors: {}, - colorFunction: mdn.css.types.color.color.__compat.support + colorFunction: mdn.css.types.color.color.__compat.support, + anyPseudo: Object.fromEntries( + Object.entries(mdn.css.selectors.is.__compat.support) + .map(([key, value]) => { + if (Array.isArray(value)) { + value = value + .filter(v => v.alternative_name?.includes('-any')) + .map(({alternative_name, ...other}) => other); + } + + if (value && value.length) { + return [key, value]; + } else { + return [key, {version_added: false}]; + } + }) + ) }; for (let feature in mdnFeatures) { @@ -239,6 +276,12 @@ addValue(compat, { ios_saf: parseVersion('10.3') }, 'p3Colors'); +addValue(compat, { + // https://github.com/WebKit/WebKit/commit/baed0d8b0abf366e1d9a6105dc378c59a5f21575 + safari: parseVersion('10.1'), + ios_saf: parseVersion('10.3') +}, 'langList'); + let prefixMapping = { webkit: 'WebKit', moz: 'Moz', @@ -376,6 +419,7 @@ fs.writeFileSync('src/compat.rs', c); function parseVersion(version) { + version = version.replace('≤', ''); let [major, minor = '0', patch = '0'] = version .split('-')[0] .split('.') diff --git a/selectors/builder.rs b/selectors/builder.rs index b428db6b..08cd9711 100644 --- a/selectors/builder.rs +++ b/selectors/builder.rs @@ -313,7 +313,7 @@ where | Component::NonTSPseudoClass(..) => { specificity.class_like_selectors += 1; } - Component::Negation(ref list) | Component::Is(ref list) => { + Component::Negation(ref list) | Component::Is(ref list) | Component::Any(_, ref list) => { // https://drafts.csswg.org/selectors/#specificity-rules: // // The specificity of an :is() pseudo-class is replaced by the diff --git a/selectors/matching.rs b/selectors/matching.rs index ceb8cae9..e51e33a7 100644 --- a/selectors/matching.rs +++ b/selectors/matching.rs @@ -795,14 +795,16 @@ where matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) && matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter) } - Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| { - for selector in &**list { - if matches_complex_selector(selector.iter(), element, context, flags_setter) { - return true; + Component::Is(ref list) | Component::Where(ref list) | Component::Any(_, ref list) => { + context.shared.nest(|context| { + for selector in &**list { + if matches_complex_selector(selector.iter(), element, context, flags_setter) { + return true; + } } - } - false - }), + false + }) + } Component::Negation(ref list) => context.shared.nest_for_negation(|context| { for selector in &**list { if matches_complex_selector(selector.iter(), element, context, flags_setter) { diff --git a/selectors/parser.rs b/selectors/parser.rs index 59dcdbb6..1c2ed61b 100644 --- a/selectors/parser.rs +++ b/selectors/parser.rs @@ -223,6 +223,7 @@ macro_rules! with_all_bounds { /// non tree-structural pseudo-classes /// (see: https://drafts.csswg.org/selectors/#structural-pseudos) type NonTSPseudoClass: $($CommonBounds)* + NonTSPseudoClass<'i, Impl = Self>; + type VendorPrefix: Sized + Eq + Clone + ToCss; /// pseudo-elements type PseudoElement: $($CommonBounds)* + PseudoElement<'i, Impl = Self>; @@ -274,8 +275,8 @@ pub trait Parser<'i> { } /// Whether the given function name is an alias for the `:is()` function. - fn is_is_alias(&self, _name: &str) -> bool { - false + fn parse_any_prefix(&self, _name: &str) -> Option<>::VendorPrefix> { + None } /// Whether to parse the `:host` pseudo-class. @@ -640,6 +641,16 @@ impl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> { self.0.is_part() } + #[inline] + pub fn append(&mut self, component: Component<'i, Impl>) { + let index = self + .1 + .iter() + .position(|c| matches!(*c, Component::Combinator(..) | Component::PseudoElement(..))) + .unwrap_or(self.1.len()); + self.1.insert(index, component); + } + #[inline] pub fn parts(&self) -> Option<&[Impl::Identifier]> { if !self.is_part() { @@ -697,6 +708,13 @@ impl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> { }) } + #[inline] + pub fn has_combinator(&self) -> bool { + self + .iter_raw_match_order() + .any(|c| matches!(*c, Component::Combinator(combinator) if combinator.is_tree_combinator())) + } + /// Returns an iterator over this selector in matching order (right-to-left). /// When a combinator is reached, the iterator will return None, and /// next_sequence() may be called to continue to the next sequence. @@ -759,6 +777,11 @@ impl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> { self.1.iter() } + #[inline] + pub fn iter_mut_raw_match_order(&mut self) -> slice::IterMut> { + self.1.iter_mut() + } + /// Returns the combinator at index `index` (zero-indexed from the left), /// or panics if the component is not a combinator. #[inline] @@ -1032,6 +1055,14 @@ impl Combinator { pub fn is_sibling(&self) -> bool { matches!(*self, Combinator::NextSibling | Combinator::LaterSibling) } + + #[inline] + pub fn is_tree_combinator(&self) -> bool { + matches!( + *self, + Combinator::Child | Combinator::Descendant | Combinator::NextSibling | Combinator::LaterSibling + ) + } } /// A CSS simple selector or combinator. We store both in the same enum for @@ -1122,6 +1153,7 @@ pub enum Component<'i, Impl: SelectorImpl<'i>> { /// /// Same comment as above re. the argument. Is(Box<[Selector<'i, Impl>]>), + Any(Impl::VendorPrefix, Box<[Selector<'i, Impl>]>), /// The `:has` pseudo-class. /// /// https://www.w3.org/TR/selectors/#relational @@ -1585,12 +1617,17 @@ impl<'i, Impl: SelectorImpl<'i>> ToCss for Component<'i, Impl> { write_affine(dest, a, b)?; dest.write_char(')') } - Is(ref list) | Where(ref list) | Negation(ref list) | Has(ref list) => { + Is(ref list) | Where(ref list) | Negation(ref list) | Has(ref list) | Any(_, ref list) => { match *self { Where(..) => dest.write_str(":where(")?, Is(..) => dest.write_str(":is(")?, Negation(..) => dest.write_str(":not(")?, Has(..) => dest.write_str(":has(")?, + Any(ref prefix, _) => { + dest.write_char(':')?; + prefix.to_css(dest)?; + dest.write_str("any(")?; + } _ => unreachable!(), } serialize_selector_list(list.iter(), dest)?; @@ -2333,6 +2370,7 @@ where } Ok(Component::Has(inner.0.into_vec().into_boxed_slice())) } + fn parse_functional_pseudo_class<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, @@ -2363,8 +2401,8 @@ where _ => {} } - if parser.parse_is_and_where() && parser.is_is_alias(&name) { - return parse_is_or_where(parser, input, state, Component::Is); + if let Some(prefix) = parser.parse_any_prefix(&name) { + return parse_is_or_where(parser, input, state, |selectors| Component::Any(prefix, selectors)); } if !state.allows_custom_functional_pseudo_classes() { @@ -2679,6 +2717,7 @@ pub mod tests { type BorrowedNamespaceUrl = DummyAtom; type NonTSPseudoClass = PseudoClass; type PseudoElement = PseudoElement; + type VendorPrefix = u8; } #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] diff --git a/src/compat.rs b/src/compat.rs index 4ceff057..0944e0cb 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -4,6 +4,7 @@ use crate::targets::Browsers; #[derive(Clone, Copy, PartialEq)] pub enum Feature { + AnyPseudo, Clamp, ColorFunction, CssAnyLink, @@ -38,6 +39,7 @@ pub enum Feature { FormValidation, Fullscreen, LabColors, + LangList, LogicalBorderRadius, LogicalBorders, LogicalInset, @@ -1745,7 +1747,52 @@ impl Feature { return false; } } - Feature::P3Colors => { + Feature::AnyPseudo => { + if let Some(version) = browsers.chrome { + if version < 1179648 { + return false; + } + } + if let Some(version) = browsers.edge { + if version < 5177344 { + return false; + } + } + if let Some(version) = browsers.firefox { + if version < 262144 { + return false; + } + } + if let Some(version) = browsers.opera { + if version < 917504 { + return false; + } + } + if let Some(version) = browsers.safari { + if version < 327680 { + return false; + } + } + if let Some(version) = browsers.ios_saf { + if version < 327680 { + return false; + } + } + if let Some(version) = browsers.samsung { + if version < 65536 { + return false; + } + } + if let Some(version) = browsers.android { + if version < 2424832 { + return false; + } + } + if browsers.ie.is_some() { + return false; + } + } + Feature::P3Colors | Feature::LangList => { if let Some(version) = browsers.safari { if version < 655616 { return false; diff --git a/src/context.rs b/src/context.rs index e4ca8f05..21970490 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,22 +1,13 @@ use crate::compat::Feature; -use crate::declaration::{DeclarationBlock, DeclarationList}; -use crate::logical::LogicalProperty; +use crate::declaration::DeclarationBlock; use crate::properties::custom::UnparsedProperty; -use crate::properties::{ - custom::{CustomProperty, Token, TokenList}, - Property, PropertyId, -}; +use crate::properties::Property; use crate::rules::supports::{SupportsCondition, SupportsRule}; -use crate::rules::Location; use crate::rules::{style::StyleRule, CssRule, CssRuleList}; -use crate::selector::{SelectorIdent, SelectorString}; +use crate::selector::{Direction, PseudoClass}; use crate::targets::Browsers; use crate::vendor_prefix::VendorPrefix; -use parcel_selectors::SelectorList; -use parcel_selectors::{ - attr::{AttrSelectorOperator, ParsedCaseSensitivity}, - parser::{Component, Selector}, -}; +use parcel_selectors::parser::Component; #[derive(Debug)] pub(crate) struct SupportsEntry<'i> { @@ -36,9 +27,10 @@ pub(crate) enum DeclarationContext { #[derive(Debug)] pub(crate) struct PropertyHandlerContext<'i> { pub targets: Option, - pub used_logical: bool, pub is_important: bool, supports: Vec>, + ltr: Vec>, + rtl: Vec>, pub context: DeclarationContext, } @@ -46,9 +38,10 @@ impl<'i> PropertyHandlerContext<'i> { pub fn new(targets: Option) -> Self { PropertyHandlerContext { targets, - used_logical: false, is_important: false, supports: Vec::new(), + ltr: Vec::new(), + rtl: Vec::new(), context: DeclarationContext::None, } } @@ -67,85 +60,46 @@ impl<'i> PropertyHandlerContext<'i> { } } - pub fn add_logical_property( - &mut self, - dest: &mut DeclarationList<'i>, - property_id: PropertyId<'i>, - ltr: Property<'i>, - rtl: Property<'i>, - ) { - self.used_logical = true; - dest.push(Property::Logical(LogicalProperty { - property_id, - ltr: Some(Box::new(ltr)), - rtl: Some(Box::new(rtl)), - })); + pub fn add_logical_rule(&mut self, ltr: Property<'i>, rtl: Property<'i>) { + self.ltr.push(ltr); + self.rtl.push(rtl); } - pub fn add_inline_logical_properties( - &mut self, - dest: &mut DeclarationList<'i>, - left: PropertyId<'i>, - right: PropertyId<'i>, - start: Option>, - end: Option>, - ) { - self.used_logical = true; - dest.push(Property::Logical(LogicalProperty { - property_id: left, - ltr: start.clone().map(|v| Box::new(v)), - rtl: end.clone().map(|v| Box::new(v)), - })); - - dest.push(Property::Logical(LogicalProperty { - property_id: right, - ltr: end.map(|v| Box::new(v)), - rtl: start.map(|v| Box::new(v)), - })); - } + pub fn get_logical_rules(&mut self, style_rule: &StyleRule<'i>) -> Vec> { + // TODO: :dir/:lang raises the specificity of the selector. Use :where to lower it? + let mut dest = Vec::new(); - pub fn add_logical_rules(&mut self, dest: &mut CssRuleList) { - // Generate rules for [dir="ltr"] and [dir="rtl"] to define --ltr and --rtl vars. - macro_rules! style_rule { - ($dir: ident, $ltr: expr, $rtl: expr) => { - dest.0.push(CssRule::Style(StyleRule { - selectors: SelectorList(smallvec::smallvec![Selector::from_vec2(vec![ - Component::AttributeInNoNamespace { - local_name: SelectorIdent("dir".into()), - operator: AttrSelectorOperator::Equal, - value: SelectorString(stringify!($dir).into()), - case_sensitivity: ParsedCaseSensitivity::CaseSensitive, - never_matches: false - } - ])]), - rules: CssRuleList(vec![]), - vendor_prefix: VendorPrefix::empty(), + macro_rules! rule { + ($dir: ident, $decls: ident) => { + let mut selectors = style_rule.selectors.clone(); + for selector in &mut selectors.0 { + selector.append(Component::NonTSPseudoClass(PseudoClass::Dir(Direction::$dir))); + } + + let rule = StyleRule { + selectors, + vendor_prefix: VendorPrefix::None, declarations: DeclarationBlock { + declarations: std::mem::take(&mut self.$decls), important_declarations: vec![], - declarations: vec![ - Property::Custom(CustomProperty { - name: "--ltr".into(), - value: TokenList(vec![$ltr.into()]), - }), - Property::Custom(CustomProperty { - name: "--rtl".into(), - value: TokenList(vec![$rtl.into()]), - }), - ], - }, - loc: Location { - source_index: 0, - line: 0, - column: 1, }, - })); + rules: CssRuleList(vec![]), + loc: style_rule.loc.clone(), + }; + + dest.push(CssRule::Style(rule)); }; } - if self.used_logical { - style_rule!(ltr, Token::Ident("initial".into()), Token::WhiteSpace(" ")); - style_rule!(rtl, Token::WhiteSpace(" "), Token::Ident("initial".into())); + if !self.ltr.is_empty() { + rule!(Ltr, ltr); + } + + if !self.rtl.is_empty() { + rule!(Rtl, rtl); } + + dest } pub fn add_conditional_property(&mut self, condition: SupportsCondition<'i>, property: Property<'i>) { diff --git a/src/declaration.rs b/src/declaration.rs index fc17c210..d551da95 100644 --- a/src/declaration.rs +++ b/src/declaration.rs @@ -125,6 +125,10 @@ impl<'i> DeclarationBlock<'i> { self.important_declarations = std::mem::take(&mut important_handler.decls); self.declarations = std::mem::take(&mut handler.decls); } + + pub fn is_empty(&self) -> bool { + return self.declarations.is_empty() && self.important_declarations.is_empty(); + } } struct PropertyDeclarationParser<'a, 'i> { @@ -192,10 +196,10 @@ pub(crate) struct DeclarationHandler<'i> { grid: GridHandler<'i>, align: AlignHandler, size: SizeHandler, - margin: MarginHandler, - padding: PaddingHandler, - scroll_margin: ScrollMarginHandler, - scroll_padding: ScrollPaddingHandler, + margin: MarginHandler<'i>, + padding: PaddingHandler<'i>, + scroll_margin: ScrollMarginHandler<'i>, + scroll_padding: ScrollPaddingHandler<'i>, font: FontHandler<'i>, text: TextDecorationHandler<'i>, list: ListStyleHandler<'i>, @@ -203,7 +207,7 @@ pub(crate) struct DeclarationHandler<'i> { animation: AnimationHandler<'i>, display: DisplayHandler<'i>, position: PositionHandler, - inset: InsetHandler, + inset: InsetHandler<'i>, overflow: OverflowHandler, transform: TransformHandler, box_shadow: BoxShadowHandler, diff --git a/src/lib.rs b/src/lib.rs index 28fa8f04..1b765b8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -863,19 +863,16 @@ mod tests { } "#, indoc! {r#" - .foo { - border-left: var(--ltr, 2px solid red); - border-right: var(--rtl, 2px solid red); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-left: 2px solid red; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-right: 2px solid red; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-right: 2px solid red; } "# }, @@ -892,19 +889,16 @@ mod tests { } "#, indoc! {r#" - .foo { - border-left-width: var(--ltr, 2px); - border-right-width: var(--rtl, 2px); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-left-width: 2px; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-right-width: 2px; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-right-width: 2px; } "# }, @@ -921,19 +915,16 @@ mod tests { } "#, indoc! {r#" - .foo { - border-right: var(--ltr, 2px solid red); - border-left: var(--rtl, 2px solid red); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-right: 2px solid red; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: 2px solid red; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: 2px solid red; } "# }, @@ -951,19 +942,19 @@ mod tests { } "#, indoc! {r#" - .foo { - border-left: var(--ltr, 2px solid red) var(--rtl, 5px solid green); - border-right: var(--ltr, 5px solid green) var(--rtl, 2px solid red); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-left: 2px solid red; + border-right: 5px solid green; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: 5px solid green; + border-right: 2px solid red; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: 5px solid green; + border-right: 2px solid red; } "# }, @@ -986,24 +977,34 @@ mod tests { } "#, indoc! {r#" - .foo { - border-left: var(--ltr, 2px solid red) var(--rtl, 5px solid green); - border-right: var(--ltr, 5px solid green) var(--rtl, 2px solid red); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-left: 2px solid red; + border-right: 5px solid green; } - .bar { - border-left: var(--ltr, 1px dotted gray) var(--rtl, 1px solid #000); - border-right: var(--ltr, 1px solid #000) var(--rtl, 1px dotted gray); + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: 5px solid green; + border-right: 2px solid red; + } + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: 5px solid green; + border-right: 2px solid red; + } + + .bar:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-left: 1px dotted gray; + border-right: 1px solid #000; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .bar:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: 1px solid #000; + border-right: 1px dotted gray; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .bar:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: 1px solid #000; + border-right: 1px dotted gray; } "# }, @@ -1020,19 +1021,16 @@ mod tests { } "#, indoc! {r#" - .foo { - border-right: var(--ltr, var(--test)); - border-left: var(--rtl, var(--test)); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-right: var(--test); } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: var(--test); } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: var(--test); } "# }, @@ -1050,19 +1048,19 @@ mod tests { } "#, indoc! {r#" - .foo { - border-left: var(--ltr, var(--start)) var(--rtl, var(--end)); - border-right: var(--ltr, var(--end)) var(--rtl, var(--start)); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-left: var(--start); + border-right: var(--end); } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-right: var(--start); + border-left: var(--end); } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-right: var(--start); + border-left: var(--end); } "# }, @@ -1198,26 +1196,19 @@ mod tests { } "#, indoc! {r#" - .foo { - border-left-color: var(--ltr, #b32323); - border-right-color: var(--rtl, #b32323); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-left-color: #b32323; + border-left-color: lab(40% 56.6 39); } - @supports (color: lab(0% 0 0)) { - .foo { - border-left-color: var(--ltr, lab(40% 56.6 39)); - border-right-color: var(--rtl, lab(40% 56.6 39)); - } + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-right-color: #b32323; + border-right-color: lab(40% 56.6 39); } - - [dir="ltr"] { - --ltr: initial; - --rtl: ; - } - - [dir="rtl"] { - --ltr: ; - --rtl: initial; + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-right-color: #b32323; + border-right-color: lab(40% 56.6 39); } "#}, Browsers { @@ -1233,26 +1224,19 @@ mod tests { } "#, indoc! {r#" - .foo { - border-right-color: var(--ltr, #b32323); - border-left-color: var(--rtl, #b32323); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-right-color: #b32323; + border-right-color: lab(40% 56.6 39); } - @supports (color: lab(0% 0 0)) { - .foo { - border-left-color: var(--rtl, lab(40% 56.6 39)); - border-right-color: var(--ltr, lab(40% 56.6 39)); - } + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left-color: #b32323; + border-left-color: lab(40% 56.6 39); } - - [dir="ltr"] { - --ltr: initial; - --rtl: ; - } - - [dir="rtl"] { - --ltr: ; - --rtl: initial; + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left-color: #b32323; + border-left-color: lab(40% 56.6 39); } "#}, Browsers { @@ -1269,26 +1253,25 @@ mod tests { } "#, indoc! {r#" - .foo { - border-left-color: var(--ltr, #b32323) var(--rtl, #ee00be); - border-right-color: var(--ltr, #ee00be) var(--rtl, #b32323); - } - - @supports (color: lab(0% 0 0)) { - .foo { - border-left-color: var(--ltr, lab(40% 56.6 39)) var(--rtl, lab(50.998% 125.506 -50.7078)); - border-right-color: var(--ltr, lab(50.998% 125.506 -50.7078)) var(--rtl, lab(40% 56.6 39)); - } + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-left-color: #b32323; + border-left-color: lab(40% 56.6 39); + border-right-color: #ee00be; + border-right-color: lch(50.998% 135.363 338); } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left-color: #ee00be; + border-left-color: lch(50.998% 135.363 338); + border-right-color: #b32323; + border-right-color: lab(40% 56.6 39); } - - [dir="rtl"] { - --ltr: ; - --rtl: initial; + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left-color: #ee00be; + border-left-color: lch(50.998% 135.363 338); + border-right-color: #b32323; + border-right-color: lab(40% 56.6 39); } "#}, Browsers { @@ -1305,33 +1288,22 @@ mod tests { } "#, indoc! {r#" - .foo { - border-left-color: var(--ltr, #b32323) var(--rtl, #ee00be); - border-right-color: var(--ltr, #ee00be) var(--rtl, #b32323); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-left-color: #b32323; + border-left-color: color(display-p3 .643308 .192455 .167712); + border-left-color: lab(40% 56.6 39); + border-right-color: #ee00be; + border-right-color: color(display-p3 .972962 -.362078 .804206); + border-right-color: lch(50.998% 135.363 338); } - - @supports (color: color(display-p3 0 0 0)) { - .foo { - border-left-color: var(--ltr, color(display-p3 .643308 .192455 .167712)) var(--rtl, color(display-p3 .972962 -.362078 .804206)); - border-right-color: var(--ltr, color(display-p3 .972962 -.362078 .804206)) var(--rtl, color(display-p3 .643308 .192455 .167712)); - } - } - - @supports (color: lab(0% 0 0)) { - .foo { - border-left-color: var(--ltr, lab(40% 56.6 39)) var(--rtl, lab(50.998% 125.506 -50.7078)); - border-right-color: var(--ltr, lab(50.998% 125.506 -50.7078)) var(--rtl, lab(40% 56.6 39)); - } - } - - [dir="ltr"] { - --ltr: initial; - --rtl: ; - } - - [dir="rtl"] { - --ltr: ; - --rtl: initial; + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left-color: #ee00be; + border-left-color: color(display-p3 .972962 -.362078 .804206); + border-left-color: lch(50.998% 135.363 338); + border-right-color: #b32323; + border-right-color: color(display-p3 .643308 .192455 .167712); + border-right-color: lab(40% 56.6 39); } "#}, Browsers { @@ -1348,26 +1320,19 @@ mod tests { } "#, indoc! {r#" - .foo { - border-left: var(--ltr, 2px solid #b32323); - border-right: var(--rtl, 2px solid #b32323); - } - - @supports (color: lab(0% 0 0)) { - .foo { - border-left: var(--ltr, 2px solid lab(40% 56.6 39)); - border-right: var(--rtl, 2px solid lab(40% 56.6 39)); - } + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-left: 2px solid #b32323; + border-left: 2px solid lab(40% 56.6 39); } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-right: 2px solid #b32323; + border-right: 2px solid lab(40% 56.6 39); } - - [dir="rtl"] { - --ltr: ; - --rtl: initial; + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-right: 2px solid #b32323; + border-right: 2px solid lab(40% 56.6 39); } "#}, Browsers { @@ -1383,26 +1348,19 @@ mod tests { } "#, indoc! {r#" - .foo { - border-right: var(--ltr, 2px solid #b32323); - border-left: var(--rtl, 2px solid #b32323); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-right: 2px solid #b32323; + border-right: 2px solid lab(40% 56.6 39); } - @supports (color: lab(0% 0 0)) { - .foo { - border-left: var(--rtl, 2px solid lab(40% 56.6 39)); - border-right: var(--ltr, 2px solid lab(40% 56.6 39)); - } - } - - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: 2px solid #b32323; + border-left: 2px solid lab(40% 56.6 39); } - - [dir="rtl"] { - --ltr: ; - --rtl: initial; + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: 2px solid #b32323; + border-left: 2px solid lab(40% 56.6 39); } "#}, Browsers { @@ -1418,26 +1376,28 @@ mod tests { } "#, indoc! {r#" - .foo { - border-right: var(--ltr, var(--border-width) solid #b32323); - border-left: var(--rtl, var(--border-width) solid #b32323); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-right: var(--border-width) solid #b32323; } @supports (color: lab(0% 0 0)) { - .foo { - border-left: var(--rtl, var(--border-width) solid lab(40% 56.6 39)); - border-right: var(--ltr, var(--border-width) solid lab(40% 56.6 39)); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + border-right: var(--border-width) solid lab(40% 56.6 39); } } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: var(--border-width) solid #b32323; + } + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: var(--border-width) solid #b32323; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + @supports (color: lab(0% 0 0)) { + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + border-left: var(--border-width) solid lab(40% 56.6 39); + } } "#}, Browsers { @@ -2130,19 +2090,12 @@ mod tests { } "#, indoc! {r#" - .foo { - border-top-left-radius: var(--ltr, 5px); - border-top-right-radius: var(--rtl, 5px); + .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) { + border-top-left-radius: 5px; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; - } - - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) { + border-top-right-radius: 5px; } "# }, @@ -2160,19 +2113,14 @@ mod tests { } "#, indoc! {r#" - .foo { - border-top-left-radius: var(--ltr, 5px) var(--rtl, 10px); - border-top-right-radius: var(--ltr, 10px) var(--rtl, 5px); + .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) { + border-top-left-radius: 5px; + border-top-right-radius: 10px; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; - } - - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) { + border-top-left-radius: 10px; + border-top-right-radius: 5px; } "# }, @@ -2190,19 +2138,14 @@ mod tests { } "#, indoc! {r#" - .foo { - border-bottom-left-radius: var(--ltr, 5px) var(--rtl, 10px); - border-bottom-right-radius: var(--ltr, 10px) var(--rtl, 5px); - } - - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 10px; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) { + border-bottom-left-radius: 10px; + border-bottom-right-radius: 5px; } "# }, @@ -2219,19 +2162,12 @@ mod tests { } "#, indoc! {r#" - .foo { - border-top-left-radius: var(--ltr, var(--radius)); - border-top-right-radius: var(--rtl, var(--radius)); - } - - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) { + border-top-left-radius: var(--radius); } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) { + border-top-right-radius: var(--radius); } "# }, @@ -2249,19 +2185,14 @@ mod tests { } "#, indoc! {r#" - .foo { - border-top-left-radius: var(--ltr, var(--start)) var(--rtl, var(--end)); - border-top-right-radius: var(--ltr, var(--end)) var(--rtl, var(--start)); - } - - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) { + border-top-left-radius: var(--start); + border-top-right-radius: var(--end); } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) { + border-top-right-radius: var(--start); + border-top-left-radius: var(--end); } "# }, @@ -2479,19 +2410,16 @@ mod tests { } "#, indoc! {r#" - .foo { - margin-left: var(--ltr, 2px); - margin-right: var(--rtl, 2px); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + margin-left: 2px; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + margin-right: 2px; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + margin-right: 2px; } "# }, @@ -2509,19 +2437,19 @@ mod tests { } "#, indoc! {r#" - .foo { - margin-left: var(--ltr, 2px) var(--rtl, 4px); - margin-right: var(--ltr, 4px) var(--rtl, 2px); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + margin-left: 2px; + margin-right: 4px; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + margin-left: 4px; + margin-right: 2px; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + margin-left: 4px; + margin-right: 2px; } "# }, @@ -2652,19 +2580,16 @@ mod tests { } "#, indoc! {r#" - .foo { - padding-left: var(--ltr, 2px); - padding-right: var(--rtl, 2px); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + padding-left: 2px; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + padding-right: 2px; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + padding-right: 2px; } "# }, @@ -2682,19 +2607,45 @@ mod tests { } "#, indoc! {r#" + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + padding-left: 2px; + padding-right: 4px; + } + + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + padding-left: 4px; + padding-right: 2px; + } + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + padding-left: 4px; + padding-right: 2px; + } + "# + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + r#" .foo { - padding-left: var(--ltr, 2px) var(--rtl, 4px); - padding-right: var(--ltr, 4px) var(--rtl, 2px); + padding-inline-start: var(--padding); + } + "#, + indoc! {r#" + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + padding-left: var(--padding); } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + padding-right: var(--padding); } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + padding-right: var(--padding); } "# }, @@ -4554,38 +4505,53 @@ mod tests { #[test] fn test_selectors() { - minify_test("[foo=\"baz\"] {}", "[foo=baz]{}"); - minify_test("[foo=\"foo bar\"] {}", "[foo=foo\\ bar]{}"); - minify_test("[foo=\"foo bar baz\"] {}", "[foo=\"foo bar baz\"]{}"); - minify_test("[foo=\"\"] {}", "[foo=\"\"]{}"); - minify_test(".test:not([foo=\"bar\"]) {}", ".test:not([foo=bar]){}"); - minify_test(".test + .foo {}", ".test+.foo{}"); - minify_test(".test ~ .foo {}", ".test~.foo{}"); - minify_test(".test .foo {}", ".test .foo{}"); - minify_test( - ".custom-range::-webkit-slider-thumb:active {}", - ".custom-range::-webkit-slider-thumb:active{}", - ); - minify_test(".test:not(.foo, .bar) {}", ".test:not(.foo,.bar){}"); - minify_test(".test:is(.foo, .bar) {}", ".test:is(.foo,.bar){}"); - minify_test(".test:where(.foo, .bar) {}", ".test:where(.foo,.bar){}"); - minify_test(".test:where(.foo, .bar) {}", ".test:where(.foo,.bar){}"); - minify_test(":host {}", ":host{}"); - minify_test(":host(.foo) {}", ":host(.foo){}"); - minify_test("::slotted(span) {}", "::slotted(span){}"); - minify_test("custom-element::part(foo) {}", "custom-element::part(foo){}"); + minify_test("[foo=\"baz\"] {color:red}", "[foo=baz]{color:red}"); + minify_test("[foo=\"foo bar\"] {color:red}", "[foo=foo\\ bar]{color:red}"); + minify_test("[foo=\"foo bar baz\"] {color:red}", "[foo=\"foo bar baz\"]{color:red}"); + minify_test("[foo=\"\"] {color:red}", "[foo=\"\"]{color:red}"); + minify_test( + ".test:not([foo=\"bar\"]) {color:red}", + ".test:not([foo=bar]){color:red}", + ); + minify_test(".test + .foo {color:red}", ".test+.foo{color:red}"); + minify_test(".test ~ .foo {color:red}", ".test~.foo{color:red}"); + minify_test(".test .foo {color:red}", ".test .foo{color:red}"); + minify_test( + ".custom-range::-webkit-slider-thumb:active {color:red}", + ".custom-range::-webkit-slider-thumb:active{color:red}", + ); + minify_test(".test:not(.foo, .bar) {color:red}", ".test:not(.foo,.bar){color:red}"); + minify_test(".test:is(.foo, .bar) {color:red}", ".test:is(.foo,.bar){color:red}"); + minify_test( + ".test:where(.foo, .bar) {color:red}", + ".test:where(.foo,.bar){color:red}", + ); + minify_test( + ".test:where(.foo, .bar) {color:red}", + ".test:where(.foo,.bar){color:red}", + ); + minify_test(":host {color:red}", ":host{color:red}"); + minify_test(":host(.foo) {color:red}", ":host(.foo){color:red}"); + minify_test("::slotted(span) {color:red", "::slotted(span){color:red}"); + minify_test( + "custom-element::part(foo) {color:red}", + "custom-element::part(foo){color:red}", + ); minify_test(".sm\\:text-5xl { font-size: 3rem }", ".sm\\:text-5xl{font-size:3rem}"); - minify_test("a:has(> img) {}", "a:has(>img){}"); - minify_test("dt:has(+ dt) {}", "dt:has(+dt){}"); + minify_test("a:has(> img) {color:red}", "a:has(>img){color:red}"); + minify_test("dt:has(+ dt) {color:red}", "dt:has(+dt){color:red}"); + minify_test( + "section:not(:has(h1, h2, h3, h4, h5, h6)) {color:red}", + "section:not(:has(h1,h2,h3,h4,h5,h6)){color:red}", + ); minify_test( - "section:not(:has(h1, h2, h3, h4, h5, h6)) {}", - "section:not(:has(h1,h2,h3,h4,h5,h6)){}", + ":has(.sibling ~ .target) {color:red}", + ":has(.sibling~.target){color:red}", ); - minify_test(":has(.sibling ~ .target) {}", ":has(.sibling~.target){}"); - minify_test(".x:has(> .a > .b) {}", ".x:has(>.a>.b){}"); - minify_test(".x:has(.bar, #foo) {}", ".x:has(.bar,#foo){}"); - minify_test(".x:has(span + span) {}", ".x:has(span+span){}"); - minify_test("a:has(:visited) {}", "a:has(:visited){}"); + minify_test(".x:has(> .a > .b) {color:red}", ".x:has(>.a>.b){color:red}"); + minify_test(".x:has(.bar, #foo) {color:red}", ".x:has(.bar,#foo){color:red}"); + minify_test(".x:has(span + span) {color:red}", ".x:has(span+span){color:red}"); + minify_test("a:has(:visited) {color:red}", "a:has(:visited){color:red}"); for element in [ "-webkit-scrollbar", "-webkit-scrollbar-button", @@ -4609,8 +4575,8 @@ mod tests { "window-inactive", ] { minify_test( - &format!("::{}:{} {{}}", element, class), - &format!("::{}:{}{{}}", element, class), + &format!("::{}:{} {{color:red}}", element, class), + &format!("::{}:{}{{color:red}}", element, class), ); } } @@ -4628,7 +4594,7 @@ mod tests { "window-inactive", ] { error_test( - &format!(":{} {{}}", class), + &format!(":{} {{color:red}}", class), ParserError::SelectorError(SelectorError::InvalidPseudoClassBeforeWebKitScrollbar), ); } @@ -4642,21 +4608,25 @@ mod tests { "-webkit-resizer", ] { error_test( - &format!("::{}:hover {{}}", element), + &format!("::{}:hover {{color:red}}", element), ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterWebKitScrollbar), ); } error_test( - "a::first-letter:last-child {}", + "a::first-letter:last-child {color:red}", ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterPseudoElement), ); - minify_test("a:last-child::first-letter {}", "a:last-child:first-letter{}"); + minify_test( + "a:last-child::first-letter {color:red}", + "a:last-child:first-letter{color:red}", + ); prefix_test( - ".test:not(.foo, .bar) {}", + ".test:not(.foo, .bar) {color:red}", indoc! {r#" .test:not(.foo):not(.bar) { + color: red; } "#}, Browsers { @@ -4665,9 +4635,10 @@ mod tests { }, ); prefix_test( - ".test:not(.foo, .bar) {}", + ".test:not(.foo, .bar) {color:red}", indoc! {r#" .test:not(.foo, .bar) { + color: red; } "#}, Browsers { @@ -4675,6 +4646,190 @@ mod tests { ..Browsers::default() }, ); + + minify_test("a:lang(en) {color:red}", "a:lang(en){color:red}"); + minify_test("a:lang(en, fr) {color:red}", "a:lang(en,fr){color:red}"); + minify_test("a:lang('en') {color:red}", "a:lang(en){color:red}"); + minify_test( + "a:-webkit-any(.foo, .bar) {color:red}", + "a:-webkit-any(.foo,.bar){color:red}", + ); + minify_test("a:-moz-any(.foo, .bar) {color:red}", "a:-moz-any(.foo,.bar){color:red}"); + + prefix_test( + "a:is(.foo, .bar) {color:red}", + indoc! {r#" + a:-webkit-any(.foo, .bar) { + color: red; + } + + a:-moz-any(.foo, .bar) { + color: red; + } + + a:is(.foo, .bar) { + color: red; + } + "#}, + Browsers { + safari: Some(11 << 16), + firefox: Some(50 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + "a:is(.foo > .bar) {color:red}", + indoc! {r#" + a:is(.foo > .bar) { + color: red; + } + "#}, + Browsers { + safari: Some(11 << 16), + firefox: Some(50 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + "a:lang(en, fr) {color:red}", + indoc! {r#" + a:-webkit-any(:lang(en), :lang(fr)) { + color: red; + } + + a:-moz-any(:lang(en), :lang(fr)) { + color: red; + } + + a:is(:lang(en), :lang(fr)) { + color: red; + } + "#}, + Browsers { + safari: Some(11 << 16), + firefox: Some(50 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + "a:lang(en, fr) {color:red}", + indoc! {r#" + a:is(:lang(en), :lang(fr)) { + color: red; + } + "#}, + Browsers { + safari: Some(14 << 16), + firefox: Some(88 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + "a:lang(en, fr) {color:red}", + indoc! {r#" + a:lang(en, fr) { + color: red; + } + "#}, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + "a:dir(rtl) {color:red}", + indoc! {r#" + a:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + color: red; + } + + a:-moz-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + color: red; + } + + a:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + color: red; + } + "#}, + Browsers { + safari: Some(11 << 16), + firefox: Some(50 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + "a:dir(ltr) {color:red}", + indoc! {r#" + a:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + color: red; + } + "#}, + Browsers { + safari: Some(11 << 16), + firefox: Some(50 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + "a:dir(rtl) {color:red}", + indoc! {r#" + a:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + color: red; + } + "#}, + Browsers { + safari: Some(14 << 16), + firefox: Some(88 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + "a:dir(ltr) {color:red}", + indoc! {r#" + a:not(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + color: red; + } + "#}, + Browsers { + safari: Some(14 << 16), + firefox: Some(88 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + "a:dir(rtl) {color:red}", + indoc! {r#" + a:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) { + color: red; + } + "#}, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + "a:dir(ltr) {color:red}", + indoc! {r#" + a:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) { + color: red; + } + "#}, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); } #[test] @@ -6602,18 +6757,16 @@ mod tests { } "#, indoc! {r#" - .foo { - transition-property: var(--ltr, margin-left) var(--rtl, margin-right); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + transition-property: margin-left; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right; } "# }, @@ -6630,18 +6783,16 @@ mod tests { } "#, indoc! {r#" - .foo { - transition-property: var(--ltr, margin-left, padding-left) var(--rtl, margin-right, padding-right); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + transition-property: margin-left, padding-left; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right, padding-right; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right, padding-right; } "# }, @@ -6658,18 +6809,16 @@ mod tests { } "#, indoc! {r#" - .foo { - transition-property: var(--ltr, margin-left, opacity, padding-left, color) var(--rtl, margin-right, opacity, padding-right, color); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + transition-property: margin-left, opacity, padding-left, color; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right, opacity, padding-right, color; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right, opacity, padding-right, color; } "# }, @@ -6704,18 +6853,16 @@ mod tests { } "#, indoc! {r#" - .foo { - transition: var(--ltr, margin-left 2s) var(--rtl, margin-right 2s); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + transition: margin-left 2s; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition: margin-right 2s; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition: margin-right 2s; } "# }, @@ -6732,18 +6879,16 @@ mod tests { } "#, indoc! {r#" - .foo { - transition: var(--ltr, margin-left 2s, padding-left 2s) var(--rtl, margin-right 2s, padding-right 2s); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + transition: margin-left 2s, padding-left 2s; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition: margin-right 2s, padding-right 2s; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition: margin-right 2s, padding-right 2s; } "# }, @@ -6797,24 +6942,41 @@ mod tests { } "#, indoc! {r#" - .foo { - -webkit-transition: var(--ltr, border-top-left-radius) var(--rtl, border-top-right-radius); - transition: var(--ltr, border-top-left-radius) var(--rtl, border-top-right-radius); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + -webkit-transition: -webkit-border-top-left-radius, border-top-left-radius; + transition: -webkit-border-top-left-radius, border-top-left-radius; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + -webkit-transition: -webkit-border-top-right-radius, border-top-right-radius; + transition: -webkit-border-top-right-radius, border-top-right-radius; } + "# + }, + Browsers { + safari: Some(4 << 16), + ..Browsers::default() + }, + ); - [dir="rtl"] { - --ltr: ; - --rtl: initial; + prefix_test( + r#" + .foo { + transition: border-start-start-radius; + } + "#, + indoc! {r#" + .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) { + transition: border-top-left-radius; + } + + .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) { + transition: border-top-right-radius; } "# }, Browsers { - safari: Some(4 << 16), + safari: Some(12 << 16), ..Browsers::default() }, ); @@ -9166,18 +9328,34 @@ mod tests { } "#, indoc! {r#" - .foo { - text-align: var(--ltr, left) var(--rtl, right); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + text-align: left; + } + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + text-align: right; } + "# + }, + Browsers { + safari: Some(2 << 16), + ..Browsers::default() + }, + ); - [dir="ltr"] { - --ltr: initial; - --rtl: ; + prefix_test( + r#" + .foo { + text-align: end; + } + "#, + indoc! {r#" + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + text-align: right; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + text-align: left; } "# }, @@ -9190,22 +9368,34 @@ mod tests { prefix_test( r#" .foo { - text-align: end; + text-align: start; } "#, indoc! {r#" .foo { - text-align: var(--ltr, right) var(--rtl, left); + text-align: start; } + "# + }, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); - [dir="ltr"] { - --ltr: initial; - --rtl: ; + prefix_test( + r#" + .foo > .bar { + text-align: start; + } + "#, + indoc! {r#" + .foo > .bar:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + text-align: left; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo > .bar:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + text-align: right; } "# }, @@ -9217,18 +9407,44 @@ mod tests { prefix_test( r#" - .foo { + .foo:after { text-align: start; } "#, indoc! {r#" - .foo { + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)):after { + text-align: left; + } + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)):after { + text-align: right; + } + "# + }, + Browsers { + safari: Some(2 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + r#" + .foo:hover { text-align: start; } + "#, + indoc! {r#" + .foo:hover:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + text-align: left; + } + + .foo:hover:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + text-align: right; + } "# }, Browsers { - safari: Some(14 << 16), + safari: Some(2 << 16), ..Browsers::default() }, ); @@ -10209,19 +10425,16 @@ mod tests { } "#, indoc! {r#" - .foo { - left: var(--ltr, 2px); - right: var(--rtl, 2px); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + left: 2px; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + right: 2px; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + right: 2px; } "# }, @@ -10239,19 +10452,19 @@ mod tests { } "#, indoc! {r#" - .foo { - left: var(--ltr, 2px) var(--rtl, 4px); - right: var(--ltr, 4px) var(--rtl, 2px); + .foo:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)) { + left: 2px; + right: 4px; } - [dir="ltr"] { - --ltr: initial; - --rtl: ; + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + left: 4px; + right: 2px; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + left: 4px; + right: 2px; } "# }, diff --git a/src/logical.rs b/src/logical.rs index acc262fe..c675a19d 100644 --- a/src/logical.rs +++ b/src/logical.rs @@ -1,42 +1,3 @@ -use crate::error::PrinterError; -use crate::printer::Printer; -use crate::properties::{Property, PropertyId}; -use crate::traits::ToCss; - -#[derive(Debug, Clone, PartialEq)] -pub struct LogicalProperty<'i> { - pub property_id: PropertyId<'i>, - pub ltr: Option>>, - pub rtl: Option>>, -} - -impl<'i> ToCss for LogicalProperty<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - if let Some(ltr) = &self.ltr { - dest.write_str("var(--ltr,")?; - dest.whitespace()?; - ltr.value_to_css(dest)?; - dest.write_char(')')?; - } - - if self.ltr.is_some() && self.rtl.is_some() { - dest.whitespace()?; - } - - if let Some(rtl) = &self.rtl { - dest.write_str("var(--rtl,")?; - dest.whitespace()?; - rtl.value_to_css(dest)?; - dest.write_char(')')?; - } - - Ok(()) - } -} - #[derive(Debug, PartialEq)] pub(crate) enum PropertyCategory { Logical, diff --git a/src/prefixes.rs b/src/prefixes.rs index 03f0ce62..29d870e9 100644 --- a/src/prefixes.rs +++ b/src/prefixes.rs @@ -17,6 +17,7 @@ pub enum Feature { AnimationName, AnimationPlayState, AnimationTimingFunction, + AnyPseudo, Appearance, AtKeyframes, AtResolution, @@ -2154,6 +2155,48 @@ impl Feature { } } } + Feature::AnyPseudo => { + if let Some(version) = browsers.chrome { + if version >= 786432 && version <= 5701632 { + prefixes |= VendorPrefix::WebKit; + } + } + if let Some(version) = browsers.edge { + if version >= 5177344 && version <= 5701632 { + prefixes |= VendorPrefix::WebKit; + } + } + if let Some(version) = browsers.firefox { + if version >= 262144 && version <= 5111808 { + prefixes |= VendorPrefix::Moz; + } + } + if let Some(version) = browsers.opera { + if version >= 917504 && version <= 4784128 { + prefixes |= VendorPrefix::WebKit; + } + } + if let Some(version) = browsers.safari { + if version >= 327680 && version <= 851968 { + prefixes |= VendorPrefix::WebKit; + } + } + if let Some(version) = browsers.ios_saf { + if version >= 327680 && version <= 851968 { + prefixes |= VendorPrefix::WebKit; + } + } + if let Some(version) = browsers.samsung { + if version >= 65536 && version <= 917504 { + prefixes |= VendorPrefix::WebKit; + } + } + if let Some(version) = browsers.android { + if version >= 2424832 && version <= 5701632 { + prefixes |= VendorPrefix::WebKit; + } + } + } } prefixes } diff --git a/src/properties/border.rs b/src/properties/border.rs index bf5f7bb0..17d5b687 100644 --- a/src/properties/border.rs +++ b/src/properties/border.rs @@ -4,7 +4,7 @@ use crate::compat::Feature; use crate::context::PropertyHandlerContext; use crate::declaration::DeclarationList; use crate::error::{ParserError, PrinterError}; -use crate::logical::{LogicalProperty, PropertyCategory}; +use crate::logical::PropertyCategory; use crate::macros::*; use crate::printer::Printer; use crate::properties::custom::UnparsedProperty; @@ -190,20 +190,6 @@ impl FallbackValues for GenericBorder { } } -impl GenericBorder { - fn get_necessary_fallbacks(&self, targets: Browsers) -> ColorFallbackKind { - self.color.get_necessary_fallbacks(targets) - } - - fn get_fallback(&self, kind: ColorFallbackKind) -> Self { - GenericBorder { - color: self.color.get_fallback(kind), - width: self.width.clone(), - style: self.style.clone(), - } - } -} - impl FallbackValues for Rect { fn get_fallbacks(&mut self, targets: Browsers) -> Vec { let mut fallbacks = ColorFallbackKind::empty(); @@ -277,28 +263,6 @@ impl BorderShorthand { } } -#[derive(Default, Debug)] -struct PhysicalToLogical { - border_left: Option, - border_left_color: Option, - border_left_style: Option, - border_left_width: Option, - border_right: Option, - border_right_color: Option, - border_right_style: Option, - border_right_width: Option, -} - -macro_rules! get_physical { - ($physical_to_logical: expr, $key: ident, $dest: ident) => { - if let Some(index) = $physical_to_logical.$key { - $dest.get_mut(index) - } else { - None - } - }; -} - #[derive(Debug)] pub(crate) struct BorderHandler<'i> { targets: Option, @@ -314,7 +278,6 @@ pub(crate) struct BorderHandler<'i> { border_image_handler: BorderImageHandler<'i>, border_radius_handler: BorderRadiusHandler<'i>, has_any: bool, - physical_to_logical: PhysicalToLogical, } impl<'i> BorderHandler<'i> { @@ -333,7 +296,6 @@ impl<'i> BorderHandler<'i> { border_image_handler: BorderImageHandler::new(targets), border_radius_handler: BorderRadiusHandler::new(targets), has_any: false, - physical_to_logical: PhysicalToLogical::default(), } } } @@ -497,7 +459,6 @@ impl<'i> PropertyHandler<'i> for BorderHandler<'i> { fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { self.flush(dest, context); - self.flush_logical_fallbacks(dest, context); self.border_image_handler.finalize(dest, context); self.border_radius_handler.finalize(dest, context); } @@ -514,28 +475,7 @@ impl<'i> BorderHandler<'i> { let logical_supported = context.is_supported(Feature::LogicalBorders); macro_rules! logical_prop { ($ltr: ident, $ltr_key: ident, $rtl: ident, $rtl_key: ident, $val: expr) => {{ - context.used_logical = true; - if let Some(Property::Logical(property)) = get_physical!(self.physical_to_logical, $ltr_key, dest) { - property.ltr = Some(Box::new(Property::$ltr($val.clone()))); - } else { - self.physical_to_logical.$ltr_key = Some(dest.len()); - dest.push(Property::Logical(LogicalProperty { - property_id: PropertyId::$ltr, - ltr: Some(Box::new(Property::$ltr($val.clone()))), - rtl: None, - })); - } - - if let Some(Property::Logical(property)) = get_physical!(self.physical_to_logical, $rtl_key, dest) { - property.rtl = Some(Box::new(Property::$rtl($val.clone()))); - } else { - self.physical_to_logical.$rtl_key = Some(dest.len()); - dest.push(Property::Logical(LogicalProperty { - property_id: PropertyId::$rtl, - ltr: None, - rtl: Some(Box::new(Property::$rtl($val.clone()))), - })); - } + context.add_logical_rule(Property::$ltr($val.clone()), Property::$rtl($val.clone())); }}; } @@ -1041,40 +981,18 @@ impl<'i> BorderHandler<'i> { macro_rules! prop { ($id: ident) => {{ - dest.push(Property::Unparsed(unparsed.with_property_id(PropertyId::$id))); + let mut unparsed = unparsed.with_property_id(PropertyId::$id); + context.add_unparsed_fallbacks(&mut unparsed); + dest.push(Property::Unparsed(unparsed)); }}; } macro_rules! logical_prop { ($ltr: ident, $ltr_key: ident, $rtl: ident, $rtl_key: ident) => {{ - let ltr = Some(Box::new(Property::Unparsed( - unparsed.with_property_id(PropertyId::$ltr), - ))); - let rtl = Some(Box::new(Property::Unparsed( - unparsed.with_property_id(PropertyId::$rtl), - ))); - context.used_logical = true; - if let Some(Property::Logical(property)) = get_physical!(self.physical_to_logical, $ltr_key, dest) { - property.ltr = ltr; - } else { - self.physical_to_logical.$ltr_key = Some(dest.len()); - dest.push(Property::Logical(LogicalProperty { - property_id: PropertyId::$ltr, - ltr, - rtl: None, - })); - } - - if let Some(Property::Logical(property)) = get_physical!(self.physical_to_logical, $rtl_key, dest) { - property.rtl = rtl; - } else { - self.physical_to_logical.$rtl_key = Some(dest.len()); - dest.push(Property::Logical(LogicalProperty { - property_id: PropertyId::$rtl, - ltr: None, - rtl, - })); - } + context.add_logical_rule( + Property::Unparsed(unparsed.with_property_id(PropertyId::$ltr)), + Property::Unparsed(unparsed.with_property_id(PropertyId::$rtl)), + ); }}; } @@ -1109,96 +1027,12 @@ impl<'i> BorderHandler<'i> { BorderBlockEndColor => prop!(BorderBottomColor), BorderBlockEndStyle => prop!(BorderBottomStyle), _ => { - dest.push(Property::Unparsed(unparsed.clone())); + let mut unparsed = unparsed.clone(); + context.add_unparsed_fallbacks(&mut unparsed); + dest.push(Property::Unparsed(unparsed)); } } } - - fn flush_logical_fallbacks(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { - // Generate color fallbacks for logical properties. - if let Some(targets) = self.targets { - macro_rules! logical_fallback { - ($prop: ident, $key: ident) => { - if let Some(Property::Logical(property)) = get_physical!(self.physical_to_logical, $key, dest) { - let mut fallbacks = ColorFallbackKind::empty(); - macro_rules! add_fallbacks { - ($val: ident) => { - match &**$val { - Property::$prop(val) => { - fallbacks |= val.get_necessary_fallbacks(targets); - } - Property::Unparsed(unparsed) => { - fallbacks |= unparsed.value.get_necessary_fallbacks(targets); - } - _ => unreachable!(), - } - }; - } - - if let Some(ltr) = &property.ltr { - add_fallbacks!(ltr); - } - - if let Some(rtl) = &property.rtl { - add_fallbacks!(rtl); - } - - let lowest_fallback = fallbacks.lowest(); - fallbacks.remove(lowest_fallback); - - macro_rules! fallback { - ($val: ident, $kind: expr) => { - property.$val.as_ref().map(|val| match &**val { - Property::$prop(val) => Box::new(Property::$prop(val.get_fallback($kind))), - Property::Unparsed(unparsed) => Box::new(Property::Unparsed(UnparsedProperty { - property_id: unparsed.property_id.clone(), - value: unparsed.value.get_fallback($kind), - })), - _ => unreachable!(), - }) - }; - } - - if fallbacks.contains(ColorFallbackKind::P3) { - context.add_conditional_property( - ColorFallbackKind::P3.supports_condition(), - Property::Logical(LogicalProperty { - property_id: PropertyId::$prop, - ltr: fallback!(ltr, ColorFallbackKind::P3), - rtl: fallback!(rtl, ColorFallbackKind::P3), - }), - ); - } - - if fallbacks.contains(ColorFallbackKind::LAB) - || (!lowest_fallback.is_empty() && lowest_fallback != ColorFallbackKind::LAB) - { - context.add_conditional_property( - ColorFallbackKind::LAB.supports_condition(), - Property::Logical(LogicalProperty { - property_id: PropertyId::$prop, - ltr: fallback!(ltr, ColorFallbackKind::LAB), - rtl: fallback!(rtl, ColorFallbackKind::LAB), - }), - ); - } - - if !lowest_fallback.is_empty() { - property.ltr = fallback!(ltr, lowest_fallback); - property.rtl = fallback!(rtl, lowest_fallback); - } - } - }; - } - - logical_fallback!(BorderLeft, border_left); - logical_fallback!(BorderRight, border_right); - logical_fallback!(BorderLeftColor, border_left_color); - logical_fallback!(BorderRightColor, border_right_color); - } - - self.physical_to_logical = PhysicalToLogical::default(); - } } fn is_border_property(property_id: &PropertyId) -> bool { diff --git a/src/properties/border_radius.rs b/src/properties/border_radius.rs index afcbe67a..a2c720b7 100644 --- a/src/properties/border_radius.rs +++ b/src/properties/border_radius.rs @@ -273,28 +273,33 @@ impl<'i> BorderRadiusHandler<'i> { let logical_supported = context.is_supported(compat::Feature::LogicalBorderRadius); macro_rules! logical_property { - ($prop: ident, $opposite_prop: ident, $key: ident, $opposite_key: ident, $ltr: ident, $rtl: ident) => { - if logical_supported { - if let Some(val) = $key { + ($prop: ident, $key: ident, $ltr: ident, $rtl: ident) => { + if let Some(val) = $key { + if logical_supported { dest.push(val); - } - if let Some(val) = $opposite_key { - dest.push(val); - } - } else if $key.is_some() || $opposite_key.is_some() { - let vp = if let Some(targets) = self.targets { - Feature::$ltr.prefixes_for(targets) } else { - VendorPrefix::None - }; - - context.add_inline_logical_properties( - dest, - PropertyId::$ltr(vp), - PropertyId::$rtl(vp), - $key, - $opposite_key, - ); + let vp = if let Some(targets) = self.targets { + Feature::$ltr.prefixes_for(targets) + } else { + VendorPrefix::None + }; + + match val { + Property::BorderStartStartRadius(val) + | Property::BorderStartEndRadius(val) + | Property::BorderEndStartRadius(val) + | Property::BorderEndEndRadius(val) => { + context.add_logical_rule(Property::$ltr(val.clone(), vp), Property::$rtl(val, vp)); + } + Property::Unparsed(val) => { + context.add_logical_rule( + Property::Unparsed(val.with_property_id(PropertyId::$ltr(vp))), + Property::Unparsed(val.with_property_id(PropertyId::$rtl(vp))), + ); + } + _ => {} + } + } } }; } @@ -305,20 +310,28 @@ impl<'i> BorderRadiusHandler<'i> { single_property!(BorderBottomRightRadius, bottom_right); logical_property!( BorderStartStartRadius, - BorderStartEndRadius, start_start, - start_end, BorderTopLeftRadius, BorderTopRightRadius ); + logical_property!( + BorderStartEndRadius, + start_end, + BorderTopRightRadius, + BorderTopLeftRadius + ); logical_property!( BorderEndStartRadius, - BorderEndEndRadius, end_start, - end_end, BorderBottomLeftRadius, BorderBottomRightRadius ); + logical_property!( + BorderEndEndRadius, + end_end, + BorderBottomRightRadius, + BorderBottomLeftRadius + ); } } diff --git a/src/properties/margin_padding.rs b/src/properties/margin_padding.rs index 8fc9bc0c..71c436b8 100644 --- a/src/properties/margin_padding.rs +++ b/src/properties/margin_padding.rs @@ -9,20 +9,20 @@ use crate::values::{length::LengthPercentageOrAuto, rect::Rect, size::Size2D}; macro_rules! side_handler { ($name: ident, $top: ident, $bottom: ident, $left: ident, $right: ident, $block_start: ident, $block_end: ident, $inline_start: ident, $inline_end: ident, $shorthand: ident, $block_shorthand: ident, $inline_shorthand: ident, $logical_shorthand: literal $(, $feature: ident)?) => { #[derive(Debug, Default)] - pub(crate) struct $name { + pub(crate) struct $name<'i> { top: Option, bottom: Option, left: Option, right: Option, - block_start: Option, - block_end: Option, - inline_start: Option, - inline_end: Option, + block_start: Option>, + block_end: Option>, + inline_start: Option>, + inline_end: Option>, has_any: bool, category: PropertyCategory } - impl<'i> PropertyHandler<'i> for $name { + impl<'i> PropertyHandler<'i> for $name<'i> { fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) -> bool { use Property::*; @@ -37,13 +37,13 @@ macro_rules! side_handler { }}; } - macro_rules! set_shorthand { - ($start: ident, $end: ident, $val: ident) => {{ + macro_rules! logical_property { + ($prop: ident, $val: expr) => {{ if self.category != PropertyCategory::Logical { self.flush(dest, context); } - self.$start = Some($val.0.clone()); - self.$end = Some($val.1.clone()); + + self.$prop = Some($val); self.category = PropertyCategory::Logical; self.has_any = true; }}; @@ -54,12 +54,18 @@ macro_rules! side_handler { $bottom(val) => property!(bottom, val, Physical), $left(val) => property!(left, val, Physical), $right(val) => property!(right, val, Physical), - $block_start(val) => property!(block_start, val, Logical), - $block_end(val) => property!(block_end, val, Logical), - $inline_start(val) => property!(inline_start, val, Logical), - $inline_end(val) => property!(inline_end, val, Logical), - $block_shorthand(val) => set_shorthand!(block_start, block_end, val), - $inline_shorthand(val) => set_shorthand!(inline_start, inline_end, val), + $block_start(_) => logical_property!(block_start, property.clone()), + $block_end(_) => logical_property!(block_end, property.clone()), + $inline_start(_) => logical_property!(inline_start, property.clone()), + $inline_end(_) => logical_property!(inline_end, property.clone()), + $block_shorthand(val) => { + logical_property!(block_start, Property::$block_start(val.0.clone())); + logical_property!(block_end, Property::$block_end(val.1.clone())); + }, + $inline_shorthand(val) => { + logical_property!(inline_start, Property::$inline_start(val.0.clone())); + logical_property!(inline_end, Property::$inline_end(val.1.clone())); + }, $shorthand(val) => { // dest.clear(); self.top = Some(val.0.clone()); @@ -73,8 +79,18 @@ macro_rules! side_handler { self.has_any = true; } Unparsed(val) if matches!(val.property_id, PropertyId::$top | PropertyId::$bottom | PropertyId::$left | PropertyId::$right | PropertyId::$block_start | PropertyId::$block_end | PropertyId::$inline_start | PropertyId::$inline_end | PropertyId::$block_shorthand | PropertyId::$inline_shorthand | PropertyId::$shorthand) => { - self.flush(dest, context); - dest.push(property.clone()); + // Even if we weren't able to parse the value (e.g. due to var() references), + // we can still add vendor prefixes to the property itself. + match &val.property_id { + PropertyId::$block_start => logical_property!(block_start, property.clone()), + PropertyId::$block_end => logical_property!(block_end, property.clone()), + PropertyId::$inline_start => logical_property!(inline_start, property.clone()), + PropertyId::$inline_end => logical_property!(inline_end, property.clone()), + _ => { + self.flush(dest, context); + dest.push(property.clone()); + } + } } _ => return false } @@ -87,7 +103,7 @@ macro_rules! side_handler { } } - impl<'i> $name { + impl<'i> $name<'i> { fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { use Property::*; @@ -131,47 +147,71 @@ macro_rules! side_handler { macro_rules! logical_side { ($start: ident, $end: ident, $shorthand_prop: ident, $start_prop: ident, $end_prop: ident) => { - if ($start.is_some() && $end.is_some()) { - let size = Size2D($start.unwrap(), $end.unwrap()); + if let (Some(Property::$start_prop(start)), Some(Property::$end_prop(end))) = (&$start, &$end) { + let size = Size2D(start.clone(), end.clone()); dest.push($shorthand_prop(size)); } else { if let Some(val) = $start { - dest.push($start_prop(val)); + dest.push(val); } if let Some(val) = $end { - dest.push($end_prop(val)); + dest.push(val); } } }; } + macro_rules! prop { + ($val: ident, $logical: ident, $physical: ident) => { + match $val { + Some(Property::$logical(val)) => { + dest.push(Property::$physical(val)); + } + Some(Property::Unparsed(val)) => { + dest.push(Property::Unparsed(val.with_property_id(PropertyId::$physical))); + } + _ => {} + } + } + } + if logical_supported { logical_side!(block_start, block_end, $block_shorthand, $block_start, $block_end); } else { - if let Some(val) = block_start { - dest.push($top(val)); - } - - if let Some(val) = block_end { - dest.push($bottom(val)); - } + prop!(block_start, $block_start, $top); + prop!(block_end, $block_end, $bottom); } if logical_supported { logical_side!(inline_start, inline_end, $inline_shorthand, $inline_start, $inline_end); } else if inline_start.is_some() || inline_end.is_some() { - if inline_start == inline_end { - dest.push($left(inline_start.unwrap())); - dest.push($right(inline_end.unwrap())); + if matches!((&inline_start, &inline_end), (Some(Property::$inline_start(start)), Some(Property::$inline_end(end))) if start == end) { + prop!(inline_start, $inline_start, $left); + prop!(inline_end, $inline_end, $right); } else { - context.add_inline_logical_properties( - dest, - PropertyId::$left, - PropertyId::$right, - inline_start.map(|v| Property::$inline_start(v)), - inline_end.map(|v| Property::$inline_end(v)), - ); + macro_rules! logical_prop { + ($val: ident, $logical: ident, $ltr: ident, $rtl: ident) => { + match $val { + Some(Property::$logical(val)) => { + context.add_logical_rule( + Property::$ltr(val.clone()), + Property::$rtl(val) + ); + } + Some(Property::Unparsed(val)) => { + context.add_logical_rule( + Property::Unparsed(val.with_property_id(PropertyId::$ltr)), + Property::Unparsed(val.with_property_id(PropertyId::$rtl)) + ); + } + _ => {} + } + } + } + + logical_prop!(inline_start, $inline_start, $left, $right); + logical_prop!(inline_end, $inline_end, $right, $left); } } } diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 90be2732..186ec02a 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -28,7 +28,6 @@ pub mod transition; pub mod ui; use crate::error::{ParserError, PrinterError}; -use crate::logical::LogicalProperty; use crate::parser::starts_with_ignore_ascii_case; use crate::parser::ParserOptions; use crate::prefixes::Feature; @@ -292,7 +291,6 @@ macro_rules! define_properties { )+ Unparsed(UnparsedProperty<'i>), Custom(CustomProperty<'i>), - Logical(LogicalProperty<'i>) } impl<'i> Property<'i> { @@ -377,7 +375,6 @@ macro_rules! define_properties { $property(_, $(vp_name!($vp, _p))?) => $name, )+ Unparsed(unparsed) => unparsed.property_id.name(), - Logical(logical) => logical.property_id.name(), Custom(custom) => &custom.name, } } @@ -398,9 +395,6 @@ macro_rules! define_properties { Custom(custom) => { custom.value.to_css(dest, custom.name.starts_with("--")) } - Logical(logical) => { - logical.to_css(dest) - } } } @@ -446,7 +440,6 @@ macro_rules! define_properties { }, )+ Unparsed(unparsed) => (unparsed.property_id.name(), unparsed.property_id.prefix()), - Logical(logical) => (logical.property_id.name(), logical.property_id.prefix()), Custom(custom) => { // Ensure custom property names are escaped. serialize_name(custom.name.as_ref(), dest)?; diff --git a/src/properties/text.rs b/src/properties/text.rs index be318076..4dce2018 100644 --- a/src/properties/text.rs +++ b/src/properties/text.rs @@ -870,9 +870,7 @@ impl<'i> PropertyHandler<'i> for TextDecorationHandler<'i> { if logical_supported { dest.push(property.clone()); } else { - context.add_logical_property( - dest, - PropertyId::TextAlign, + context.add_logical_rule( Property::TextAlign(TextAlign::$ltr), Property::TextAlign(TextAlign::$rtl), ); diff --git a/src/properties/transition.rs b/src/properties/transition.rs index ca84e78b..a8fc9107 100644 --- a/src/properties/transition.rs +++ b/src/properties/transition.rs @@ -276,9 +276,7 @@ impl<'i> TransitionHandler<'i> { if let Some(rtl_properties) = &rtl_properties { let rtl_transitions = get_transitions!(rtl_properties); - context.add_logical_property( - dest, - PropertyId::Transition(intersection), + context.add_logical_rule( Property::Transition(transitions, intersection), Property::Transition(rtl_transitions, intersection), ); @@ -296,9 +294,7 @@ impl<'i> TransitionHandler<'i> { if let Some((properties, prefix)) = properties { if !prefix.is_empty() { if let Some(rtl_properties) = rtl_properties { - context.add_logical_property( - dest, - PropertyId::TransitionProperty(prefix), + context.add_logical_rule( Property::TransitionProperty(properties, prefix), Property::TransitionProperty(rtl_properties, prefix), ); diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 0d0a7e0a..0c9a1a14 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -24,7 +24,7 @@ use crate::dependencies::{Dependency, ImportDependency}; use crate::error::{MinifyError, PrinterError}; use crate::prefixes::Feature; use crate::printer::Printer; -use crate::selector::{get_necessary_prefixes, get_prefix, is_equivalent}; +use crate::selector::{downlevel_selectors, get_prefix, is_equivalent}; use crate::targets::Browsers; use crate::traits::ToCss; use crate::values::string::CowArcStr; @@ -219,7 +219,7 @@ impl<'i> CssRuleList<'i> { if let Some(targets) = context.targets { style.vendor_prefix = get_prefix(&style.selectors); if style.vendor_prefix.contains(VendorPrefix::None) { - style.vendor_prefix = get_necessary_prefixes(&style.selectors, *targets); + style.vendor_prefix = downlevel_selectors(&mut style.selectors, *targets); } } @@ -275,7 +275,17 @@ impl<'i> CssRuleList<'i> { } let supports = context.handler_context.get_supports_rules(&style); - rules.push(rule); + let logical = context.handler_context.get_logical_rules(&style); + if !style.is_empty() { + rules.push(rule); + } + + if !logical.is_empty() { + let mut logical = CssRuleList(logical); + logical.minify(context, parent_is_unused)?; + rules.extend(logical.0) + } + rules.extend(supports); continue; } diff --git a/src/rules/style.rs b/src/rules/style.rs index e579ce07..38c690c4 100644 --- a/src/rules/style.rs +++ b/src/rules/style.rs @@ -56,6 +56,10 @@ impl<'i> StyleRule<'i> { Ok(false) } + pub fn is_empty(&self) -> bool { + self.declarations.is_empty() && self.rules.0.is_empty() + } + pub fn is_compatible(&self, targets: Option) -> bool { is_compatible(&self.selectors, targets) } diff --git a/src/selector.rs b/src/selector.rs index 5e369224..92f53e43 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -3,9 +3,9 @@ use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; use crate::rules::{StyleContext, ToCssWithContext}; use crate::targets::Browsers; -use crate::traits::ToCss; -use crate::values::string::CowArcStr; +use crate::traits::{Parse, ToCss}; use crate::vendor_prefix::VendorPrefix; +use crate::{macros::enum_property, values::string::CowArcStr}; use cssparser::*; use parcel_selectors::{ attr::{AttrSelectorOperator, ParsedAttrSelectorOperation, ParsedCaseSensitivity}, @@ -77,6 +77,7 @@ impl<'i> SelectorImpl<'i> for Selectors { type NonTSPseudoClass = PseudoClass<'i>; type PseudoElement = PseudoElement<'i>; + type VendorPrefix = VendorPrefix; type ExtraMatchingData = (); @@ -198,8 +199,15 @@ impl<'a, 'i> parcel_selectors::parser::Parser<'i> for SelectorParser<'a, 'i> { ) -> Result, ParseError<'i, Self::Error>> { use PseudoClass::*; let pseudo_class = match_ignore_ascii_case! { &name, - "lang" => Lang(parser.expect_ident_or_string()?.as_ref().into()), - "dir" => Dir(parser.expect_ident_or_string()?.as_ref().into()), + "lang" => { + let langs = parser.parse_comma_separated(|parser| { + parser.expect_ident_or_string() + .map(|s| s.into()) + .map_err(|e| e.into()) + })?; + Lang(langs) + }, + "dir" => Dir(Direction::parse(parser)?), "local" if self.css_modules => Local(Box::new(parcel_selectors::parser::Selector::parse(self, parser)?)), "global" if self.css_modules => Global(Box::new(parcel_selectors::parser::Selector::parse(self, parser)?)), _ => return Err(parser.new_custom_error(parcel_selectors::parser::SelectorParseErrorKind::UnexpectedIdent(name.clone()))), @@ -208,6 +216,14 @@ impl<'a, 'i> parcel_selectors::parser::Parser<'i> for SelectorParser<'a, 'i> { Ok(pseudo_class) } + fn parse_any_prefix<'t>(&self, name: &str) -> Option { + match_ignore_ascii_case! { &name, + "-webkit-any" => Some(VendorPrefix::WebKit), + "-moz-any" => Some(VendorPrefix::Moz), + _ => None + } + } + fn parse_pseudo_element( &self, _: SourceLocation, @@ -280,12 +296,20 @@ impl<'a, 'i> parcel_selectors::parser::Parser<'i> for SelectorParser<'a, 'i> { } } +enum_property! { + #[derive(Eq)] + pub enum Direction { + Ltr, + Rtl, + } +} + /// https://drafts.csswg.org/selectors-4/#structural-pseudos #[derive(Clone, Eq, PartialEq)] pub enum PseudoClass<'i> { // https://drafts.csswg.org/selectors-4/#linguistic-pseudos - Lang(Box), - Dir(Box), + Lang(Vec>), + Dir(Direction), // https://drafts.csswg.org/selectors-4/#useraction-pseudos Hover, @@ -408,12 +432,20 @@ impl<'a, 'i> ToCssWithContext<'a, 'i> for PseudoClass<'i> { match &self { Lang(lang) => { dest.write_str(":lang(")?; - serialize_identifier(lang, dest)?; + let mut first = true; + for lang in lang { + if first { + first = false; + } else { + dest.delim(',', false)?; + } + serialize_identifier(lang, dest)?; + } return dest.write_str(")"); } Dir(dir) => { dest.write_str(":dir(")?; - serialize_identifier(dir, dest)?; + dir.to_css(dest)?; return dest.write_str(")"); } _ => {} @@ -1036,11 +1068,25 @@ impl<'a, 'i> ToCssWithContext<'a, 'i> for Component<'i, Selectors> { } dest.write_char(']') } - Is(ref list) | Where(ref list) | Negation(ref list) => { + Is(ref list) | Where(ref list) | Negation(ref list) | Any(_, ref list) => { match *self { Where(..) => dest.write_str(":where(")?, - Is(..) => dest.write_str(":is(")?, + Is(..) => { + let vp = dest.vendor_prefix; + if !vp.is_empty() && vp != VendorPrefix::None { + dest.write_char(':')?; + vp.to_css(dest)?; + dest.write_str("any(")?; + } else { + dest.write_str(":is(")?; + } + } Negation(..) => return serialize_negation(list.iter(), dest, context), + Any(ref prefix, ..) => { + dest.write_char(':')?; + prefix.to_css(dest)?; + dest.write_str("any(")?; + } _ => unreachable!(), } serialize_selector_list(list.iter(), dest, context, false)?; @@ -1255,6 +1301,7 @@ pub fn is_compatible(selectors: &SelectorList, targets: Option Feature::CssSel3, Component::Is(_) | Component::Nesting => Feature::CssMatchesPseudo, + Component::Any(..) => Feature::AnyPseudo, Component::Has(_) => Feature::CssHas, Component::Scope | Component::Host(_) | Component::Slotted(_) => Feature::Shadowdomv1, @@ -1384,6 +1431,10 @@ pub fn get_prefix(selectors: &SelectorList) -> VendorPrefix { for selector in &selectors.0 { for component in selector.iter() { let p = match component { + // Return none rather than empty for these so that we call downlevel_selectors. + Component::NonTSPseudoClass(PseudoClass::Lang(..)) + | Component::NonTSPseudoClass(PseudoClass::Dir(..)) + | Component::Is(..) => VendorPrefix::None, Component::NonTSPseudoClass(pc) => pc.get_prefix(), Component::PseudoElement(pe) => pe.get_prefix(), _ => VendorPrefix::empty(), @@ -1402,26 +1453,95 @@ pub fn get_prefix(selectors: &SelectorList) -> VendorPrefix { prefix } -/// Returns the necessary vendor prefixes for a given selector list to meet the provided browser targets. -pub fn get_necessary_prefixes(selectors: &SelectorList, targets: Browsers) -> VendorPrefix { - let mut necessary_prefixes = VendorPrefix::empty(); - for selector in &selectors.0 { - for component in selector.iter() { - let prefixes = match component { - Component::NonTSPseudoClass(pc) => pc.get_necessary_prefixes(targets), - Component::PseudoElement(pe) => pe.get_necessary_prefixes(targets), - _ => VendorPrefix::empty(), - }; +const RTL_LANGS: &[&str] = &[ + "ae", "ar", "arc", "bcc", "bqi", "ckb", "dv", "fa", "glk", "he", "ku", "mzn", "nqo", "pnb", "ps", "sd", "ug", + "ur", "yi", +]; - necessary_prefixes |= prefixes; +/// Downlevels the given selectors to be compatible with the given browser targets. +/// Returns the necessary vendor prefixes. +pub fn downlevel_selectors(selectors: &mut SelectorList, targets: Browsers) -> VendorPrefix { + let mut necessary_prefixes = VendorPrefix::empty(); + for selector in &mut selectors.0 { + for component in selector.iter_mut_raw_match_order() { + necessary_prefixes |= downlevel_component(component, targets); } } necessary_prefixes } +fn downlevel_component<'i>(component: &mut Component<'i, Selectors>, targets: Browsers) -> VendorPrefix { + match component { + Component::NonTSPseudoClass(pc) => { + match pc { + PseudoClass::Dir(dir) => { + if !Feature::CssDirPseudo.is_compatible(targets) { + *component = downlevel_dir(*dir, targets); + downlevel_component(component, targets) + } else { + VendorPrefix::empty() + } + } + PseudoClass::Lang(langs) => { + // :lang() with multiple languages is not supported everywhere. + // compile this to :is(:lang(a), :lang(b)) etc. + if langs.len() > 1 && !Feature::LangList.is_compatible(targets) { + *component = Component::Is(lang_list_to_selectors(&langs)); + downlevel_component(component, targets) + } else { + VendorPrefix::empty() + } + } + _ => pc.get_necessary_prefixes(targets), + } + } + Component::PseudoElement(pe) => pe.get_necessary_prefixes(targets), + Component::Is(ref selectors) => { + // Convert :is to :-webkit-any/:-moz-any if needed. + // All selectors must be simple, no combinators are supported. + if !Feature::CssMatchesPseudo.is_compatible(targets) + && selectors.iter().all(|selector| !selector.has_combinator()) + { + crate::prefixes::Feature::AnyPseudo.prefixes_for(targets) + } else { + VendorPrefix::empty() + } + } + _ => VendorPrefix::empty(), + } +} + +fn lang_list_to_selectors<'i>(langs: &Vec>) -> Box<[Selector<'i, Selectors>]> { + langs + .iter() + .map(|lang| Selector::from_vec2(vec![Component::NonTSPseudoClass(PseudoClass::Lang(vec![lang.clone()]))])) + .collect::>>() + .into_boxed_slice() +} + +fn downlevel_dir<'i>(dir: Direction, targets: Browsers) -> Component<'i, Selectors> { + // Convert :dir to :lang. If supported, use a list of languages in a single :lang, + // otherwise, use :is/:not, which may be further downleveled to e.g. :-webkit-any. + let langs = RTL_LANGS.iter().map(|lang| (*lang).into()).collect(); + if Feature::LangList.is_compatible(targets) { + let c = Component::NonTSPseudoClass(PseudoClass::Lang(langs)); + if dir == Direction::Ltr { + Component::Negation(vec![Selector::from_vec2(vec![c])].into_boxed_slice()) + } else { + c + } + } else { + if dir == Direction::Ltr { + Component::Negation(lang_list_to_selectors(&langs)) + } else { + Component::Is(lang_list_to_selectors(&langs)) + } + } +} + /// Determines whether a selector list contains only unused selectors. -/// A selector is considered unused if it contains a class or id component that exists in the set of unsed symbols. +/// A selector is considered unused if it contains a class or id component that exists in the set of unused symbols. pub fn is_unused( selectors: &mut std::slice::Iter>, unused_symbols: &HashSet, @@ -1439,7 +1559,7 @@ pub fn is_unused( return true; } } - Component::Is(is) | Component::Where(is) => { + Component::Is(is) | Component::Where(is) | Component::Any(_, is) => { if is_unused(&mut is.iter(), unused_symbols, parent_is_unused) { return true; } diff --git a/src/stylesheet.rs b/src/stylesheet.rs index 402b5712..d74f28aa 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -119,7 +119,6 @@ impl<'i> StyleSheet<'i> { )), })?; - context.add_logical_rules(&mut self.rules); Ok(()) } diff --git a/src/vendor_prefix.rs b/src/vendor_prefix.rs index fb9f1be8..8ecafd6a 100644 --- a/src/vendor_prefix.rs +++ b/src/vendor_prefix.rs @@ -35,6 +35,16 @@ impl VendorPrefix { impl ToCss for VendorPrefix { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + cssparser::ToCss::to_css(self, dest)?; + Ok(()) + } +} + +impl cssparser::ToCss for VendorPrefix { + fn to_css(&self, dest: &mut W) -> std::fmt::Result where W: std::fmt::Write, {