From d8eedd47e681a9d2ed9cdf2c409904ce0001bbdb Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 3 Apr 2022 14:28:44 -0400 Subject: [PATCH 1/3] Downlevel :is, :lang, and :dir selectors --- scripts/build-prefixes.js | 46 ++++++- selectors/builder.rs | 2 +- selectors/matching.rs | 2 +- selectors/parser.rs | 36 +++++- src/compat.rs | 50 +++++++- src/lib.rs | 247 +++++++++++++++++++++++++++++++++----- src/prefixes.rs | 43 +++++++ src/rules/mod.rs | 4 +- src/selector.rs | 169 ++++++++++++++++++++++---- src/vendor_prefix.rs | 7 ++ 10 files changed, 540 insertions(+), 66 deletions(-) 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 27bdfa09..2844f12a 100644 --- a/selectors/builder.rs +++ b/selectors/builder.rs @@ -317,7 +317,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 22dea86b..028e58ed 100644 --- a/selectors/matching.rs +++ b/selectors/matching.rs @@ -842,7 +842,7 @@ 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| { + 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; diff --git a/selectors/parser.rs b/selectors/parser.rs index b1bd3315..72215190 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. @@ -728,6 +729,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. @@ -793,6 +801,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] @@ -1077,6 +1090,11 @@ 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 @@ -1170,6 +1188,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 @@ -1647,12 +1666,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)?; @@ -2431,6 +2455,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>, @@ -2461,8 +2486,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() { @@ -2788,6 +2813,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 a10263bd..87f9edbf 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, @@ -1719,7 +1721,53 @@ 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/lib.rs b/src/lib.rs index 6f2de477..9381ebb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3657,51 +3657,52 @@ 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("section:not(:has(h1, h2, h3, h4, h5, h6)) {}", "section:not(:has(h1,h2,h3,h4,h5,h6)){}"); - 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("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(":has(.sibling ~ .target) {color:red}", ":has(.sibling~.target){color:red}"); + 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", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece", "-webkit-scrollbar-thumb", "-webkit-scrollbar-corner", "-webkit-resizer"] { for class in ["horizontal", "vertical", "decrement", "increment", "start", "end", "double-button", "single-button", "no-button", "corner-present", "window-inactive"] { - minify_test(&format!("::{}:{} {{}}", element, class), &format!("::{}:{}{{}}", element, class)); + minify_test(&format!("::{}:{} {{color:red}}", element, class), &format!("::{}:{}{{color:red}}", element, class)); } } for class in ["horizontal", "vertical", "decrement", "increment", "start", "end", "double-button", "single-button", "no-button", "corner-present", "window-inactive"] { - error_test(&format!(":{} {{}}", class), ParserError::SelectorError(SelectorError::InvalidPseudoClassBeforeWebKitScrollbar)); + error_test(&format!(":{} {{color:red}}", class), ParserError::SelectorError(SelectorError::InvalidPseudoClassBeforeWebKitScrollbar)); } for element in ["-webkit-scrollbar", "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece", "-webkit-scrollbar-thumb", "-webkit-scrollbar-corner", "-webkit-resizer"] { - error_test(&format!("::{}:hover {{}}", element), ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterWebKitScrollbar)); + error_test(&format!("::{}:hover {{color:red}}", element), ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterWebKitScrollbar)); } - error_test("a::first-letter:last-child {}", ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterPseudoElement)); - minify_test("a:last-child::first-letter {}", "a:last-child:first-letter{}"); + error_test("a::first-letter:last-child {color:red}", ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterPseudoElement)); + 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 { @@ -3710,13 +3711,195 @@ mod tests { } ); prefix_test( - ".test:not(.foo, .bar) {}", + ".test:not(.foo, .bar) {color:red}", indoc! {r#" .test:not(.foo, .bar) { + color: red; + } + "#}, + Browsers { + safari: Some(11 << 16), + ..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() } ); diff --git a/src/prefixes.rs b/src/prefixes.rs index 17301686..abc25e6a 100644 --- a/src/prefixes.rs +++ b/src/prefixes.rs @@ -17,6 +17,7 @@ pub enum Feature { AnimationName, AnimationPlayState, AnimationTimingFunction, + AnyPseudo, Appearance, AtKeyframes, AtResolution, @@ -2172,6 +2173,48 @@ impl Feature { prefixes |= VendorPrefix::WebKit; } } + }, + 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/rules/mod.rs b/src/rules/mod.rs index d8531447..b9b80c67 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -37,7 +37,7 @@ use crate::vendor_prefix::VendorPrefix; use crate::prefixes::Feature; use crate::targets::Browsers; use std::collections::{HashMap, HashSet}; -use crate::selector::{is_equivalent, get_prefix, get_necessary_prefixes}; +use crate::selector::{is_equivalent, get_prefix, downlevel_selectors}; use crate::error::{MinifyError, PrinterError}; use crate::context::PropertyHandlerContext; use crate::dependencies::{Dependency, ImportDependency}; @@ -199,7 +199,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); } } diff --git a/src/selector.rs b/src/selector.rs index e7dce79e..0882a51f 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -1,10 +1,10 @@ -use crate::values::string::CowArcStr; +use crate::{values::string::CowArcStr, macros::enum_property}; use cssparser::*; use parcel_selectors::{SelectorList, parser::{SelectorImpl, Selector, Combinator, Component}, attr::{AttrSelectorOperator, ParsedAttrSelectorOperation, ParsedCaseSensitivity}}; use std::fmt; use std::fmt::Write; use crate::printer::Printer; -use crate::traits::ToCss; +use crate::traits::{Parse, ToCss}; use crate::compat::Feature; use crate::vendor_prefix::VendorPrefix; use crate::targets::Browsers; @@ -64,6 +64,7 @@ impl<'i> SelectorImpl<'i> for Selectors { type NonTSPseudoClass = PseudoClass<'i>; type PseudoElement = PseudoElement<'i>; + type VendorPrefix = VendorPrefix; type ExtraMatchingData = (); @@ -185,8 +186,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()))), @@ -195,6 +203,17 @@ 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, @@ -267,12 +286,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, @@ -385,12 +412,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(")"); } _ => {} @@ -1006,11 +1041,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)?; @@ -1212,6 +1261,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 | @@ -1352,6 +1402,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() @@ -1370,26 +1424,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, parent_is_unused: bool) -> bool { if unused_symbols.is_empty() { return false @@ -1403,7 +1526,7 @@ pub fn is_unused(selectors: &mut std::slice::Iter>, unused_s 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/vendor_prefix.rs b/src/vendor_prefix.rs index b49ed4a5..10f4b994 100644 --- a/src/vendor_prefix.rs +++ b/src/vendor_prefix.rs @@ -35,6 +35,13 @@ 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 { match *self { VendorPrefix::WebKit => dest.write_str("-webkit-"), VendorPrefix::Moz => dest.write_str("-moz-"), From daaa192406f969d0efe2d849120f10076981766c Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 3 Apr 2022 14:52:33 -0400 Subject: [PATCH 2/3] Refactor logical properties to use :dir/:lang --- selectors/parser.rs | 9 + src/context.rs | 122 ++---- src/declaration.rs | 14 +- src/lib.rs | 721 +++++++++++++++---------------- src/logical.rs | 36 -- src/properties/border.rs | 191 +------- src/properties/border_radius.rs | 54 ++- src/properties/margin_padding.rs | 120 +++-- src/properties/mod.rs | 7 - src/properties/text.rs | 4 +- src/properties/transition.rs | 8 +- src/rules/mod.rs | 12 +- src/rules/style.rs | 4 + src/stylesheet.rs | 1 - 14 files changed, 563 insertions(+), 740 deletions(-) diff --git a/selectors/parser.rs b/selectors/parser.rs index 72215190..62ef82d1 100644 --- a/selectors/parser.rs +++ b/selectors/parser.rs @@ -672,6 +672,15 @@ 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() { diff --git a/src/context.rs b/src/context.rs index 6b0df27a..6ef9facc 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,23 +1,13 @@ -use crate::logical::LogicalProperty; +use crate::compat::Feature; use crate::properties::custom::UnparsedProperty; -use crate::rules::Location; use crate::rules::supports::{SupportsRule, SupportsCondition}; use crate::rules::{CssRule, CssRuleList, style::StyleRule}; -use parcel_selectors::SelectorList; -use crate::selector::{SelectorIdent, SelectorString}; -use crate::declaration::{DeclarationBlock, DeclarationList}; -use crate::vendor_prefix::VendorPrefix; -use crate::compat::Feature; +use crate::selector::{PseudoClass, Direction}; +use crate::declaration::DeclarationBlock; use crate::targets::Browsers; -use parcel_selectors::{ - parser::{Selector, Component}, - attr::{AttrSelectorOperator, ParsedCaseSensitivity} -}; -use crate::properties::{ - Property, - PropertyId, - custom::{CustomProperty, TokenList, Token}, -}; +use crate::vendor_prefix::VendorPrefix; +use parcel_selectors::parser::Component; +use crate::properties::Property; #[derive(Debug)] pub(crate) struct SupportsEntry<'i> { @@ -37,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 } @@ -47,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 } } @@ -68,74 +60,48 @@ 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 { - 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()]) - }) - ] + declarations: std::mem::take(&mut self.$decls), + important_declarations: vec![] }, - 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 3e6dbce3..246ec6a1 100644 --- a/src/declaration.rs +++ b/src/declaration.rs @@ -116,6 +116,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> { @@ -175,10 +179,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>, @@ -186,7 +190,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 9381ebb3..a62e35e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -652,19 +652,16 @@ mod tests { border-inline-start: 2px solid red; } "#, 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; } "# }, Browsers { @@ -677,19 +674,16 @@ mod tests { border-inline-start-width: 2px; } "#, 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; } "# }, Browsers { @@ -702,19 +696,16 @@ mod tests { border-inline-end: 2px solid red; } "#, 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; } "# }, Browsers { @@ -728,19 +719,19 @@ mod tests { border-inline-end: 5px solid green; } "#, 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; } "# }, Browsers { @@ -759,24 +750,34 @@ mod tests { border-inline-end: 1px solid black; } "#, 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; } - [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)) { + border-left: 5px solid green; + border-right: 2px solid red; } - [dir="rtl"] { - --ltr: ; - --rtl: initial; + .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; + } + + .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; + } + + .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; } "# }, Browsers { @@ -789,19 +790,16 @@ mod tests { border-inline-end: var(--test); } "#, 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); } "# }, Browsers { @@ -815,19 +813,19 @@ mod tests { border-inline-end: var(--end); } "#, 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); } "# }, Browsers { @@ -893,26 +891,19 @@ mod tests { border-inline-start-color: lab(40% 56.6 39); } "#, 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 { safari: Some(8 << 16), @@ -924,26 +915,19 @@ mod tests { border-inline-end-color: lab(40% 56.6 39); } "#, indoc! {r#" - .foo { - border-right-color: var(--ltr, #b32323); - border-left-color: var(--rtl, #b32323); - } - - @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: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); } - [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: #b32323; + border-left-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: #b32323; + border-left-color: lab(40% 56.6 39); } "#}, Browsers { safari: Some(8 << 16), @@ -956,26 +940,25 @@ mod tests { border-inline-end-color: lch(50.998% 135.363 338); } "#, 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: lab(40% 56.6 39); + border-right-color: #ee00be; + border-right-color: lch(50.998% 135.363 338); } - @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:-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="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: lch(50.998% 135.363 338); + border-right-color: #b32323; + border-right-color: lab(40% 56.6 39); } "#}, Browsers { safari: Some(8 << 16), @@ -988,33 +971,22 @@ mod tests { border-inline-end-color: lch(50.998% 135.363 338); } "#, indoc! {r#" - .foo { - border-left-color: var(--ltr, #b32323) var(--rtl, #ee00be); - border-right-color: var(--ltr, #ee00be) var(--rtl, #b32323); - } - - @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)); - } + .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: 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 { chrome: Some(8 << 16), @@ -1027,26 +999,19 @@ mod tests { border-inline-start: 2px solid lab(40% 56.6 39); } "#, indoc! {r#" - .foo { - border-left: var(--ltr, 2px solid #b32323); - border-right: 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-left: 2px solid #b32323; + border-left: 2px solid lab(40% 56.6 39); } - @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:-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="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: 2px solid #b32323; + border-right: 2px solid lab(40% 56.6 39); } "#}, Browsers { safari: Some(8 << 16), @@ -1058,26 +1023,19 @@ mod tests { border-inline-end: 2px solid lab(40% 56.6 39); } "#, indoc! {r#" - .foo { - border-right: var(--ltr, 2px solid #b32323); - border-left: var(--rtl, 2px solid #b32323); - } - - @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)); - } + .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); } - [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 { safari: Some(8 << 16), @@ -1089,26 +1047,28 @@ mod tests { border-inline-end: var(--border-width) solid lab(40% 56.6 39); } "#, 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 { safari: Some(8 << 16), @@ -1673,19 +1633,12 @@ mod tests { border-start-start-radius: 5px; } "#, indoc! {r#" - .foo { - border-top-left-radius: var(--ltr, 5px); - border-top-right-radius: 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-top-left-radius: 5px; } - [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; } "# }, Browsers { @@ -1699,19 +1652,14 @@ mod tests { border-start-end-radius: 10px; } "#, indoc! {r#" - .foo { - border-top-left-radius: var(--ltr, 5px) var(--rtl, 10px); - border-top-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-top-left-radius: 5px; + border-top-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-top-left-radius: 10px; + border-top-right-radius: 5px; } "# }, Browsers { @@ -1725,19 +1673,14 @@ mod tests { border-end-end-radius: 10px; } "#, indoc! {r#" - .foo { - border-bottom-left-radius: var(--ltr, 5px) var(--rtl, 10px); - border-bottom-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-bottom-left-radius: 5px; + border-bottom-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-bottom-left-radius: 10px; + border-bottom-right-radius: 5px; } "# }, Browsers { @@ -1750,19 +1693,12 @@ mod tests { border-start-start-radius: var(--radius); } "#, indoc! {r#" - .foo { - border-top-left-radius: var(--ltr, var(--radius)); - border-top-right-radius: var(--rtl, var(--radius)); + .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="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: var(--radius); } "# }, Browsers { @@ -1776,19 +1712,14 @@ mod tests { border-start-end-radius: var(--end); } "#, 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); } "# }, Browsers { @@ -1975,19 +1906,16 @@ mod tests { margin-inline-start: 2px; } "#, 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; } "# }, Browsers { @@ -2001,19 +1929,19 @@ mod tests { margin-inline-end: 4px; } "#, 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; } "# }, Browsers { @@ -2119,19 +2047,16 @@ mod tests { padding-inline-start: 2px; } "#, 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; } "# }, Browsers { @@ -2145,19 +2070,41 @@ mod tests { padding-inline-end: 4px; } "#, 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); } "# }, Browsers { @@ -5335,18 +5282,16 @@ mod tests { transition-property: margin-inline-start; } "#, 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; } "# }, Browsers { @@ -5359,18 +5304,16 @@ mod tests { transition-property: margin-inline-start, padding-inline-start; } "#, 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; } "# }, Browsers { @@ -5383,18 +5326,16 @@ mod tests { transition-property: margin-inline-start, opacity, padding-inline-start, color; } "#, 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; } "# }, Browsers { @@ -5421,18 +5362,16 @@ mod tests { transition: margin-inline-start 2s; } "#, 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; } "# }, Browsers { @@ -5445,18 +5384,16 @@ mod tests { transition: margin-inline-start 2s, padding-inline-start 2s; } "#, 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; } "# }, Browsers { @@ -5498,23 +5435,36 @@ mod tests { transition: border-start-start-radius; } "#, 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; + } + + .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="ltr"] { - --ltr: initial; - --rtl: ; + 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; } - [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) { + transition: border-top-right-radius; } "# }, Browsers { - safari: Some(4 << 16), + safari: Some(12 << 16), ..Browsers::default() }); @@ -7519,18 +7469,30 @@ mod tests { text-align: start; } "#, 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; } - [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)) { + text-align: right; } + "# + }, Browsers { + safari: Some(2 << 16), + ..Browsers::default() + }); - [dir="rtl"] { - --ltr: ; - --rtl: initial; + 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; + } + + .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; } "# }, Browsers { @@ -7540,21 +7502,29 @@ 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; } "# }, Browsers { @@ -7563,16 +7533,38 @@ 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() }); } @@ -8313,19 +8305,16 @@ mod tests { inset-inline-start: 2px; } "#, 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; } "# }, Browsers { @@ -8339,19 +8328,19 @@ mod tests { inset-inline-end: 4px; } "#, 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; } "# }, Browsers { diff --git a/src/logical.rs b/src/logical.rs index 21dcb8db..f9bb0c07 100644 --- a/src/logical.rs +++ b/src/logical.rs @@ -1,39 +1,3 @@ -use crate::traits::ToCss; -use crate::printer::Printer; -use crate::error::PrinterError; -use crate::properties::{Property, PropertyId}; - -#[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/properties/border.rs b/src/properties/border.rs index e9048834..7f5bbdf6 100644 --- a/src/properties/border.rs +++ b/src/properties/border.rs @@ -4,7 +4,7 @@ use crate::traits::{Parse, ToCss, PropertyHandler, FallbackValues}; use crate::values::color::{CssColor, ColorFallbackKind}; use crate::properties::{Property, PropertyId}; use crate::declaration::DeclarationList; -use crate::logical::{LogicalProperty, PropertyCategory}; +use crate::logical::PropertyCategory; use crate::context::PropertyHandlerContext; use crate::values::rect::Rect; use crate::macros::*; @@ -183,20 +183,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(); @@ -270,28 +256,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, @@ -306,8 +270,7 @@ pub(crate) struct BorderHandler<'i> { category: PropertyCategory, border_image_handler: BorderImageHandler<'i>, border_radius_handler: BorderRadiusHandler<'i>, - has_any: bool, - physical_to_logical: PhysicalToLogical + has_any: bool } impl<'i> BorderHandler<'i> { @@ -326,7 +289,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() } } } @@ -484,7 +446,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); } @@ -501,28 +462,10 @@ 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()) + ); }}; } @@ -1023,36 +966,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)) + ); }}; } @@ -1075,96 +1000,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 b8aad2ec..5a76f23f 100644 --- a/src/properties/border_radius.rs +++ b/src/properties/border_radius.rs @@ -244,28 +244,36 @@ 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))) + ); + } + _ => {} + } + } } }; } @@ -274,8 +282,10 @@ impl<'i> BorderRadiusHandler<'i> { single_property!(BorderTopRightRadius, top_right); single_property!(BorderBottomLeftRadius, bottom_left); single_property!(BorderBottomRightRadius, bottom_right); - logical_property!(BorderStartStartRadius, BorderStartEndRadius, start_start, start_end, BorderTopLeftRadius, BorderTopRightRadius); - logical_property!(BorderEndStartRadius, BorderEndEndRadius, end_start, end_end, BorderBottomLeftRadius, BorderBottomRightRadius); + logical_property!(BorderStartStartRadius, start_start, BorderTopLeftRadius, BorderTopRightRadius); + logical_property!(BorderStartEndRadius, start_end, BorderTopRightRadius, BorderTopLeftRadius); + logical_property!(BorderEndStartRadius, end_start, BorderBottomLeftRadius, BorderBottomRightRadius); + logical_property!(BorderEndEndRadius, end_end, BorderBottomRightRadius, BorderBottomLeftRadius); } } diff --git a/src/properties/margin_padding.rs b/src/properties/margin_padding.rs index 4c0f0022..b6c01990 100644 --- a/src/properties/margin_padding.rs +++ b/src/properties/margin_padding.rs @@ -13,20 +13,20 @@ use crate::compat::Feature; 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::*; @@ -41,13 +41,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; }}; @@ -58,12 +58,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()); @@ -77,8 +83,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 } @@ -91,7 +107,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::*; @@ -135,47 +151,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 f5fd7f39..8351fe60 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -61,7 +61,6 @@ use smallvec::{SmallVec, smallvec}; use crate::vendor_prefix::VendorPrefix; use crate::parser::ParserOptions; use crate::error::{ParserError, PrinterError}; -use crate::logical::LogicalProperty; use crate::targets::Browsers; use crate::prefixes::Feature; use crate::parser::starts_with_ignore_ascii_case; @@ -289,7 +288,6 @@ macro_rules! define_properties { )+ Unparsed(UnparsedProperty<'i>), Custom(CustomProperty<'i>), - Logical(LogicalProperty<'i>) } impl<'i> Property<'i> { @@ -374,7 +372,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, } } @@ -395,9 +392,6 @@ macro_rules! define_properties { Custom(custom) => { custom.value.to_css(dest, custom.name.starts_with("--")) } - Logical(logical) => { - logical.to_css(dest) - } } } @@ -443,7 +437,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 f4d20ccc..4d9627fa 100644 --- a/src/properties/text.rs +++ b/src/properties/text.rs @@ -835,9 +835,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 e69de440..ff9c6c37 100644 --- a/src/properties/transition.rs +++ b/src/properties/transition.rs @@ -259,9 +259,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) ); @@ -279,9 +277,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 b9b80c67..eb3521a9 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -237,7 +237,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 81c47b37..6aa96388 100644 --- a/src/rules/style.rs +++ b/src/rules/style.rs @@ -50,6 +50,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/stylesheet.rs b/src/stylesheet.rs index e30abe73..a8bf4905 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -110,7 +110,6 @@ impl<'i> StyleSheet<'i> { loc: Some(ErrorLocation::from(e.loc, self.sources[e.loc.source_index as usize].clone())) })?; - context.add_logical_rules(&mut self.rules); Ok(()) } From e492857fac8816b4e0695010a7dff4507fba2a98 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 3 Apr 2022 15:03:09 -0400 Subject: [PATCH 3/3] Add selector downleveling features to readme --- README.md | 5 +++++ 1 file changed, 5 insertions(+) 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`)