From 824ede6237f3d3f10eb98a9ed42ffaa6551aa0c5 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Fri, 8 Dec 2023 15:40:24 -0500 Subject: [PATCH] css: simplify `color()` duplication fallback logic --- internal/css_parser/css_decls.go | 60 ++++++++++----------- internal/css_parser/css_decls_box_shadow.go | 8 +-- internal/css_parser/css_decls_color.go | 14 ++--- 3 files changed, 38 insertions(+), 44 deletions(-) diff --git a/internal/css_parser/css_decls.go b/internal/css_parser/css_decls.go index 304fea2e646..281945dcc19 100644 --- a/internal/css_parser/css_decls.go +++ b/internal/css_parser/css_decls.go @@ -88,6 +88,7 @@ func (p *parser) processDeclarations(rules []css_ast.Rule, composesContext *comp borderRadius := borderRadiusTracker{} rewrittenRules = make([]css_ast.Rule, 0, len(rules)) didWarnAboutComposes := false + wouldClipColorFlag := false var declarationKeys map[string]struct{} // Don't automatically generate the "inset" property if it's not supported @@ -118,13 +119,31 @@ func (p *parser) processDeclarations(rules []css_ast.Rule, composesContext *comp } } - for _, rule := range rules { + for i := 0; i < len(rules); i++ { + rule := rules[i] rewrittenRules = append(rewrittenRules, rule) decl, ok := rule.Data.(*css_ast.RDeclaration) if !ok { continue } + // If the previous loop iteration would have clipped a color, we duplicate + // it and insert the clipped copy before the unclipped copy. But we only do + // this if there was no previous instance of that property so we avoid + // overwriting any manually-specified fallback values. + var wouldClipColor *bool + if wouldClipColorFlag && p.options.unsupportedCSSFeatures.Has(compat.ColorFunction) { + wouldClipColorFlag = false + clone := *decl + clone.Value = css_ast.CloneTokensWithoutImportRecords(clone.Value) + decl = &clone + rule.Data = decl + n := len(rewrittenRules) - 2 + rewrittenRules = append(rewrittenRules[:n], rule, rewrittenRules[n]) + } else { + wouldClipColor = &wouldClipColorFlag + } + switch decl.Key { case css_ast.DComposes: // Only process "composes" directives if we're in "local-css" or @@ -151,19 +170,8 @@ func (p *parser) processDeclarations(rules []css_ast.Rule, composesContext *comp } case css_ast.DBackground: - wouldClamp := false for i, t := range decl.Value { - decl.Value[i] = p.lowerAndMinifyColor(t, &wouldClamp) - } - - // If this contains values outside of sRGB, duplicate and clamp - if wouldClamp && p.options.unsupportedCSSFeatures.Has(compat.ColorFunction) { - clamped := *decl - clamped.Value = css_ast.CloneTokensWithoutImportRecords(clamped.Value) - for i, t := range clamped.Value { - clamped.Value[i] = p.lowerAndMinifyColor(t, nil) - } - rewrittenRules = append(rewrittenRules[:len(rewrittenRules)-1], css_ast.Rule{Loc: rule.Loc, Data: &clamped}, rule) + decl.Value[i] = p.lowerAndMinifyColor(t, wouldClipColor) } case css_ast.DBackgroundColor, @@ -189,16 +197,7 @@ func (p *parser) processDeclarations(rules []css_ast.Rule, composesContext *comp css_ast.DTextEmphasisColor: if len(decl.Value) == 1 { - wouldClamp := false - decl.Value[0] = p.lowerAndMinifyColor(decl.Value[0], &wouldClamp) - - // If this contains values outside of sRGB, duplicate and clamp - if wouldClamp && p.options.unsupportedCSSFeatures.Has(compat.ColorFunction) && len(decl.Value) == 1 { - clamped := *decl - clamped.Value = css_ast.CloneTokensWithoutImportRecords(clamped.Value) - clamped.Value[0] = p.lowerAndMinifyColor(clamped.Value[0], nil) - rewrittenRules = append(rewrittenRules[:len(rewrittenRules)-1], css_ast.Rule{Loc: rule.Loc, Data: &clamped}, rule) - } + decl.Value[0] = p.lowerAndMinifyColor(decl.Value[0], wouldClipColor) } case css_ast.DTransform: @@ -207,16 +206,7 @@ func (p *parser) processDeclarations(rules []css_ast.Rule, composesContext *comp } case css_ast.DBoxShadow: - wouldClamp := false - decl.Value = p.lowerAndMangleBoxShadows(decl.Value, &wouldClamp) - - // If this contains values outside of sRGB, duplicate and clamp - if wouldClamp && p.options.unsupportedCSSFeatures.Has(compat.ColorFunction) { - clamped := *decl - clamped.Value = css_ast.CloneTokensWithoutImportRecords(clamped.Value) - clamped.Value = p.lowerAndMangleBoxShadows(clamped.Value, nil) - rewrittenRules = append(rewrittenRules[:len(rewrittenRules)-1], css_ast.Rule{Loc: rule.Loc, Data: &clamped}, rule) - } + decl.Value = p.lowerAndMangleBoxShadows(decl.Value, wouldClipColor) // Container name case css_ast.DContainer: @@ -381,6 +371,10 @@ func (p *parser) processDeclarations(rules []css_ast.Rule, composesContext *comp rewrittenRules = p.insertPrefixedDeclaration(rewrittenRules, "-o-", rule.Loc, decl, declarationKeys) } } + + if wouldClipColorFlag && p.options.unsupportedCSSFeatures.Has(compat.ColorFunction) { + i -= 1 + } } // Compact removed rules diff --git a/internal/css_parser/css_decls_box_shadow.go b/internal/css_parser/css_decls_box_shadow.go index 2fae8c5aaa5..1d77861c393 100644 --- a/internal/css_parser/css_decls_box_shadow.go +++ b/internal/css_parser/css_decls_box_shadow.go @@ -7,7 +7,7 @@ import ( "github.com/evanw/esbuild/internal/css_lexer" ) -func (p *parser) lowerAndMangleBoxShadow(tokens []css_ast.Token, wouldClamp *bool) []css_ast.Token { +func (p *parser) lowerAndMangleBoxShadow(tokens []css_ast.Token, wouldClipColor *bool) []css_ast.Token { insetCount := 0 colorCount := 0 numbersBegin := 0 @@ -38,7 +38,7 @@ func (p *parser) lowerAndMangleBoxShadow(tokens []css_ast.Token, wouldClamp *boo if _, ok := parseColor(t); ok { colorCount++ - tokens[i] = p.lowerAndMinifyColor(t, wouldClamp) + tokens[i] = p.lowerAndMinifyColor(t, wouldClipColor) } else if t.Kind == css_lexer.TIdent && strings.EqualFold(t.Text, "inset") { insetCount++ } else { @@ -78,7 +78,7 @@ func (p *parser) lowerAndMangleBoxShadow(tokens []css_ast.Token, wouldClamp *boo return tokens } -func (p *parser) lowerAndMangleBoxShadows(tokens []css_ast.Token, wouldClamp *bool) []css_ast.Token { +func (p *parser) lowerAndMangleBoxShadows(tokens []css_ast.Token, wouldClipColor *bool) []css_ast.Token { n := len(tokens) end := 0 i := 0 @@ -91,7 +91,7 @@ func (p *parser) lowerAndMangleBoxShadows(tokens []css_ast.Token, wouldClamp *bo } // Mangle this individual shadow - end += copy(tokens[end:], p.lowerAndMangleBoxShadow(tokens[i:comma], wouldClamp)) + end += copy(tokens[end:], p.lowerAndMangleBoxShadow(tokens[i:comma], wouldClipColor)) // Skip over the comma if comma < n { diff --git a/internal/css_parser/css_decls_color.go b/internal/css_parser/css_decls_color.go index 240b2a61998..16c6f34cb4e 100644 --- a/internal/css_parser/css_decls_color.go +++ b/internal/css_parser/css_decls_color.go @@ -277,7 +277,7 @@ func lowerAlphaPercentageToNumber(token css_ast.Token) css_ast.Token { } // Convert newer color syntax to older color syntax for older browsers -func (p *parser) lowerAndMinifyColor(token css_ast.Token, wouldClamp *bool) css_ast.Token { +func (p *parser) lowerAndMinifyColor(token css_ast.Token, wouldClipColor *bool) css_ast.Token { text := token.Text switch token.Kind { @@ -396,14 +396,14 @@ func (p *parser) lowerAndMinifyColor(token css_ast.Token, wouldClamp *bool) css_ case "hwb": if p.options.unsupportedCSSFeatures.Has(compat.HWB) { if color, ok := parseColor(token); ok { - return p.tryToGenerateColor(token, color, wouldClamp) + return p.tryToGenerateColor(token, color, wouldClipColor) } } case "color": if p.options.unsupportedCSSFeatures.Has(compat.ColorFunction) { if color, ok := parseColor(token); ok { - return p.tryToGenerateColor(token, color, wouldClamp) + return p.tryToGenerateColor(token, color, wouldClipColor) } } } @@ -413,7 +413,7 @@ func (p *parser) lowerAndMinifyColor(token css_ast.Token, wouldClamp *bool) css_ // the color because we always print it out using the shortest encoding. if p.options.minifySyntax { if hex, ok := parseColor(token); ok { - token = p.tryToGenerateColor(token, hex, wouldClamp) + token = p.tryToGenerateColor(token, hex, wouldClipColor) } } @@ -740,7 +740,7 @@ func parseColorByte(token css_ast.Token, scale float64) (uint32, bool) { return uint32(i), ok } -func (p *parser) tryToGenerateColor(token css_ast.Token, color parsedColor, wouldClamp *bool) css_ast.Token { +func (p *parser) tryToGenerateColor(token css_ast.Token, color parsedColor, wouldClipColor *bool) css_ast.Token { // Note: Do NOT remove color information from fully transparent colors. // Safari behaves differently than other browsers for color interpolation: // https://css-tricks.com/thing-know-gradients-transparent-black/ @@ -753,8 +753,8 @@ func (p *parser) tryToGenerateColor(token css_ast.Token, color parsedColor, woul } else { r, g, b := gam_srgb(xyz_to_lin_srgb(color.x, color.y, color.z)) if r < -0.5/255 || r > 255.5/255 || g < -0.5/255 || g > 255.5/255 || b < -0.5/255 || b > 255.5/255 { - if wouldClamp != nil { - *wouldClamp = true + if wouldClipColor != nil { + *wouldClipColor = true return token } r, g, b = gamut_mapping_xyz_to_srgb(color.x, color.y, color.z)