diff --git a/client/src/utils/__tests__/svg.test.ts b/client/src/utils/__tests__/svg.test.ts index 2d7c01f8ba..9a200e9166 100644 --- a/client/src/utils/__tests__/svg.test.ts +++ b/client/src/utils/__tests__/svg.test.ts @@ -143,6 +143,43 @@ describe('isMonochromeSvg', () => { expect(isMonochromeSvg(svg)).toBe(false); }); }); + + describe('embedded raster/foreign content (not tintable)', () => { + it('rejects an SVG wrapping an embedded raster image (href)', () => { + const svg = ''; + expect(isMonochromeSvg(svg)).toBe(false); + }); + + it('rejects an SVG wrapping an embedded raster image (xlink:href)', () => { + const svg = + ''; + expect(isMonochromeSvg(svg)).toBe(false); + }); + + it('rejects an SVG embedding foreignObject content', () => { + const svg = '
hi
'; + expect(isMonochromeSvg(svg)).toBe(false); + }); + }); + + describe('parser robustness', () => { + it('tints a namespaced monochrome svg', () => { + const svg = + ''; + expect(isMonochromeSvg(svg)).toBe(true); + }); + + it('preserves a namespaced multi-color svg', () => { + const svg = + ''; + expect(isMonochromeSvg(svg)).toBe(false); + }); + + it('does not tint unparseable input', () => { + expect(isMonochromeSvg('')).toBe(false); + expect(isMonochromeSvg(' { diff --git a/client/src/utils/svg.ts b/client/src/utils/svg.ts index 9989af6f5e..51e00c3298 100644 --- a/client/src/utils/svg.ts +++ b/client/src/utils/svg.ts @@ -3,12 +3,11 @@ import DOMPurify from 'dompurify'; /** * Heuristics for deciding whether a custom SVG icon is a monochrome glyph that * should be tinted to `currentColor` (so it follows the active theme) or a - * multi-color logo that must keep its original colors. + * multi-color logo / embedded raster that must keep its original colors. The SVG + * is parsed with `DOMParser` and its elements are inspected, rather than scraped + * with regexes. */ -const COLOR_REGEX = - /(?:fill|stroke|stop-color|flood-color|lighting-color|color)\s*[:=]\s*["']?\s*(#[0-9a-fA-F]{3,8}|rgba?\([^)]*\)|hsla?\([^)]*\)|[a-zA-Z]+)/gi; - /** Color keywords that carry no chromatic information and are ignored. */ const IGNORABLE_COLORS = new Set(['none', 'transparent', 'inherit', 'currentcolor']); @@ -29,6 +28,12 @@ const GRAY_NAMES = new Set([ 'dimgrey', ]); +/** Paint properties whose color values determine whether an SVG is monochrome. */ +const PAINT_PROPS = ['fill', 'stroke', 'stop-color']; + +/** Matches color declarations inside a `