diff --git a/src/lib.rs b/src/lib.rs index 46ce5e39..b5c10b4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1476,6 +1476,33 @@ mod tests { ..Browsers::default() }, ); + + prefix_test( + &format!( + r#" + @supports (color: lab(0% 0 0)) {{ + .foo {{ + {}: var(--border-width) solid lab(40% 56.6 39); + }} + }} + "#, + prop + ), + &format!( + indoc! {r#" + @supports (color: lab(0% 0 0)) {{ + .foo {{ + {}: var(--border-width) solid lab(40% 56.6 39); + }} + }} + "#}, + prop, + ), + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); } prefix_test( @@ -13645,6 +13672,27 @@ mod tests { ..Browsers::default() }, ); + prefix_test( + r#"@supports (color: lab(0% 0 0)) { + @font-palette-values --Cooler { + font-family: Handover Sans; + base-palette: 3; + override-colors: 1 var(--foo), 3 lab(50.998% 125.506 -50.7078); + } + }"#, + indoc! {r#"@supports (color: lab(0% 0 0)) { + @font-palette-values --Cooler { + font-family: Handover Sans; + base-palette: 3; + override-colors: 1 var(--foo), 3 lab(50.998% 125.506 -50.7078); + } + } + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); minify_test(".foo { font-palette: --Custom; }", ".foo{font-palette:--Custom}"); } @@ -15454,6 +15502,27 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + .foo { + text-decoration: lab(50.998% 125.506 -50.7078) var(--style); + } + } + "#, + indoc! {r#" + @supports (color: lab(0% 0 0)) { + .foo { + text-decoration: lab(50.998% 125.506 -50.7078) var(--style); + } + } + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); + prefix_test( r#" .foo { @@ -15821,6 +15890,27 @@ mod tests { ..Browsers::default() }, ); + + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + .foo { + text-emphasis: lab(50.998% 125.506 -50.7078) var(--style); + } + } + "#, + indoc! {r#" + @supports (color: lab(0% 0 0)) { + .foo { + text-emphasis: lab(50.998% 125.506 -50.7078) var(--style); + } + } + "#}, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); } #[test] @@ -15908,6 +15998,27 @@ mod tests { ..Browsers::default() }, ); + + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + .foo { + text-shadow: var(--foo) 12px lab(40% 56.6 39); + } + } + "#, + indoc! { r#" + @supports (color: lab(0% 0 0)) { + .foo { + text-shadow: var(--foo) 12px lab(40% 56.6 39); + } + } + "#}, + Browsers { + chrome: Some(4 << 16), + ..Browsers::default() + }, + ); } #[test] @@ -16618,6 +16729,27 @@ mod tests { ..Browsers::default() }, ); + + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + .foo { + caret: lab(50.998% 125.506 -50.7078) var(--foo); + } + } + "#, + indoc! { r#" + @supports (color: lab(0% 0 0)) { + .foo { + caret: lab(50.998% 125.506 -50.7078) var(--foo); + } + } + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); } #[test] @@ -16746,6 +16878,27 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + .foo { + list-style: var(--foo) linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)); + } + } + "#, + indoc! { r#" + @supports (color: lab(0% 0 0)) { + .foo { + list-style: var(--foo) linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)); + } + } + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); + test( r#" .foo { @@ -17567,6 +17720,27 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + .foo { + background: var(--image) lab(40% 56.6 39); + } + } + "#, + indoc! { r#" + @supports (color: lab(0% 0 0)) { + .foo { + background: var(--image) lab(40% 56.6 39); + } + } + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); + prefix_test( r#" .foo { @@ -17669,6 +17843,28 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + .foo { + color: var(--foo, lab(40% 56.6 39)); + } + } + "#, + indoc! {r#" + @supports (color: lab(0% 0 0)) { + .foo { + color: var(--foo, lab(40% 56.6 39)); + } + } + "# + }, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); + prefix_test( r#" .foo { @@ -21981,6 +22177,27 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + .foo { + --custom: lab(40% 56.6 39); + } + } + "#, + indoc! {r#" + @supports (color: lab(0% 0 0)) { + .foo { + --custom: lab(40% 56.6 39); + } + } + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); + prefix_test( r#" .foo { @@ -22004,6 +22221,27 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + .foo { + --custom: lab(40% 56.6 39) !important; + } + } + "#, + indoc! {r#" + @supports (color: lab(0% 0 0)) { + .foo { + --custom: lab(40% 56.6 39) !important; + } + } + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); + prefix_test( r#" .foo { @@ -22036,13 +22274,23 @@ mod tests { prefix_test( r#" - .foo { - --custom: lab(40% 56.6 39); + @supports (color: color(display-p3 0 0 0)) { + .foo { + --custom: color(display-p3 .643308 .192455 .167712); + } + } + + @supports (color: lab(0% 0 0)) { + .foo { + --custom: lab(40% 56.6 39); + } } "#, indoc! {r#" - .foo { - --custom: color(display-p3 .643308 .192455 .167712); + @supports (color: color(display-p3 0 0 0)) { + .foo { + --custom: color(display-p3 .643308 .192455 .167712); + } } @supports (color: lab(0% 0 0)) { @@ -22052,6 +22300,7 @@ mod tests { } "#}, Browsers { + chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() }, @@ -22065,7 +22314,30 @@ mod tests { "#, indoc! {r#" .foo { - --custom: lab(40% 56.6 39); + --custom: color(display-p3 .643308 .192455 .167712); + } + + @supports (color: lab(0% 0 0)) { + .foo { + --custom: lab(40% 56.6 39); + } + } + "#}, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + r#" + .foo { + --custom: lab(40% 56.6 39); + } + "#, + indoc! {r#" + .foo { + --custom: lab(40% 56.6 39); } "#}, Browsers { @@ -22202,6 +22474,28 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: color(display-p3 0 0 0)) { + .foo { + --foo: color(display-p3 0 1 0); + } + } + "#, + indoc! {r#" + @supports (color: color(display-p3 0 0 0)) { + .foo { + --foo: color(display-p3 0 1 0); + } + } + "#}, + Browsers { + safari: Some(14 << 16), + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); + prefix_test( r#" .foo { @@ -22366,6 +22660,39 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + @keyframes foo { + from { + --custom: lab(40% 56.6 39); + } + + to { + --custom: lab(50.998% 125.506 -50.7078); + } + } + } + "#, + indoc! {r#" + @supports (color: lab(0% 0 0)) { + @keyframes foo { + from { + --custom: lab(40% 56.6 39); + } + + to { + --custom: lab(50.998% 125.506 -50.7078); + } + } + } + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); + prefix_test( r#" @keyframes foo { @@ -22420,6 +22747,64 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: color(display-p3 0 0 0)) { + @keyframes foo { + from { + --custom: color(display-p3 .643308 .192455 .167712); + } + + to { + --custom: color(display-p3 .972962 -.362078 .804206); + } + } + } + + @supports (color: lab(0% 0 0)) { + @keyframes foo { + from { + --custom: lab(40% 56.6 39); + } + + to { + --custom: lab(50.998% 125.506 -50.7078); + } + } + } + "#, + indoc! {r#" + @supports (color: color(display-p3 0 0 0)) { + @keyframes foo { + from { + --custom: color(display-p3 .643308 .192455 .167712); + } + + to { + --custom: color(display-p3 .972962 -.362078 .804206); + } + } + } + + @supports (color: lab(0% 0 0)) { + @keyframes foo { + from { + --custom: lab(40% 56.6 39); + } + + to { + --custom: lab(50.998% 125.506 -50.7078); + } + } + } + "#}, + Browsers { + chrome: Some(90 << 16), + safari: Some(14 << 16), + ..Browsers::default() + }, + ); + prefix_test( r#" @keyframes foo { @@ -25622,6 +26007,27 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + .foo { + fill: var(--url) lab(50.998% 125.506 -50.7078); + } + } + "#, + indoc! { r#" + @supports (color: lab(0% 0 0)) { + .foo { + fill: var(--url) lab(50.998% 125.506 -50.7078); + } + } + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); + prefix_test( ".foo { mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }", indoc! { r#" @@ -25737,6 +26143,28 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: lab(0% 0 0)) { + .foo { + mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo); + } + } + "#, + indoc! { r#" + @supports (color: lab(0% 0 0)) { + .foo { + -webkit-mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo); + mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo); + } + } + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); + prefix_test( ".foo { mask: url(masks.svg#star) luminance }", indoc! { r#" @@ -28489,6 +28917,28 @@ mod tests { }, ); + prefix_test( + r#" + @supports (color: color(display-p3 0 0 0)) { + .foo { + color: env(--brand-color, color(display-p3 0 1 0)); + } + } + "#, + indoc! {r#" + @supports (color: color(display-p3 0 0 0)) { + .foo { + color: env(--brand-color, color(display-p3 0 1 0)); + } + } + "#}, + Browsers { + safari: Some(15 << 16), + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); + css_modules_test( r#" @media (max-width: env(--branding-small)) { @@ -28882,4 +29332,99 @@ mod tests { "@layer{@view-transition{navigation:auto;types:foo bar}}", ); } + + #[test] + fn test_skip_generating_unnecessary_fallbacks() { + prefix_test( + r#" + @supports (color: lab(0% 0 0)) and (color: color(display-p3 0 0 0)) { + .foo { + color: lab(40% 56.6 39); + } + + .bar { + color: color(display-p3 .643308 .192455 .167712); + } + } + "#, + indoc! {r#" + @supports (color: lab(0% 0 0)) and (color: color(display-p3 0 0 0)) { + .foo { + color: lab(40% 56.6 39); + } + + .bar { + color: color(display-p3 .643308 .192455 .167712); + } + } + "#}, + Browsers { + chrome: Some(4 << 16), + ..Browsers::default() + }, + ); + + // NOTE: fallback for lab is not necessary + prefix_test( + r#" + @supports (color: lab(0% 0 0)) and (not (color: color(display-p3 0 0 0))) { + .foo { + color: lab(40% 56.6 39); + } + + .bar { + color: color(display-p3 .643308 .192455 .167712); + } + } + "#, + indoc! {r#" + @supports (color: lab(0% 0 0)) and (not (color: color(display-p3 0 0 0))) { + .foo { + color: #b32323; + color: lab(40% 56.6 39); + } + + .bar { + color: #b32323; + color: color(display-p3 .643308 .192455 .167712); + } + } + "#}, + Browsers { + chrome: Some(4 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + r#" + @supports (color: lab(0% 0 0)) or (color: color(display-p3 0 0 0)) { + .foo { + color: lab(40% 56.6 39); + } + + .bar { + color: color(display-p3 .643308 .192455 .167712); + } + } + "#, + indoc! {r#" + @supports (color: lab(0% 0 0)) or (color: color(display-p3 0 0 0)) { + .foo { + color: #b32323; + color: lab(40% 56.6 39); + } + + .bar { + color: #b32323; + color: color(display-p3 .643308 .192455 .167712); + } + } + "#}, + Browsers { + chrome: Some(4 << 16), + ..Browsers::default() + }, + ); + } } diff --git a/src/rules/font_palette_values.rs b/src/rules/font_palette_values.rs index 95ca014c..af06c487 100644 --- a/src/rules/font_palette_values.rs +++ b/src/rules/font_palette_values.rs @@ -261,7 +261,7 @@ impl<'i> FontPaletteValuesRule<'i> { // Generate color fallbacks. let mut fallbacks = ColorFallbackKind::empty(); for o in override_colors { - fallbacks |= o.color.get_necessary_fallbacks(*context.targets); + fallbacks |= o.color.get_necessary_fallbacks(context.targets.current); } if fallbacks.contains(ColorFallbackKind::RGB) { diff --git a/src/rules/media.rs b/src/rules/media.rs index 1340bea3..c398b9b0 100644 --- a/src/rules/media.rs +++ b/src/rules/media.rs @@ -39,7 +39,7 @@ impl<'i, T: Clone> MediaRule<'i, T> { self.query.transform_custom_media(self.loc, custom_media)?; } - self.query.transform_resolution(*context.targets); + self.query.transform_resolution(context.targets.current); Ok(self.rules.0.is_empty() || self.query.never_matches()) } } diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 4909961a..8f815bbe 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -73,7 +73,7 @@ use crate::printer::Printer; use crate::rules::keyframes::KeyframesName; use crate::selector::{is_compatible, is_equivalent, Component, Selector, SelectorList}; use crate::stylesheet::ParserOptions; -use crate::targets::Targets; +use crate::targets::TargetsWithSupportsScope; use crate::traits::{AtRuleParser, ToCss}; use crate::values::string::CowArcStr; use crate::vendor_prefix::VendorPrefix; @@ -499,7 +499,7 @@ impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for Css } pub(crate) struct MinifyContext<'a, 'i> { - pub targets: &'a Targets, + pub targets: TargetsWithSupportsScope, pub handler: &'a mut DeclarationHandler<'i>, pub important_handler: &'a mut DeclarationHandler<'i>, pub handler_context: PropertyHandlerContext<'i, 'a>, @@ -535,7 +535,8 @@ impl<'i, T: Clone> CssRuleList<'i, T> { macro_rules! set_prefix { ($keyframes: ident) => { - $keyframes.vendor_prefix = context.targets.prefixes($keyframes.vendor_prefix, Feature::AtKeyframes); + $keyframes.vendor_prefix = + context.targets.current.prefixes($keyframes.vendor_prefix, Feature::AtKeyframes); }; } @@ -559,7 +560,7 @@ impl<'i, T: Clone> CssRuleList<'i, T> { set_prefix!(keyframes); keyframe_rules.insert(keyframes.name.clone(), rules.len()); - let fallbacks = keyframes.get_fallbacks(context.targets); + let fallbacks = keyframes.get_fallbacks(&context.targets.current); rules.push(rule); rules.extend(fallbacks); continue; @@ -647,14 +648,14 @@ impl<'i, T: Clone> CssRuleList<'i, T> { // If some of the selectors in this rule are not compatible with the targets, // we need to either wrap in :is() or split them into multiple rules. let incompatible = if style.selectors.0.len() > 1 - && context.targets.should_compile_selectors() - && !style.is_compatible(*context.targets) + && context.targets.current.should_compile_selectors() + && !style.is_compatible(context.targets.current) { // The :is() selector accepts a forgiving selector list, so use that if possible. // Note that :is() does not allow pseudo elements, so we need to check for that. // In addition, :is() takes the highest specificity of its arguments, so if the selectors // have different weights, we need to split them into separate rules as well. - if context.targets.is_compatible(crate::compat::Feature::IsSelector) + if context.targets.current.is_compatible(crate::compat::Feature::IsSelector) && !style.selectors.0.iter().any(|selector| selector.has_pseudo_element()) && style.selectors.0.iter().map(|selector| selector.specificity()).all_equal() { @@ -673,7 +674,7 @@ impl<'i, T: Clone> CssRuleList<'i, T> { .cloned() .partition::, _>(|selector| { let list = SelectorList::new(smallvec![selector.clone()]); - is_compatible(&list.0, *context.targets) + is_compatible(&list.0, context.targets.current) }); style.selectors = SelectorList::new(compatible); incompatible @@ -827,7 +828,7 @@ impl<'i, T: Clone> CssRuleList<'i, T> { f.minify(context, parent_is_unused); - let fallbacks = f.get_fallbacks(*context.targets); + let fallbacks = f.get_fallbacks(context.targets.current); rules.push(rule); rules.extend(fallbacks); continue; @@ -931,8 +932,8 @@ fn merge_style_rules<'i, T>( ) -> bool { // Merge declarations if the selectors are equivalent, and both are compatible with all targets. if style.selectors == last_style_rule.selectors - && style.is_compatible(*context.targets) - && last_style_rule.is_compatible(*context.targets) + && style.is_compatible(context.targets.current) + && last_style_rule.is_compatible(context.targets.current) && style.rules.0.is_empty() && last_style_rule.rules.0.is_empty() && (!context.css_modules || style.loc.source_index == last_style_rule.loc.source_index) @@ -961,7 +962,7 @@ fn merge_style_rules<'i, T>( { // If the new rule is unprefixed, replace the prefixes of the last rule. // Otherwise, add the new prefix. - if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.should_compile_selectors() { + if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.current.should_compile_selectors() { last_style_rule.vendor_prefix = style.vendor_prefix; } else { last_style_rule.vendor_prefix |= style.vendor_prefix; @@ -970,9 +971,9 @@ fn merge_style_rules<'i, T>( } // Append the selectors to the last rule if the declarations are the same, and all selectors are compatible. - if style.is_compatible(*context.targets) && last_style_rule.is_compatible(*context.targets) { + if style.is_compatible(context.targets.current) && last_style_rule.is_compatible(context.targets.current) { last_style_rule.selectors.0.extend(style.selectors.0.drain(..)); - if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.should_compile_selectors() { + if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.current.should_compile_selectors() { last_style_rule.vendor_prefix = style.vendor_prefix; } else { last_style_rule.vendor_prefix |= style.vendor_prefix; diff --git a/src/rules/style.rs b/src/rules/style.rs index d18ec4f9..851a918b 100644 --- a/src/rules/style.rs +++ b/src/rules/style.rs @@ -173,8 +173,8 @@ impl<'i, T> StyleRule<'i, T> { pub(crate) fn update_prefix(&mut self, context: &mut MinifyContext<'_, 'i>) { self.vendor_prefix = get_prefix(&self.selectors); - if self.vendor_prefix.contains(VendorPrefix::None) && context.targets.should_compile_selectors() { - self.vendor_prefix = downlevel_selectors(self.selectors.0.as_mut_slice(), *context.targets); + if self.vendor_prefix.contains(VendorPrefix::None) && context.targets.current.should_compile_selectors() { + self.vendor_prefix = downlevel_selectors(self.selectors.0.as_mut_slice(), context.targets.current); } } } diff --git a/src/rules/supports.rs b/src/rules/supports.rs index 04f1fdd3..904da503 100644 --- a/src/rules/supports.rs +++ b/src/rules/supports.rs @@ -8,8 +8,9 @@ use crate::error::{MinifyError, ParserError, PrinterError}; use crate::parser::DefaultAtRule; use crate::printer::Printer; use crate::properties::PropertyId; -use crate::targets::Targets; +use crate::targets::{Features, FeaturesIterator, Targets}; use crate::traits::{Parse, ToCss}; +use crate::values::color::ColorFallbackKind; use crate::values::string::CowArcStr; use crate::vendor_prefix::VendorPrefix; #[cfg(feature = "visitor")] @@ -42,8 +43,19 @@ impl<'i, T: Clone> SupportsRule<'i, T> { context: &mut MinifyContext<'_, 'i>, parent_is_unused: bool, ) -> Result<(), MinifyError> { - self.condition.set_prefixes_for_targets(&context.targets); - self.rules.minify(context, parent_is_unused) + let inserted = context.targets.enter_supports(self.condition.get_supported_features()); + if inserted { + context.handler_context.targets = context.targets.current; + } + + self.condition.set_prefixes_for_targets(&context.targets.current); + let result = self.rules.minify(context, parent_is_unused); + + if inserted { + context.targets.exit_supports(); + context.handler_context.targets = context.targets.current; + } + result } } @@ -149,6 +161,31 @@ impl<'i> SupportsCondition<'i> { _ => {} } } + + fn get_supported_features(&self) -> Features { + const COLOR_P3_SUPPORTS_CONDITION: &str = ColorFallbackKind::P3.supports_condition_value(); + const COLOR_LAB_SUPPORTS_CONDITION: &str = ColorFallbackKind::LAB.supports_condition_value(); + fn get_supported_features_internal(value: &SupportsCondition) -> Option { + match value { + SupportsCondition::And(list) => list.iter().map(|c| get_supported_features_internal(c)).try_union_all(), + SupportsCondition::Declaration { property_id, value } => match property_id { + PropertyId::Color => Some(match value.as_ref() { + COLOR_P3_SUPPORTS_CONDITION => Features::P3Colors | Features::ColorFunction, + COLOR_LAB_SUPPORTS_CONDITION => Features::LabColors, + _ => Features::empty(), + }), + _ => Some(Features::empty()), + }, + // bail out if "not" or "or" exists for now + SupportsCondition::Not(_) | SupportsCondition::Or(_) => None, + SupportsCondition::Selector(_) | SupportsCondition::Unknown(_) => { + Some(Features::empty()) + } + } + } + + get_supported_features_internal(self).unwrap_or(Features::empty()) + } } impl<'i> Parse<'i> for SupportsCondition<'i> { diff --git a/src/stylesheet.rs b/src/stylesheet.rs index bb5a704f..9ba762a2 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -11,7 +11,7 @@ use crate::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterEr use crate::parser::{DefaultAtRule, DefaultAtRuleParser, TopLevelRuleParser}; use crate::printer::Printer; use crate::rules::{CssRule, CssRuleList, MinifyContext}; -use crate::targets::{should_compile, Targets}; +use crate::targets::{should_compile, Targets, TargetsWithSupportsScope}; use crate::traits::{AtRuleParser, ToCss}; use crate::values::string::CowArcStr; #[cfg(feature = "visitor")] @@ -242,7 +242,7 @@ where }; let mut ctx = MinifyContext { - targets: &options.targets, + targets: TargetsWithSupportsScope::new(options.targets), handler: &mut handler, important_handler: &mut important_handler, handler_context: context, diff --git a/src/targets.rs b/src/targets.rs index 6c1f429a..9eedf56b 100644 --- a/src/targets.rs +++ b/src/targets.rs @@ -2,6 +2,8 @@ #![allow(missing_docs)] +use std::borrow::Borrow; + use crate::vendor_prefix::VendorPrefix; use bitflags::bitflags; #[cfg(any(feature = "serde", feature = "nodejs"))] @@ -136,7 +138,7 @@ fn parse_version(version: &str) -> Option { bitflags! { /// Features to explicitly enable or disable. - #[derive(Debug, Default, Clone, Copy)] + #[derive(Debug, Default, Clone, Copy, Hash, Eq, PartialEq)] pub struct Features: u32 { const Nesting = 1 << 0; const NotSelectorList = 1 << 1; @@ -165,6 +167,18 @@ bitflags! { } } +pub(crate) trait FeaturesIterator: Sized + Iterator { + fn try_union_all(&mut self) -> Option + where + Self: Iterator>, + T: Borrow, + { + self.try_fold(Features::empty(), |a, b| b.map(|b| a | *b.borrow())) + } +} + +impl FeaturesIterator for I where I: Iterator {} + /// Target browsers and features to compile. #[derive(Debug, Clone, Copy, Default)] pub struct Targets { @@ -225,6 +239,67 @@ impl Targets { } } +#[derive(Debug)] +pub(crate) struct TargetsWithSupportsScope { + stack: Vec, + pub(crate) current: Targets, +} + +impl TargetsWithSupportsScope { + pub fn new(targets: Targets) -> Self { + Self { + stack: Vec::new(), + current: targets, + } + } + + /// Returns true if inserted + pub fn enter_supports(&mut self, features: Features) -> bool { + if features.is_empty() || self.current.exclude.contains(features) { + // Already excluding all features + return false; + } + + let newly_excluded = features - self.current.exclude; + self.stack.push(newly_excluded); + self.current.exclude.insert(newly_excluded); + true + } + + /// Should be only called if inserted + pub fn exit_supports(&mut self) { + if let Some(last) = self.stack.pop() { + self.current.exclude.remove(last); + } + } +} + +#[test] +fn supports_scope_correctly() { + let mut targets = TargetsWithSupportsScope::new(Targets::default()); + assert!(!targets.current.exclude.contains(Features::OklabColors)); + assert!(!targets.current.exclude.contains(Features::LabColors)); + assert!(!targets.current.exclude.contains(Features::P3Colors)); + + targets.enter_supports(Features::OklabColors | Features::LabColors); + assert!(targets.current.exclude.contains(Features::OklabColors)); + assert!(targets.current.exclude.contains(Features::LabColors)); + + targets.enter_supports(Features::P3Colors | Features::LabColors); + assert!(targets.current.exclude.contains(Features::OklabColors)); + assert!(targets.current.exclude.contains(Features::LabColors)); + assert!(targets.current.exclude.contains(Features::P3Colors)); + + targets.exit_supports(); + assert!(targets.current.exclude.contains(Features::OklabColors)); + assert!(targets.current.exclude.contains(Features::LabColors)); + assert!(!targets.current.exclude.contains(Features::P3Colors)); + + targets.exit_supports(); + assert!(!targets.current.exclude.contains(Features::OklabColors)); + assert!(!targets.current.exclude.contains(Features::LabColors)); +} + macro_rules! should_compile { ($targets: expr, $feature: ident) => { $targets.should_compile( diff --git a/src/values/color.rs b/src/values/color.rs index 093d5c12..8d76ba72 100644 --- a/src/values/color.rs +++ b/src/values/color.rs @@ -231,7 +231,7 @@ pub enum FloatColor { bitflags! { /// A color type that is used as a fallback when compiling colors for older browsers. - #[derive(PartialEq, Eq, Clone, Copy)] + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct ColorFallbackKind: u8 { /// An RGB color fallback. const RGB = 0b01; @@ -303,13 +303,16 @@ impl ColorFallbackKind { *self | ColorFallbackKind::from_bits_truncate(self.bits() - 1) } - pub(crate) fn supports_condition<'i>(&self) -> SupportsCondition<'i> { - let s = match *self { + pub(crate) const fn supports_condition_value(&self) -> &'static str { + match *self { ColorFallbackKind::P3 => "color(display-p3 0 0 0)", ColorFallbackKind::LAB => "lab(0% 0 0)", _ => unreachable!(), - }; + } + } + pub(crate) fn supports_condition<'i>(&self) -> SupportsCondition<'i> { + let s = self.supports_condition_value(); SupportsCondition::Declaration { property_id: PropertyId::Color, value: s.into(),