Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/css/css_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2376,7 +2376,7 @@ mod stylesheet_impl {

let mut minify_ctx = MinifyContext {
arena,
targets: &options.targets,
targets: options.targets,
handler: &mut handler,
important_handler: &mut important_handler,
handler_context: ctx,
Expand Down
31 changes: 31 additions & 0 deletions src/css/properties/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,37 @@ impl TokenList {
fallbacks
}

/// The features that the colors in these tokens use.
pub(crate) fn get_features(&self) -> css::targets::Features {
use css::targets::Features;
let mut features = Features::empty();
for token_or_value in self.v.iter() {
match token_or_value {
TokenOrValue::Color(color) => features |= color.get_features(),
TokenOrValue::UnresolvedColor(UnresolvedColor::RGB { .. })
| TokenOrValue::UnresolvedColor(UnresolvedColor::HSL { .. }) => {
features |= Features::SPACE_SEPARATED_COLOR_NOTATION;
}
TokenOrValue::UnresolvedColor(UnresolvedColor::LightDark { light, dark }) => {
features |= light.get_features() | dark.get_features();
}
TokenOrValue::Function(f) => features |= f.arguments.get_features(),
TokenOrValue::Var(v) => {
if let Some(fallback) = &v.fallback {
features |= fallback.get_features();
}
}
TokenOrValue::Env(v) => {
if let Some(fallback) = &v.fallback {
features |= fallback.get_features();
}
}
_ => {}
}
}
features
}

// eql / hash / deep_clone — provided by `#[derive(CssEql, CssHash, DeepClone)]`.
}

Expand Down
16 changes: 9 additions & 7 deletions src/css/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ fn minify_style_arm<R: for<'b> css::generics::DeepClone<'b>>(
// we need to either wrap in :is() or split them into multiple rules.
let mut incompatible: SmallList<Selector, 1> = if sty.selectors.v.len() > 1
&& context.targets.should_compile_selectors()
&& !sty.is_compatible(context.targets)
&& !sty.is_compatible(&context.targets)
{
// 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.
Expand All @@ -691,7 +691,7 @@ fn minify_style_arm<R: for<'b> css::generics::DeepClone<'b>>(
while i < sty.selectors.v.len() {
if selector::is_compatible(
&sty.selectors.v.slice()[i as usize..i as usize + 1],
context.targets,
&context.targets,
) {
i += 1;
} else {
Expand Down Expand Up @@ -1108,8 +1108,8 @@ fn merge_style_rules<R>(
// Merge declarations if the selectors are equivalent, and both are compatible with all targets.
// Does not apply if css modules are enabled.
if src.selectors.eql(&dst.selectors)
&& cached_is_compatible(src, src_compat, context.targets)
&& cached_is_compatible(dst, dst_compat, context.targets)
&& cached_is_compatible(src, src_compat, &context.targets)
&& cached_is_compatible(dst, dst_compat, &context.targets)
&& src.rules.v.is_empty()
&& dst.rules.v.is_empty()
&& (!context.css_modules || src.loc.source_index == dst.loc.source_index)
Expand Down Expand Up @@ -1153,8 +1153,8 @@ fn merge_style_rules<R>(
}

// Append the selectors to the last rule if the declarations are the same, and all selectors are compatible.
if cached_is_compatible(src, src_compat, context.targets)
&& cached_is_compatible(dst, dst_compat, context.targets)
if cached_is_compatible(src, src_compat, &context.targets)
&& cached_is_compatible(dst, dst_compat, &context.targets)
{
let moved = core::mem::take(&mut src.selectors.v);
// `reserve` (not `ensure_total_capacity`) so capacity grows
Expand Down Expand Up @@ -1219,7 +1219,9 @@ pub(crate) const MAX_SELECTOR_EXPANSION: u32 = 65_536;
pub struct MinifyContext<'a, 'bump> {
/// Arena that owns the AST being minified (same arena it was parsed into).
pub(crate) arena: &'bump bun_alloc::Arena,
pub(crate) targets: &'a css::targets::Targets,
/// The stylesheet's targets, narrowed while inside an `@supports` block
/// (see `SupportsRule::minify`). `handler_context.targets` is kept equal.
Comment thread
robobun marked this conversation as resolved.
Resolved
pub(crate) targets: css::targets::Targets,
pub(crate) handler: &'a mut css::DeclarationHandler<'bump>,
pub(crate) important_handler: &'a mut css::DeclarationHandler<'bump>,
pub(crate) handler_context: css::PropertyHandlerContext<'bump>,
Expand Down
4 changes: 2 additions & 2 deletions src/css/rules/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl<R> StyleRule<R> {
self.vendor_prefix = selector::downlevel_selectors(
context.arena,
self.selectors.v.slice_mut(),
context.targets,
&context.targets,
);
}
}
Expand Down Expand Up @@ -434,7 +434,7 @@ impl<R> StyleRule<R> {
let saved_expansion_multiplier = context.selector_expansion_multiplier;
let selectors_incompatible = self.selectors.v.len() > 1
&& context.targets.should_compile_selectors()
&& !self.is_compatible(context.targets);
&& !self.is_compatible(&context.targets);
let splits_selectors = selectors_incompatible
&& !(context.targets.is_compatible(css::Feature::IsSelector)
&& !self.selectors.any_has_pseudo_element()
Expand Down
64 changes: 62 additions & 2 deletions src/css/rules/supports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,46 @@ impl crate::generics::CssEql for SupportsCondition {
}

impl SupportsCondition {
/// The features that the condition tests for. The rules inside the
/// `@supports` block can use them without fallbacks.
Comment thread
robobun marked this conversation as resolved.
Resolved
pub(crate) fn get_supported_features(&self, arena: &bun_alloc::Arena) -> css::Features {
self.supported_features(arena).unwrap_or_default()
}

/// `None` if the condition contains `not` or `or`. Nothing is inferred then.
fn supported_features(&self, arena: &bun_alloc::Arena) -> Option<css::Features> {
match self {
SupportsCondition::And(conditions) => {
let mut features = css::Features::empty();
for condition in conditions {
features |= condition.supported_features(arena)?;
}
Some(features)
}
// `parse_in_parens` keeps a `(property: value)` group as the raw
// text of an `Unknown`, so scan it like a declaration value.
SupportsCondition::Declaration(Declaration { value, .. })
| SupportsCondition::Unknown(value) => {
Comment thread
claude[bot] marked this conversation as resolved.
Resolved
let mut input = css::ParserInput::new(value, arena);
let mut parser = css::Parser::new(
&mut input,
None,
css::css_parser::ParserOpts::default(),
None,
);
let tokens =
css::TokenList::parse(&mut parser, &css::ParserOptions::default(None), 0);
Some(
tokens
.map(|tokens| tokens.get_features())
.unwrap_or_default(),
)
}
SupportsCondition::Not(_) | SupportsCondition::Or(_) => None,
SupportsCondition::Selector(_) => Some(css::Features::empty()),
}
}

fn needs_parens(&self, parent: &SupportsCondition) -> bool {
match self {
SupportsCondition::Not(_) => true,
Expand Down Expand Up @@ -432,7 +472,19 @@ impl<R> SupportsRule<R> {
// behind it multiply against the enclosing nesting levels exactly like
// plain nested rules — leaving them unvisited here lets the printer
// expand them exponentially.
self.rules.minify(context, parent_is_unused)
//
// The nested rules need no fallbacks for the features the condition
// tests for, so exclude those features while minifying them.
Comment thread
robobun marked this conversation as resolved.
Resolved
let exclude = context.targets.exclude;
context
.targets
.exclude
.insert(self.condition.get_supported_features(context.arena));
context.handler_context.targets = context.targets;
let result = self.rules.minify(context, parent_is_unused);
context.targets.exclude = exclude;
context.handler_context.targets = context.targets;
result
}
}

Expand All @@ -443,10 +495,18 @@ impl<R> SupportsRule<R> {

dest.write_str(b"@supports ")?;
self.condition.to_css(dest)?;

// Don't downlevel the features the condition tests for inside the block.
let exclude = dest.targets.exclude;
dest.targets
.exclude
.insert(self.condition.get_supported_features(dest.arena));
dest.block(|d| {
d.newline()?;
self.rules.to_css(d)
})
})?;
dest.targets.exclude = exclude;
Ok(())
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/css/values/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,24 @@ impl CssColor {
fallbacks
}

/// The features that this color uses.
pub(crate) fn get_features(&self) -> targets::Features {
use targets::Features;
match self {
CssColor::Lab(lab) => match **lab {
LABColor::Lab(_) | LABColor::Lch(_) => Features::LAB_COLORS,
LABColor::Oklab(_) | LABColor::Oklch(_) => Features::OKLAB_COLORS,
},
CssColor::Predefined(predefined) => match **predefined {
PredefinedColor::DisplayP3(_) => Features::COLOR_FUNCTION | Features::P3_COLORS,
_ => Features::COLOR_FUNCTION,
},
CssColor::Float(_) => Features::SPACE_SEPARATED_COLOR_NOTATION,
CssColor::LightDark { light, dark } => light.get_features() | dark.get_features(),
CssColor::CurrentColor | CssColor::Rgba(_) | CssColor::System(_) => Features::empty(),
}
}

#[inline]
pub(crate) fn deep_clone(&self, _arena: &Arena) -> CssColor {
self.clone()
Expand Down
Loading
Loading